From 5ff7ed0cf3ac9e8110579ee4f0f711e30fb2511e Mon Sep 17 00:00:00 2001 From: "Lovett, Trevor" Date: Mon, 13 May 2019 14:01:09 -0500 Subject: [VVP] Update validations based on VNFRQTS-637 Update to the latest bundled requirements text Update aap_exempt message to better reflect verbiage Remove unneeded test: tests_neutron_port_addresses (requirement removed) Map aap_exempt requirement to associated tests Also adding new helper scripts to help detect divergences between VNF Requirements and VVP as well as other VVP best practices: checks.py - Pre-commit checks - requirements are up-to-date with VNFRQTS - all testable requirements have tests - all non-testable requirements are *not* mapped to tests - flake8 passes - self-test passes update_reqs.py - Updates the the contents of heat_requirements.json with latest req'ts from VNFRQTS Nexus artifact Change-Id: Ia197de3254a1a0369224939f66a5f98c601a314d Issue-ID: VVP-216 Signed-off-by: Lovett, Trevor --- checks.py | 186 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 checks.py (limited to 'checks.py') diff --git a/checks.py b/checks.py new file mode 100644 index 0000000..70bdcd2 --- /dev/null +++ b/checks.py @@ -0,0 +1,186 @@ +# -*- coding: utf8 -*- +# ============LICENSE_START==================================================== +# org.onap.vvp/validation-scripts +# =================================================================== +# Copyright © 2019 AT&T Intellectual Property. All rights reserved. +# =================================================================== +# +# Unless otherwise specified, all software contained herein is licensed +# under the Apache License, Version 2.0 (the "License"); +# you may not use this software except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# +# +# Unless otherwise specified, all documentation contained herein is licensed +# under the Creative Commons License, Attribution 4.0 Intl. (the "License"); +# you may not use this documentation except in compliance with the License. +# You may obtain a copy of the License at +# +# https://creativecommons.org/licenses/by/4.0/ +# +# Unless required by applicable law or agreed to in writing, documentation +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ============LICENSE_END============================================ +# +import csv +import json +import os +import subprocess +import sys + +import pytest + +from update_reqs import get_requirements + +THIS_DIR = os.path.dirname(os.path.abspath(__file__)) +CURRENT_NEEDS_PATH = os.path.join(THIS_DIR, "ice_validator/heat_requirements.json") + + +class Traceability: + + PATH = os.path.join(THIS_DIR, "ice_validator/output/traceability.csv") + TEST_FILE = 6 + TEST_NAME = 7 + IS_TESTABLE = 5 + REQ_ID = 0 + + def __init__(self): + with open(self.PATH, "r") as f: + rows = csv.reader(f) + next(rows) # skip header + self.mappings = list(rows) + + def unmapped_requirement_errors(self): + """ + Returns list of errors where a requirement is testable, but no test was found. + """ + testable_mappings = [m for m in self.mappings if m[self.IS_TESTABLE] == "True"] + return [ + f"Missing test for {m[self.REQ_ID]}" + for m in testable_mappings + if not m[self.TEST_NAME] + ] + + def mapped_non_testable_requirement_errors(self): + """ + Returns list of errors where the requirement isn't testable, but a test was + found. + """ + non_testables = [m for m in self.mappings if m[self.IS_TESTABLE] == "False"] + return [ + ( + f"No test for {m[0]} is needed, but found: " + f"{m[self.TEST_FILE]}::{m[self.TEST_NAME]} " + ) + for m in non_testables + if m[self.TEST_NAME] + ] + + +def current_version(needs): + """Extracts and returns the needs under the current version""" + return needs["versions"][needs["current_version"]]["needs"] + + +def in_scope(_, req_metadata): + """ + Checks if requirement is relevant to VVP. + + :param: _: not used + :param req_metadata: needs metadata about the requirement + :return: True if the requirement is a testable, Heat requirement + """ + return ( + "Heat" in req_metadata.get("docname", "") + and "MUST" in req_metadata.get("keyword", "").upper() + and req_metadata.get("validation_mode", "").lower() != "none" + ) + + +def select_items(predicate, source_dict): + """ + Creates a new dict from the source dict where the items match the given predicate + :param predicate: predicate function that must accept a two arguments (key & value) + :param source_dict: input dictionary to select from + :return: filtered dict + """ + return {k: v for k, v in source_dict.items() if predicate(k, v)} + + +def check_requirements_up_to_date(): + """ + Checks if the requirements file packaged with VVP has meaningful differences + to the requirements file published from VNFRQTS. + :return: list of errors found + """ + msg = ["heat_requirements.json is out-of-date. Run update_reqs.py to update."] + latest_needs = json.load(get_requirements()) + with open(CURRENT_NEEDS_PATH, "r") as f: + current_needs = json.load(f) + latest_reqs = select_items(in_scope, current_version(latest_needs)) + current_reqs = select_items(in_scope, current_version(current_needs)) + if set(latest_reqs.keys()) != set(current_reqs.keys()): + return msg + if not all( + latest["description"] == current_reqs[r_id]["description"] + for r_id, latest in latest_reqs.items() + ): + return msg + return None + + +def check_self_test_pass(): + """ + Run pytest self-test and ensure it passes + :return: + """ + original_dir = os.getcwd() + try: + os.chdir(os.path.join(THIS_DIR, "ice_validator")) + if pytest.main(["tests", "--self-test"]) != 0: + return ["VVP self-test failed. Run pytest --self-test and fix errors."] + finally: + os.chdir(original_dir) + + +def check_testable_requirements_are_mapped(): + tracing = Traceability() + return tracing.unmapped_requirement_errors() + + +def check_non_testable_requirements_are_not_mapped(): + tracing = Traceability() + return tracing.mapped_non_testable_requirement_errors() + + +def check_flake8_passes(): + result = subprocess.run(["flake8", "."], encoding="utf-8", capture_output=True) + msgs = result.stdout.split("\n") if result.returncode != 0 else [] + return ["flake8 errors detected:"] + [f" {e}" for e in msgs] if msgs else [] + + +if __name__ == "__main__": + checks = [ + check_self_test_pass, + check_requirements_up_to_date, + check_testable_requirements_are_mapped, + check_non_testable_requirements_are_not_mapped, + check_flake8_passes, + ] + results = [check() for check in checks] + errors = "\n".join("\n".join(msg) for msg in results if msg) + print(errors or "Everything looks good!") + sys.exit(1 if errors else 0) -- cgit 1.2.3-korg