[VVP] Update validations based on VNFRQTS-637
[vvp/validation-scripts.git] / checks.py
1 # -*- coding: utf8 -*-
2 # ============LICENSE_START====================================================
3 # org.onap.vvp/validation-scripts
4 # ===================================================================
5 # Copyright © 2019 AT&T Intellectual Property. All rights reserved.
6 # ===================================================================
7 #
8 # Unless otherwise specified, all software contained herein is licensed
9 # under the Apache License, Version 2.0 (the "License");
10 # you may not use this software except in compliance with the License.
11 # You may obtain a copy of the License at
12 #
13 #             http://www.apache.org/licenses/LICENSE-2.0
14 #
15 # Unless required by applicable law or agreed to in writing, software
16 # distributed under the License is distributed on an "AS IS" BASIS,
17 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 # See the License for the specific language governing permissions and
19 # limitations under the License.
20 #
21 #
22 #
23 # Unless otherwise specified, all documentation contained herein is licensed
24 # under the Creative Commons License, Attribution 4.0 Intl. (the "License");
25 # you may not use this documentation except in compliance with the License.
26 # You may obtain a copy of the License at
27 #
28 #             https://creativecommons.org/licenses/by/4.0/
29 #
30 # Unless required by applicable law or agreed to in writing, documentation
31 # distributed under the License is distributed on an "AS IS" BASIS,
32 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
33 # See the License for the specific language governing permissions and
34 # limitations under the License.
35 #
36 # ============LICENSE_END============================================
37 #
38 import csv
39 import json
40 import os
41 import subprocess
42 import sys
43
44 import pytest
45
46 from update_reqs import get_requirements
47
48 THIS_DIR = os.path.dirname(os.path.abspath(__file__))
49 CURRENT_NEEDS_PATH = os.path.join(THIS_DIR, "ice_validator/heat_requirements.json")
50
51
52 class Traceability:
53
54     PATH = os.path.join(THIS_DIR, "ice_validator/output/traceability.csv")
55     TEST_FILE = 6
56     TEST_NAME = 7
57     IS_TESTABLE = 5
58     REQ_ID = 0
59
60     def __init__(self):
61         with open(self.PATH, "r") as f:
62             rows = csv.reader(f)
63             next(rows)  # skip header
64             self.mappings = list(rows)
65
66     def unmapped_requirement_errors(self):
67         """
68         Returns list of errors where a requirement is testable, but no test was found.
69         """
70         testable_mappings = [m for m in self.mappings if m[self.IS_TESTABLE] == "True"]
71         return [
72             f"Missing test for {m[self.REQ_ID]}"
73             for m in testable_mappings
74             if not m[self.TEST_NAME]
75         ]
76
77     def mapped_non_testable_requirement_errors(self):
78         """
79         Returns list of errors where the requirement isn't testable, but a test was
80         found.
81         """
82         non_testables = [m for m in self.mappings if m[self.IS_TESTABLE] == "False"]
83         return [
84             (
85                 f"No test for {m[0]} is needed, but found: "
86                 f"{m[self.TEST_FILE]}::{m[self.TEST_NAME]} "
87             )
88             for m in non_testables
89             if m[self.TEST_NAME]
90         ]
91
92
93 def current_version(needs):
94     """Extracts and returns the needs under the current version"""
95     return needs["versions"][needs["current_version"]]["needs"]
96
97
98 def in_scope(_, req_metadata):
99     """
100     Checks if requirement is relevant to VVP.
101
102     :param: _: not used
103     :param req_metadata: needs metadata about the requirement
104     :return: True if the requirement is a testable, Heat requirement
105     """
106     return (
107         "Heat" in req_metadata.get("docname", "")
108         and "MUST" in req_metadata.get("keyword", "").upper()
109         and req_metadata.get("validation_mode", "").lower() != "none"
110     )
111
112
113 def select_items(predicate, source_dict):
114     """
115     Creates a new dict from the source dict where the items match the given predicate
116     :param predicate: predicate function that must accept a two arguments (key & value)
117     :param source_dict: input dictionary to select from
118     :return: filtered dict
119     """
120     return {k: v for k, v in source_dict.items() if predicate(k, v)}
121
122
123 def check_requirements_up_to_date():
124     """
125     Checks if the requirements file packaged with VVP has meaningful differences
126     to the requirements file published from VNFRQTS.
127     :return: list of errors found
128     """
129     msg = ["heat_requirements.json is out-of-date. Run update_reqs.py to update."]
130     latest_needs = json.load(get_requirements())
131     with open(CURRENT_NEEDS_PATH, "r") as f:
132         current_needs = json.load(f)
133     latest_reqs = select_items(in_scope, current_version(latest_needs))
134     current_reqs = select_items(in_scope, current_version(current_needs))
135     if set(latest_reqs.keys()) != set(current_reqs.keys()):
136         return msg
137     if not all(
138         latest["description"] == current_reqs[r_id]["description"]
139         for r_id, latest in latest_reqs.items()
140     ):
141         return msg
142     return None
143
144
145 def check_self_test_pass():
146     """
147     Run pytest self-test and ensure it passes
148     :return:
149     """
150     original_dir = os.getcwd()
151     try:
152         os.chdir(os.path.join(THIS_DIR, "ice_validator"))
153         if pytest.main(["tests", "--self-test"]) != 0:
154             return ["VVP self-test failed. Run pytest --self-test and fix errors."]
155     finally:
156         os.chdir(original_dir)
157
158
159 def check_testable_requirements_are_mapped():
160     tracing = Traceability()
161     return tracing.unmapped_requirement_errors()
162
163
164 def check_non_testable_requirements_are_not_mapped():
165     tracing = Traceability()
166     return tracing.mapped_non_testable_requirement_errors()
167
168
169 def check_flake8_passes():
170     result = subprocess.run(["flake8", "."], encoding="utf-8", capture_output=True)
171     msgs = result.stdout.split("\n") if result.returncode != 0 else []
172     return ["flake8 errors detected:"] + [f"  {e}" for e in msgs] if msgs else []
173
174
175 if __name__ == "__main__":
176     checks = [
177         check_self_test_pass,
178         check_requirements_up_to_date,
179         check_testable_requirements_are_mapped,
180         check_non_testable_requirements_are_not_mapped,
181         check_flake8_passes,
182     ]
183     results = [check() for check in checks]
184     errors = "\n".join("\n".join(msg) for msg in results if msg)
185     print(errors or "Everything looks good!")
186     sys.exit(1 if errors else 0)