diff options
author | Jason Pellerin <jpellerin@gmail.com> | 2007-02-04 04:56:55 +0000 |
---|---|---|
committer | Jason Pellerin <jpellerin@gmail.com> | 2007-02-04 04:56:55 +0000 |
commit | e595105d6ccac29343c5629097bb35e4c6f77485 (patch) | |
tree | e9b17a8f4704e4367bb26cee3fbb1d35eb8a9d34 | |
parent | 8d86fe2391e00e7807c51ec022593cdebecd9785 (diff) | |
download | nose-e595105d6ccac29343c5629097bb35e4c6f77485.tar.gz |
Started work on experimental fixture context
-rw-r--r-- | NOTES | 62 | ||||
-rw-r--r-- | fixtr.py | 77 | ||||
-rw-r--r-- | nose/loader.py | 51 | ||||
-rw-r--r-- | test_fixtr.py | 158 | ||||
-rw-r--r-- | work.py | 18 |
5 files changed, 343 insertions, 23 deletions
@@ -1,3 +1,65 @@ +-- 2/3/07 + +is the selector useful? can it die, if we assume a more directed loading +approach? + +The loader is the heart of the discovery system. It should be simple, clear, +and close to unittest's loader whereever possible. The complication comes from +supporting proper fixture setup and teardown when the test name requested is a +or is inside of a dotted module. Say we run like this: + +nosetests foo/bar/baz.py + +that should look in foo for setup, then baz for setup, but only after +importing the target module (baz) and finding any tests therein. If baz has +tests, then foo.setup runs, bar.setup runs, baz.setup runs, baz's tests run, +then baz.teardown, bar.teardown, foo.teardown. + +nosetests w/o argument is identical in meaning to nosetests . +-> loader.loadTestsFromNames(names=[.]) + +nosetests foo and nosetests -w foo are identical in meaning +-> loader.loadTestsFromNames(names=['foo']) + +loadTestsFromName(name, module=None): + if module is None: + module, name = importable module parts of name, the rest + or, name is a dir + if module: + find name within the module + find all tests in that object (could be the module itself) + return a suite + elif dir: + find all the names in the dir that look like test modules + recurse into load tests from names with that name list + +loadTestsFromNames(names, module=None): + for name in names: + yield self.suiteClass(self.loadTestsFromName(name, module)) + +responsibility for proper setup/teardown lies in the runner, or the suite +class? + +how do they know the running context? + +the loader returns tests wrapped in a Context() closure +the Context() keeps track of what fixtures have been run and what fixtures +need to be run at setup and teardown + +setup is easy -- the first test triggers a cascade of setup calls up to the +package level + +but how can we know when to run teardowns? the last test in a module, the last +test in a package should trigger the teardowns at that level... it's not clear +how to know what test is the last? + +we know what's last because tests for a given package don't start running +until they have all been collected. + + +the process of + +-- old notes on loading from modules this pretty much all has to take place inside of the _tests iterator. diff --git a/fixtr.py b/fixtr.py new file mode 100644 index 0000000..0adf86e --- /dev/null +++ b/fixtr.py @@ -0,0 +1,77 @@ +import unittest + +class Context(object): + + def __init__(self): + self.modules = {} + self.tests = {} + self.setup_fired = {} + self.setup_ok = {} + + # FIXME this won't work unless there are tests in the top-level package + # and each of the intermediary levels... the test has to be added and + # has to reference every module up the chain, and be popped off of all + # of those lists when it hits teardown + def add(self, module, test): + if isinstance(module, basestring): + module = __import__(module) + self.modules.setdefault(module, []).append(test) + self.tests[test] = module + return Case(self, test) + + def setup(self, test): + # if this is the first for any surrounding package or module of + # this test, fire the package and module setup; record that it + # was fired + mod = self.tests.get(test) + if not mod: + # not my test? + raise Exception("Module for test %s not found in context") + if self.setup_fired.get(mod): + return + self.setup_fired[mod] = True + if hasattr(mod, 'setup'): + mod.setup(mod) # FIXME -- try all the names, etc + self.setup_ok[mod] = True + + def teardown(self, test): + # if this is the last for an surrounding package or module, and setup + # fired for that module or package, fire teardown for the module + # /package too. otherwise pop off of the stack for that module/ + # package + mod = self.tests.get(test) + if not mod: + # not my test? + raise Exception("Module for test %s not found in context") + self.modules[mod].remove(test) + if (not self.modules[mod] + and self.setup_ok.get(mod) + and hasattr(mod, 'teardown')): + mod.teardown(mod) + + +class Case(unittest.TestCase): + + def __init__(self, context, test): + print "Case %s %s" % (context, test) + self.context = context + self.test = test + unittest.TestCase.__init__(self) + + def __call__(self, *arg, **kwarg): + print "call %s %s %s" % (self, arg, kwarg) + return self.run(*arg, **kwarg) + + def setUp(self): + print "setup %s" % self + self.context.setup(self.test) + + def run(self, result): + self.result = result + unittest.TestCase.run(self, result) + + def runTest(self): + self.test(self.result) + + def tearDown(self): + self.context.teardown(self.test) diff --git a/nose/loader.py b/nose/loader.py index 7e8d7c1..0c00b37 100644 --- a/nose/loader.py +++ b/nose/loader.py @@ -192,36 +192,41 @@ class TestLoader(unittest.TestLoader): for test in plug.loadTestsFromName(name, module, importPath): yield test - def loadTestsFromNames(self, names, module=None): + def loadTestsFromNames(self, names=None, module=None): """Load tests from names. Behavior is compatible with unittest: if module is specified, all names are translated to be relative to that module; the tests are appended to conf.tests, and loadTestsFromModule() is called. Otherwise, the names are loaded one by one using loadTestsFromName. - """ - def rel(name, mod): - if not name.startswith(':'): - name = ':' + name - return "%s%s" % (mod, name) + """ + if names is None: + names = [ self.cwd() ] + for name in names: + yield self.loadTestsFromName(name, module) + +# def rel(name, mod): +# if not name.startswith(':'): +# name = ':' + name +# return "%s%s" % (mod, name) - if module: - log.debug("load tests from module %r" % module) - # configure system to load only requested tests from module - if names: - self.conf.tests.extend([ rel(n, module.__name__) - for n in names ]) - try: - mpath = os.path.dirname(module.__path__[0]) - except AttributeError: - mpath = os.path.dirname(module.__file__) +# if module: +# log.debug("load tests from module %r" % module) +# # configure system to load only requested tests from module +# if names: +# self.conf.tests.extend([ rel(n, module.__name__) +# for n in names ]) +# try: +# mpath = os.path.dirname(module.__path__[0]) +# except AttributeError: +# mpath = os.path.dirname(module.__file__) - return self.loadTestsFromModule(module, importPath=mpath) - else: - tests = [] - for name in names: - for test in self.loadTestsFromName(name): - tests.append(test) - return self.suiteClass(tests) +# return self.loadTestsFromModule(module, importPath=mpath) +# else: +# tests = [] +# for name in names: +# for test in self.loadTestsFromName(name): +# tests.append(test) +# return self.suiteClass(tests) def loadTestsFromPath(self, path, module=None, importPath=None): """Load tests from file or directory at path. diff --git a/test_fixtr.py b/test_fixtr.py new file mode 100644 index 0000000..5d9dee4 --- /dev/null +++ b/test_fixtr.py @@ -0,0 +1,158 @@ +import imp +import unittest +from fixtr import Context + +class TestFixtureContext(unittest.TestCase): + + def test_case_proxy(self): + class TC(unittest.TestCase): + state = None + def setUp(self): + self.state = ['setUp'] + def runTest(self): + self.state += ['runTest'] + def tearDown(self): + self.state += ['tearDown'] + def testSomething(self): + self.state += ['testSomething'] + + case = TC() + cx = Context() + in_context = cx.add(__name__, case) + + result = unittest.TestResult() + in_context(result) + print result.errors + self.assertEqual(case.state, ['setUp', 'runTest', 'tearDown']) + + def test_case_proxy_test_method(self): + class TC(unittest.TestCase): + state = None + def setUp(self): + self.state = ['setUp'] + def tearDown(self): + self.state += ['tearDown'] + def testSomething(self): + self.state += ['testSomething'] + + case = TC('testSomething') + cx = Context() + in_context = cx.add(__name__, case) + result = unittest.TestResult() + in_context(result) + print result.errors + self.assertEqual(case.state, ['setUp', 'testSomething', 'tearDown']) + + def test_case_error(self): + class TC(unittest.TestCase): + state = None + def setUp(self): + self.state = ['setUp'] + def runTest(self): + self.state += ['runTest'] + raise TypeError("Some error") + + def tearDown(self): + self.state += ['tearDown'] + def testSomething(self): + self.state += ['testSomething'] + + case = TC() + cx = Context() + in_context = cx.add(__name__, case) + + result = unittest.TestResult() + in_context(result) + assert result.errors + assert case.state == ['setUp', 'runTest', 'tearDown'] + + def test_case_setup_error(self): + class TC(unittest.TestCase): + state = None + def setUp(self): + self.state = ['setUp'] + raise TypeError("Some error") + def runTest(self): + self.state += ['runTest'] + def tearDown(self): + self.state += ['tearDown'] + def testSomething(self): + self.state += ['testSomething'] + + case = TC() + cx = Context() + in_context = cx.add(__name__, case) + + result = unittest.TestResult() + in_context(result) + assert result.errors + assert case.state == ['setUp'] + + def test_module_setup(self): + + def setup(m): + m.state += ['setUp'] + def teardown(m): + m.state += ['tearDown'] + + class TC(unittest.TestCase): + state = None + def setUp(self): + self.state = ['setUp'] + def errTest(self): + self.state += ['errTest'] + raise TypeError("an error") + def failTest(self): + self.state += ['failTest'] + assert False, "Fail the test" + def runTest(self): + self.state += ['runTest'] + def tearDown(self): + self.state += ['tearDown'] + def testSomething(self): + self.state += ['testSomething'] + + mod = imp.new_module('test_module') + mod.state = [] + mod.setup = setup + mod.teardown = teardown + mod.TC = TC + case = mod.TC() + err = mod.TC('errTest') + fail = mod.TC('failTest') + + # module with only one test collected + cx = Context() + in_context = cx.add(mod, case) + result = unittest.TestResult() + in_context(result) + assert not result.errors, result.errors + assert case.state == ['setUp', 'runTest', 'tearDown'] + assert mod.state == ['setUp', 'tearDown'] + + # multiple test cases from the module + mod.state = [] + cx = Context() + in_context = cx.add(mod, case) + err_context = cx.add(mod, err) + fail_context = cx.add(mod, fail) + + in_context(result) + assert not result.errors, result.errors + self.assertEqual(case.state, ['setUp', 'runTest', 'tearDown']) + self.assertEqual(mod.state, ['setUp']) + + err_context(result) + assert result.errors + self.assertEqual(err.state, ['setUp', 'errTest', 'tearDown']) + self.assertEqual(mod.state, ['setUp']) + + fail_context(result) + assert result.failures + self.assertEqual(fail.state, ['setUp', 'failTest', 'tearDown']) + self.assertEqual(mod.state, ['setUp', 'tearDown']) + + + +if __name__ == '__main__': + unittest.main() @@ -177,6 +177,24 @@ class TestLoader: print " ", test return [] + def loadTestsFromName(self, name, module=None): + if module not is None: + # assume all names are relative to the module + pass + # if the name is a directory... findTests in that directory + + # if the name is a module name ... import the module + # and load its tests (depth first), returning a suite + # may have to backtrack to ensure that any package-level + # setup is run, or is that someone else's problem? + + # if the name is a file ... figure out what module it is + # and load tests from that module + + def loadTestsFromNames(self, names, module=None): + for name in names: + yield self.loadTestsFromName(name, module) + if __name__ == '__main__': import sys l = TestLoader() |