[conduit: 89/138] Add EnvironmentWrapper objects. Allows us to avoid spaghetti for profilers, coverage checks and (eep



commit e220f2f8a6e86ad1a86e4a09acf942c0438c2504
Author: John Carr <john carr unrouted co uk>
Date:   Mon May 4 12:50:57 2009 -0700

    Add EnvironmentWrapper objects. Allows us to avoid spaghetti for profilers, coverage checks and (eep) custom dbus sessions
---
 test/soup/env/__init__.py |   44 ++++++++++++++++++++++++++++++++++++++++++++
 test/soup/env/cov.py      |   34 ++++++++++++++++++++++++++++++++++
 test/soup/env/profile.py  |   29 +++++++++++++++++++++++++++++
 test/soup/result.py       |   33 +++++++++++++++++++++++++++++++--
 test/soup/soup            |   26 ++++++++------------------
 5 files changed, 146 insertions(+), 20 deletions(-)

diff --git a/test/soup/env/__init__.py b/test/soup/env/__init__.py
new file mode 100644
index 0000000..d7f7c34
--- /dev/null
+++ b/test/soup/env/__init__.py
@@ -0,0 +1,44 @@
+
+import os, sys
+
+class EnvironmentWrapper(object):
+
+    @classmethod
+    def enabled(cls, opts):
+        return True
+
+    def prepare_environment(self):
+        """ Modify the environment that the tests are running in """
+        pass
+
+    def decorate_test(self, test):
+        """ Decorate a callable so that it can be run in the modified environment """
+        return test
+
+    def finalize_environment(self):
+        """ Clean up the environment. Called at the very end of the test suite. """
+        pass
+
+
+def load_modules():
+    basepath = os.path.dirname(__file__)
+    for root, dirs, files in os.walk(basepath):
+        for dir in dirs:
+            if dir[:1] != ".":
+                load_module(dir)
+        for file in files:
+            if file.endswith(".py") and not file.startswith("__"):
+                load_module(file[:-3])
+        break
+
+def load_module(module):
+    if sys.modules.has_key(module):
+        reload(sys.modules[module])
+    else:
+        __import__("soup.env", {}, {}, [module])
+
+def get_all():
+    if len(EnvironmentWrapper.__subclasses__()) == 0:
+        load_modules()
+    return EnvironmentWrapper.__subclasses__()
+
diff --git a/test/soup/env/cov.py b/test/soup/env/cov.py
new file mode 100644
index 0000000..8fc656b
--- /dev/null
+++ b/test/soup/env/cov.py
@@ -0,0 +1,34 @@
+
+import soup
+
+try:
+    import coverage
+    supports_coverage = True
+except:
+    supports_coverage = False
+
+from glob import glob
+
+
+class Coverage(soup.env.EnvironmentWrapper):
+
+    @classmethod
+    def enabled(cls, opts):
+        if not opts.coverage:
+            return False
+        #FIXME: Should try importing and fail gracefully if user requests
+        # coverage but cant have it
+        assert supports_coverage
+        return True
+
+    def prepare_environment(self):
+        import coverage
+        coverage.erase()
+        coverage.start()
+
+    def finalize_environment(self):
+        coverage.stop()
+        modules = glob("conduit/*.py") + glob("conduit/*/*.py") + glob("conduit/*/*/*.py")
+        coverage.report(modules, ignore_errors=1, show_missing=0)
+        coverage.erase()
+
diff --git a/test/soup/env/profile.py b/test/soup/env/profile.py
new file mode 100644
index 0000000..502c3d4
--- /dev/null
+++ b/test/soup/env/profile.py
@@ -0,0 +1,29 @@
+
+import soup
+
+try:
+    import cProfile
+    import pstats
+    supported = True
+except ImportError:
+    supported = False
+
+
+class Profile(soup.env.EnvironmentWrapper):
+
+    @classmethod
+    def enabled(self, opts):
+        if not opts.profile:
+            return False
+        assert supported, "You need python-profiler to profile the test cases"
+        return True
+
+    def decorate_test(self, test):
+        def _(*args, **kwargs):
+            p = cProfile.Profile()
+            res = p.runcall(test, *args, **kwargs)
+            #FIXME: Need some way to attach profiling data to report object
+            # p.print_stats()
+            return res
+        return _
+
diff --git a/test/soup/result.py b/test/soup/result.py
index f7251f9..da2ea55 100644
--- a/test/soup/result.py
+++ b/test/soup/result.py
@@ -1,4 +1,5 @@
 
+import soup.env
 from soup import UnavailableFeature
 from soup.utils import progressbar
 
@@ -140,11 +141,17 @@ class VerboseConsoleTextResult(TextTestResult):
 
 class TestRunner(object):
 
-    def __init__(self, stream=sys.stderr, descriptions=0, verbosity=1):
+    def __init__(self, opts, stream=sys.stderr, descriptions=0, verbosity=1):
         self.stream = unittest._WritelnDecorator(stream)
         self.descriptions = 0
         self.verbosity = 0
 
+        # Discover all enabled EnvironmentWrapper objects
+        self.env = []
+        for e in soup.env.get_all():
+            if e.enabled(opts):
+                self.env.append(e())
+
     def make_results(self, tests):
         if self.verbosity > 1:
             klass = VerboseConsoleTextResult
@@ -153,12 +160,34 @@ class TestRunner(object):
 
         return klass(self.stream, self.descriptions, self.verbosity, num_tests=tests.countTestCases())
 
+    def iter_tests(self, tests):
+        if isinstance(tests, unittest.TestSuite):
+            for test in tests:
+                for subtest in self.iter_tests(test):
+                    yield subtest
+        else:
+            yield tests
+
     def run(self, tests):
         result = self.make_results(tests)
         result.report_starting()
+
+        for e in self.env:
+            e.prepare_environment()
+
         start_time = time.time()
-        tests.run(result)
+
+        for t in self.iter_tests(tests):
+            tr = t.run
+            for e in self.env:
+                tr = e.decorate_test(tr)
+            tr(result)
+
         time_taken = time.time() - start_time
+
+        for e in self.env:
+            e.finalize_environment()
+
         result.report_finished(time_taken)
         return result
 
diff --git a/test/soup/soup b/test/soup/soup
index 9fa679a..510c029 100755
--- a/test/soup/soup
+++ b/test/soup/soup
@@ -1,7 +1,6 @@
 #! /usr/bin/env python
 
 import sys, os, unittest, logging
-from glob import glob
 
 testsdir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
 sys.path.insert(0, testsdir)
@@ -24,28 +23,17 @@ from test_synchronization import *
 
 import result
 
-def run_tests(tests, verbose=False, do_coverage=False):
-    runner = result.TestRunner(verbosity=2 if verbose else 1)
+def run_tests(tests, opts):
+    runner = result.TestRunner(opts)
 
-    if verbose:
+    if opts.verbose:
         logging.basicConfig(level=logging.DEBUG)
 
-    if do_coverage:
-        import coverage
-        coverage.erase()
-        coverage.start()
-
     res = runner.run(unittest.TestSuite(tests))
 
-    if do_coverage:
-        coverage.stop()
-        modules = glob('conduit/*.py')+glob('conduit/*/*.py')+glob('conduit/*/*/*.py')
-        coverage.report(modules, ignore_errors=1, show_missing=0)
-        coverage.erase()
-
     sys.exit(not res.wasSuccessful())
 
-def list_tests(tests):
+def list_tests(tests, opts):
     for test in tests:
         print test.name(), test.testMethodName
     sys.exit(0)
@@ -67,6 +55,8 @@ if __name__ == "__main__":
                       help="Output lots of noise as tests are run")
     parser.add_option("-c", "--coverage", action="store_true", dest="coverage",
                       help="Enable code coverage")
+    parser.add_option("-p", "--profile", action="store_true", dest="profile",
+                      help="Profile execution")
 
     # Add the different execution modes..
     parser.add_option("-l", "--list", action="store_const", const="list", dest="mode",
@@ -90,7 +80,7 @@ if __name__ == "__main__":
 
     # And run.
     if opts.mode == "execute":
-        run_tests(tests, verbose=opts.verbose, do_coverage=opts.coverage)
+        run_tests(tests, opts)
     elif opts.mode == "list":
-        list_tests(tests)
+        list_tests(tests, opts)
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]