On 8/25/07, Gregory P. Smith <[EMAIL PROTECTED]> wrote: > I like this idea.
Yay! Now, I ain't the only one. ;) > Be sure to have an option to ignore dependancies and run all tests. Yes, I planned to add a such option. > Also when skipping tests because a depedancy failed have unittest > print out an indication that a test was skipped due to a dependancy > rather than silently running fewer tests. Otherwise it could be > deceptive and appear that only one test was affected. However, that was never planned. I added the ignore_dependencies option. Also, I fixed the sub-optimal dependency resolution algorithm that was in my original example implementation. -- Alexandre --- dep.py.old 2007-08-25 19:54:27.000000000 -0400 +++ dep.py 2007-08-25 20:02:55.000000000 -0400 @@ -2,8 +2,9 @@ class CycleError(Exception): pass +class TestGraph: -class TestCase: + ignore_dependencies = False def __init__(self): self.graph = {} @@ -19,16 +20,16 @@ graph = self.graph toskip = set() msgs = [] - while graph: + if self.ignore_dependencies: + for test in graph: + graph[test].clear() # find tests without any pending dependencies - source = [test for test, deps in graph.items() if not deps] - if not source: - raise CycleError - for testname in source: + queue = [test for test, deps in graph.items() if not deps] + while queue: + testname = queue.pop() if testname in toskip: msgs.append("%s... skipped" % testname) - resolvedeps(graph, testname) - del graph[testname] + queue.extend(resolve(graph, testname)) continue test = getattr(self, testname) try: @@ -42,8 +43,9 @@ else: msgs.append("%s... ok" % testname) finally: - resolvedeps(graph, testname) - del graph[testname] + queue.extend(resolve(graph, testname)) + if graph: + raise CycleError for msg in sorted(msgs): print(msg) @@ -60,10 +62,15 @@ rdeps.update(getrevdeps(graph, x)) return rdeps - def resolvedeps(graph, testname): +def resolve(graph, testname): + toqueue = [] for test in graph: if testname in graph[test]: graph[test].remove(testname) + if not graph[test]: + toqueue.append(test) + del graph[testname] + return toqueue def depends(*args): def decorator(test): @@ -75,7 +82,9 @@ return decorator -class MyTest(TestCase): +class MyTest(TestGraph): + + ignore_dependencies = True @depends('test_foo') def test_nah(self):
class CycleError(Exception): pass class TestGraph: ignore_dependencies = False def __init__(self): self.graph = {} tests = [x for x in dir(self) if x.startswith('test')] for testname in tests: test = getattr(self, testname) if hasattr(test, 'deps'): self.graph[testname] = test.deps else: self.graph[testname] = set() def run(self): graph = self.graph toskip = set() msgs = [] if self.ignore_dependencies: for test in graph: graph[test].clear() # find tests without any pending dependencies queue = [test for test, deps in graph.items() if not deps] while queue: testname = queue.pop() if testname in toskip: msgs.append("%s... skipped" % testname) queue.extend(resolve(graph, testname)) continue test = getattr(self, testname) try: test() except AssertionError: toskip.update(getrevdeps(graph, testname)) msgs.append("%s... failed" % testname) except: toskip.update(getrevdeps(graph, testname)) msgs.append("%s... error" % testname) else: msgs.append("%s... ok" % testname) finally: queue.extend(resolve(graph, testname)) if graph: raise CycleError for msg in sorted(msgs): print(msg) def getrevdeps(graph, testname): """Return the reverse depencencies of a test""" rdeps = set() for x in graph: if testname in graph[x]: rdeps.add(x) if rdeps: # propagate depencencies recursively for x in rdeps.copy(): rdeps.update(getrevdeps(graph, x)) return rdeps def resolve(graph, testname): toqueue = [] for test in graph: if testname in graph[test]: graph[test].remove(testname) if not graph[test]: toqueue.append(test) del graph[testname] return toqueue def depends(*args): def decorator(test): if hasattr(test, 'deps'): test.deps.update(args) else: test.deps = set(args) return test return decorator class MyTest(TestGraph): ignore_dependencies = True @depends('test_foo') def test_nah(self): pass @depends('test_bar', 'test_baz') def test_foo(self): pass @depends('test_tin') def test_bar(self): self.fail() def test_baz(self): self.error() def test_tin(self): pass def error(self): raise ValueError def fail(self): raise AssertionError if __name__ == '__main__': t = MyTest() t.run()
_______________________________________________ Python-Dev mailing list Python-Dev@python.org http://mail.python.org/mailman/listinfo/python-dev Unsubscribe: http://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com