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 diff --git a/src/pytest_dependency.py b/src/pytest_dependency.py index 43224ee..ba44eb9 100644 --- a/src/pytest_dependency.py +++ b/src/pytest_dependency.py @@ -18,7 +18,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] @@ -30,6 +30,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. @@ -73,10 +76,28 @@ def addResult(self, item, name, rep): else: raise RuntimeError("Internal error: invalid scope '%s'" % self.scope) - status = self.results.setdefault(name, DependencyItemStatus()) + # 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 + if not name.endswith(original): + # remove the parametrization part at the end + index = name.rindex(original) + len(original) + 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) + + if name not in self.results: + self.results[name] = DependencyItemStatus() + # 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 ...", @@ -126,19 +147,25 @@ def depends(request, other, scope='module'): def pytest_addoption(parser): - parser.addini("automark_dependency", - "Add the dependency marker to all tests automatically", - type="bool", default=False) - parser.addoption("--ignore-unknown-dependency", - action="store_true", default=False, - help="ignore dependencies whose outcome is not known") + parser.addini( + "automark_dependency", + "Add the dependency marker to all tests automatically", + type="bool", + default=False, + ) + parser.addoption( + "--ignore-unknown-dependency", + action="store_true", + default=False, + help="ignore dependencies whose outcome is not known", + ) def pytest_configure(config): global _automark, _ignore_unknown _automark = 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.") diff --git a/tests/test_03_param.py b/tests/test_03_param.py index 62cb5ba..cdf5c30 100644 --- a/tests/test_03_param.py +++ b/tests/test_03_param.py @@ -4,6 +4,58 @@ 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): + # 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_d(): + pass + + @pytest.mark.dependency(depends=["test_b"]) + def test_e(): + pass + + @pytest.mark.dependency(depends=["test_c"]) + def test_f(): + pass + """) + result = ctestdir.runpytest("--verbose") + 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\] FAILED + .*::test_b\[1\] PASSED + .*::test_c\[0\] PASSED + .*::test_c\[1\] PASSED + .*::test_d SKIPPED(?:\s+\(.*\))? + .*::test_e SKIPPED(?:\s+\(.*\))? + .*::test_f PASSED + """) + + def test_simple_params(ctestdir): """Simple test for a dependency on a parametrized test.