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

Reply via email to