From 4595c2e0f77d2096daa00341d574bdc8223688c0 Mon Sep 17 00:00:00 2001 From: Joshua McClung <57977199+jeffeth-donaldson@users.noreply.github.com> Date: Mon, 12 Jun 2023 08:03:41 -0400 Subject: [PATCH] Add option to check fixed resolutions (#134) * Add option to check against resolutions * update readme * fix flake errors --------- Co-authored-by: Josh McClung --- README.rst | 5 ++- pytest_jira.py | 38 +++++++++++++++++++++-- tests/test_jira.py | 76 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index d918177..619635a 100644 --- a/README.rst +++ b/README.rst @@ -125,6 +125,8 @@ If you specify version, open issues will be **unresolved** only if they also aff Even when the issue is closed, but your version was affected and it was not fixed for your version, the issue will be considered **unresolved**. +If you specify fixed resolutions closed issues will be **unresolved** if they do not also have a **resolved** resolution. + Fixture usage ------------- @@ -181,7 +183,7 @@ Usage [DEFAULT] url = https://jira.atlassian.com - username = USERNAME (or blank for no authentication + username = USERNAME (or blank for no authentication) password = PASSWORD (or blank for no authentication) token = TOKEN (either use token or username and password) # ssl_verification = True/False @@ -191,6 +193,7 @@ Usage # docs_search = False (disable searching for issue id in docs) # issue_regex = REGEX (replace default `[A-Z]+-[0-9]+` regular expression) # resolved_statuses = comma separated list of statuses (closed, resolved) + # resolved_resolutions = comma separated list of resolutions (done, fixed) # run_test_case = True (default value for 'run' parameter) # connection_error_strategy [strict|skip|ignore] Choose how to handle connection errors # return_jira_metadata = False (return Jira issue with metadata instead of boolean result) diff --git a/pytest_jira.py b/pytest_jira.py index babb52c..ab9e959 100644 --- a/pytest_jira.py +++ b/pytest_jira.py @@ -41,6 +41,7 @@ def __init__( version=None, components=None, resolved_statuses=None, + resolved_resolutions=None, run_test_case=DEFAULT_RUN_TEST_CASE, strict_xfail=False, connection_error_strategy=None, @@ -54,6 +55,10 @@ def __init__( self.resolved_statuses = resolved_statuses else: self.resolved_statuses = DEFAULT_RESOLVE_STATUSES + if resolved_resolutions: + self.resolved_resolutions = resolved_resolutions + else: + self.resolved_resolutions = [] self.run_test_case = run_test_case self.connection_error_strategy = connection_error_strategy # Speed up JIRA lookups for duplicate issues @@ -85,8 +90,13 @@ def is_issue_resolved(self, issue_id): # Skip test if issue remains unresolved if self.issue_cache[issue_id] is None: return True - - if self.issue_cache[issue_id]['status'] in self.resolved_statuses: + if self.issue_cache[issue_id]['status'] in self.resolved_statuses and ( + # Issue is resolved if resolutions are not specified + # Or if the issue's resolution mathces a resolved_resolution + len(self.resolved_resolutions) == 0 or \ + self.issue_cache[issue_id]['resolution'] in \ + self.resolved_resolutions + ): return self.fixed_in_version(issue_id) else: return not self.is_affected(issue_id) @@ -261,6 +271,8 @@ def get_issue(self, issue_id, return_jira_metadata): v['name'] for v in field.get('fixVersions', set()) ), 'status': field['status']['name'].lower(), + 'resolution': field['resolution']['name'].lower() if + field['resolution'] else None, } def get_url(self): @@ -452,6 +464,15 @@ def pytest_addoption(parser): help='Comma separated list of resolved statuses (closed, ' 'resolved)' ) + group.addoption('--jira-resolved-resolutions', + action='store', + dest='jira_resolved_resolutions', + default=_get_value( + config, 'DEFAULT', 'resolved_resolutions' + ), + help='Comma separated list of resolved resolutions (done, ' + 'fixed)' + ) group.addoption('--jira-do-not-run-test-case', action='store_false', dest='jira_run_test_case', @@ -479,7 +500,8 @@ def pytest_addoption(parser): action='store_true', dest='return_jira_metadata', default=_get_value( - config, 'DEFAULT', 'return_jira_metadata'), + config, 'DEFAULT', 'return_jira_metadata' + ), help='If set, will return Jira issue with ticket metadata' ) @@ -512,6 +534,15 @@ def pytest_configure(config): if not resolved_statuses: resolved_statuses = list(DEFAULT_RESOLVE_STATUSES) + resolved_resolutions = config.getvalue('jira_resolved_resolutions') + if isinstance(resolved_resolutions, six.string_types): + resolved_resolutions = [ + s.strip().lower() for s in resolved_resolutions.split(',') + if s.strip() + ] + if not resolved_resolutions: + resolved_resolutions = [] + if config.getvalue('jira') and config.getvalue('jira_url'): jira_connection = JiraSiteConnection( config.getvalue('jira_url'), @@ -532,6 +563,7 @@ def pytest_configure(config): config.getvalue('jira_product_version'), components, resolved_statuses, + resolved_resolutions, config.getvalue('jira_run_test_case'), config.getini("xfail_strict"), config.getvalue('jira_connection_error_strategy'), diff --git a/tests/test_jira.py b/tests/test_jira.py index bdab418..adb4fa0 100644 --- a/tests/test_jira.py +++ b/tests/test_jira.py @@ -52,6 +52,20 @@ "fixed_versions": set(["foo-0.2"]), "status": "closed", }, + "ORG-1515": { + "components": set(['component2', 'component3']), + "versions": set(["foo-0.1", "foo-0.2"]), + "fixed_versions": set(["foo-0.2"]), + "status": "closed", + "resolution": "won't fix" + }, + "ORG-1516": { + "components": set(['component2', 'component3']), + "versions": set(["foo-0.1", "foo-0.2"]), + "fixed_versions": set(["foo-0.2"]), + "status": "closed", + "resolution": "done" + }, } @@ -1053,3 +1067,65 @@ def test_pass(jira_issue): ) result = testdir.runpytest(*ARGS) result.assert_outcomes(1, 0, 0) + + +def test_closed_nofix_nooption(testdir): + testdir.makeconftest(CONFTEST) + testdir.makepyfile(""" + import pytest + + @pytest.mark.jira("ORG-1515", run=False) + def test_pass(): + assert False + """) + result = testdir.runpytest(*PLUGIN_ARGS) + result.assert_outcomes(0, 0, 1) + + +def test_closed_nofix_option(testdir): + testdir.makeconftest(CONFTEST) + testdir.makepyfile(""" + import pytest + + @pytest.mark.jira("ORG-1515", run=False) + def test_pass(): + assert False + """) + ARGS = ( + '--jira', + '--jira-url', PUBLIC_JIRA_SERVER, + '--jira-resolved-resolutions', 'done,fixed,completed' + ) + result = testdir.runpytest(*ARGS) + result.assert_outcomes(0, 1, 0) + + +def test_closed_fixed_nooption(testdir): + testdir.makeconftest(CONFTEST) + testdir.makepyfile(""" + import pytest + + @pytest.mark.jira("ORG-1516", run=False) + def test_pass(): + assert True + """) + result = testdir.runpytest(*PLUGIN_ARGS) + result.assert_outcomes(1, 0, 0) + + +def test_closed_fixed_option(testdir): + testdir.makeconftest(CONFTEST) + testdir.makepyfile(""" + import pytest + + @pytest.mark.jira("ORG-1516", run=False) + def test_pass(): + assert True + """) + ARGS = ( + '--jira', + '--jira-url', PUBLIC_JIRA_SERVER, + '--jira-resolved-resolutions', 'done,fixed,completed' + ) + result = testdir.runpytest(*ARGS) + result.assert_outcomes(1, 0, 0)