diff options
-rw-r--r-- | TODO | 30 | ||||
-rw-r--r-- | functional_tests/test_plugins.py | 2 | ||||
-rw-r--r-- | nose/case.py | 38 | ||||
-rw-r--r-- | nose/plugins/builtin.py | 45 | ||||
-rw-r--r-- | nose/plugins/doctests.py | 106 | ||||
-rw-r--r-- | nose/proxy.py | 2 | ||||
-rw-r--r-- | unit_tests/test_plugins.py | 4 |
7 files changed, 105 insertions, 122 deletions
@@ -11,12 +11,22 @@ DOCUMENTATION FEATURES -doctest: must return wrapped suite +doctest: + -- can't use DocTestCase/DocTestSuite/DocFileSuite -- the cases + returned don't implement things correctly. Need to subclass DocTestCase + locally and return instances of that subclass. May have to do something + icky like call DocTestSuite and then pull the ._dt_test out of the cases? + + - good thing: might make it possible to support file tests in 2.3 new plugin: testid -- needs tests with doctests - -- needs to be able to handle cases w/out address() method + -- change the command line parsing to look for just digits -- # is a shell + meta character that causes parsing to stop othe options: + * digit-dot: 3. + * dash digit -3 + * -id digit -id 3 rename Failure and split into subclasses for Import and other, and make it optionally include the name of the file being considered so that @@ -43,18 +53,6 @@ OK (skipped=2) BUGS -BIG PROBLEM - -given an input like: - -python nose/core.py functional_tests/support/idp/example.py:add_one --with-doctest - -the natural expectation is that the target function will be loaded as -a DOCTEST not as a regular test. Doctest probably needs to implement -loadTestsFromName? or makeTest? -- maybe plugins need FIRST crack at -makeTest? One way or another, the doctest plugin has to be allowed to -examine the proposed test object first and turn it into a test if it -wants to. -- when run vs spine suite, makeTest seemingly was called on an object @@ -105,7 +103,9 @@ PROFILE need to profile -- on 250 tests with discovery, 0.10-dev is ~ 1 second slower than 0.9. Profile and optimize. - +one possiblity -- instead of instantiating a result proxy for every +test, tag each test with a weakref to the nose.case.Test that wraps +it. older notes: diff --git a/functional_tests/test_plugins.py b/functional_tests/test_plugins.py index 42b0476..1d86b03 100644 --- a/functional_tests/test_plugins.py +++ b/functional_tests/test_plugins.py @@ -33,7 +33,7 @@ class TestPluginCalls(unittest.TestCase): 'prepareTestRunner', 'prepareTest', 'setOutputStream', 'prepareTestResult', 'beforeDirectory', 'wantFile', 'wantDirectory', 'beforeImport', 'afterImport', 'wantModule', - 'wantClass', 'wantFunction', 'wantMethod', + 'wantClass', 'wantFunction', 'makeTest', 'wantMethod', 'loadTestsFromTestClass', 'loadTestsFromModule', 'beforeTest', 'prepareTestCase', 'startTest', 'addSuccess', 'stopTest', 'afterTest', 'loadTestsFromDir', 'afterDirectory', diff --git a/nose/case.py b/nose/case.py index f2135f2..df0d21c 100644 --- a/nose/case.py +++ b/nose/case.py @@ -373,41 +373,3 @@ class MethodTestCase(TestBase): return self.descriptor, self.arg else: return self.method, self.arg - - -class TestWrapper(object): - """A proxy for test cases constructed by plugins. Subclass this - class to provide compatible wrappers for your test - cases. Instances of this class (or subclasses) act as proxies to - test cases, adding two attributes: an address() method that may be - used to return the test address of the object made into a test - (which you may have to override) and a _nose_case property that - the nose ResultProxy can use for sanity checking to be sure that - the case it sees belongs to the nose.case.Test to which it is - bound.""" - __test__ = False # do not collect - _nose_case = None - _nose_obj = None - - def __init__(self, case, obj=None): - self.__dict__['_nose_case'] = case - self.__dict__['_nose_obj'] = obj - - def address(self): - if self.__dict__['_nose_obj'] is not None: - return test_address(self.__dict__['_nose_obj']) - - def __getattr__(self, attr): - return getattr(self.__dict__['_nose_case'], attr) - - def __setattr__(self, attr, val): - setattr(self.__dict__['_nose_case'], attr, val) - - def __call__(self, *arg, **kw): - return self.__dict__['_nose_case'](*arg, **kw) - - def __str__(self): - return str(self.__dict__['_nose_case']) - - def __repr__(self): - return repr(self.__dict__['_nose_case']) diff --git a/nose/plugins/builtin.py b/nose/plugins/builtin.py index 195b3b1..d0b6421 100644 --- a/nose/plugins/builtin.py +++ b/nose/plugins/builtin.py @@ -1,27 +1,26 @@ """ Lists builtin plugins """ +plugins = [] +builtins = ( + ('nose.plugins.attrib', 'AttributeSelector'), + ('nose.plugins.capture', 'Capture'), + ('nose.plugins.cover', 'Coverage'), + ('nose.plugins.debug', 'Pdb'), + ('nose.plugins.deprecated', 'Deprecated'), + ('nose.plugins.doctests', 'Doctest'), + ## ('nose.plugins.isolation', 'Isolation'), + ('nose.plugins.failuredetail', 'FailureDetail'), + ('nose.plugins.prof', 'Profile'), + ('nose.plugins.skip', 'Skip'), + ('nose.plugins.testid', 'TestId') + ) -from nose.plugins.attrib import AttributeSelector -from nose.plugins.capture import Capture -from nose.plugins.cover import Coverage -from nose.plugins.debug import Pdb -from nose.plugins.deprecated import Deprecated -from nose.plugins.doctests import Doctest -## from nose.plugins.isolation import -from nose.plugins.failuredetail import FailureDetail -from nose.plugins.prof import Profile -from nose.plugins.skip import Skip -from nose.plugins.testid import TestId - -plugins = [ - AttributeSelector, - Capture, - Coverage, - Deprecated, - Skip, - Doctest, - FailureDetail, - Pdb, - Profile, - TestId] +for module, cls in builtins: + try: + plugmod = __import__(module, globals(), locals(), [cls]) + except ImportError: + continue + plug = getattr(plugmod, cls) + plugins.append(plug) + globals()[cls] = plug diff --git a/nose/plugins/doctests.py b/nose/plugins/doctests.py index 847e5b0..a5f1019 100644 --- a/nose/plugins/doctests.py +++ b/nose/plugins/doctests.py @@ -18,9 +18,9 @@ import doctest import logging import os import sys -from nose.case import TestWrapper +from inspect import getmodule from nose.plugins.base import Plugin -from nose.util import anyp, test_address, resolve_name, tolist +from nose.util import anyp, getpackage, test_address, resolve_name, tolist log = logging.getLogger(__name__) @@ -72,50 +72,44 @@ class Doctest(Plugin): if not self.matches(module.__name__): log.debug("Doctest doesn't want module %s", module) return - try: - doctests = doctest.DocTestSuite(module) - except ValueError: - log.debug("No doctests in %s", module) + tests = self.finder.find(module) + if not tests: return - else: - return self.makeTests(doctests) + tests.sort() + module_file = module.__file__ + if module_file[-4:] in ('.pyc', '.pyo'): + module_file = module_file[:-1] + for test in tests: + if not test.examples: + continue + if not test.filename: + test.filename = module_file + yield DocTestCase(test) def loadTestsFromFile(self, filename): if self.extension and anyp(filename.endswith, self.extension): + name = os.path.basename(filename) + dh = open(filename) try: - return self.makeTests( - doctest.DocFileSuite(filename, module_relative=False)) - except AttributeError: - raise Exception("Doctests in files other than .py " - "(python source) not supported in this " - "version of doctest") - else: - return - + doc = dh.read() + finally: + dh.close() + parser = doctest.DocTestParser() + test = parser.get_doctest( + doc, globs={}, name=name, filename=filename, lineno=0) + if test.examples: + yield DocFileCase(test) + def makeTest(self, obj, parent): """Look for doctests in the given object, which will be a function, method or class. """ - try: - # FIXME really find the module, don't assume parent - # is a module - doctests = self.finder.find(obj, module=parent) - except ValueError: - yield Failure(*sys.exc_info()) - return + doctests = self.finder.find(obj, module=getmodule(parent)) if doctests: for test in doctests: if len(test.examples) == 0: continue - yield DoctestWrapper(doctest.DocTestCase(test), obj) - - - def makeTests(self, doctests): - if hasattr(doctests, '__iter__'): - doctest_suite = doctests - else: - doctest_suite = doctests._tests - return map(DoctestWrapper, doctest_suite) + yield DocTestCase(test, obj=obj) def matches(self, name): """Doctest wants only non-test modules in general. @@ -148,21 +142,49 @@ class Doctest(Plugin): return None -class DoctestWrapper(TestWrapper): +class DocTestCase(doctest.DocTestCase): """Proxy for DocTestCase: provides an address() method that returns the correct address for the doctest case. Otherwise acts as a proxy to the test case. To provide hints for address(), an obj may also be passed -- this will be used as the test object for purposes of determining the test address, if it is provided. """ + def __init__(self, test, optionflags=0, setUp=None, tearDown=None, + checker=None, obj=None): + self._nose_obj = obj + super(DocTestCase, self).__init__( + test, optionflags=optionflags, setUp=None, tearDown=None, + checker=None) + def address(self): - adr = super(DoctestWrapper, self).address() - if adr is None: - adr = test_address( - resolve_name(self.__dict__['_nose_case']._dt_test.name)) - return adr - - # FIXME + if self._nose_obj is not None: + return test_address(self._nose_obj) + return test_address(resolve_name(self._dt_test.name)) + # Annoyingly, doctests loaded via find(obj) omit the module name - # so we need to override id, __str__ and shortDescription + # so we need to override id, __repr__ and shortDescription # bonus: this will squash a 2.3 vs 2.4 incompatiblity + def id(self): + name = self._dt_test.name + filename = self._dt_test.filename + if filename is not None: + pk = getpackage(filename) + if not name.startswith(pk): + name = "%s.%s" % (pk, name) + return name + + def __repr__(self): + name = self.id() + name = name.split('.') + return "%s (%s)" % (name[-1], '.'.join(name[:-1])) + __str__ = __repr__ + + def shortDescription(self): + return 'Doctest: %s' % self.id() + + +class DocFileCase(doctest.DocFileCase): + """Overrides to provide filename + """ + def address(self): + return (self._dt_test_filename, None, None) diff --git a/nose/proxy.py b/nose/proxy.py index 7b8f9c5..ade3cfe 100644 --- a/nose/proxy.py +++ b/nose/proxy.py @@ -88,7 +88,7 @@ class ResultProxy(object): case = getattr(self.test, 'test', None) assert (test is self.test or test is case - or test is getattr(case, 'case', None), + or test is getattr(case, '_nose_case', None), "ResultProxy for %r (%s) was called with test %r (%s)" % (self.test, id(self.test), test, id(test))) diff --git a/unit_tests/test_plugins.py b/unit_tests/test_plugins.py index c711c1b..3999819 100644 --- a/unit_tests/test_plugins.py +++ b/unit_tests/test_plugins.py @@ -205,8 +205,8 @@ class TestDoctestPlugin(unittest.TestCase): here = os.path.abspath(os.path.dirname(__file__)) support = os.path.join(here, 'support') plug = Doctest() - suite = plug.loadTestsFromFile(os.path.join(support, 'foo')) - assert not suite + for test in plug.loadTestsFromFile(os.path.join(support, 'foo')): + self.fail("Expected no tests, got %s" % test) class TestAttribPlugin(unittest.TestCase): |