From 0881560af6cf1026dc95c8d9e31e433899b83973 Mon Sep 17 00:00:00 2001 From: DevilXD Date: Sun, 7 Jun 2020 09:38:48 +0200 Subject: [PATCH 01/26] 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 57ae2fa92ab3ea8b4b4865554d55ce7a602a61e3 Mon Sep 17 00:00:00 2001 From: DevilXD Date: Wed, 10 Jun 2020 16:38:10 +0200 Subject: [PATCH 02/26] PEP8, consistency and formatting --- pytest_dependency.py | 143 ++++++++++++++++++++++++------------------- 1 file changed, 81 insertions(+), 62 deletions(-) diff --git a/pytest_dependency.py b/pytest_dependency.py index ced5f20..c1908c0 100644 --- a/pytest_dependency.py +++ b/pytest_dependency.py @@ -12,12 +12,13 @@ def _get_bool(value): - """Evaluate string representation of a boolean value. + """ + Evaluate string representation of a boolean value. """ if value: - if value.lower() in ["0", "no", "n", "false", "f", "off"]: + if value.lower() in ("0", "no", "n", "false", "f", "off"): return False - elif value.lower() in ["1", "yes", "y", "true", "t", "on"]: + elif value.lower() in ("1", "yes", "y", "true", "t", "on"): return True else: raise ValueError("Invalid truth value '%s'" % value) @@ -26,48 +27,52 @@ def _get_bool(value): class DependencyItemStatus(object): - """Status of a test item in a dependency manager. + """ + Status of a test item in a dependency manager. """ - Phases = ('setup', 'call', 'teardown') + 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] - return "Status(%s)" % ", ".join(l) + return "Status(%s)" % ", ".join( + "%s: %s" % (w, self.results[w]) for w in self.phases + ) - def addResult(self, rep): + def add_result(self, rep): self.results[rep.when] = rep.outcome - def isSuccess(self): - return list(self.results.values()) == ['passed', 'passed', 'passed'] + def is_success(self): + return all(v == "passed" for v in self.results.values()) - def isDone(self): + def is_done(self): return None not in self.results.values() class DependencyManager(object): - """Dependency manager, stores the results of tests. + """ + Dependency manager, stores the results of tests. """ - ScopeCls = { - 'session': pytest.Session, - 'package': pytest.Package, - 'module': pytest.Module, - 'class': pytest.Class, + scope_cls = { + "session": pytest.Session, + "package": pytest.Package, + "module": pytest.Module, + "class": pytest.Class, } @classmethod - def getManager(cls, item, scope): - """Get the DependencyManager object from the node at scope level. + def get_manager(cls, item, scope): + """ + Get the DependencyManager object from the node at scope level. Create it, if not yet present. """ - node = item.getparent(cls.ScopeCls[scope]) + node = item.getparent(cls.scope_cls[scope]) if not node: return None - if not hasattr(node, 'dependencyManager'): + if not hasattr(node, "dependencyManager"): node.dependencyManager = cls(scope) return node.dependencyManager @@ -75,21 +80,22 @@ def __init__(self, scope): self.results = {} self.scope = scope - def addResult(self, item, name, rep): + def add_result(self, item, name, rep): if not name: # Old versions of pytest used to add an extra "::()" to # the node ids of class methods to denote the class # instance. This has been removed in pytest 4.0.0. nodeid = item.nodeid.replace("::()::", "::") - if self.scope == 'session' or self.scope == 'package': + if self.scope == "session" or self.scope == "package": name = nodeid - elif self.scope == 'module': + elif self.scope == "module": name = nodeid.split("::", 1)[1] - elif self.scope == 'class': + elif self.scope == "class": name = nodeid.split("::", 2)[2] else: - raise RuntimeError("Internal error: invalid scope '%s'" - % self.scope) + raise RuntimeError( + "Internal error: invalid scope '%s'" % self.scope + ) original = item.originalname if item.originalname is not None else item.name # remove the parametrization part at the end @@ -101,20 +107,24 @@ def addResult(self, item, name, rep): if name not in self.results: self.results[name] = DependencyItemStatus() status = self.results[name] - if status.isDone() and not status.isSuccess(): + if status.is_done() and not status.is_success(): return # add the result - logger.debug("register %s %s %s in %s scope", - rep.when, name, rep.outcome, self.scope) - status.addResult(rep) - - def checkDepend(self, depends, item): - logger.debug("check dependencies of %s in %s scope ...", - item.name, self.scope) + logger.debug( + "register %s %s %s in %s scope", + rep.when, name, rep.outcome, self.scope + ) + status.add_result(rep) + + def check_depend(self, depends, item): + logger.debug( + "check dependencies of %s in %s scope ...", + item.name, self.scope + ) for i in depends: if i in self.results: - if self.results[i].isSuccess(): + if self.results[i].is_success(): logger.debug("... %s succeeded", i) continue else: @@ -127,8 +137,9 @@ def checkDepend(self, depends, item): pytest.skip("%s depends on %s" % (item.name, i)) -def depends(request, other, scope='module'): - """Add dependency on other test. +def depends(request, other, scope="module"): + """ + Add dependency on other test. Call pytest.skip() unless a successful outcome of all of the tests in other has been registered previously. This has the same effect as @@ -152,52 +163,60 @@ def depends(request, other, scope='module'): the scope parameter has been added. """ item = request.node - manager = DependencyManager.getManager(item, scope=scope) - manager.checkDepend(other, item) + manager = DependencyManager.get_manager(item, scope=scope) + manager.check_depend(other, item) def pytest_addoption(parser): - parser.addini("automark_dependency", - "Add the dependency marker to all tests automatically", - 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", + 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 = _get_bool(config.getini("automark_dependency")) _ignore_unknown = config.getoption("--ignore-unknown-dependency") - 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.") + 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." + ) @pytest.hookimpl(tryfirst=True, hookwrapper=True) def pytest_runtest_makereport(item, call): - """Store the test outcome if this item is marked "dependency". + """ + Store the test outcome if this item is marked "dependency". """ outcome = yield marker = item.get_closest_marker("dependency") if marker is not None or _automark: rep = outcome.get_result() - name = marker.kwargs.get('name') if marker is not None else None - for scope in DependencyManager.ScopeCls: - manager = DependencyManager.getManager(item, scope=scope) - if (manager): - manager.addResult(item, name, rep) + name = marker.kwargs.get("name") if marker is not None else None + for scope in DependencyManager.scope_cls: + manager = DependencyManager.get_manager(item, scope=scope) + if manager is not None: + manager.add_result(item, name, rep) def pytest_runtest_setup(item): - """Check dependencies if this item is marked "dependency". + """ + Check dependencies if this item is marked "dependency". Skip if any of the dependencies has not been run successfully. """ marker = item.get_closest_marker("dependency") if marker is not None: - depends = marker.kwargs.get('depends') + depends = marker.kwargs.get("depends") if depends: - scope = marker.kwargs.get('scope', 'module') - manager = DependencyManager.getManager(item, scope=scope) - manager.checkDepend(depends, item) + scope = marker.kwargs.get("scope", "module") + manager = DependencyManager.get_manager(item, scope=scope) + manager.check_depend(depends, item) From 8f5ab12c523e0267929814e7b7451f78beb75a80 Mon Sep 17 00:00:00 2001 From: DevilXD Date: Wed, 10 Jun 2020 16:43:13 +0200 Subject: [PATCH 03/26] Move this to the DependencyManager itself --- pytest_dependency.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/pytest_dependency.py b/pytest_dependency.py index c1908c0..b5b4e2b 100644 --- a/pytest_dependency.py +++ b/pytest_dependency.py @@ -117,6 +117,13 @@ def add_result(self, item, name, rep): ) status.add_result(rep) + @classmethod + def add_all_scopes(cls, item, name, rep): + for scope in cls.scope_cls: + manager = cls.get_manager(item, scope=scope) + if manager is not None: + manager.add_result(item, name, rep) + def check_depend(self, depends, item): logger.debug( "check dependencies of %s in %s scope ...", @@ -202,10 +209,7 @@ def pytest_runtest_makereport(item, call): if marker is not None or _automark: rep = outcome.get_result() name = marker.kwargs.get("name") if marker is not None else None - for scope in DependencyManager.scope_cls: - manager = DependencyManager.get_manager(item, scope=scope) - if manager is not None: - manager.add_result(item, name, rep) + DependencyManager.add_all_scopes(item, name, rep) def pytest_runtest_setup(item): From b128a924d26b054429c3e69867b328324be9e04a Mon Sep 17 00:00:00 2001 From: DevilXD Date: Wed, 10 Jun 2020 16:48:20 +0200 Subject: [PATCH 04/26] Rename the attribute to be PEP8 compilant --- pytest_dependency.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pytest_dependency.py b/pytest_dependency.py index b5b4e2b..18c2770 100644 --- a/pytest_dependency.py +++ b/pytest_dependency.py @@ -72,9 +72,9 @@ def get_manager(cls, item, scope): node = item.getparent(cls.scope_cls[scope]) if not node: return None - if not hasattr(node, "dependencyManager"): - node.dependencyManager = cls(scope) - return node.dependencyManager + if not hasattr(node, "dependency_manager"): + node.dependency_manager = cls(scope) + return node.dependency_manager def __init__(self, scope): self.results = {} From a49069c5e827057183cc98ec75936cf3e81d848f Mon Sep 17 00:00:00 2001 From: DevilXD Date: Wed, 10 Jun 2020 17:45:31 +0200 Subject: [PATCH 05/26] Cleanup imports ordering --- pytest_dependency.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pytest_dependency.py b/pytest_dependency.py index 18c2770..3d4aadd 100644 --- a/pytest_dependency.py +++ b/pytest_dependency.py @@ -1,10 +1,12 @@ """$DOC""" -__version__ = "$VERSION" - import logging + import pytest + +__version__ = "$VERSION" + logger = logging.getLogger(__name__) _automark = False From e704f694f929b9f6f8b8763773b1f41270e72ec5 Mon Sep 17 00:00:00 2001 From: DevilXD Date: Wed, 10 Jun 2020 17:51:47 +0200 Subject: [PATCH 06/26] Added a reordering hook --- pytest_dependency.py | 122 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 98 insertions(+), 24 deletions(-) diff --git a/pytest_dependency.py b/pytest_dependency.py index 3d4aadd..3d5b85b 100644 --- a/pytest_dependency.py +++ b/pytest_dependency.py @@ -1,6 +1,7 @@ """$DOC""" import logging +from collections import deque import pytest @@ -28,6 +29,30 @@ def _get_bool(value): return False +def _remove_parametrization(item, scope): + # Old versions of pytest used to add an extra "::()" to + # the node ids of class methods to denote the class + # instance. This has been removed in pytest 4.0.0. + nodeid = item.nodeid.replace("::()::", "::") + if scope == "session" or scope == "package": + name = nodeid + elif scope == "module": + name = nodeid.split("::", 1)[1] + elif scope == "class": + name = nodeid.split("::", 2)[2] + else: + raise RuntimeError( + "Internal error: invalid scope '%s'" % self.scope + ) + + 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] + return name + + class DependencyItemStatus(object): """ Status of a test item in a dependency manager. @@ -79,31 +104,20 @@ def get_manager(cls, item, scope): return node.dependency_manager def __init__(self, scope): - self.results = {} self.scope = scope + self.results = {} + self.names = set() + self.dependencies = {} + + def register_name(self, name): + self.names.add(name) + + def register_dependency(self, item, name): + self.dependencies[name] = item def add_result(self, item, name, rep): if not name: - # Old versions of pytest used to add an extra "::()" to - # the node ids of class methods to denote the class - # instance. This has been removed in pytest 4.0.0. - nodeid = item.nodeid.replace("::()::", "::") - if self.scope == "session" or self.scope == "package": - name = nodeid - elif self.scope == "module": - name = nodeid.split("::", 1)[1] - elif self.scope == "class": - name = nodeid.split("::", 2)[2] - else: - raise RuntimeError( - "Internal error: invalid scope '%s'" % self.scope - ) - - 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] + name = _remove_parametrization(item, self.scope) # check if we failed - if so, return without adding the result if name not in self.results: @@ -126,7 +140,7 @@ def add_all_scopes(cls, item, name, rep): if manager is not None: manager.add_result(item, name, rep) - def check_depend(self, depends, item): + def check_depends(self, depends, item): logger.debug( "check dependencies of %s in %s scope ...", item.name, self.scope @@ -145,6 +159,18 @@ def check_depend(self, depends, item): logger.info("skip %s because it depends on %s", item.name, i) pytest.skip("%s depends on %s" % (item.name, i)) + def check_order(self, depends, item, name): + if not all(d in self.dependencies for d in depends): + # check to see if we're ever gonna see a dep like that + for d in depends: + if d not in self.names: + item.warn(pytest.PytestWarning( + "Dependency '%s' of '%s' doesn't exist, " + "or has incorrect scope!" % (d, name) + )) + return False + return True + def depends(request, other, scope="module"): """ @@ -173,7 +199,7 @@ def depends(request, other, scope="module"): """ item = request.node manager = DependencyManager.get_manager(item, scope=scope) - manager.check_depend(other, item) + manager.check_depends(other, item) def pytest_addoption(parser): @@ -225,4 +251,52 @@ def pytest_runtest_setup(item): if depends: scope = marker.kwargs.get("scope", "module") manager = DependencyManager.get_manager(item, scope=scope) - manager.check_depend(depends, item) + manager.check_depends(depends, item) + + +# special hook to make pytest-dependency support reordering based on deps +def pytest_collection_modifyitems(items): + # gather dependency names + for item in items: + for marker in item.iter_markers("dependency"): + scope = marker.kwargs.get("scope", "module") + name = marker.kwargs.get("name") + if not name: + name = _remove_parametrization(item, scope) + + manager = DependencyManager.get_manager(item, scope) + manager.register_name(name) + + final_items = [] + + # group the dependencies by their scopes + cycles = 0 + deque_items = deque(items) + while deque_items: + if cycles > len(deque_items): + # seems like we're stuck in a loop now + # just add the remaining items and finish up + final_items.extend(deque_items) + break + item = deque_items.popleft() + for marker in item.iter_markers("dependency"): + depends = marker.kwargs.get("depends", []) + scope = marker.kwargs.get("scope", "module") + name = marker.kwargs.get("name") + if not name: + name = _remove_parametrization(item, scope) + + manager = DependencyManager.get_manager(item, scope) + if manager.check_order(depends, item, name): + manager.register_dependency(item, name) + else: + deque_items.append(item) + cycles += 1 + break + else: + # runs only when the for loop wasn't broken out of + final_items.append(item) + cycles = 0 + + assert len(items) == len(final_items) and all(i in items for i in final_items) + items[:] = final_items From 596c109a58514e1d37c36d315aac0549b671ebf6 Mon Sep 17 00:00:00 2001 From: DevilXD Date: Wed, 10 Jun 2020 19:32:32 +0200 Subject: [PATCH 07/26] Rename the attribute in tests too --- tests/test_01_marker.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_01_marker.py b/tests/test_01_marker.py index 7be11f1..f1337f5 100644 --- a/tests/test_01_marker.py +++ b/tests/test_01_marker.py @@ -19,9 +19,9 @@ def test_marker(ctestdir): @pytest.mark.dependency() def test_marker(request): node = request.node.getparent(pytest.Module) - assert hasattr(node, 'dependencyManager') - assert isinstance(node.dependencyManager, DependencyManager) - assert 'test_marker' in node.dependencyManager.results + assert hasattr(node, 'dependency_manager') + assert isinstance(node.dependency_manager, DependencyManager) + assert 'test_marker' in node.dependency_manager.results """) result = ctestdir.runpytest("--verbose") result.assert_outcomes(passed=1) From 36501bc8652ac20336e02e9ce8cb19082f874883 Mon Sep 17 00:00:00 2001 From: DevilXD Date: Wed, 10 Jun 2020 19:33:05 +0200 Subject: [PATCH 08/26] Reorder the tests in the output here --- tests/test_03_scope.py | 43 ++++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/tests/test_03_scope.py b/tests/test_03_scope.py index ba28377..9e981fe 100644 --- a/tests/test_03_scope.py +++ b/tests/test_03_scope.py @@ -5,7 +5,8 @@ def test_scope_module(ctestdir): - """One single module, module scope is explicitely set in the + """ + One single module, module scope is explicitely set in the pytest.mark.dependency() marker. """ ctestdir.makepyfile(""" @@ -42,7 +43,8 @@ def test_e(): """) def test_scope_session(ctestdir): - """Two modules, some cross module dependencies in session scope. + """ + Two modules, some cross module dependencies in session scope. """ ctestdir.makepyfile(test_scope_session_01=""" import pytest @@ -115,7 +117,8 @@ def test_h(): """) def test_scope_package(ctestdir): - """Two packages, some cross module dependencies within the package and + """ + Two packages, some cross module dependencies within the package and across package boundaries. """ ctestdir.mkpydir("test_scope_package_a") @@ -185,7 +188,8 @@ def test_h(): """) def test_scope_class(ctestdir): - """Dependencies in class scope. + """ + Dependencies in class scope. """ ctestdir.makepyfile(""" import pytest @@ -250,7 +254,8 @@ def test_h(self): """) def test_scope_nodeid(ctestdir): - """The default name of a test is the node id. + """ + The default name of a test is the node id. The references to the default names must be adapted according to the scope. """ @@ -363,23 +368,24 @@ def test_o(self): result.stdout.fnmatch_lines(""" test_scope_nodeid.py::test_a PASSED test_scope_nodeid.py::test_b PASSED + test_scope_nodeid.py::TestClass::test_f PASSED + test_scope_nodeid.py::TestClass::test_k PASSED + test_scope_nodeid.py::TestClass::test_l SKIPPED + test_scope_nodeid.py::TestClass::test_m SKIPPED + test_scope_nodeid.py::TestClass::test_n SKIPPED + test_scope_nodeid.py::TestClass::test_o PASSED test_scope_nodeid.py::test_c SKIPPED test_scope_nodeid.py::test_d SKIPPED test_scope_nodeid.py::test_e PASSED - test_scope_nodeid.py::TestClass::test_f PASSED test_scope_nodeid.py::TestClass::test_g PASSED test_scope_nodeid.py::TestClass::test_h SKIPPED test_scope_nodeid.py::TestClass::test_i SKIPPED test_scope_nodeid.py::TestClass::test_j SKIPPED - test_scope_nodeid.py::TestClass::test_k PASSED - test_scope_nodeid.py::TestClass::test_l SKIPPED - test_scope_nodeid.py::TestClass::test_m SKIPPED - test_scope_nodeid.py::TestClass::test_n SKIPPED - test_scope_nodeid.py::TestClass::test_o PASSED """) def test_scope_named(ctestdir): - """Explicitely named tests are always referenced by that name, + """ + Explicitely named tests are always referenced by that name, regardless of the scope. """ ctestdir.makepyfile(""" @@ -470,20 +476,21 @@ def test_l(self): result.stdout.fnmatch_lines(""" test_scope_named.py::test_a PASSED test_scope_named.py::test_b PASSED - test_scope_named.py::test_c SKIPPED - test_scope_named.py::test_d PASSED - test_scope_named.py::test_e SKIPPED test_scope_named.py::TestClass::test_f PASSED - test_scope_named.py::TestClass::test_g PASSED - test_scope_named.py::TestClass::test_h SKIPPED test_scope_named.py::TestClass::test_i PASSED test_scope_named.py::TestClass::test_j SKIPPED test_scope_named.py::TestClass::test_k PASSED test_scope_named.py::TestClass::test_l SKIPPED + test_scope_named.py::test_c SKIPPED + test_scope_named.py::test_d PASSED + test_scope_named.py::test_e SKIPPED + test_scope_named.py::TestClass::test_g PASSED + test_scope_named.py::TestClass::test_h SKIPPED """) def test_scope_dependsfunc(ctestdir): - """Test the scope argument to the depends() function. + """ + Test the scope argument to the depends() function. """ ctestdir.makepyfile(test_scope_dependsfunc_01=""" import pytest From a26ba070408a055ff33fc8e2f281af687bffa5f5 Mon Sep 17 00:00:00 2001 From: DevilXD Date: Wed, 10 Jun 2020 19:33:23 +0200 Subject: [PATCH 09/26] Tiny fix related to keeping the original ordering --- pytest_dependency.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytest_dependency.py b/pytest_dependency.py index 3d5b85b..0c4f552 100644 --- a/pytest_dependency.py +++ b/pytest_dependency.py @@ -273,7 +273,7 @@ def pytest_collection_modifyitems(items): cycles = 0 deque_items = deque(items) while deque_items: - if cycles > len(deque_items): + if cycles >= len(deque_items): # seems like we're stuck in a loop now # just add the remaining items and finish up final_items.extend(deque_items) From 1a2126179f73913233aaaf8c3939abd46f33207b Mon Sep 17 00:00:00 2001 From: DevilXD Date: Mon, 15 Jun 2020 14:34:40 +0200 Subject: [PATCH 10/26] Fix 'scope' being undefined here --- pytest_dependency.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytest_dependency.py b/pytest_dependency.py index 0c4f552..20a4e6e 100644 --- a/pytest_dependency.py +++ b/pytest_dependency.py @@ -42,7 +42,7 @@ def _remove_parametrization(item, scope): name = nodeid.split("::", 2)[2] else: raise RuntimeError( - "Internal error: invalid scope '%s'" % self.scope + "Internal error: invalid scope '%s'" % scope ) original = item.originalname if item.originalname is not None else item.name From ef36808633b2afdc59b02f32cc41cea48dfba07c Mon Sep 17 00:00:00 2001 From: DevilXD Date: Mon, 15 Jun 2020 22:33:46 +0200 Subject: [PATCH 11/26] Revert "Reorder the tests in the output here" This reverts commit 36501bc8652ac20336e02e9ce8cb19082f874883. --- tests/test_03_scope.py | 43 ++++++++++++++++++------------------------ 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/tests/test_03_scope.py b/tests/test_03_scope.py index 9e981fe..ba28377 100644 --- a/tests/test_03_scope.py +++ b/tests/test_03_scope.py @@ -5,8 +5,7 @@ def test_scope_module(ctestdir): - """ - One single module, module scope is explicitely set in the + """One single module, module scope is explicitely set in the pytest.mark.dependency() marker. """ ctestdir.makepyfile(""" @@ -43,8 +42,7 @@ def test_e(): """) def test_scope_session(ctestdir): - """ - Two modules, some cross module dependencies in session scope. + """Two modules, some cross module dependencies in session scope. """ ctestdir.makepyfile(test_scope_session_01=""" import pytest @@ -117,8 +115,7 @@ def test_h(): """) def test_scope_package(ctestdir): - """ - Two packages, some cross module dependencies within the package and + """Two packages, some cross module dependencies within the package and across package boundaries. """ ctestdir.mkpydir("test_scope_package_a") @@ -188,8 +185,7 @@ def test_h(): """) def test_scope_class(ctestdir): - """ - Dependencies in class scope. + """Dependencies in class scope. """ ctestdir.makepyfile(""" import pytest @@ -254,8 +250,7 @@ def test_h(self): """) def test_scope_nodeid(ctestdir): - """ - The default name of a test is the node id. + """The default name of a test is the node id. The references to the default names must be adapted according to the scope. """ @@ -368,24 +363,23 @@ def test_o(self): result.stdout.fnmatch_lines(""" test_scope_nodeid.py::test_a PASSED test_scope_nodeid.py::test_b PASSED - test_scope_nodeid.py::TestClass::test_f PASSED - test_scope_nodeid.py::TestClass::test_k PASSED - test_scope_nodeid.py::TestClass::test_l SKIPPED - test_scope_nodeid.py::TestClass::test_m SKIPPED - test_scope_nodeid.py::TestClass::test_n SKIPPED - test_scope_nodeid.py::TestClass::test_o PASSED test_scope_nodeid.py::test_c SKIPPED test_scope_nodeid.py::test_d SKIPPED test_scope_nodeid.py::test_e PASSED + test_scope_nodeid.py::TestClass::test_f PASSED test_scope_nodeid.py::TestClass::test_g PASSED test_scope_nodeid.py::TestClass::test_h SKIPPED test_scope_nodeid.py::TestClass::test_i SKIPPED test_scope_nodeid.py::TestClass::test_j SKIPPED + test_scope_nodeid.py::TestClass::test_k PASSED + test_scope_nodeid.py::TestClass::test_l SKIPPED + test_scope_nodeid.py::TestClass::test_m SKIPPED + test_scope_nodeid.py::TestClass::test_n SKIPPED + test_scope_nodeid.py::TestClass::test_o PASSED """) def test_scope_named(ctestdir): - """ - Explicitely named tests are always referenced by that name, + """Explicitely named tests are always referenced by that name, regardless of the scope. """ ctestdir.makepyfile(""" @@ -476,21 +470,20 @@ def test_l(self): result.stdout.fnmatch_lines(""" test_scope_named.py::test_a PASSED test_scope_named.py::test_b PASSED - test_scope_named.py::TestClass::test_f PASSED - test_scope_named.py::TestClass::test_i PASSED - test_scope_named.py::TestClass::test_j SKIPPED - test_scope_named.py::TestClass::test_k PASSED - test_scope_named.py::TestClass::test_l SKIPPED test_scope_named.py::test_c SKIPPED test_scope_named.py::test_d PASSED test_scope_named.py::test_e SKIPPED + test_scope_named.py::TestClass::test_f PASSED test_scope_named.py::TestClass::test_g PASSED test_scope_named.py::TestClass::test_h SKIPPED + test_scope_named.py::TestClass::test_i PASSED + test_scope_named.py::TestClass::test_j SKIPPED + test_scope_named.py::TestClass::test_k PASSED + test_scope_named.py::TestClass::test_l SKIPPED """) def test_scope_dependsfunc(ctestdir): - """ - Test the scope argument to the depends() function. + """Test the scope argument to the depends() function. """ ctestdir.makepyfile(test_scope_dependsfunc_01=""" import pytest From 8744fe9021e59ea05a12011a6a9e814edc7c8d10 Mon Sep 17 00:00:00 2001 From: DevilXD Date: Mon, 15 Jun 2020 22:34:41 +0200 Subject: [PATCH 12/26] Change to a more efficient sorting method --- pytest_dependency.py | 82 ++++++++++++++++++-------------------------- 1 file changed, 34 insertions(+), 48 deletions(-) diff --git a/pytest_dependency.py b/pytest_dependency.py index 20a4e6e..840c407 100644 --- a/pytest_dependency.py +++ b/pytest_dependency.py @@ -106,14 +106,10 @@ def get_manager(cls, item, scope): def __init__(self, scope): self.scope = scope self.results = {} - self.names = set() self.dependencies = {} - def register_name(self, name): - self.names.add(name) - - def register_dependency(self, item, name): - self.dependencies[name] = item + def register_dependency(self, name, index): + self.dependencies[name] = index def add_result(self, item, name, rep): if not name: @@ -159,17 +155,18 @@ def check_depends(self, depends, item): logger.info("skip %s because it depends on %s", item.name, i) pytest.skip("%s depends on %s" % (item.name, i)) - def check_order(self, depends, item, name): - if not all(d in self.dependencies for d in depends): - # check to see if we're ever gonna see a dep like that - for d in depends: - if d not in self.names: - item.warn(pytest.PytestWarning( - "Dependency '%s' of '%s' doesn't exist, " - "or has incorrect scope!" % (d, name) - )) - return False - return True + def reorder(self, depends, item, name, index): + for dep in depends: + dv = self.dependencies.get(dep) + if dv is None: + item.warn(pytest.PytestWarning( + "Dependency '%s' of '%s' doesn't exist, " + "or has incorrect scope!" % (dep, name) + )) + continue + if dv > index: + self.dependencies[name] = index = dv + 1 + return self.dependencies[name] def depends(request, other, scope="module"): @@ -256,47 +253,36 @@ def pytest_runtest_setup(item): # special hook to make pytest-dependency support reordering based on deps def pytest_collection_modifyitems(items): - # gather dependency names - for item in items: + # register items and their names, according to scopes + for i, item in enumerate(items): for marker in item.iter_markers("dependency"): + depends = marker.kwargs.get("depends", []) scope = marker.kwargs.get("scope", "module") name = marker.kwargs.get("name") if not name: name = _remove_parametrization(item, scope) - manager = DependencyManager.get_manager(item, scope) - manager.register_name(name) - - final_items = [] - - # group the dependencies by their scopes - cycles = 0 - deque_items = deque(items) - while deque_items: - if cycles >= len(deque_items): - # seems like we're stuck in a loop now - # just add the remaining items and finish up - final_items.extend(deque_items) - break - item = deque_items.popleft() + if manager is None: + continue + manager.register_dependency(name, i) + # prepare a dictionary of final, highest indexes + highest_indexes = {} + # change stored indexes so that they point at + # 'index + 1' of the furthest / latest dependency + for i, item in enumerate(items): + highest_index = i for marker in item.iter_markers("dependency"): depends = marker.kwargs.get("depends", []) scope = marker.kwargs.get("scope", "module") name = marker.kwargs.get("name") if not name: name = _remove_parametrization(item, scope) - manager = DependencyManager.get_manager(item, scope) - if manager.check_order(depends, item, name): - manager.register_dependency(item, name) - else: - deque_items.append(item) - cycles += 1 - break - else: - # runs only when the for loop wasn't broken out of - final_items.append(item) - cycles = 0 - - assert len(items) == len(final_items) and all(i in items for i in final_items) - items[:] = final_items + if manager is None: + continue + highest_index = max( + highest_index, manager.reorder(depends, item, name, i) + ) + highest_indexes[item] = highest_index + # sort the results - this ensures a stable sort too + items.sort(key=lambda i: highest_indexes[i]) From 511c058ce2252d2daf16e9bcfbeeba35d4442c48 Mon Sep 17 00:00:00 2001 From: DevilXD Date: Mon, 15 Jun 2020 22:41:49 +0200 Subject: [PATCH 13/26] More fitting var name and useful comments --- pytest_dependency.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pytest_dependency.py b/pytest_dependency.py index 840c407..cfa2602 100644 --- a/pytest_dependency.py +++ b/pytest_dependency.py @@ -157,15 +157,17 @@ def check_depends(self, depends, item): def reorder(self, depends, item, name, index): for dep in depends: - dv = self.dependencies.get(dep) - if dv is None: + di = self.dependencies.get(dep) + if di is None: item.warn(pytest.PytestWarning( "Dependency '%s' of '%s' doesn't exist, " "or has incorrect scope!" % (dep, name) )) continue - if dv > index: - self.dependencies[name] = index = dv + 1 + # change the index for the current item so that it'll execute + # after the dependency + if di > index: + self.dependencies[name] = index = di + 1 return self.dependencies[name] @@ -270,6 +272,7 @@ def pytest_collection_modifyitems(items): # change stored indexes so that they point at # 'index + 1' of the furthest / latest dependency for i, item in enumerate(items): + # this keeps track of the highest index between different markers highest_index = i for marker in item.iter_markers("dependency"): depends = marker.kwargs.get("depends", []) From cb9eb488445cbbf9538a4febb0fd94e9678da6db Mon Sep 17 00:00:00 2001 From: DevilXD Date: Mon, 15 Jun 2020 22:45:19 +0200 Subject: [PATCH 14/26] Remove unused import --- pytest_dependency.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pytest_dependency.py b/pytest_dependency.py index cfa2602..e7af38e 100644 --- a/pytest_dependency.py +++ b/pytest_dependency.py @@ -1,7 +1,6 @@ """$DOC""" import logging -from collections import deque import pytest From f428dec243b362047b02081790505b860efda06b Mon Sep 17 00:00:00 2001 From: DevilXD Date: Wed, 17 Jun 2020 09:57:30 +0200 Subject: [PATCH 15/26] Add ordering tests --- tests/test_05_ordering.py | 147 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 tests/test_05_ordering.py diff --git a/tests/test_05_ordering.py b/tests/test_05_ordering.py new file mode 100644 index 0000000..772d800 --- /dev/null +++ b/tests/test_05_ordering.py @@ -0,0 +1,147 @@ +""" +Tests ordering. +""" + + +def test_order_standard(ctestdir): + """ + One module and 7 tests designed to cover most cases the ordering can fail in. + """ + ctestdir.makepyfile(""" + import pytest + + # this empty one should stay first + @pytest.mark.dependency() + def test_a(): + pass + + # misordered dependencies, this should end up near the bottom + @pytest.mark.dependency(depends=["test_f", "test_d", "test_e"]) + def test_b(): + pass + + # this empty one should occur after 'test_a' but before 'test_d' + @pytest.mark.dependency(depends=["test_a"]) + def test_c(): + pass + + # right after 'test_c' + @pytest.mark.dependency() + def test_d(): + pass + + # correct order already + @pytest.mark.dependency(depends=["test_d"]) + def test_e(): + pass + + # same here + @pytest.mark.dependency(depends=["test_b", "test_c"]) + def test_f(): + pass + + # and here - 'test_b' should land just before this test + @pytest.mark.dependency(depends=["test_c"]) + def test_g(): + pass + """) + result = ctestdir.runpytest("--verbose") + result.assert_outcomes(passed=7, skipped=0, failed=0) + result.stdout.fnmatch_lines(""" + test_order_standard.py::test_a PASSED + test_order_standard.py::test_c PASSED + test_order_standard.py::test_d PASSED + test_order_standard.py::test_e PASSED + test_order_standard.py::test_f PASSED + test_order_standard.py::test_b PASSED + test_order_standard.py::test_g PASSED + """) + + +def test_order_cycles(ctestdir): + """ + 4 tests, with 2 of them creating an "accidental" cycle. + """ + ctestdir.makepyfile(""" + import pytest + + @pytest.mark.dependency() + def test_a(): + pass + + # depends on 'test_d' - cycle + @pytest.mark.dependency(depends=["test_d") + def test_b(): + pass + + @pytest.mark.dependency() + def test_c(): + pass + + # depends on 'test_b' - cycle + @pytest.mark.dependency(depends=["test_b", "test_c"]) + def test_d(): + pass + """) + result = ctestdir.runpytest("--verbose") + result.assert_outcomes(passed=4, skipped=0, failed=0) + result.stdout.fnmatch_lines(""" + test_order_cycles.py::test_a PASSED + test_order_cycles.py::test_c PASSED + test_order_cycles.py::test_b PASSED + test_order_cycles.py::test_d PASSED + """) + + +def test_order_nesting(ctestdir): + """ + 8 tests, with tests depending on tests that depend on + other tests that might be reordered later too. + """ + ctestdir.makepyfile(""" + import pytest + + @pytest.mark.dependency() + def test_a(): + pass + + @pytest.mark.dependency(depends=["test_d") + def test_b(): + pass + + @pytest.mark.dependency() + def test_c(): + pass + + @pytest.mark.dependency(depends=["test_f"]) + def test_d(): + pass + + @pytest.mark.dependency() + def test_e(): + pass + + @pytest.mark.dependency(depends=["test_g"]) + def test_f(): + pass + + @pytest.mark.dependency() + def test_g(): + pass + + @pytest.mark.dependency() + def test_h(): + pass + """) + result = ctestdir.runpytest("--verbose") + result.assert_outcomes(passed=8, skipped=0, failed=0) + result.stdout.fnmatch_lines(""" + test_order_nesting.py::test_a PASSED + test_order_nesting.py::test_c PASSED + test_order_nesting.py::test_e PASSED + test_order_nesting.py::test_g PASSED + test_order_nesting.py::test_f PASSED + test_order_nesting.py::test_d PASSED + test_order_nesting.py::test_b PASSED + test_order_nesting.py::test_h PASSED + """) From 8d670e4045094aebb6cd95580db67367a2debc3d Mon Sep 17 00:00:00 2001 From: DevilXD Date: Wed, 17 Jun 2020 10:04:36 +0200 Subject: [PATCH 16/26] Remove unused imports and ensure style consistency --- tests/test_01_marker.py | 5 ++--- tests/test_02_simple_dependency.py | 23 ++++++++++++-------- tests/test_03_class.py | 13 +++++------ tests/test_03_multiple_dependency.py | 5 ++--- tests/test_03_param.py | 5 ++--- tests/test_03_runtime.py | 8 +++---- tests/test_03_scope.py | 32 +++++++++++++++++++--------- tests/test_03_skipmsgs.py | 8 +++---- tests/test_04_automark.py | 14 ++++++------ tests/test_04_ignore_unknown.py | 13 +++++------ 10 files changed, 72 insertions(+), 54 deletions(-) diff --git a/tests/test_01_marker.py b/tests/test_01_marker.py index f1337f5..e9f9b87 100644 --- a/tests/test_01_marker.py +++ b/tests/test_01_marker.py @@ -1,7 +1,6 @@ -"""The most basic test: check that the marker works. """ - -import pytest +The most basic test: check that the marker works. +""" def test_marker_registered(ctestdir): diff --git a/tests/test_02_simple_dependency.py b/tests/test_02_simple_dependency.py index a010f80..3e33928 100644 --- a/tests/test_02_simple_dependency.py +++ b/tests/test_02_simple_dependency.py @@ -1,11 +1,11 @@ -"""Simple dependencies between tests. """ - -import pytest +Simple dependencies between tests. +""" def test_no_skip(ctestdir): - """One test is skipped, but no other test depends on it, + """ + One test is skipped, but no other test depends on it, so all other tests pass. """ ctestdir.makepyfile(""" @@ -38,7 +38,8 @@ def test_d(): def test_skip_depend(ctestdir): - """One test is skipped, other dependent tests are skipped as well. + """ + One test is skipped, other dependent tests are skipped as well. This also includes indirect dependencies. """ ctestdir.makepyfile(""" @@ -71,7 +72,8 @@ def test_d(): def test_fail_depend(ctestdir): - """One test fails, other dependent tests are skipped. + """ + One test fails, other dependent tests are skipped. This also includes indirect dependencies. """ ctestdir.makepyfile(""" @@ -104,7 +106,8 @@ def test_d(): def test_named_fail_depend(ctestdir): - """Same as test_fail_depend, but using custom test names. + """ + Same as test_fail_depend, but using custom test names. """ ctestdir.makepyfile(""" import pytest @@ -136,7 +139,8 @@ def test_d(): def test_explicit_select(ctestdir): - """Explicitly select only a single test that depends on another one. + """ + Explicitly select only a single test that depends on another one. Since the other test has not been run at all, the selected test will be skipped. @@ -168,7 +172,8 @@ def test_d(): def test_depend_unknown(ctestdir): - """Depend on an unknown test that is not even defined in the test set. + """ + Depend on an unknown test that is not even defined in the test set. Note that is not an error to depend on an undefined test, but the dependent test will be skipped since the non-existent dependency diff --git a/tests/test_03_class.py b/tests/test_03_class.py index 2ebf811..13dad99 100644 --- a/tests/test_03_class.py +++ b/tests/test_03_class.py @@ -1,11 +1,11 @@ -"""Usage with test classes. """ - -import pytest +Usage with test classes. +""" def test_class_simple(ctestdir): - """Simple dependencies of test methods in a class. + """ + Simple dependencies of test methods in a class. test_a() deliberately fails, some other methods depend on it, some don't. """ ctestdir.makepyfile(""" @@ -29,7 +29,7 @@ def test_c(self): def test_d(self): pass - @pytest.mark.dependency(depends=["TestClass::test_b", + @pytest.mark.dependency(depends=["TestClass::test_b", "TestClass::test_c"]) def test_e(self): pass @@ -46,7 +46,8 @@ def test_e(self): def test_class_simple_named(ctestdir): - """Mostly the same as test_class_simple(), but name the test methods + """ + Mostly the same as test_class_simple(), but name the test methods now explicitely. """ ctestdir.makepyfile(""" diff --git a/tests/test_03_multiple_dependency.py b/tests/test_03_multiple_dependency.py index aaea86b..844e48b 100644 --- a/tests/test_03_multiple_dependency.py +++ b/tests/test_03_multiple_dependency.py @@ -1,7 +1,6 @@ -"""A complicated scenario with tests having multiple dependencies. """ - -import pytest +A complicated scenario with tests having multiple dependencies. +""" def test_multiple(ctestdir): diff --git a/tests/test_03_param.py b/tests/test_03_param.py index 8c0e53d..5e88836 100644 --- a/tests/test_03_param.py +++ b/tests/test_03_param.py @@ -1,7 +1,6 @@ -"""A scenario featuring parametrized tests. """ - -import pytest +A scenario featuring parametrized tests. +""" def test_multiple(ctestdir): diff --git a/tests/test_03_runtime.py b/tests/test_03_runtime.py index fd35b2b..9ddbaec 100644 --- a/tests/test_03_runtime.py +++ b/tests/test_03_runtime.py @@ -1,11 +1,11 @@ -"""Using depends() to mark dependencies at runtime. """ - -import pytest +Using depends() to mark dependencies at runtime. +""" def test_skip_depend_runtime(ctestdir): - """One test is skipped, other dependent tests are skipped as well. + """ + One test is skipped, other dependent tests are skipped as well. This also includes indirect dependencies. """ ctestdir.makepyfile(""" diff --git a/tests/test_03_scope.py b/tests/test_03_scope.py index ba28377..7e0571d 100644 --- a/tests/test_03_scope.py +++ b/tests/test_03_scope.py @@ -1,11 +1,11 @@ -"""Specifying the scope of dependencies. """ - -import pytest +Specifying the scope of dependencies. +""" def test_scope_module(ctestdir): - """One single module, module scope is explicitely set in the + """ + One single module, module scope is explicitely set in the pytest.mark.dependency() marker. """ ctestdir.makepyfile(""" @@ -41,8 +41,10 @@ def test_e(): test_scope_module.py::test_e SKIPPED """) + def test_scope_session(ctestdir): - """Two modules, some cross module dependencies in session scope. + """ + Two modules, some cross module dependencies in session scope. """ ctestdir.makepyfile(test_scope_session_01=""" import pytest @@ -114,8 +116,10 @@ def test_h(): test_scope_session_02.py::test_h PASSED """) + def test_scope_package(ctestdir): - """Two packages, some cross module dependencies within the package and + """ + Two packages, some cross module dependencies within the package and across package boundaries. """ ctestdir.mkpydir("test_scope_package_a") @@ -184,8 +188,10 @@ def test_h(): test_scope_package_b/test_03.py::test_h SKIPPED """) + def test_scope_class(ctestdir): - """Dependencies in class scope. + """ + Dependencies in class scope. """ ctestdir.makepyfile(""" import pytest @@ -249,8 +255,10 @@ def test_h(self): test_scope_class.py::TestClass2::test_h SKIPPED """) + def test_scope_nodeid(ctestdir): - """The default name of a test is the node id. + """ + The default name of a test is the node id. The references to the default names must be adapted according to the scope. """ @@ -378,8 +386,10 @@ def test_o(self): test_scope_nodeid.py::TestClass::test_o PASSED """) + def test_scope_named(ctestdir): - """Explicitely named tests are always referenced by that name, + """ + Explicitely named tests are always referenced by that name, regardless of the scope. """ ctestdir.makepyfile(""" @@ -482,8 +492,10 @@ def test_l(self): test_scope_named.py::TestClass::test_l SKIPPED """) + def test_scope_dependsfunc(ctestdir): - """Test the scope argument to the depends() function. + """ + Test the scope argument to the depends() function. """ ctestdir.makepyfile(test_scope_dependsfunc_01=""" import pytest diff --git a/tests/test_03_skipmsgs.py b/tests/test_03_skipmsgs.py index d501285..2baddda 100644 --- a/tests/test_03_skipmsgs.py +++ b/tests/test_03_skipmsgs.py @@ -1,11 +1,11 @@ -"""Verify the messages issued when a dependent test is skipped. """ - -import pytest +Verify the messages issued when a dependent test is skipped. +""" def test_simple(ctestdir): - """One test fails, other dependent tests are skipped. + """ + One test fails, other dependent tests are skipped. This also includes indirect dependencies. """ ctestdir.makepyfile(""" diff --git a/tests/test_04_automark.py b/tests/test_04_automark.py index bcc2810..07ded56 100644 --- a/tests/test_04_automark.py +++ b/tests/test_04_automark.py @@ -1,11 +1,11 @@ -"""Test the automark_dependency option. """ - -import pytest +Test the automark_dependency option. +""" def test_not_set(ctestdir): - """No pytest.ini file, e.g. automark_dependency is not set. + """ + No pytest.ini file, e.g. automark_dependency is not set. Since automark_dependency defaults to false and test_a is not marked, the outcome of test_a will not be recorded. As a result, @@ -30,7 +30,8 @@ def test_b(): def test_set_false(ctestdir): - """A pytest.ini is present, automark_dependency is set to false. + """ + A pytest.ini is present, automark_dependency is set to false. Since automark_dependency is set to false and test_a is not marked, the outcome of test_a will not be recorded. As a result, @@ -60,7 +61,8 @@ def test_b(): def test_set_true(ctestdir): - """A pytest.ini is present, automark_dependency is set to false. + """ + A pytest.ini is present, automark_dependency is set to false. Since automark_dependency is set to true, the outcome of test_a will be recorded, even though it is not marked. As a result, diff --git a/tests/test_04_ignore_unknown.py b/tests/test_04_ignore_unknown.py index 3cee600..647a298 100644 --- a/tests/test_04_ignore_unknown.py +++ b/tests/test_04_ignore_unknown.py @@ -1,11 +1,11 @@ -"""Test the ignore-unknown-dependency command line option. """ - -import pytest +Test the ignore-unknown-dependency command line option. +""" def test_no_ignore(ctestdir): - """No command line option, e.g. ignore-unknown-dependency is not set. + """ + No command line option, e.g. ignore-unknown-dependency is not set. Explicitly select only a single test that depends on another one. Since the other test has not been run at all, the selected test @@ -38,7 +38,8 @@ def test_d(): def test_ignore(ctestdir): - """Set the ignore-unknown-dependency command line option. + """ + Set the ignore-unknown-dependency command line option. Explicitly select only a single test that depends on another one. The other test has not been run at all, but since unknown @@ -64,7 +65,7 @@ def test_c(): def test_d(): pass """) - result = ctestdir.runpytest("--verbose", "--ignore-unknown-dependency", + result = ctestdir.runpytest("--verbose", "--ignore-unknown-dependency", "test_ignore.py::test_d") result.assert_outcomes(passed=1, skipped=0, failed=0) result.stdout.fnmatch_lines(""" From 9e5366712c6090890efbc505b73242f1f125c3ff Mon Sep 17 00:00:00 2001 From: DevilXD Date: Wed, 17 Jun 2020 18:48:07 +0200 Subject: [PATCH 17/26] Fixup messed up tests --- tests/test_05_ordering.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/test_05_ordering.py b/tests/test_05_ordering.py index 772d800..cff86ba 100644 --- a/tests/test_05_ordering.py +++ b/tests/test_05_ordering.py @@ -36,7 +36,7 @@ def test_e(): pass # same here - @pytest.mark.dependency(depends=["test_b", "test_c"]) + @pytest.mark.dependency(depends=["test_c", "test_d"]) def test_f(): pass @@ -70,7 +70,7 @@ def test_a(): pass # depends on 'test_d' - cycle - @pytest.mark.dependency(depends=["test_d") + @pytest.mark.dependency(depends=["test_d"]) def test_b(): pass @@ -84,12 +84,12 @@ def test_d(): pass """) result = ctestdir.runpytest("--verbose") - result.assert_outcomes(passed=4, skipped=0, failed=0) + result.assert_outcomes(passed=2, skipped=2, failed=0) result.stdout.fnmatch_lines(""" test_order_cycles.py::test_a PASSED test_order_cycles.py::test_c PASSED - test_order_cycles.py::test_b PASSED - test_order_cycles.py::test_d PASSED + test_order_cycles.py::test_b SKIPPED + test_order_cycles.py::test_d SKIPPED """) @@ -105,7 +105,7 @@ def test_order_nesting(ctestdir): def test_a(): pass - @pytest.mark.dependency(depends=["test_d") + @pytest.mark.dependency(depends=["test_d"]) def test_b(): pass From 662a543b998a1415bfb4675e7b3dcad8be2d9127 Mon Sep 17 00:00:00 2001 From: DevilXD Date: Wed, 17 Jun 2020 18:48:33 +0200 Subject: [PATCH 18/26] Reorder expected test results here --- tests/test_03_scope.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_03_scope.py b/tests/test_03_scope.py index 7e0571d..2d679a9 100644 --- a/tests/test_03_scope.py +++ b/tests/test_03_scope.py @@ -371,15 +371,15 @@ def test_o(self): result.stdout.fnmatch_lines(""" test_scope_nodeid.py::test_a PASSED test_scope_nodeid.py::test_b PASSED + test_scope_nodeid.py::TestClass::test_f PASSED + test_scope_nodeid.py::TestClass::test_k PASSED test_scope_nodeid.py::test_c SKIPPED test_scope_nodeid.py::test_d SKIPPED test_scope_nodeid.py::test_e PASSED - test_scope_nodeid.py::TestClass::test_f PASSED test_scope_nodeid.py::TestClass::test_g PASSED test_scope_nodeid.py::TestClass::test_h SKIPPED test_scope_nodeid.py::TestClass::test_i SKIPPED test_scope_nodeid.py::TestClass::test_j SKIPPED - test_scope_nodeid.py::TestClass::test_k PASSED test_scope_nodeid.py::TestClass::test_l SKIPPED test_scope_nodeid.py::TestClass::test_m SKIPPED test_scope_nodeid.py::TestClass::test_n SKIPPED @@ -480,13 +480,13 @@ def test_l(self): result.stdout.fnmatch_lines(""" test_scope_named.py::test_a PASSED test_scope_named.py::test_b PASSED + test_scope_named.py::TestClass::test_f PASSED + test_scope_named.py::TestClass::test_i PASSED test_scope_named.py::test_c SKIPPED test_scope_named.py::test_d PASSED test_scope_named.py::test_e SKIPPED - test_scope_named.py::TestClass::test_f PASSED test_scope_named.py::TestClass::test_g PASSED test_scope_named.py::TestClass::test_h SKIPPED - test_scope_named.py::TestClass::test_i PASSED test_scope_named.py::TestClass::test_j SKIPPED test_scope_named.py::TestClass::test_k PASSED test_scope_named.py::TestClass::test_l SKIPPED From 19608f905e8523d220f4798f9fe93213ef4aa357 Mon Sep 17 00:00:00 2001 From: DevilXD Date: Wed, 17 Jun 2020 18:54:49 +0200 Subject: [PATCH 19/26] Properly handle items reordering --- pytest_dependency.py | 92 ++++++++++++++++++++++++++------------------ 1 file changed, 54 insertions(+), 38 deletions(-) diff --git a/pytest_dependency.py b/pytest_dependency.py index e7af38e..aeb5183 100644 --- a/pytest_dependency.py +++ b/pytest_dependency.py @@ -1,6 +1,7 @@ """$DOC""" import logging +from collections import deque import pytest @@ -105,10 +106,14 @@ def get_manager(cls, item, scope): def __init__(self, scope): self.scope = scope self.results = {} - self.dependencies = {} + self.names = set() + self.dependencies = set() - def register_dependency(self, name, index): - self.dependencies[name] = index + def register_dependency(self, name): + self.dependencies.add(name) + + def register_dependency_name(self, name): + self.names.add(name) def add_result(self, item, name, rep): if not name: @@ -154,20 +159,20 @@ def check_depends(self, depends, item): logger.info("skip %s because it depends on %s", item.name, i) pytest.skip("%s depends on %s" % (item.name, i)) - def reorder(self, depends, item, name, index): - for dep in depends: - di = self.dependencies.get(dep) - if di is None: + def check_order(self, depends, item, name): + for d in depends: + if d not in self.names: item.warn(pytest.PytestWarning( "Dependency '%s' of '%s' doesn't exist, " - "or has incorrect scope!" % (dep, name) + "or has incorrect scope!" % (d, name) )) - continue - # change the index for the current item so that it'll execute - # after the dependency - if di > index: - self.dependencies[name] = index = di + 1 - return self.dependencies[name] + if _ignore_unknown: + continue + else: + return False + elif d not in self.dependencies: + return False + return True def depends(request, other, scope="module"): @@ -254,25 +259,11 @@ def pytest_runtest_setup(item): # special hook to make pytest-dependency support reordering based on deps def pytest_collection_modifyitems(items): + # store the markers between passes - improves speed + markers_cache = {} # register items and their names, according to scopes - for i, item in enumerate(items): - for marker in item.iter_markers("dependency"): - depends = marker.kwargs.get("depends", []) - scope = marker.kwargs.get("scope", "module") - name = marker.kwargs.get("name") - if not name: - name = _remove_parametrization(item, scope) - manager = DependencyManager.get_manager(item, scope) - if manager is None: - continue - manager.register_dependency(name, i) - # prepare a dictionary of final, highest indexes - highest_indexes = {} - # change stored indexes so that they point at - # 'index + 1' of the furthest / latest dependency - for i, item in enumerate(items): - # this keeps track of the highest index between different markers - highest_index = i + for item in items: + markers_cache[item] = markers = [] for marker in item.iter_markers("dependency"): depends = marker.kwargs.get("depends", []) scope = marker.kwargs.get("scope", "module") @@ -282,9 +273,34 @@ def pytest_collection_modifyitems(items): manager = DependencyManager.get_manager(item, scope) if manager is None: continue - highest_index = max( - highest_index, manager.reorder(depends, item, name, i) - ) - highest_indexes[item] = highest_index - # sort the results - this ensures a stable sort too - items.sort(key=lambda i: highest_indexes[i]) + markers.append((depends, name, manager)) + manager.register_dependency_name(name) + miss_list = [] + final_items = [] + deque_items = deque(items) + # loop until all items are sorted out + while deque_items: + item = deque_items.popleft() + # store managers and only register when adding to the final list + to_register = [] + for depends, name, manager in markers_cache[item]: + if manager.check_order(depends, item, name): + to_register.append((manager, name)) + else: + miss_list.append(item) + break + else: + # runs only when the for loop wasn't broken out of + for manager, name in to_register: + manager.register_dependency(name) + final_items.append(item) + # add the missing items back in the correct order + if miss_list: + deque_items.extendleft(reversed(miss_list)) + miss_list.clear() + if miss_list: + # this list being non-empty here means there are + # cyclic or missing dependencies + final_items.extend(miss_list) + assert len(items) == len(final_items) + items[:] = final_items From 18179c3fd2814498404cdaa360ba969588b6f148 Mon Sep 17 00:00:00 2001 From: DevilXD Date: Wed, 17 Jun 2020 19:01:02 +0200 Subject: [PATCH 20/26] Fix for Python 2.7 --- pytest_dependency.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytest_dependency.py b/pytest_dependency.py index aeb5183..a41d7d5 100644 --- a/pytest_dependency.py +++ b/pytest_dependency.py @@ -297,7 +297,7 @@ def pytest_collection_modifyitems(items): # add the missing items back in the correct order if miss_list: deque_items.extendleft(reversed(miss_list)) - miss_list.clear() + miss_list = [] # can't use '.clear()' in 2.7 if miss_list: # this list being non-empty here means there are # cyclic or missing dependencies From 080f938add434b184827f167b645ec365af3af5d Mon Sep 17 00:00:00 2001 From: DevilXD Date: Wed, 17 Jun 2020 19:25:58 +0200 Subject: [PATCH 21/26] Better ordering tests --- tests/test_05_ordering.py | 110 +++++++++++++++++++++++++++++++++++++- 1 file changed, 108 insertions(+), 2 deletions(-) diff --git a/tests/test_05_ordering.py b/tests/test_05_ordering.py index cff86ba..f1babc6 100644 --- a/tests/test_05_ordering.py +++ b/tests/test_05_ordering.py @@ -58,9 +58,49 @@ def test_g(): """) +def test_order_missing(ctestdir): + """ + 5 tests, with 2 of them having mislabeled (missing) dependencies. + """ + ctestdir.makepyfile(""" + import pytest + + @pytest.mark.dependency() + def test_a(): + pass + + # mislabeled 'test_c' + @pytest.mark.dependency(depends=["tets_c"]) + def test_b(): + pass + + @pytest.mark.dependency() + def test_c(): + pass + + # mislabeled 'test_a' + @pytest.mark.dependency(depends=["tets_a"]) + def test_d(): + pass + + @pytest.mark.dependency() + def test_e(): + pass + """) + result = ctestdir.runpytest("--verbose") + result.assert_outcomes(passed=3, skipped=2, failed=0) + result.stdout.fnmatch_lines(""" + test_order_missing.py::test_a PASSED + test_order_missing.py::test_c PASSED + test_order_missing.py::test_e PASSED + test_order_missing.py::test_b SKIPPED + test_order_missing.py::test_d SKIPPED + """) + + def test_order_cycles(ctestdir): """ - 4 tests, with 2 of them creating an "accidental" cycle. + 5 tests, with 2 of them creating an "accidental" cycle. """ ctestdir.makepyfile(""" import pytest @@ -82,12 +122,17 @@ def test_c(): @pytest.mark.dependency(depends=["test_b", "test_c"]) def test_d(): pass + + @pytest.mark.dependency() + def test_e(): + pass """) result = ctestdir.runpytest("--verbose") - result.assert_outcomes(passed=2, skipped=2, failed=0) + result.assert_outcomes(passed=3, skipped=2, failed=0) result.stdout.fnmatch_lines(""" test_order_cycles.py::test_a PASSED test_order_cycles.py::test_c PASSED + test_order_cycles.py::test_e PASSED test_order_cycles.py::test_b SKIPPED test_order_cycles.py::test_d SKIPPED """) @@ -145,3 +190,64 @@ def test_h(): test_order_nesting.py::test_b PASSED test_order_nesting.py::test_h PASSED """) + + +def test_order_scopes(ctestdir): + """ + 9 tests, with dependencies spread between 'module' and 'session' scopes. + """ + ctestdir.makepyfile(""" + import pytest + + @pytest.mark.dependency() + def test_a(): + pass + + @pytest.mark.dependency(depends=["test_c"]) + def test_b(): + pass + + @pytest.mark.dependency() + def test_c(): + pass + + @pytest.mark.dependency(depends=["test_order_scopes.py::test_e"], scope="session") + def test_d(): + pass + + @pytest.mark.dependency() + @pytest.mark.dependency(scope="session") + def test_e(): + pass + + @pytest.mark.dependency(depends=["test_g"]) + @pytest.mark.dependency(depends=["test_order_scopes.py::test_e"], scope="session") + def test_f(): + pass + + @pytest.mark.dependency() + def test_g(): + pass + + @pytest.mark.dependency(depends=["test_order_scopes.py::test_i"], scope="session") + @pytest.mark.dependency(depends=["test_e"]) + def test_h(): + pass + + @pytest.mark.dependency(scope="session") + def test_i(): + pass + """) + result = ctestdir.runpytest("--verbose") + result.assert_outcomes(passed=9, skipped=0, failed=0) + result.stdout.fnmatch_lines(""" + test_order_scopes.py::test_a PASSED + test_order_scopes.py::test_c PASSED + test_order_scopes.py::test_b PASSED + test_order_scopes.py::test_e PASSED + test_order_scopes.py::test_d PASSED + test_order_scopes.py::test_g PASSED + test_order_scopes.py::test_f PASSED + test_order_scopes.py::test_i PASSED + test_order_scopes.py::test_h PASSED + """) From 953c16e0743e4f0efa0ac5d5f939475305a48cf6 Mon Sep 17 00:00:00 2001 From: DevilXD Date: Sat, 3 Apr 2021 22:57:53 +0200 Subject: [PATCH 22/26] Update reordering PR to the newest upstream/develop --- .req | 2 +- .travis.yml | 1 + .vscode/launch.json | 15 ++++++ .vscode/settings.json | 3 ++ README.rst | 2 +- doc/src/changelog.rst | 7 ++- doc/src/configuration.rst | 4 +- doc/src/install.rst | 2 +- setup.py | 10 ++-- tests/pytest.ini | 2 +- tests/test_02_simple_dependency.py | 54 +++++++++---------- tests/test_03_class.py | 32 ++++++------ tests/test_03_multiple_dependency.py | 24 ++++----- tests/test_03_param.py | 28 +++++----- tests/test_03_runtime.py | 10 ++-- tests/test_03_scope.py | 62 +++++++++++----------- tests/test_03_skipmsgs.py | 10 ++-- tests/test_04_automark.py | 18 +++---- tests/test_04_ignore_unknown.py | 8 +-- tests/test_05_ordering.py | 78 ++++++++++++++-------------- 20 files changed, 199 insertions(+), 173 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json diff --git a/.req b/.req index 04f6903..d8b46d9 100644 --- a/.req +++ b/.req @@ -1,2 +1,2 @@ -pytest >=3.6.0 +pytest >=3.7.0 setuptools_scm diff --git a/.travis.yml b/.travis.yml index 4f82760..7553bdd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ python: - "3.6" - "3.7" - "3.8" + - "3.9" install: pip install -r .req script: make test PYTHON=python diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..30ae3da --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Run Tests", + "type": "python", + "request": "launch", + "module": "pytest", + "args": ["-vvx", "tests/test_05_ordering.py::test_order_scopes"] + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..3cce948 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "restructuredtext.confPath": "" +} \ No newline at end of file diff --git a/README.rst b/README.rst index 9fdfde2..f611646 100644 --- a/README.rst +++ b/README.rst @@ -33,7 +33,7 @@ System requirements + Python 2.7 or 3.4 and newer. + `setuptools`_. -+ `pytest`_ 3.6.0 or newer. ++ `pytest`_ 3.7.0 or newer. Optional library packages: diff --git a/doc/src/changelog.rst b/doc/src/changelog.rst index 86d22bd..9b1c2de 100644 --- a/doc/src/changelog.rst +++ b/doc/src/changelog.rst @@ -4,11 +4,13 @@ History of changes to pytest-dependency dev (not yet released) Bug fixes and minor changes + `#40`_: add logging. + + `#50`_, `#51`_: test suite incompatibility with pytest 6.2.0. .. _#40: https://github.com/RKrahl/pytest-dependency/issues/40 +.. _#50: https://github.com/RKrahl/pytest-dependency/issues/50 +.. _#51: https://github.com/RKrahl/pytest-dependency/pull/51 0.5.1 (2020-02-14) - Bug fixes and minor changes + Fix failing documentation build. @@ -17,6 +19,9 @@ dev (not yet released) + `#3`_, `#35`_: add a scope to dependencies. (Thanks to JoeSc and selenareneephillips!) + Incompatible changes + + Require pytest version 3.7.0 or newer. + Bug fixes and minor changes + `#34`_: failing test with pytest 4.2.0 and newer. + Use setuptools_scm to manage the version number. diff --git a/doc/src/configuration.rst b/doc/src/configuration.rst index b917047..151a1ec 100644 --- a/doc/src/configuration.rst +++ b/doc/src/configuration.rst @@ -21,8 +21,8 @@ Configuration file options can be set in the `ini file`. minversion This is a builtin configuration option of pytest itself. Since - pytest-dependency requires pytest 3.6.0 or newer, it is recommended - to set this option accordingly, either to 3.6.0 or to a newer + pytest-dependency requires pytest 3.7.0 or newer, it is recommended + to set this option accordingly, either to 3.7.0 or to a newer version, if required by your test code. automark_dependency diff --git a/doc/src/install.rst b/doc/src/install.rst index 9b6417a..482b33e 100644 --- a/doc/src/install.rst +++ b/doc/src/install.rst @@ -6,7 +6,7 @@ System requirements + Python 2.7 or 3.4 and newer. + `setuptools`_. -+ `pytest`_ 3.6.0 or newer. ++ `pytest`_ 3.7.0 or newer. .. _install-other-packages: diff --git a/setup.py b/setup.py index 94de125..46c02e6 100755 --- a/setup.py +++ b/setup.py @@ -6,14 +6,15 @@ skipped if any of the dependencies did fail or has been skipped. """ -from distutils.cmd import Command as du_cmd -import distutils.command.sdist -import distutils.log import os import os.path import re import stat import string +import setuptools +from distutils.cmd import Command as du_cmd +import distutils.command.sdist +import distutils.log from setuptools import setup import setuptools.command.build_py try: @@ -90,7 +91,7 @@ class sdist(copy_file_mixin, distutils.command.sdist.sdist): 'Source Code': 'https://github.com/RKrahl/pytest-dependency', }, py_modules=['pytest_dependency'], - install_requires=['pytest >= 3.6.0'], + install_requires=['pytest >= 3.7.0'], classifiers=[ 'Development Status :: 4 - Beta', 'Framework :: Pytest', @@ -105,6 +106,7 @@ class sdist(copy_file_mixin, distutils.command.sdist.sdist): 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', 'Operating System :: OS Independent', 'License :: OSI Approved :: Apache Software License', ], diff --git a/tests/pytest.ini b/tests/pytest.ini index 1f80c58..7cec0d1 100644 --- a/tests/pytest.ini +++ b/tests/pytest.ini @@ -1,2 +1,2 @@ [pytest] -minversion = 3.6 +minversion = 3.7 diff --git a/tests/test_02_simple_dependency.py b/tests/test_02_simple_dependency.py index 3e33928..83a73c8 100644 --- a/tests/test_02_simple_dependency.py +++ b/tests/test_02_simple_dependency.py @@ -29,11 +29,11 @@ def test_d(): """) result = ctestdir.runpytest("--verbose") result.assert_outcomes(passed=3, skipped=1, failed=0) - result.stdout.fnmatch_lines(""" - *::test_a SKIPPED - *::test_b PASSED - *::test_c PASSED - *::test_d PASSED + result.stdout.re_match_lines(r""" + .*::test_a SKIPPED(?:\s+\(.*\))? + .*::test_b PASSED + .*::test_c PASSED + .*::test_d PASSED """) @@ -63,11 +63,11 @@ def test_d(): """) result = ctestdir.runpytest("--verbose") result.assert_outcomes(passed=1, skipped=3, failed=0) - result.stdout.fnmatch_lines(""" - *::test_a PASSED - *::test_b SKIPPED - *::test_c SKIPPED - *::test_d SKIPPED + result.stdout.re_match_lines(r""" + .*::test_a PASSED + .*::test_b SKIPPED(?:\s+\(.*\))? + .*::test_c SKIPPED(?:\s+\(.*\))? + .*::test_d SKIPPED(?:\s+\(.*\))? """) @@ -97,11 +97,11 @@ def test_d(): """) result = ctestdir.runpytest("--verbose") result.assert_outcomes(passed=1, skipped=2, failed=1) - result.stdout.fnmatch_lines(""" - *::test_a PASSED - *::test_b FAILED - *::test_c SKIPPED - *::test_d SKIPPED + result.stdout.re_match_lines(r""" + .*::test_a PASSED + .*::test_b FAILED + .*::test_c SKIPPED(?:\s+\(.*\))? + .*::test_d SKIPPED(?:\s+\(.*\))? """) @@ -130,11 +130,11 @@ def test_d(): """) result = ctestdir.runpytest("--verbose") result.assert_outcomes(passed=1, skipped=2, failed=1) - result.stdout.fnmatch_lines(""" - *::test_a PASSED - *::test_b FAILED - *::test_c SKIPPED - *::test_d SKIPPED + result.stdout.re_match_lines(r""" + .*::test_a PASSED + .*::test_b FAILED + .*::test_c SKIPPED(?:\s+\(.*\))? + .*::test_d SKIPPED(?:\s+\(.*\))? """) @@ -166,8 +166,8 @@ def test_d(): """) result = ctestdir.runpytest("--verbose", "test_explicit_select.py::test_d") result.assert_outcomes(passed=0, skipped=1, failed=0) - result.stdout.fnmatch_lines(""" - *::test_d SKIPPED + result.stdout.re_match_lines(r""" + .*::test_d SKIPPED(?:\s+\(.*\))? """) @@ -200,9 +200,9 @@ def test_d(): """) result = ctestdir.runpytest("--verbose") result.assert_outcomes(passed=3, skipped=1, failed=0) - result.stdout.fnmatch_lines(""" - *::test_a PASSED - *::test_b PASSED - *::test_c PASSED - *::test_d SKIPPED + result.stdout.re_match_lines(r""" + .*::test_a PASSED + .*::test_b PASSED + .*::test_c PASSED + .*::test_d SKIPPED(?:\s+\(.*\))? """) diff --git a/tests/test_03_class.py b/tests/test_03_class.py index 13dad99..b7b8348 100644 --- a/tests/test_03_class.py +++ b/tests/test_03_class.py @@ -36,12 +36,12 @@ def test_e(self): """) result = ctestdir.runpytest("--verbose") result.assert_outcomes(passed=2, skipped=2, failed=1) - result.stdout.fnmatch_lines(""" - *::TestClass::test_a FAILED - *::TestClass::test_b PASSED - *::TestClass::test_c SKIPPED - *::TestClass::test_d PASSED - *::TestClass::test_e SKIPPED + result.stdout.re_match_lines(r""" + .*::TestClass::test_a FAILED + .*::TestClass::test_b PASSED + .*::TestClass::test_c SKIPPED(?:\s+\(.*\))? + .*::TestClass::test_d PASSED + .*::TestClass::test_e SKIPPED(?:\s+\(.*\))? """) @@ -77,12 +77,12 @@ def test_e(self): """) result = ctestdir.runpytest("--verbose") result.assert_outcomes(passed=2, skipped=2, failed=1) - result.stdout.fnmatch_lines(""" - *::TestClassNamed::test_a FAILED - *::TestClassNamed::test_b PASSED - *::TestClassNamed::test_c SKIPPED - *::TestClassNamed::test_d PASSED - *::TestClassNamed::test_e SKIPPED + result.stdout.re_match_lines(r""" + .*::TestClassNamed::test_a FAILED + .*::TestClassNamed::test_b PASSED + .*::TestClassNamed::test_c SKIPPED(?:\s+\(.*\))? + .*::TestClassNamed::test_d PASSED + .*::TestClassNamed::test_e SKIPPED(?:\s+\(.*\))? """) @@ -115,8 +115,8 @@ def test_b(): """) result = ctestdir.runpytest("--verbose") result.assert_outcomes(passed=1, skipped=1, failed=1) - result.stdout.fnmatch_lines(""" - *::test_a FAILED - *::TestClass::test_a PASSED - *::test_b SKIPPED + result.stdout.re_match_lines(r""" + .*::test_a FAILED + .*::TestClass::test_a PASSED + .*::test_b SKIPPED(?:\s+\(.*\))? """) diff --git a/tests/test_03_multiple_dependency.py b/tests/test_03_multiple_dependency.py index 844e48b..df55eaf 100644 --- a/tests/test_03_multiple_dependency.py +++ b/tests/test_03_multiple_dependency.py @@ -53,16 +53,16 @@ def test_k(): """) result = ctestdir.runpytest("--verbose") result.assert_outcomes(passed=5, skipped=5, failed=1) - result.stdout.fnmatch_lines(""" - *::test_a SKIPPED - *::test_b FAILED - *::test_c PASSED - *::test_d PASSED - *::test_e PASSED - *::test_f SKIPPED - *::test_g SKIPPED - *::test_h PASSED - *::test_i SKIPPED - *::test_j PASSED - *::test_k SKIPPED + result.stdout.re_match_lines(r""" + .*::test_a SKIPPED(?:\s+\(.*\))? + .*::test_b FAILED + .*::test_c PASSED + .*::test_d PASSED + .*::test_e PASSED + .*::test_f SKIPPED(?:\s+\(.*\))? + .*::test_g SKIPPED(?:\s+\(.*\))? + .*::test_h PASSED + .*::test_i SKIPPED(?:\s+\(.*\))? + .*::test_j PASSED + .*::test_k SKIPPED(?:\s+\(.*\))? """) diff --git a/tests/test_03_param.py b/tests/test_03_param.py index 5e88836..b2273fd 100644 --- a/tests/test_03_param.py +++ b/tests/test_03_param.py @@ -39,18 +39,18 @@ def test_c(w): """) result = ctestdir.runpytest("--verbose") result.assert_outcomes(passed=7, skipped=5, failed=1) - result.stdout.fnmatch_lines(""" - *::test_a?0-0? PASSED - *::test_a?0-1? PASSED - *::test_a?1-0? PASSED - *::test_a?1-1? FAILED - *::test_b?1-2? PASSED - *::test_b?1-3? PASSED - *::test_b?1-4? SKIPPED - *::test_b?2-3? PASSED - *::test_b?2-4? SKIPPED - *::test_b?3-4? SKIPPED - *::test_c?1? SKIPPED - *::test_c?2? SKIPPED - *::test_c?3? PASSED + result.stdout.re_match_lines(r""" + .*::test_a\[0-0\] PASSED + .*::test_a\[0-1\] PASSED + .*::test_a\[1-0\] PASSED + .*::test_a\[1-1\] FAILED + .*::test_b\[1-2\] PASSED + .*::test_b\[1-3\] PASSED + .*::test_b\[1-4\] SKIPPED(?:\s+\(.*\))? + .*::test_b\[2-3\] PASSED + .*::test_b\[2-4\] SKIPPED(?:\s+\(.*\))? + .*::test_b\[3-4\] SKIPPED(?:\s+\(.*\))? + .*::test_c\[1\] SKIPPED(?:\s+\(.*\))? + .*::test_c\[2\] SKIPPED(?:\s+\(.*\))? + .*::test_c\[3\] PASSED """) diff --git a/tests/test_03_runtime.py b/tests/test_03_runtime.py index 9ddbaec..253e4f5 100644 --- a/tests/test_03_runtime.py +++ b/tests/test_03_runtime.py @@ -32,9 +32,9 @@ def test_d(request): """) result = ctestdir.runpytest("--verbose") result.assert_outcomes(passed=1, skipped=3, failed=0) - result.stdout.fnmatch_lines(""" - *::test_a PASSED - *::test_b SKIPPED - *::test_c SKIPPED - *::test_d SKIPPED + result.stdout.re_match_lines(r""" + .*::test_a PASSED + .*::test_b SKIPPED(?:\s+\(.*\))? + .*::test_c SKIPPED(?:\s+\(.*\))? + .*::test_d SKIPPED(?:\s+\(.*\))? """) diff --git a/tests/test_03_scope.py b/tests/test_03_scope.py index 2d679a9..492e468 100644 --- a/tests/test_03_scope.py +++ b/tests/test_03_scope.py @@ -33,12 +33,12 @@ def test_e(): """) result = ctestdir.runpytest("--verbose") result.assert_outcomes(passed=2, skipped=2, failed=1) - result.stdout.fnmatch_lines(""" + result.stdout.re_match_lines(r""" test_scope_module.py::test_a FAILED test_scope_module.py::test_b PASSED - test_scope_module.py::test_c SKIPPED + test_scope_module.py::test_c SKIPPED(?:\s+\(.*\))? test_scope_module.py::test_d PASSED - test_scope_module.py::test_e SKIPPED + test_scope_module.py::test_e SKIPPED(?:\s+\(.*\))? """) @@ -104,14 +104,14 @@ def test_h(): """) result = ctestdir.runpytest("--verbose") result.assert_outcomes(passed=6, skipped=1, failed=2) - result.stdout.fnmatch_lines(""" + result.stdout.re_match_lines(r""" test_scope_session_01.py::test_a PASSED test_scope_session_01.py::test_b FAILED test_scope_session_01.py::test_c PASSED test_scope_session_01.py::TestClass::test_b PASSED test_scope_session_02.py::test_a FAILED test_scope_session_02.py::test_e PASSED - test_scope_session_02.py::test_f SKIPPED + test_scope_session_02.py::test_f SKIPPED(?:\s+\(.*\))? test_scope_session_02.py::test_g PASSED test_scope_session_02.py::test_h PASSED """) @@ -178,14 +178,14 @@ def test_h(): ctestdir.makepyfile(**srcs) result = ctestdir.runpytest("--verbose") result.assert_outcomes(passed=4, skipped=2, failed=1) - result.stdout.fnmatch_lines(""" + result.stdout.re_match_lines(r""" test_scope_package_a/test_01.py::test_a PASSED test_scope_package_b/test_02.py::test_c PASSED test_scope_package_b/test_02.py::test_d FAILED test_scope_package_b/test_03.py::test_e PASSED - test_scope_package_b/test_03.py::test_f SKIPPED + test_scope_package_b/test_03.py::test_f SKIPPED(?:\s+\(.*\))? test_scope_package_b/test_03.py::test_g PASSED - test_scope_package_b/test_03.py::test_h SKIPPED + test_scope_package_b/test_03.py::test_h SKIPPED(?:\s+\(.*\))? """) @@ -242,17 +242,17 @@ def test_h(self): """) result = ctestdir.runpytest("--verbose") result.assert_outcomes(passed=5, skipped=3, failed=2) - result.stdout.fnmatch_lines(""" + result.stdout.re_match_lines(r""" test_scope_class.py::test_a FAILED test_scope_class.py::test_b PASSED test_scope_class.py::TestClass1::test_c PASSED test_scope_class.py::TestClass2::test_a PASSED test_scope_class.py::TestClass2::test_b FAILED - test_scope_class.py::TestClass2::test_d SKIPPED + test_scope_class.py::TestClass2::test_d SKIPPED(?:\s+\(.*\))? test_scope_class.py::TestClass2::test_e PASSED test_scope_class.py::TestClass2::test_f PASSED - test_scope_class.py::TestClass2::test_g SKIPPED - test_scope_class.py::TestClass2::test_h SKIPPED + test_scope_class.py::TestClass2::test_g SKIPPED(?:\s+\(.*\))? + test_scope_class.py::TestClass2::test_h SKIPPED(?:\s+\(.*\))? """) @@ -368,21 +368,21 @@ def test_o(self): """) result = ctestdir.runpytest("--verbose") result.assert_outcomes(passed=7, skipped=8, failed=0) - result.stdout.fnmatch_lines(""" + result.stdout.re_match_lines(r""" test_scope_nodeid.py::test_a PASSED test_scope_nodeid.py::test_b PASSED test_scope_nodeid.py::TestClass::test_f PASSED test_scope_nodeid.py::TestClass::test_k PASSED - test_scope_nodeid.py::test_c SKIPPED - test_scope_nodeid.py::test_d SKIPPED + test_scope_nodeid.py::test_c SKIPPED(?:\s+\(.*\))? + test_scope_nodeid.py::test_d SKIPPED(?:\s+\(.*\))? test_scope_nodeid.py::test_e PASSED test_scope_nodeid.py::TestClass::test_g PASSED - test_scope_nodeid.py::TestClass::test_h SKIPPED - test_scope_nodeid.py::TestClass::test_i SKIPPED - test_scope_nodeid.py::TestClass::test_j SKIPPED - test_scope_nodeid.py::TestClass::test_l SKIPPED - test_scope_nodeid.py::TestClass::test_m SKIPPED - test_scope_nodeid.py::TestClass::test_n SKIPPED + test_scope_nodeid.py::TestClass::test_h SKIPPED(?:\s+\(.*\))? + test_scope_nodeid.py::TestClass::test_i SKIPPED(?:\s+\(.*\))? + test_scope_nodeid.py::TestClass::test_j SKIPPED(?:\s+\(.*\))? + test_scope_nodeid.py::TestClass::test_l SKIPPED(?:\s+\(.*\))? + test_scope_nodeid.py::TestClass::test_m SKIPPED(?:\s+\(.*\))? + test_scope_nodeid.py::TestClass::test_n SKIPPED(?:\s+\(.*\))? test_scope_nodeid.py::TestClass::test_o PASSED """) @@ -477,19 +477,19 @@ def test_l(self): """) result = ctestdir.runpytest("--verbose") result.assert_outcomes(passed=7, skipped=5, failed=0) - result.stdout.fnmatch_lines(""" + result.stdout.re_match_lines(r""" test_scope_named.py::test_a PASSED test_scope_named.py::test_b PASSED test_scope_named.py::TestClass::test_f PASSED test_scope_named.py::TestClass::test_i PASSED - test_scope_named.py::test_c SKIPPED + test_scope_named.py::test_c SKIPPED(?:\s+\(.*\))? test_scope_named.py::test_d PASSED - test_scope_named.py::test_e SKIPPED + test_scope_named.py::test_e SKIPPED(?:\s+\(.*\))? test_scope_named.py::TestClass::test_g PASSED - test_scope_named.py::TestClass::test_h SKIPPED - test_scope_named.py::TestClass::test_j SKIPPED + test_scope_named.py::TestClass::test_h SKIPPED(?:\s+\(.*\))? + test_scope_named.py::TestClass::test_j SKIPPED(?:\s+\(.*\))? test_scope_named.py::TestClass::test_k PASSED - test_scope_named.py::TestClass::test_l SKIPPED + test_scope_named.py::TestClass::test_l SKIPPED(?:\s+\(.*\))? """) @@ -590,7 +590,7 @@ def test_d(self, request): """) result = ctestdir.runpytest("--verbose") result.assert_outcomes(passed=10, skipped=3, failed=3) - result.stdout.fnmatch_lines(""" + result.stdout.re_match_lines(r""" test_scope_dependsfunc_01.py::test_a PASSED test_scope_dependsfunc_01.py::test_b FAILED test_scope_dependsfunc_01.py::test_c PASSED @@ -598,13 +598,13 @@ def test_d(self, request): test_scope_dependsfunc_02.py::test_a FAILED test_scope_dependsfunc_02.py::test_b PASSED test_scope_dependsfunc_02.py::test_e PASSED - test_scope_dependsfunc_02.py::test_f SKIPPED + test_scope_dependsfunc_02.py::test_f SKIPPED(?:\s+\(.*\))? test_scope_dependsfunc_02.py::test_g PASSED test_scope_dependsfunc_02.py::test_h PASSED - test_scope_dependsfunc_02.py::test_i SKIPPED + test_scope_dependsfunc_02.py::test_i SKIPPED(?:\s+\(.*\))? test_scope_dependsfunc_02.py::test_j PASSED test_scope_dependsfunc_02.py::TestClass::test_a PASSED test_scope_dependsfunc_02.py::TestClass::test_b FAILED test_scope_dependsfunc_02.py::TestClass::test_c PASSED - test_scope_dependsfunc_02.py::TestClass::test_d SKIPPED + test_scope_dependsfunc_02.py::TestClass::test_d SKIPPED(?:\s+\(.*\))? """) diff --git a/tests/test_03_skipmsgs.py b/tests/test_03_skipmsgs.py index 2baddda..6e4b4d1 100644 --- a/tests/test_03_skipmsgs.py +++ b/tests/test_03_skipmsgs.py @@ -29,11 +29,11 @@ def test_d(): """) result = ctestdir.runpytest("--verbose", "-rs") result.assert_outcomes(passed=1, skipped=2, failed=1) - result.stdout.fnmatch_lines(""" - *::test_a PASSED - *::test_b FAILED - *::test_c SKIPPED - *::test_d SKIPPED + result.stdout.re_match_lines(r""" + .*::test_a PASSED + .*::test_b FAILED + .*::test_c SKIPPED(?:\s+\(.*\))? + .*::test_d SKIPPED(?:\s+\(.*\))? """) result.stdout.fnmatch_lines_random(""" SKIP* test_c depends on test_b diff --git a/tests/test_04_automark.py b/tests/test_04_automark.py index 07ded56..0e573db 100644 --- a/tests/test_04_automark.py +++ b/tests/test_04_automark.py @@ -23,9 +23,9 @@ def test_b(): """) result = ctestdir.runpytest("--verbose", "-rs") result.assert_outcomes(passed=1, skipped=1, failed=0) - result.stdout.fnmatch_lines(""" - *::test_a PASSED - *::test_b SKIPPED + result.stdout.re_match_lines(r""" + .*::test_a PASSED + .*::test_b SKIPPED(?:\s+\(.*\))? """) @@ -54,9 +54,9 @@ def test_b(): """) result = ctestdir.runpytest("--verbose", "-rs") result.assert_outcomes(passed=1, skipped=1, failed=0) - result.stdout.fnmatch_lines(""" - *::test_a PASSED - *::test_b SKIPPED + result.stdout.re_match_lines(r""" + .*::test_a PASSED + .*::test_b SKIPPED(?:\s+\(.*\))? """) @@ -85,7 +85,7 @@ def test_b(): """) result = ctestdir.runpytest("--verbose", "-rs") result.assert_outcomes(passed=2, skipped=0, failed=0) - result.stdout.fnmatch_lines(""" - *::test_a PASSED - *::test_b PASSED + result.stdout.re_match_lines(r""" + .*::test_a PASSED + .*::test_b PASSED """) diff --git a/tests/test_04_ignore_unknown.py b/tests/test_04_ignore_unknown.py index 647a298..c3e6362 100644 --- a/tests/test_04_ignore_unknown.py +++ b/tests/test_04_ignore_unknown.py @@ -32,8 +32,8 @@ def test_d(): """) result = ctestdir.runpytest("--verbose", "test_no_ignore.py::test_d") result.assert_outcomes(passed=0, skipped=1, failed=0) - result.stdout.fnmatch_lines(""" - *::test_d SKIPPED + result.stdout.re_match_lines(r""" + .*::test_d SKIPPED(?:\s+\(.*\))? """) @@ -68,6 +68,6 @@ def test_d(): result = ctestdir.runpytest("--verbose", "--ignore-unknown-dependency", "test_ignore.py::test_d") result.assert_outcomes(passed=1, skipped=0, failed=0) - result.stdout.fnmatch_lines(""" - *::test_d PASSED + result.stdout.re_match_lines(r""" + .*::test_d PASSED """) diff --git a/tests/test_05_ordering.py b/tests/test_05_ordering.py index f1babc6..6bc188b 100644 --- a/tests/test_05_ordering.py +++ b/tests/test_05_ordering.py @@ -47,14 +47,14 @@ def test_g(): """) result = ctestdir.runpytest("--verbose") result.assert_outcomes(passed=7, skipped=0, failed=0) - result.stdout.fnmatch_lines(""" - test_order_standard.py::test_a PASSED - test_order_standard.py::test_c PASSED - test_order_standard.py::test_d PASSED - test_order_standard.py::test_e PASSED - test_order_standard.py::test_f PASSED - test_order_standard.py::test_b PASSED - test_order_standard.py::test_g PASSED + result.stdout.re_match_lines(r""" + .*::test_a PASSED + .*::test_c PASSED + .*::test_d PASSED + .*::test_e PASSED + .*::test_f PASSED + .*::test_b PASSED + .*::test_g PASSED """) @@ -89,12 +89,12 @@ def test_e(): """) result = ctestdir.runpytest("--verbose") result.assert_outcomes(passed=3, skipped=2, failed=0) - result.stdout.fnmatch_lines(""" - test_order_missing.py::test_a PASSED - test_order_missing.py::test_c PASSED - test_order_missing.py::test_e PASSED - test_order_missing.py::test_b SKIPPED - test_order_missing.py::test_d SKIPPED + result.stdout.re_match_lines(r""" + .*::test_a PASSED + .*::test_c PASSED + .*::test_e PASSED + .*::test_b SKIPPED(?:\s+\(.*\))? + .*::test_d SKIPPED(?:\s+\(.*\))? """) @@ -129,12 +129,12 @@ def test_e(): """) result = ctestdir.runpytest("--verbose") result.assert_outcomes(passed=3, skipped=2, failed=0) - result.stdout.fnmatch_lines(""" - test_order_cycles.py::test_a PASSED - test_order_cycles.py::test_c PASSED - test_order_cycles.py::test_e PASSED - test_order_cycles.py::test_b SKIPPED - test_order_cycles.py::test_d SKIPPED + result.stdout.re_match_lines(r""" + .*::test_a PASSED + .*::test_c PASSED + .*::test_e PASSED + .*::test_b SKIPPED(?:\s+\(.*\))? + .*::test_d SKIPPED(?:\s+\(.*\))? """) @@ -180,15 +180,15 @@ def test_h(): """) result = ctestdir.runpytest("--verbose") result.assert_outcomes(passed=8, skipped=0, failed=0) - result.stdout.fnmatch_lines(""" - test_order_nesting.py::test_a PASSED - test_order_nesting.py::test_c PASSED - test_order_nesting.py::test_e PASSED - test_order_nesting.py::test_g PASSED - test_order_nesting.py::test_f PASSED - test_order_nesting.py::test_d PASSED - test_order_nesting.py::test_b PASSED - test_order_nesting.py::test_h PASSED + result.stdout.re_match_lines(r""" + .*::test_a PASSED + .*::test_c PASSED + .*::test_e PASSED + .*::test_g PASSED + .*::test_f PASSED + .*::test_d PASSED + .*::test_b PASSED + .*::test_h PASSED """) @@ -240,14 +240,14 @@ def test_i(): """) result = ctestdir.runpytest("--verbose") result.assert_outcomes(passed=9, skipped=0, failed=0) - result.stdout.fnmatch_lines(""" - test_order_scopes.py::test_a PASSED - test_order_scopes.py::test_c PASSED - test_order_scopes.py::test_b PASSED - test_order_scopes.py::test_e PASSED - test_order_scopes.py::test_d PASSED - test_order_scopes.py::test_g PASSED - test_order_scopes.py::test_f PASSED - test_order_scopes.py::test_i PASSED - test_order_scopes.py::test_h PASSED + result.stdout.re_match_lines(r""" + .*::test_a PASSED + .*::test_c PASSED + .*::test_b PASSED + .*::test_e PASSED + .*::test_d PASSED + .*::test_g PASSED + .*::test_f PASSED + .*::test_i PASSED + .*::test_h PASSED """) From 67b32c3edadfae4e898c5f34a87ad2ed254cdf6b Mon Sep 17 00:00:00 2001 From: DevilXD Date: Sat, 8 Jan 2022 21:07:24 +0100 Subject: [PATCH 23/26] 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 24/26] 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 25/26] 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 26/26] 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 """)