From 0881560af6cf1026dc95c8d9e31e433899b83973 Mon Sep 17 00:00:00 2001 From: DevilXD Date: Sun, 7 Jun 2020 09:38:48 +0200 Subject: [PATCH 1/5] Remove parametrization when storing dependency results --- pytest_dependency.py | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/pytest_dependency.py b/pytest_dependency.py index 9ebf94a..ced5f20 100644 --- a/pytest_dependency.py +++ b/pytest_dependency.py @@ -32,7 +32,7 @@ class DependencyItemStatus(object): Phases = ('setup', 'call', 'teardown') def __init__(self): - self.results = { w:None for w in self.Phases } + self.results = {w: None for w in self.Phases} def __str__(self): l = ["%s: %s" % (w, self.results[w]) for w in self.Phases] @@ -44,6 +44,9 @@ def addResult(self, rep): def isSuccess(self): return list(self.results.values()) == ['passed', 'passed', 'passed'] + def isDone(self): + return None not in self.results.values() + class DependencyManager(object): """Dependency manager, stores the results of tests. @@ -87,7 +90,21 @@ def addResult(self, item, name, rep): else: raise RuntimeError("Internal error: invalid scope '%s'" % self.scope) - status = self.results.setdefault(name, DependencyItemStatus()) + + original = item.originalname if item.originalname is not None else item.name + # remove the parametrization part at the end + if not name.endswith(original): + index = name.rindex(original) + len(original) + name = name[:index] + + # check if we failed - if so, return without adding the result + if name not in self.results: + self.results[name] = DependencyItemStatus() + status = self.results[name] + if status.isDone() and not status.isSuccess(): + return + + # add the result logger.debug("register %s %s %s in %s scope", rep.when, name, rep.outcome, self.scope) status.addResult(rep) @@ -140,11 +157,11 @@ def depends(request, other, scope='module'): def pytest_addoption(parser): - parser.addini("automark_dependency", - "Add the dependency marker to all tests automatically", + parser.addini("automark_dependency", + "Add the dependency marker to all tests automatically", default=False) - parser.addoption("--ignore-unknown-dependency", - action="store_true", default=False, + parser.addoption("--ignore-unknown-dependency", + action="store_true", default=False, help="ignore dependencies whose outcome is not known") @@ -152,7 +169,7 @@ def pytest_configure(config): global _automark, _ignore_unknown _automark = _get_bool(config.getini("automark_dependency")) _ignore_unknown = config.getoption("--ignore-unknown-dependency") - config.addinivalue_line("markers", + config.addinivalue_line("markers", "dependency(name=None, depends=[]): " "mark a test to be used as a dependency for " "other tests or to depend on other tests.") From 67b32c3edadfae4e898c5f34a87ad2ed254cdf6b Mon Sep 17 00:00:00 2001 From: DevilXD Date: Sat, 8 Jan 2022 21:07:24 +0100 Subject: [PATCH 2/5] gitignore local editable install --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 9fb4879..5929919 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,5 @@ __pycache__/ /doc/latex/ /doc/linkcheck/ /pytest_dependency.egg-info/ +/src/pytest_dependency.egg-info/ /python2_6.patch From 03fa9032489053d37c513c33af5227c41b9f4e15 Mon Sep 17 00:00:00 2001 From: DevilXD Date: Sat, 8 Jan 2022 21:08:09 +0100 Subject: [PATCH 3/5] Fix parameterless dependencies breaking some test cases --- src/pytest_dependency.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/pytest_dependency.py b/src/pytest_dependency.py index cea7d72..ba44eb9 100644 --- a/src/pytest_dependency.py +++ b/src/pytest_dependency.py @@ -76,24 +76,28 @@ def addResult(self, item, name, rep): else: raise RuntimeError("Internal error: invalid scope '%s'" % self.scope) - + # store an extra result for parameterless name + # this enables dependencies based on an overall test status original = item.originalname if item.originalname is not None else item.name - # remove the parametrization part at the end if not name.endswith(original): + # remove the parametrization part at the end index = name.rindex(original) + len(original) - name = name[:index] + parameterless_name = name[:index] + if parameterless_name not in self.results: + self.results[parameterless_name] = DependencyItemStatus() + status = self.results[parameterless_name] + # only add the result if the status is incomplete or it's (still) a success + # this prevents overwriting a failed status of one parametrized test, + # with a success status of the following tests + if not status.isDone() or status.isSuccess(): + status.addResult(rep) - # check if we failed - if so, return without adding the result if name not in self.results: self.results[name] = DependencyItemStatus() - status = self.results[name] - if status.isDone() and not status.isSuccess(): - return - # add the result logger.debug("register %s %s %s in %s scope", rep.when, name, rep.outcome, self.scope) - status.addResult(rep) + self.results[name].addResult(rep) def checkDepend(self, depends, item): logger.debug("check dependencies of %s in %s scope ...", From bdc3deffca54ddf954f05c3bf46ee9dbffb99f24 Mon Sep 17 00:00:00 2001 From: DevilXD Date: Sat, 8 Jan 2022 21:08:42 +0100 Subject: [PATCH 4/5] Add a test for parameterless dependency --- tests/test_03_param.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/test_03_param.py b/tests/test_03_param.py index 62cb5ba..e4a94c7 100644 --- a/tests/test_03_param.py +++ b/tests/test_03_param.py @@ -4,6 +4,43 @@ import pytest +def test_removed_params(ctestdir): + """ + Test for a dependency on a parametrized test, but with parametrization removed. + """ + ctestdir.makepyfile(""" + import pytest + + @pytest.mark.parametrize("x", [ 0, 1 ]) + @pytest.mark.dependency() + def test_a(x): + assert x == 0 + + @pytest.mark.parametrize("x", [ 0, 1 ]) + @pytest.mark.dependency() + def test_b(x): + pass + + @pytest.mark.dependency(depends=["test_a"]) + def test_c(): + pass + + @pytest.mark.dependency(depends=["test_b"]) + def test_d(): + pass + """) + result = ctestdir.runpytest("--verbose") + result.assert_outcomes(passed=4, skipped=1, failed=1) + result.stdout.re_match_lines(r""" + .*::test_a\[0\] PASSED + .*::test_a\[1\] FAILED + .*::test_b\[0\] PASSED + .*::test_b\[1\] PASSED + .*::test_c SKIPPED(?:\s+\(.*\))? + .*::test_d PASSED + """) + + def test_simple_params(ctestdir): """Simple test for a dependency on a parametrized test. From 56468270d472b9543aae565da4e36604f1157f6a Mon Sep 17 00:00:00 2001 From: DevilXD Date: Sat, 8 Jan 2022 21:14:13 +0100 Subject: [PATCH 5/5] Improve the test to properly cover the if statement --- tests/test_03_param.py | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/tests/test_03_param.py b/tests/test_03_param.py index e4a94c7..cdf5c30 100644 --- a/tests/test_03_param.py +++ b/tests/test_03_param.py @@ -14,30 +14,45 @@ def test_removed_params(ctestdir): @pytest.mark.parametrize("x", [ 0, 1 ]) @pytest.mark.dependency() def test_a(x): + # passes, then fails assert x == 0 @pytest.mark.parametrize("x", [ 0, 1 ]) @pytest.mark.dependency() def test_b(x): + # fails, then passes + assert x == 1 + + @pytest.mark.parametrize("x", [ 0, 1 ]) + @pytest.mark.dependency() + def test_c(x): + # always passes pass @pytest.mark.dependency(depends=["test_a"]) - def test_c(): + def test_d(): pass @pytest.mark.dependency(depends=["test_b"]) - def test_d(): + def test_e(): + pass + + @pytest.mark.dependency(depends=["test_c"]) + def test_f(): pass """) result = ctestdir.runpytest("--verbose") - result.assert_outcomes(passed=4, skipped=1, failed=1) + result.assert_outcomes(passed=5, skipped=2, failed=2) result.stdout.re_match_lines(r""" .*::test_a\[0\] PASSED .*::test_a\[1\] FAILED - .*::test_b\[0\] PASSED + .*::test_b\[0\] FAILED .*::test_b\[1\] PASSED - .*::test_c SKIPPED(?:\s+\(.*\))? - .*::test_d PASSED + .*::test_c\[0\] PASSED + .*::test_c\[1\] PASSED + .*::test_d SKIPPED(?:\s+\(.*\))? + .*::test_e SKIPPED(?:\s+\(.*\))? + .*::test_f PASSED """)