Permalink
Browse files

Started work on experimental fixture context

  • Loading branch information...
1 parent 8d86fe2 commit e595105d6ccac29343c5629097bb35e4c6f77485 @jpellerin jpellerin committed Feb 4, 2007
Showing with 343 additions and 23 deletions.
  1. +62 −0 NOTES
  2. +77 −0 fixtr.py
  3. +28 −23 nose/loader.py
  4. +158 −0 test_fixtr.py
  5. +18 −0 work.py
View
62 NOTES
@@ -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.
View
@@ -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)
View
@@ -192,36 +192,41 @@ def loadTestsFromName(self, name, module=None, importPath=None):
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.
View
@@ -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()
View
18 work.py
@@ -177,6 +177,24 @@ def loadTestsFromModule(self, module, tests=None):
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()

0 comments on commit e595105

Please sign in to comment.