Commits

Anonymous committed 7fab173 Merge

Merged upstream

Comments (0)

Files changed (5)

     * display the time of individual tests in verbose reports
     * display a progress indicator as tests are run ([39/430] format) in verbose reports
     * allow arbitrary channels for messaging instead of just the three verbosity levels
+    * A plugin that auto-loads all plugins from a user specified directory
 
 In addition I intend to create a plugin that outputs junit compatible xml from a test run (for integration with tools like the hudson continuous integration server) and a test runner that runs tests in parallel using multiprocessing.
 
 Should it instead be called once with a MetaEvent that collects all the
 errors.
 
-startReport, reportResult and reportSummary events for customizing how the test
-run is reported?
+Inserted tests may have the "wrong" class and module, causing class and module
+level setup / teardown to be re-executed. A way of faking this, or indicating
+that they should be ignored for this purpose, should be available.
 
 The junit-xml plugin needs access to stdout / stderr for the test. This is
 only currently available if the test is run with buffering on.
 line? (This would be 'tricky' as ``handleFile`` will have to be called for the
 containing module and then the correct name pulled out of the module.)
 
-A plugin that adds a "user plugin directory" and automatically activates
-(imports) all plugins located there. New plugins can be added just by dropping
-them in the directory (zero config).
-
 The location of the user config file is not settled, and will probably need
 to be platform specific. See: http://bugs.python.org/issue7175
 
 replaced. executeTests in startTestRun should only be able to be set by one
 plugin.
 
-Inserted tests may have the "wrong" class and module, causing class and module
-level setup / teardown to be re-executed. A way of faking this, or indicating
-that they should be ignored for this purpose, should be available.
+For customising reporting there are beforeSummaryReport and afterSummaryReport events. Should there also be an event that lets you customize how reports from individual tests are output?
 
 Add an epilogue to the optparse help messages.
 
 
 Output by the TextTestRunner and the TextTestResult goes through the 'message' event. To support this the ``_WritelnDecorator`` takes an optional 'runner' argument in the constructor and has a new `write` method. If the 'runner' argument is used then all calls to `write` and `writeln` are sent on to the ``runner.message`` method (which writes directly to the underlying stream).
 
-TextTestResult has a new addReport method used by the TextTestRunner for test reporting. This adds report objects to the ``.reports`` attribute and also delegates to the standard ``addError`` / ``addFailure`` etc methods for tests with standard outcomes. For non-standard outcomes ``addReport`` reports whatever information is specified in the XXXXX
+TextTestResult has a new addReport method used by the TextTestRunner for test reporting. This adds report objects to the ``.reports`` attribute and also delegates to the standard ``addError`` / ``addFailure`` etc methods for tests with standard outcomes. Test reporting at the end of the test run is done from the reports. If a TestResult object without an ``addReport`` method is used then the old system for test reporting is used (backwards compatible).
 
 Test discovery has been improved. The initial implementation in Python 2.7 was very conservative. The new implementation supports many more common package layouts. It supports the package code being in a 'src' or a 'lib' subdirectory (that isn't itself a package). It also supports tests being in any top level directory that isn't a package, so long as the directory name contains 'test' in it.
 
     unittest2.plugins.timed
     unittest2.plugins.counttests
     unittest2.plugins.logchannels
-    unittest2.plugins.attrib
-    unittest2.plugins.testid
-    
+    unittest2.plugins.pluginsdir
+
 excluded-plugins =
     foo.bar.baz
 
 
 [count]
 always-on = True
-enhanced = False
+enhanced = False
+
+[plugins-dir]
+# This plugin can only be enabled from a config file
+always-on = False
+plugin-path = ~/.unittest

unittest2/__init__.py

     'FunctionTestCase', 'main', 'defaultTestLoader', 'SkipTest', 'skip', 
     'skipIf', 'skipUnless', 'expectedFailure', 'TextTestResult', '__version__',
     'collector', 'setRunner', 'message', 'Plugin', 'loadedPlugins', 'addOption',
-    'addDiscoveryOption', 'loadConfig', 'getConfig'
+    'addDiscoveryOption', 'loadConfig', 'getConfig', 'loadPlugin'
 ]
 
 __version__ = '0.6.0 alpha (plugins branch)'
 
 
 from unittest2.events import (
-    Plugin, loadedPlugins, addOption, addDiscoveryOption
+    Plugin, loadedPlugins, addOption, addDiscoveryOption,
+    loadPlugin
 )
 
 from unittest2.config import loadConfig, getConfig

unittest2/plugins/moduleloading.py

     parametersEnabled = False
     configSection = 'functions'
     commandLineSwitch = (None, 'functions', 'Load tests from functions')
+    unpack = enumerate
 
     def loadTestsFromName(self, event):
         name = event.name
             args['tearDown'] = tearDown
 
         paramList = getattr(obj, 'paramList', None)
-        isGenerator = getattr(obj, 'testGenerator', False)
+        isGenerator = self.isGenerator(obj)
         if self.parametersEnabled and paramList is not None:
             for index, argSet in enumerate(paramList):
                 def func(argSet=argSet, obj=obj):
             name = '%s.%s' % (obj.__module__, obj.__name__)
             def createTest(name):
                 return GeneratorFunctionCase(name, **args)
-            tests.extend(testsFromGenerator(name, extras, createTest))
+            tests.extend(testsFromGenerator(name, extras, createTest,
+                                            self.unpack))
         else:
             case = FunctionTestCase(obj, **args)
             tests.append(case)
             return [tests[testIndex-1]]
         return tests
 
+    def isGenerator(self, obj):
+        return getattr(obj, 'testGenerator', None) is not None
+
 
 class GeneratorFunctionCase(FunctionTestCase):
     def __init__(self, name, **args):
 
     configSection = 'generators'
     commandLineSwitch = (None, 'generators', 'Load tests from generators')
+    unpack = enumerate
 
     def pluginsLoaded(self, event):
         Functions.generatorsEnabled = True
         testCaseClass = event.testCase
         for name in dir(testCaseClass):
             method = getattr(testCaseClass, name)
-            if getattr(method, 'testGenerator', None) is not None:
+            if self.isGenerator(method):
                 instance = testCaseClass(name)
                 event.extraTests.extend(
-                    testsFromGenerator(name, method(instance), testCaseClass)
+                    testsFromGenerator(name, method(instance), testCaseClass,
+                                       self.unpack)
                 )
 
+    def isGenerator(self, obj):
+        return getattr(obj, 'testGenerator', None) is not None
+        
     def getTestCaseNames(self, event):
         names = filter(event.isTestMethod, dir(event.testCase))
         klass = event.testCase
         for name in names:
             method = getattr(klass, name)
-            if getattr(method, 'testGenerator', None) is not None:
+            if self.isGenerator(method):
                 event.excludedNames.append(name)
 
     def loadTestsFromName(self, event):
         parent, obj, name, index = result
         if (index is None or not isinstance(parent, type) or 
             not issubclass(parent, TestCase) or 
-            not getattr(obj, 'testGenerator', False)):
+            not self.isGenerator(obj)):
             # we're only handling TestCase generator methods here
             return
 
         instance = parent(obj.__name__)
         
         try:
-            test = list(testsFromGenerator(name, obj(instance), parent))[index-1]
+            test = list(testsFromGenerator(name, obj(instance), parent, 
+                                           self.unpack))[index-1]
         except IndexError:
             raise TestNotFoundError(original_name)
         
         return suite
 
 
-def testsFromGenerator(name, generator, testCaseClass):
+def testsFromGenerator(name, generator, testCaseClass, unpack):
     try:
-        for index, (func, args) in enumerate(generator):
+        for index, (func, args) in unpack(generator):
             method_name = name_from_args(name, index, args)
             setattr(testCaseClass, method_name, None)
             instance = testCaseClass(method_name)

unittest2/plugins/pluginsdir.py

+import os
+import sys
+from unittest2 import getConfig, loadPlugin
+
+defaultPath = '~/.unittest'
+
+config = getConfig('plugins-dir')
+thePath = config.get('path', defaultPath)
+excludedPlugins = getConfig('unittest')['excluded-plugins']
+
+on = config.as_bool('always-on', default=False)
+
+def loadPlugins(thePath):
+    if not os.path.isdir(thePath):
+        return
+    sys.path.append(thePath)
+    for entry in os.listdir(thePath):
+        fullPath = os.path.join(thePath, entry)
+        if entry.lower().endswith('.py') and os.path.isfile(fullPath):
+            name = entry[:-3]
+            if name in excludedPlugins:
+                continue
+            loadPlugin(name)
+        elif (os.path.isdir(fullPath) and 
+              os.path.isfile(os.path.join(fullPath, '__init__.py'))):
+            if entry not in excludedPlugins:
+                loadPlugin(entry)
+
+if on:
+    loadPlugins(os.path.expanduser(thePath))
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.