From 88182d29e44e4bae3503eb48cb7d21e14d684fd6 Mon Sep 17 00:00:00 2001 From: Lukas Holecek Date: Jun 07 2018 13:51:21 +0000 Subject: Print satisfied requirements when verbose flag is true Fixes #124 --- diff --git a/functional-tests/test_api_v1.py b/functional-tests/test_api_v1.py index fcf62cf..45a023c 100644 --- a/functional-tests/test_api_v1.py +++ b/functional-tests/test_api_v1.py @@ -244,6 +244,15 @@ def test_make_a_decision_with_verbose_flag(requests_session, greenwave_server, t expected_waivers = [] assert res_data['waivers'] == expected_waivers + expected_satisfied_requirements = [ + { + 'result_id': result['id'], + 'testcase': result['testcase']['name'], + 'type': 'test-result-passed', + } for result in results + ] + assert res_data['satisfied_requirements'] == expected_satisfied_requirements + def test_make_a_decision_on_failed_result_with_waiver( requests_session, greenwave_server, testdatabuilder): diff --git a/greenwave/api_v1.py b/greenwave/api_v1.py index af49fa8..b0f95da 100644 --- a/greenwave/api_v1.py +++ b/greenwave/api_v1.py @@ -207,6 +207,9 @@ def make_decision(): 'type': 'test-result-missing' } ], + "satisfied_requirements": [ + ... + ], "results": [ { 'data': { @@ -350,6 +353,8 @@ def make_decision(): res.update({ 'results': results, 'waivers': waivers, + 'satisfied_requirements': + [answer.to_json() for answer in answers if answer.is_satisfied], }) resp = jsonify(res) resp = insert_headers(resp) diff --git a/greenwave/policies.py b/greenwave/policies.py index 35d8186..b067ca2 100644 --- a/greenwave/policies.py +++ b/greenwave/policies.py @@ -50,7 +50,11 @@ class Answer(object): a subclass, depending on what the answer was. """ - pass + def to_json(self): + """ + Returns a machine-readable description of the problem for API responses. + """ + raise NotImplementedError() class RuleSatisfied(Answer): @@ -71,12 +75,6 @@ class RuleNotSatisfied(Answer): is_satisfied = False - def to_json(self): - """ - Returns a machine-readable description of the problem for API responses. - """ - raise NotImplementedError() - class TestResultMissing(RuleNotSatisfied): """ @@ -102,6 +100,26 @@ class TestResultMissing(RuleNotSatisfied): } +class TestResultMissingWaived(RuleSatisfied): + """ + Same as TestResultMissing but the result was waived. + """ + def __init__(self, subject_type, subject_identifier, test_case_name, scenario): + self.subject_type = subject_type + self.subject_identifier = subject_identifier + self.test_case_name = test_case_name + self.scenario = scenario + + def to_json(self): + return { + 'type': 'test-result-missing-waived', + 'testcase': self.test_case_name, + 'subject_type': self.subject_type, + 'subject_identifier': self.subject_identifier, + 'scenario': self.scenario, + } + + class TestResultFailed(RuleNotSatisfied): """ A required test case did not pass (that is, its outcome in ResultsDB was @@ -128,6 +146,55 @@ class TestResultFailed(RuleNotSatisfied): } +class TestResultPassed(RuleSatisfied): + """ + A required test case passed (that is, its outcome in ResultsDB was + ``PASSED`` or ``INFO``) or a corresponding waiver was found. + """ + def __init__(self, test_case_name, result_id): + self.test_case_name = test_case_name + self.result_id = result_id + + def to_json(self): + return { + 'type': 'test-result-passed', + 'testcase': self.test_case_name, + 'result_id': self.result_id, + } + + +class TestCaseNotApplicable(RuleSatisfied): + """ + A required test case is not applicable to given subject. + """ + def __init__(self, subject_type, subject_identifier, test_case_name): + self.subject_type = subject_type + self.subject_identifier = subject_identifier + self.test_case_name = test_case_name + + def to_json(self): + return { + 'type': 'test-case-not-applicable', + 'testcase': self.test_case_name, + 'subject_type': self.subject_type, + 'subject_identifier': self.subject_identifier, + } + + +class BlacklistedInPolicy(RuleSatisfied): + """ + Package was blacklisted in policy. + """ + def __init__(self, subject_identifier): + self.subject_identifier = subject_identifier + + def to_json(self): + return { + 'type': 'blacklisted', + 'subject_identifier': self.subject_identifier, + } + + def summarize_answers(answers): """ Produces a one-sentence human-readable summary of the result of evaluating a policy. @@ -194,31 +261,31 @@ class RemoteOriginalSpecNvrRule(Rule): def check(self, subject_type, subject_identifier, results, waivers): if subject_type != 'koji_build': - return RuleSatisfied() + return [] pkg_name = subject_identifier.rsplit('-', 2)[0] rev = greenwave.resources.retrieve_rev_from_koji(subject_identifier) response = greenwave.resources.retrieve_yaml_remote_original_spec_nvr_rule(rev, pkg_name) - if isinstance(response, RuleSatisfied): + if response is None: # greenwave extension file not found - return RuleSatisfied() - else: - policies = yaml.safe_load_all(response) - # policies is a generator, so listifying it - policies = list(policies) - # policies in dist-git are always about a package - for policy in policies: - policy.subject_type = 'koji_build' - validate_policies(policies, [RemoteOriginalSpecNvrRule]) - answers = [] - for policy in policies: - response = policy.check(subject_identifier, results, waivers) - if isinstance(response, list): - answers.extend(response) - else: - answers.append(response) - return answers + return [] + + policies = yaml.safe_load_all(response) + # policies is a generator, so listifying it + policies = list(policies) + # policies in dist-git are always about a package + for policy in policies: + policy.subject_type = 'koji_build' + validate_policies(policies, [RemoteOriginalSpecNvrRule]) + answers = [] + for policy in policies: + response = policy.check(subject_identifier, results, waivers) + if isinstance(response, list): + answers.extend(response) + else: + answers.append(response) + return answers def to_json(self): return { @@ -250,23 +317,22 @@ class PassingTestCaseRule(Rule): if not matching_waivers: return TestResultMissing(subject_type, subject_identifier, self.test_case_name, self._scenario) - else: - # The result is absent, but the absence is waived. - return RuleSatisfied() + return TestResultMissingWaived( + subject_type, subject_identifier, self.test_case_name, self._scenario) # If we find multiple matching results, we always use the first one which # will be the latest chronologically, because ResultsDB always returns # results ordered by `submit_time` descending. matching_result = matching_results[0] if matching_result['outcome'] in ['PASSED', 'INFO']: - return RuleSatisfied() + return TestResultPassed(self.test_case_name, matching_result['id']) # XXX limit who is allowed to waive if any(w['subject'] == dict([(key, value[0]) for key, value in matching_result['data'].items()]) and w['testcase'] == matching_result['testcase']['name'] and w['waived'] for w in waivers): - return RuleSatisfied() + return TestResultPassed(self.test_case_name, matching_result['id']) return TestResultFailed(subject_type, subject_identifier, self.test_case_name, self._scenario, matching_result['id']) @@ -313,11 +379,11 @@ class PackageSpecificRule(Rule): """ if subject_type != 'koji_build': - return RuleSatisfied() + return TestCaseNotApplicable(subject_type, subject_identifier, self.test_case_name) pkg_name = subject_identifier.rsplit('-', 2)[0] if not any(fnmatch(pkg_name, repo) for repo in self.repos): - return RuleSatisfied() + return TestCaseNotApplicable(subject_type, subject_identifier, self.test_case_name) rule = PassingTestCaseRule() # pylint: disable=attribute-defined-outside-init @@ -360,7 +426,7 @@ class Policy(yaml.YAMLObject): if self.subject_type == 'koji_build': name = subject_identifier.rsplit('-', 2)[0] if name in self.blacklist: - return [RuleSatisfied() for rule in self.rules] + return [BlacklistedInPolicy(subject_identifier) for rule in self.rules] answers = [] for rule in self.rules: response = rule.check(self.subject_type, subject_identifier, results, waivers) diff --git a/greenwave/resources.py b/greenwave/resources.py index dcdac53..d8b8a8d 100644 --- a/greenwave/resources.py +++ b/greenwave/resources.py @@ -18,7 +18,6 @@ from werkzeug.exceptions import BadGateway from greenwave.cache import cached import greenwave.utils -import greenwave.policies log = logging.getLogger(__name__) @@ -58,8 +57,9 @@ def retrieve_yaml_remote_original_spec_nvr_rule(rev, pkg_name): headers={'Content-Type': 'application/json'}, timeout=60) if response.status_code == 404: - return greenwave.policies.RuleSatisfied() - elif response.status_code != 200: + return None + + if response.status_code != 200: raise BadGateway('Error occurred looking for gating.yaml file in the dist-git repo.') # gating.yaml found...