From 44eee6ca0607353257867c252653bdd25a9feadc Mon Sep 17 00:00:00 2001 From: Jason Pellerin Date: Wed, 4 Feb 2009 03:03:03 +0000 Subject: Applied patch making namespace package path traversal optional --- functional_tests/support/empty/.hidden | 0 functional_tests/test_namespace_pkg.py | 27 ++++++++++++++++++++------- nose/config.py | 7 +++++++ nose/loader.py | 17 +++++++++++------ nose/plugins/base.py | 4 +++- nose/plugins/manager.py | 5 +++++ 6 files changed, 46 insertions(+), 14 deletions(-) create mode 100644 functional_tests/support/empty/.hidden diff --git a/functional_tests/support/empty/.hidden b/functional_tests/support/empty/.hidden new file mode 100644 index 0000000..e69de29 diff --git a/functional_tests/test_namespace_pkg.py b/functional_tests/test_namespace_pkg.py index 8e29c1a..ba366b3 100644 --- a/functional_tests/test_namespace_pkg.py +++ b/functional_tests/test_namespace_pkg.py @@ -8,7 +8,7 @@ from test_program import TestRunner here = os.path.dirname(__file__) support = os.path.join(here, 'support') -class TestTestProgram(unittest.TestCase): +class TestNamespacePackages(unittest.TestCase): def setUp(self): self.cwd = os.getcwd() @@ -21,12 +21,8 @@ class TestTestProgram(unittest.TestCase): sys.path = self.orig_path os.chdir(self.cwd) - def test_run_support_ctx(self): - """Collect and run tests in functional_tests/support/ctx - - This should collect no tests in the default configuration, since - none of the modules have test-like names. - """ + def test_namespace_pkg(self): + """Ensure namespace packages work/can import from each other""" stream = StringIO() runner = TestRunner(stream=stream) runner.verbosity = 2 @@ -40,6 +36,23 @@ class TestTestProgram(unittest.TestCase): assert not res.errors assert not res.failures + def test_no_traverse_namespace(self): + """Ensure the --no-traverse-namespace option only tests the specified + module, not its other namespace package sibling. + """ + stream = StringIO() + runner = TestRunner(stream=stream) + runner.verbosity = 2 + prog = TestProgram(argv=['', '--no-traverse-namespace'], + testRunner=runner, + exit=False) + res = runner.result + self.assertEqual(res.testsRun, 1, + "Expected to run 1 tests, ran %s" % res.testsRun) + assert res.wasSuccessful() + assert not res.errors + assert not res.failures + if __name__ == '__main__': unittest.main() diff --git a/nose/config.py b/nose/config.py index 90bd5a9..a3a752d 100644 --- a/nose/config.py +++ b/nose/config.py @@ -203,6 +203,7 @@ class Config(object): self.verbosity = int(env.get('NOSE_VERBOSE', 1)) self.where = () self.workingDir = os.getcwd() + self.traverseNamespace = True self._default = self.__dict__.copy() self.update(kw) @@ -270,6 +271,7 @@ class Config(object): self.stopOnError = options.stopOnError self.verbosity = options.verbosity self.includeExe = options.includeExe + self.traverseNamespace = options.traverseNamespace self.debug = options.debug self.debugLog = options.debugLog self.loggingConfig = options.loggingConfig @@ -479,6 +481,11 @@ class Config(object): help="DO NOT look for tests in python modules that are " "executable. (The default on the windows platform is to " "do so.)") + parser.add_option( + "--no-traverse-namespace", action="store_false", + default=self.traverseNamespace, dest="traverseNamespace", + help="DO NOT traverse through all path entries of a " + "namespace package") self.plugins.loadPlugins() self.pluginOpts(parser) diff --git a/nose/loader.py b/nose/loader.py index 15544be..6a3874e 100644 --- a/nose/loader.py +++ b/nose/loader.py @@ -260,7 +260,7 @@ class TestLoader(unittest.TestLoader): "%s is not a function or method" % test_func) return self.suiteClass(generate, context=generator, can_split=False) - def loadTestsFromModule(self, module, discovered=False): + def loadTestsFromModule(self, module, path=None, discovered=False): """Load all tests from module and return a suite containing them. If the module has been discovered and is not test-like, the suite will be empty by default, though plugins may add @@ -290,11 +290,15 @@ class TestLoader(unittest.TestLoader): # Now, descend into packages # FIXME can or should this be lazy? # is this syntax 2.2 compatible? - paths = getattr(module, '__path__', []) - for path in paths: - tests.extend(self.loadTestsFromDir(path)) + module_paths = getattr(module, '__path__', []) + if path: + path = os.path.realpath(path) + for module_path in module_paths: + if (self.config.traverseNamespace or not path) or \ + os.path.realpath(module_path).startswith(path): + tests.extend(self.loadTestsFromDir(module_path)) - for test in self.config.plugins.loadTestsFromModule(module): + for test in self.config.plugins.loadTestsFromModule(module, path): tests.append(test) return self.suiteClass(ContextList(tests, context=module)) @@ -368,7 +372,8 @@ class TestLoader(unittest.TestLoader): return self.loadTestsFromName(addr.call, module) else: return self.loadTestsFromModule( - module, discovered=discovered) + module, addr.filename, + discovered=discovered) elif addr.filename: path = addr.filename if addr.call: diff --git a/nose/plugins/base.py b/nose/plugins/base.py index 7f6309f..dbab752 100644 --- a/nose/plugins/base.py +++ b/nose/plugins/base.py @@ -524,7 +524,7 @@ class IPluginInterface(object): loadTestsFromDir.generative = True loadTestsFromDir._new = True - def loadTestsFromModule(self, module): + def loadTestsFromModule(self, module, path=None): """Return iterable of tests in a module. May be a generator. Each item returned must be a runnable unittest.TestCase (or subclass) instance. @@ -534,6 +534,8 @@ class IPluginInterface(object): :Parameters: module : python module The module object + path : the path of the module to search, to + distinguish from namespace package modules """ pass loadTestsFromModule.generative = True diff --git a/nose/plugins/manager.py b/nose/plugins/manager.py index 7cf6918..31a9571 100644 --- a/nose/plugins/manager.py +++ b/nose/plugins/manager.py @@ -44,6 +44,7 @@ you wish to make available. Make sure to call other than ``PluginManager``. """ +import inspect import logging import os import sys @@ -86,6 +87,10 @@ class PluginProxy(object): """ meth = getattr(plugin, call, None) if meth is not None: + if call == 'loadTestsFromModule' and \ + len(inspect.getargspec(meth)[0]) == 2: + orig_meth = meth + meth = lambda module, path, **kwargs: orig_meth(module) self.plugins.append((plugin, meth)) def makeCall(self, call): -- cgit v1.2.1