summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AUTHORS1
-rw-r--r--CHANGELOG39
-rw-r--r--functional_tests/doc_tests/test_issue097/plugintest_environment.rst78
-rw-r--r--functional_tests/doc_tests/test_issue119/empty_plugin.rst61
-rw-r--r--functional_tests/doc_tests/test_issue119/test_zeronine.py26
-rw-r--r--functional_tests/support/fdp/test_fdp_no_capt.py9
-rw-r--r--functional_tests/support/issue130/test.py5
-rw-r--r--functional_tests/support/issue143/not-a-package/__init__.py1
-rw-r--r--functional_tests/support/issue143/not-a-package/test.py2
-rw-r--r--functional_tests/test_failuredetail_plugin.py21
-rw-r--r--functional_tests/test_issue120/support/some_test.py3
-rw-r--r--functional_tests/test_issue120/test_named_test_with_doctest.rst25
-rw-r--r--functional_tests/test_program.py58
-rw-r--r--nose/__init__.py2
-rw-r--r--nose/case.py3
-rw-r--r--nose/commands.py9
-rw-r--r--nose/config.py7
-rw-r--r--nose/core.py3
-rw-r--r--nose/loader.py7
-rw-r--r--nose/plugins/capture.py7
-rw-r--r--nose/plugins/cover.py5
-rw-r--r--nose/plugins/doctests.py13
-rw-r--r--nose/plugins/manager.py3
-rw-r--r--nose/plugins/plugintest.py2
-rw-r--r--nose/plugins/prof.py16
-rw-r--r--nose/proxy.py40
-rw-r--r--nose/result.py4
-rw-r--r--nose/selector.py5
-rw-r--r--nose/util.py78
-rwxr-xr-xscripts/mkrelease.py69
-rw-r--r--scripts/rst2wiki.py125
-rw-r--r--unit_tests/test_capture_plugin.py2
-rw-r--r--unit_tests/test_result_proxy.py23
-rw-r--r--unit_tests/test_utils.py21
34 files changed, 537 insertions, 236 deletions
diff --git a/AUTHORS b/AUTHORS
index a520db0..b2c5d31 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -6,3 +6,4 @@ Scot Doyle
James Casbon
Antoine Pitrou
John J Lee
+Allen Bierbaum
diff --git a/CHANGELOG b/CHANGELOG
index 5f87dfd..af0c1c8 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,42 @@
+0.10.1
+
+- Fixed bug in capture plugin that caused it to record captured output
+ on the test in the wrong attribute (#113).
+- Fixed bug in result proxy that caused tests to fail if they accessed
+ certain result attibutes directly (#114). Thanks to Neilen Marais
+ for the bug report.
+- Fixed bug in capture plugin that caused other error formatters
+ changes to be lost if no output was captured (#124). Thanks to
+ someone at ilorentz.org for the bug report.
+- Fixed several bugs in the nosetests setup command that made some
+ options unusable and the command itself unusable when no options
+ were set (#125, #126, #128). Thanks to Alain Poirier for the bug
+ reports.
+- Fixed bug in handling of string errors (#130). Thanks to schl... at
+ uni-oldenburg.de for the bug report.
+- Fixed bug in coverage plugin option handling that prevented
+ --cover-package=mod1,mod2 from working (#117). Thanks to Allen
+ Bierbaum for the patch.
+- Fixed bug in profiler plugin that prevented output from being
+ produced when output capture was enabled on python 2.5
+ (#129). Thanks to James Casbon for the patch.
+- Fixed bug in adapting 0.9 plugins to 0.10 (#119 part one). Thanks to
+ John J Lee for the bug report and tests.
+- Fixed bug in handling of argv in config and plugin test utilities
+ (#119 part two). Thanks to John J Lee for the bug report and patch.
+- Fixed bug where Failure cases due to invalid test name
+ specifications were passed to plugins makeTest (#120). Thanks to
+ John J Lee for the bug report and patch.
+- Fixed bugs in doc css that mangled display in small windows. Thanks
+ to Ben Hoyt for the bug report and Michal Kwiatkowski for the fix.
+- Made it possible to pass a list or comma-separated string as
+ defaultTest to main(). Thanks to Allen Bierbaum for the suggestion
+ and patch.
+- Fixed a bug in nose.selector and nose.util.getpackage that caused
+ directories with names that are not legal python identifiers to be
+ collected as packages (#143). Thanks to John J Lee for the bug
+ report.
+
0.10.0
- Fixed bug that broke plugins with names containing underscores or
diff --git a/functional_tests/doc_tests/test_issue097/plugintest_environment.rst b/functional_tests/doc_tests/test_issue097/plugintest_environment.rst
index da7982c..1b3f914 100644
--- a/functional_tests/doc_tests/test_issue097/plugintest_environment.rst
+++ b/functional_tests/doc_tests/test_issue097/plugintest_environment.rst
@@ -1,15 +1,16 @@
-nose.plugins.plugintest and os.environ
---------------------------------------
+nose.plugins.plugintest, os.environ and sys.argv
+------------------------------------------------
`nose.plugins.plugintest.PluginTester`_ and
`nose.plugins.plugintest.run()`_ are utilities for testing nose
plugins. When testing plugins, it should be possible to control the
environment seen plugins under test, and that environment should never
-be affected by ``os.environ``.
+be affected by ``os.environ`` or ``sys.argv``.
>>> import os
+ >>> import sys
>>> import unittest
- >>> from nose.config import Config
+ >>> import nose.config
>>> from nose.plugins import Plugin
>>> from nose.plugins.builtin import FailureDetail, Capture
>>> from nose.plugins.plugintest import PluginTester
@@ -28,7 +29,21 @@ environment it's given by nose.
... self.conf = conf
...
... def options(self, parser, env={}):
- ... print env
+ ... print "env:", env
+
+To test the argv, we use a config class that prints the argv it's
+given by nose. We need to monkeypatch nose.config.Config, so that we
+can test the cases where that is used as the default.
+
+ >>> old_config = nose.config.Config
+
+ >>> class PrintArgvConfig(old_config):
+ ...
+ ... def configure(self, argv=None, doc=None):
+ ... print "argv:", argv
+ ... old_config.configure(self, argv, doc)
+
+ >>> nose.config.Config = PrintArgvConfig
The class under test, PluginTester, is designed to be used by
subclassing.
@@ -44,16 +59,21 @@ subclassing.
... return unittest.TestSuite(tests=[])
-For the purposes of this test, we need a known ``os.environ``.
+For the purposes of this test, we need a known ``os.environ`` and
+``sys.argv``.
>>> old_environ = os.environ
+ >>> old_argv = sys.argv
>>> os.environ = {"spam": "eggs"}
+ >>> sys.argv = ["spamtests"]
+PluginTester always uses the [nosetests, self.activate] as its argv.
If ``env`` is not overridden, the default is an empty ``env``.
>>> tester = Tester()
>>> tester.setUp()
- {}
+ argv: ['nosetests', '-v']
+ env: {}
An empty ``env`` is respected...
@@ -62,7 +82,8 @@ An empty ``env`` is respected...
>>> tester = EmptyEnvTester()
>>> tester.setUp()
- {}
+ argv: ['nosetests', '-v']
+ env: {}
... as is a non-empty ``env``.
@@ -71,7 +92,8 @@ An empty ``env`` is respected...
>>> tester = NonEmptyEnvTester()
>>> tester.setUp()
- {'foo': 'bar'}
+ argv: ['nosetests', '-v']
+ env: {'foo': 'bar'}
``nose.plugins.plugintest.run()`` should work analogously.
@@ -80,7 +102,8 @@ An empty ``env`` is respected...
>>> run(suite=unittest.TestSuite(tests=[]),
... plugins=[PrintEnvPlugin()]) # doctest: +REPORT_NDIFF
- {}
+ argv: ['nosetests', '-v']
+ env: {}
----------------------------------------------------------------------
Ran 0 tests in ...s
<BLANKLINE>
@@ -89,7 +112,8 @@ An empty ``env`` is respected...
>>> run(env={},
... suite=unittest.TestSuite(tests=[]),
... plugins=[PrintEnvPlugin()]) # doctest: +REPORT_NDIFF
- {}
+ argv: ['nosetests', '-v']
+ env: {}
----------------------------------------------------------------------
Ran 0 tests in ...s
<BLANKLINE>
@@ -98,7 +122,35 @@ An empty ``env`` is respected...
>>> run(env={"foo": "bar"},
... suite=unittest.TestSuite(tests=[]),
... plugins=[PrintEnvPlugin()]) # doctest: +REPORT_NDIFF
- {'foo': 'bar'}
+ argv: ['nosetests', '-v']
+ env: {'foo': 'bar'}
+ ----------------------------------------------------------------------
+ Ran 0 tests in ...s
+ <BLANKLINE>
+ OK
+
+An explicit argv parameter is honoured:
+
+ >>> run(argv=["spam"],
+ ... suite=unittest.TestSuite(tests=[]),
+ ... plugins=[PrintEnvPlugin()]) # doctest: +REPORT_NDIFF
+ argv: ['spam']
+ env: {}
+ ----------------------------------------------------------------------
+ Ran 0 tests in ...s
+ <BLANKLINE>
+ OK
+
+An explicit config parameter with an env is honoured:
+
+ >>> from nose.plugins.manager import PluginManager
+
+ >>> manager = PluginManager(plugins=[PrintEnvPlugin()])
+ >>> config = PrintArgvConfig(env={"foo": "bar"}, plugins=manager)
+ >>> run(config=config,
+ ... suite=unittest.TestSuite(tests=[])) # doctest: +REPORT_NDIFF
+ argv: ['nosetests', '-v']
+ env: {'foo': 'bar'}
----------------------------------------------------------------------
Ran 0 tests in ...s
<BLANKLINE>
@@ -108,3 +160,5 @@ An empty ``env`` is respected...
Clean up.
>>> os.environ = old_environ
+ >>> sys.argv = old_argv
+ >>> nose.config.Config = old_config
diff --git a/functional_tests/doc_tests/test_issue119/empty_plugin.rst b/functional_tests/doc_tests/test_issue119/empty_plugin.rst
new file mode 100644
index 0000000..644af32
--- /dev/null
+++ b/functional_tests/doc_tests/test_issue119/empty_plugin.rst
@@ -0,0 +1,61 @@
+Minimal plugin
+--------------
+
+Plugins work as long as they implement the minimal interface required
+by nose.plugins.base . They do not have to derive from
+nose.plugins.Plugin .
+
+ >>> class NullPlugin(object):
+ ...
+ ... enabled = True
+ ... name = "null"
+ ... score = 100
+ ...
+ ... def options(self, parser, env):
+ ... pass
+ ...
+ ... def configure(self, options, conf):
+ ... pass
+
+ >>> import unittest
+ >>> from nose.plugins.plugintest import run
+
+ >>> run(suite=unittest.TestSuite(tests=[]),
+ ... plugins=[NullPlugin()]) # doctest: +REPORT_NDIFF
+ ----------------------------------------------------------------------
+ Ran 0 tests in ...s
+ <BLANKLINE>
+ OK
+
+Plugins can derive from nose.plugins.base and do nothing except set a
+name.
+
+ >>> import os
+ >>> from nose.plugins import Plugin
+
+ >>> class DerivedNullPlugin(Plugin):
+ ...
+ ... name = "derived-null"
+
+Enabled plugin that's otherwise empty
+
+ >>> class EnabledDerivedNullPlugin(Plugin):
+ ...
+ ... enabled = True
+ ... name = "enabled-derived-null"
+ ...
+ ... def options(self, parser, env=os.environ):
+ ... pass
+ ...
+ ... def configure(self, options, conf):
+ ... if not self.can_configure:
+ ... return
+ ... self.conf = conf
+
+ >>> run(suite=unittest.TestSuite(tests=[]),
+ ... plugins=[DerivedNullPlugin(), EnabledDerivedNullPlugin()])
+ ... # doctest: +REPORT_NDIFF
+ ----------------------------------------------------------------------
+ Ran 0 tests in ...s
+ <BLANKLINE>
+ OK
diff --git a/functional_tests/doc_tests/test_issue119/test_zeronine.py b/functional_tests/doc_tests/test_issue119/test_zeronine.py
new file mode 100644
index 0000000..6a4f450
--- /dev/null
+++ b/functional_tests/doc_tests/test_issue119/test_zeronine.py
@@ -0,0 +1,26 @@
+import os
+import unittest
+from nose.plugins import Plugin
+from nose.plugins.plugintest import PluginTester
+from nose.plugins.manager import ZeroNinePlugin
+
+here = os.path.abspath(os.path.dirname(__file__))
+
+support = os.path.join(os.path.dirname(os.path.dirname(here)), 'support')
+
+
+class EmptyPlugin(Plugin):
+ pass
+
+class TestEmptyPlugin(PluginTester, unittest.TestCase):
+ activate = '--with-empty'
+ plugins = [ZeroNinePlugin(EmptyPlugin())]
+ suitepath = os.path.join(here, 'empty_plugin.rst')
+
+ def test_empty_zero_nine_does_not_crash(self):
+ print self.output
+ assert "'EmptyPlugin' object has no attribute 'loadTestsFromPath'" \
+ not in self.output
+
+
+
diff --git a/functional_tests/support/fdp/test_fdp_no_capt.py b/functional_tests/support/fdp/test_fdp_no_capt.py
new file mode 100644
index 0000000..30b7b99
--- /dev/null
+++ b/functional_tests/support/fdp/test_fdp_no_capt.py
@@ -0,0 +1,9 @@
+def test_err():
+ raise TypeError("I can't type")
+
+def test_fail():
+ a = 2
+ assert a == 4, "a is not 4"
+
+def test_ok():
+ pass
diff --git a/functional_tests/support/issue130/test.py b/functional_tests/support/issue130/test.py
new file mode 100644
index 0000000..9778eef
--- /dev/null
+++ b/functional_tests/support/issue130/test.py
@@ -0,0 +1,5 @@
+def setup():
+ raise "KABOOM"
+
+def test_foo():
+ assert(1==1)
diff --git a/functional_tests/support/issue143/not-a-package/__init__.py b/functional_tests/support/issue143/not-a-package/__init__.py
new file mode 100644
index 0000000..2ae2839
--- /dev/null
+++ b/functional_tests/support/issue143/not-a-package/__init__.py
@@ -0,0 +1 @@
+pass
diff --git a/functional_tests/support/issue143/not-a-package/test.py b/functional_tests/support/issue143/not-a-package/test.py
new file mode 100644
index 0000000..c1fb1c2
--- /dev/null
+++ b/functional_tests/support/issue143/not-a-package/test.py
@@ -0,0 +1,2 @@
+def test():
+ raise Exception("do not run")
diff --git a/functional_tests/test_failuredetail_plugin.py b/functional_tests/test_failuredetail_plugin.py
index b97e432..284cf49 100644
--- a/functional_tests/test_failuredetail_plugin.py
+++ b/functional_tests/test_failuredetail_plugin.py
@@ -2,6 +2,7 @@ import os
import sys
import unittest
from nose.plugins.failuredetail import FailureDetail
+from nose.plugins.capture import Capture
from nose.plugins import PluginTester
support = os.path.join(os.path.dirname(__file__), 'support')
@@ -25,5 +26,25 @@ class TestFailureDetail(PluginTester, unittest.TestCase):
assert expect in self.output
+
+class TestFailureDetailWithCapture(PluginTester, unittest.TestCase):
+ activate = "-d"
+ args = ['-v']
+ plugins = [FailureDetail(), Capture()]
+ suitepath = os.path.join(support, 'fdp/test_fdp_no_capt.py')
+
+ def runTest(self):
+ print '*' * 70
+ print str(self.output)
+ print '*' * 70
+
+ expect = \
+ 'AssertionError: a is not 4\n'
+ ' print "Hello"\n'
+ ' 2 = 2\n'
+ '>> assert 2 == 4, "a is not 4"'
+
+ assert expect in self.output
+
if __name__ == '__main__':
unittest.main()
diff --git a/functional_tests/test_issue120/support/some_test.py b/functional_tests/test_issue120/support/some_test.py
new file mode 100644
index 0000000..9947266
--- /dev/null
+++ b/functional_tests/test_issue120/support/some_test.py
@@ -0,0 +1,3 @@
+def some_test():
+ pass
+
diff --git a/functional_tests/test_issue120/test_named_test_with_doctest.rst b/functional_tests/test_issue120/test_named_test_with_doctest.rst
new file mode 100644
index 0000000..d6980c2
--- /dev/null
+++ b/functional_tests/test_issue120/test_named_test_with_doctest.rst
@@ -0,0 +1,25 @@
+Naming a non-existent test using the colon syntax (foo.py:my_test)
+with plugin doctests enabled used to cause a failure with a ValueError
+from module doctest, losing the original failure (failure to find the
+test).
+
+ >>> import os
+ >>> from nose.plugins.plugintest import run
+ >>> from nose.plugins.doctests import Doctest
+
+ >>> support = os.path.join(os.path.dirname(__file__), 'support')
+ >>> test_name = os.path.join(support, 'some_test.py') + ':nonexistent'
+ >>> run(argv=['nosetests', '--with-doctest', test_name],
+ ... plugins=[Doctest()])
+ E
+ ======================================================================
+ ERROR: Failure: ValueError (No such test nonexistent)
+ ----------------------------------------------------------------------
+ Traceback (most recent call last):
+ ...
+ ValueError: No such test nonexistent
+ <BLANKLINE>
+ ----------------------------------------------------------------------
+ Ran 1 test in ...s
+ <BLANKLINE>
+ FAILED (errors=1)
diff --git a/functional_tests/test_program.py b/functional_tests/test_program.py
index b0db11e..afa72fe 100644
--- a/functional_tests/test_program.py
+++ b/functional_tests/test_program.py
@@ -3,6 +3,7 @@ import unittest
from cStringIO import StringIO
from nose.core import TestProgram
from nose.config import Config
+from nose.plugins.manager import DefaultPluginManager
here = os.path.dirname(__file__)
support = os.path.join(here, 'support')
@@ -118,6 +119,63 @@ class TestTestProgram(unittest.TestCase):
assert not res.wasSuccessful()
assert len(res.errors) == 1
assert len(res.failures) == 2
+
+ def test_issue_130(self):
+ """Collect and run tests in support/issue130 without error.
+
+ This tests that the result and error classes can handle string
+ exceptions.
+ """
+ import warnings
+ warnings.filterwarnings('ignore', category=DeprecationWarning,
+ module='test')
+
+ stream = StringIO()
+ runner = TestRunner(stream=stream, verbosity=2)
+
+ prog = TestProgram(defaultTest=os.path.join(support, 'issue130'),
+ argv=['test_issue_130'],
+ testRunner=runner,
+ config=Config(stream=stream,
+ plugins=DefaultPluginManager()),
+ exit=False)
+ res = runner.result
+ print stream.getvalue()
+ self.assertEqual(res.testsRun, 0) # error is in setup
+ assert not res.wasSuccessful()
+ assert res.errors
+ assert not res.failures
+
+ def test_defaultTest_list(self):
+ stream = StringIO()
+ runner = TestRunner(stream=stream, verbosity=2)
+ tests = [os.path.join(support, 'package2'),
+ os.path.join(support, 'package3')]
+ prog = TestProgram(defaultTest=tests,
+ argv=['test_run_support_package2_3', '-v'],
+ testRunner=runner,
+ config=Config(),
+ exit=False)
+ res = runner.result
+ print stream.getvalue()
+ self.assertEqual(res.testsRun, 7)
+
+ def test_illegal_packages_not_selected(self):
+ stream = StringIO()
+ runner = TestRunner(stream=stream, verbosity=2)
+
+ prog = TestProgram(defaultTest=os.path.join(support, 'issue143'),
+ argv=['test_issue_143'],
+ testRunner=runner,
+ config=Config(stream=stream,
+ plugins=DefaultPluginManager()),
+ exit=False)
+ res = runner.result
+ print stream.getvalue()
+ self.assertEqual(res.testsRun, 0)
+ assert res.wasSuccessful()
+ assert not res.errors
+ assert not res.failures
if __name__ == '__main__':
diff --git a/nose/__init__.py b/nose/__init__.py
index 7ec2f06..7c8fa53 100644
--- a/nose/__init__.py
+++ b/nose/__init__.py
@@ -397,7 +397,7 @@ from nose.exc import SkipTest, DeprecatedTest
from nose.tools import with_setup
__author__ = 'Jason Pellerin'
-__versioninfo__ = (0, 11, 0)
+__versioninfo__ = (0, 10, 1)
__version__ = '.'.join(map(str, __versioninfo__))
__all__ = [
diff --git a/nose/case.py b/nose/case.py
index 30b78fe..739b608 100644
--- a/nose/case.py
+++ b/nose/case.py
@@ -26,7 +26,8 @@ class Failure(unittest.TestCase):
unittest.TestCase.__init__(self)
def __str__(self):
- return "Failure: %s(%s)" % (self.exc_class, self.exc_val)
+ return "Failure: %s (%s)" % (
+ getattr(self.exc_class, '__name__', self.exc_class), self.exc_val)
def runTest(self):
if self.tb is not None:
diff --git a/nose/commands.py b/nose/commands.py
index 80913e3..30743b4 100644
--- a/nose/commands.py
+++ b/nose/commands.py
@@ -59,7 +59,7 @@ else:
if opt._long_opts[0][2:] in option_blacklist:
continue
long_name = opt._long_opts[0][2:]
- if opt.action != 'store_true':
+ if opt.action not in ('store_true', 'store_false'):
long_name = long_name + "="
short_name = None
if opt._short_opts:
@@ -76,7 +76,10 @@ else:
user_options = get_user_options(__parser)
def initialize_options(self):
- """create the member variables, but change hyphens to underscores"""
+ """create the member variables, but change hyphens to
+ underscores
+ """
+
self.option_to_cmds = {}
for opt in self.__parser.option_list:
cmd_name = opt._long_opts[0][2:]
@@ -102,7 +105,7 @@ else:
self.distribution.fetch_build_eggs(
self.distribution.tests_require)
- argv = []
+ argv = ['nosetests']
for (option_name, cmd_name) in self.option_to_cmds.items():
if option_name in option_blacklist:
continue
diff --git a/nose/config.py b/nose/config.py
index 203f4d6..5bc3798 100644
--- a/nose/config.py
+++ b/nose/config.py
@@ -457,10 +457,15 @@ def all_config_files():
# used when parsing config files
def flag(val):
"""Does the value look like an on/off flag?"""
+ if val == 1:
+ return True
+ elif val == 0:
+ return False
+ val = str(val)
if len(val) > 5:
return False
return val.upper() in ('1', '0', 'F', 'T', 'TRUE', 'FALSE', 'ON', 'OFF')
def _bool(val):
- return val.upper() in ('1', 'T', 'TRUE', 'ON')
+ return str(val).upper() in ('1', 'T', 'TRUE', 'ON')
diff --git a/nose/core.py b/nose/core.py
index 76581ac..2ebdea2 100644
--- a/nose/core.py
+++ b/nose/core.py
@@ -262,7 +262,8 @@ class TestProgram(unittest.TestProgram):
if self.config.testNames:
self.testNames = self.config.testNames
else:
- self.testNames = (self.defaultTest,)
+ self.testNames = tolist(self.defaultTest)
+ log.debug('defaultTest %s', self.defaultTest)
log.debug('Test names are %s', self.testNames)
if self.config.workingDir is not None:
os.chdir(self.config.workingDir)
diff --git a/nose/loader.py b/nose/loader.py
index df60a91..c605a1f 100644
--- a/nose/loader.py
+++ b/nose/loader.py
@@ -341,8 +341,11 @@ class TestLoader(unittest.TestLoader):
if addr.call:
name = addr.call
parent, obj = self.resolve(name, module)
- return suite(ContextList([self.makeTest(obj, parent)],
- context=parent))
+ if isinstance(obj, Failure):
+ return suite([obj])
+ else:
+ return suite(ContextList([self.makeTest(obj, parent)],
+ context=parent))
else:
if addr.module:
try:
diff --git a/nose/plugins/capture.py b/nose/plugins/capture.py
index 9bda3ec..cf2a734 100644
--- a/nose/plugins/capture.py
+++ b/nose/plugins/capture.py
@@ -56,10 +56,13 @@ class Capture(Plugin):
self.start()
def formatError(self, test, err):
- test.captured_output = output = self.buffer
+ test.capturedOutput = output = self.buffer
self._buf = None
if not output:
- return
+ # Don't return None as that will prevent other
+ # formatters from formatting and remove earlier formatters
+ # formats, instead return the err we got
+ return err
ec, ev, tb = err
return (ec, self.addCaptureToErr(ev, output), tb)
diff --git a/nose/plugins/cover.py b/nose/plugins/cover.py
index 3a34e46..fd9e578 100644
--- a/nose/plugins/cover.py
+++ b/nose/plugins/cover.py
@@ -72,7 +72,10 @@ class Coverage(Plugin):
self.conf = config
self.coverErase = options.cover_erase
self.coverTests = options.cover_tests
- self.coverPackages = tolist(options.cover_packages)
+ self.coverPackages = []
+ if options.cover_packages:
+ for pkgs in [tolist(x) for x in options.cover_packages]:
+ self.coverPackages.extend(pkgs)
self.coverInclusive = options.cover_inclusive
if self.coverPackages:
log.info("Coverage report will include only packages: %s",
diff --git a/nose/plugins/doctests.py b/nose/plugins/doctests.py
index f15fd78..8fe9854 100644
--- a/nose/plugins/doctests.py
+++ b/nose/plugins/doctests.py
@@ -183,11 +183,11 @@ class Doctest(Plugin):
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.
+ """Overrides DocTestCase to
+ provide an address() method that returns the correct address for
+ the doctest 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):
@@ -224,7 +224,8 @@ class DocTestCase(doctest.DocTestCase):
class DocFileCase(doctest.DocFileCase):
- """Overrides to provide filename
+ """Overrides to provide address() method that returns the correct
+ address for the doc file case.
"""
def address(self):
return (self._dt_test.filename, None, None)
diff --git a/nose/plugins/manager.py b/nose/plugins/manager.py
index 6b72b8c..8ecfc4a 100644
--- a/nose/plugins/manager.py
+++ b/nose/plugins/manager.py
@@ -263,7 +263,8 @@ class ZeroNinePlugin:
return self.plugin.addError(test.test, err, capt)
def loadTestsFromFile(self, filename):
- return self.plugin.loadTestsFromPath(filename)
+ if hasattr(self.plugin, 'loadTestsFromPath'):
+ return self.plugin.loadTestsFromPath(filename)
def addFailure(self, test, err):
if not hasattr(self.plugin, 'addFailure'):
diff --git a/nose/plugins/plugintest.py b/nose/plugins/plugintest.py
index 7ead48e..b766543 100644
--- a/nose/plugins/plugintest.py
+++ b/nose/plugins/plugintest.py
@@ -191,6 +191,8 @@ def run(*arg, **kw):
plugins = kw.pop('plugins', None)
env = kw.pop('env', {})
kw['config'] = Config(env=env, plugins=PluginManager(plugins=plugins))
+ if 'argv' not in kw:
+ kw['argv'] = ['nosetests', '-v']
kw['config'].stream = buffer
run(*arg, **kw)
out = buffer.getvalue()
diff --git a/nose/plugins/prof.py b/nose/plugins/prof.py
index f255e87..f48ff1c 100644
--- a/nose/plugins/prof.py
+++ b/nose/plugins/prof.py
@@ -70,16 +70,28 @@ class Profile(Plugin):
self.prof.close()
stats = hotshot.stats.load(self.pfile)
stats.sort_stats(self.sort)
- try:
+
+ # 2.5 has completely different stream handling from 2.4 and earlier.
+ # Before 2.5, stats objects have no stream attribute; in 2.5 and later
+ # a reference sys.stdout is stored before we can tweak it.
+ compat_25 = hasattr(stats, 'stream')
+ if compat_25:
+ tmp = stats.stream
+ stats.stream = stream
+ else:
tmp = sys.stdout
sys.stdout = stream
+ try:
if self.restrict:
log.debug('setting profiler restriction to %s', self.restrict)
stats.print_stats(*self.restrict)
else:
stats.print_stats()
finally:
- sys.stdout = tmp
+ if compat_25:
+ stats.stream = tmp
+ else:
+ sys.stdout = tmp
def finalize(self, result):
try:
diff --git a/nose/proxy.py b/nose/proxy.py
index b163cd5..f7aa968 100644
--- a/nose/proxy.py
+++ b/nose/proxy.py
@@ -21,6 +21,20 @@ from nose.config import Config
log = logging.getLogger(__name__)
+
+def proxied_attribute(local_attr, proxied_attr, doc):
+ """Create a property that proxies attribute ``proxied_attr`` through
+ the local attribute ``local_attr``.
+ """
+ def fget(self):
+ return getattr(getattr(self, local_attr), proxied_attr)
+ def fset(self, value):
+ setattr(getattr(self, local_attr), proxied_attr, value)
+ def fdel(self):
+ delattr(getattr(self, local_attr), proxied_attr)
+ return property(fget, fset, fdel, doc)
+
+
class ResultProxyFactory(object):
"""Factory for result proxies. Generates a ResultProxy bound to each test
and the result passed to the test.
@@ -53,10 +67,12 @@ class ResultProxyFactory(object):
class ResultProxy(object):
"""Proxy to TestResults (or other results handler).
- One ResultProxy is created for each nose.case.Test. The result proxy
- calls plugins with the nose.case.Test instance (instead of the
- wrapped test case) as each result call is made. Finally, the real result
- method is called with the wrapped test.
+ One ResultProxy is created for each nose.case.Test. The result
+ proxy calls plugins with the nose.case.Test instance (instead of
+ the wrapped test case) as each result call is made. Finally, the
+ real result method is called, also with the nose.case.Test
+ instance as the test parameter.
+
"""
def __init__(self, result, test, config=None):
if config is None:
@@ -141,12 +157,14 @@ class ResultProxy(object):
self.assertMyTest(test)
self.plugins.stopTest(self.test)
self.result.stopTest(self.test)
-
- def get_shouldStop(self):
- return self.result.shouldStop
- def set_shouldStop(self, shouldStop):
- self.result.shouldStop = shouldStop
+ # proxied attributes
+ shouldStop = proxied_attribute('result', 'shouldStop',
+ """Should the test run stop?""")
+ errors = proxied_attribute('result', 'errors',
+ """Tests that raised an exception""")
+ failures = proxied_attribute('result', 'failures',
+ """Tests that failed""")
+ testsRun = proxied_attribute('result', 'testsRun',
+ """Number of tests run""")
- shouldStop = property(get_shouldStop, set_shouldStop, None,
- """Should the test run stop?""")
diff --git a/nose/result.py b/nose/result.py
index be665da..d5fff1f 100644
--- a/nose/result.py
+++ b/nose/result.py
@@ -11,7 +11,7 @@ reporting.
import logging
from unittest import _TextTestResult
from nose.config import Config
-from nose.util import odict, ln as _ln # backwards compat
+from nose.util import isclass, odict, ln as _ln # backwards compat
log = logging.getLogger('nose.result')
@@ -44,7 +44,7 @@ class TextTestResult(_TextTestResult):
# 2.3 compat
exc_info = self._exc_info_to_string(err)
for cls, (storage, label, isfail) in self.errorClasses.items():
- if issubclass(ec, cls):
+ if isclass(ec) and issubclass(ec, cls):
storage.append((test, exc_info))
# Might get patched into a streamless result
if stream is not None:
diff --git a/nose/selector.py b/nose/selector.py
index a794288..2390c0a 100644
--- a/nose/selector.py
+++ b/nose/selector.py
@@ -10,7 +10,7 @@ import logging
import os
import unittest
from nose.config import Config
-from nose.util import split_test_name, src, getfilename, getpackage
+from nose.util import split_test_name, src, getfilename, getpackage, ispackage
log = logging.getLogger(__name__)
@@ -86,9 +86,8 @@ class Selector(object):
All package directories match, so long as they do not match exclude.
All other directories must match test requirements.
"""
- init = op_join(dirname, '__init__.py')
tail = op_basename(dirname)
- if op_exists(init):
+ if ispackage(dirname):
wanted = (not self.exclude
or not filter(None,
[exc.search(tail) for exc in self.exclude]
diff --git a/nose/util.py b/nose/util.py
index 440f52a..f0bdd91 100644
--- a/nose/util.py
+++ b/nose/util.py
@@ -134,12 +134,14 @@ def ispackage(path):
>>> ispackage('nose/loader.py')
False
"""
- if os.path.isdir(path):
- init = [e for e in os.listdir(path)
- if os.path.isfile(os.path.join(path, e))
- and src(e) == '__init__.py']
- if init:
- return True
+ if os.path.isdir(path):
+ # at least the end of the path must be a legal python identifier
+ # and __init__.py[co] must exist
+ end = os.path.basename(path)
+ if ident_re.match(end):
+ for init in ('__init__.py', '__init__.pyc', '__init__.pyo'):
+ if os.path.isfile(os.path.join(path, init)):
+ return True
return False
@@ -263,32 +265,51 @@ def split_test_name(test):
Either side of the : may be dotted. To change the splitting behavior, you
can alter nose.util.split_test_re.
"""
- parts = test.split(':')
- num = len(parts)
- if num == 1:
+ norm = os.path.normpath
+ file_or_mod = test
+ fn = None
+ if not ':' in test:
# only a file or mod part
if file_like(test):
- return (test, None, None)
+ return (norm(test), None, None)
else:
return (None, test, None)
- elif num >= 3:
- # definitely popped off a windows driveletter
- file_or_mod = ':'.join(parts[0:-1])
- fn = parts[-1]
+
+ # could be path|mod:callable, or a : in the file path someplace
+ head, tail = os.path.split(test)
+ if not head:
+ # this is a case like 'foo:bar' -- generally a module
+ # name followed by a callable, but also may be a windows
+ # drive letter followed by a path
+ try:
+ file_or_mod, fn = test.split(':')
+ if file_like(fn):
+ # must be a funny path
+ file_or_mod, fn = test, None
+ except ValueError:
+ # more than one : in the test
+ # this is a case like c:\some\path.py:a_test
+ parts = test.split(':')
+ if len(parts[0]) == 1:
+ file_or_mod, fn = ':'.join(parts[:-1]), parts[-1]
+ else:
+ # nonsense like foo:bar:baz
+ raise ValueError("Test name '%s' could not be parsed. Please "
+ "format test names as path:callable or "
+ "module:callable.")
+ elif not tail:
+ # this is a case like 'foo:bar/'
+ # : must be part of the file path, so ignore it
+ file_or_mod = test
else:
- # only a file or mod part, or a test part, or
- # we mistakenly split off a windows driveletter
- file_or_mod, fn = parts
- if len(file_or_mod) == 1:
- # windows drive letter: must be a file
- if not file_like(fn):
- raise ValueError("Test name '%s' is ambiguous; can't tell "
- "if ':%s' refers to a module or callable"
- % (test, fn))
- return (test, None, None)
+ if ':' in tail:
+ file_part, fn = tail.split(':')
+ else:
+ file_part = tail
+ file_or_mod = os.sep.join([head, file_part])
if file_or_mod:
if file_like(file_or_mod):
- return (file_or_mod, None, fn)
+ return (norm(file_or_mod), None, fn)
else:
return (None, file_or_mod, fn)
else:
@@ -411,6 +432,13 @@ def match_last(a, b, regex):
def tolist(val):
"""Convert a value that may be a list or a (possibly comma-separated)
string into a list. The exception: None is returned as None, not [None].
+
+ >>> tolist(["one", "two"])
+ ['one', 'two']
+ >>> tolist("hello")
+ ['hello']
+ >>> tolist("separate,values, with, commas, spaces , are ,ok")
+ ['separate', 'values', 'with', 'commas', 'spaces', 'are', 'ok']
"""
if val is None:
return None
diff --git a/scripts/mkrelease.py b/scripts/mkrelease.py
index 14e21c4..a96b7f8 100755
--- a/scripts/mkrelease.py
+++ b/scripts/mkrelease.py
@@ -13,11 +13,20 @@ current = os.getcwd()
version = nose.__version__
here = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
parts = here.split('/')
-branchindex = parts.index('branches')
-svnroot = os.path.join('/', *parts[:branchindex])
+if 'branches' in parts:
+ lindex = parts.index('branches')
+elif 'tags' in parts:
+ lindex = parts.index('tags')
+elif 'trunk' in parts:
+ lindex = parts.index('trunk')
+else:
+ raise Exception("Unable to find svnroot from %s" % here)
+svnroot = os.path.join('/', *parts[:lindex])
+
branchroot = os.path.join(svnroot, 'branches')
tagroot = os.path.join(svnroot, 'tags')
svntrunk = os.path.join(svnroot, 'trunk')
+svn_base_url = 'https://python-nose.googlecode.com/svn'
svn_trunk_url = 'https://python-nose.googlecode.com/svn/trunk'
SIMULATE = 'exec' not in sys.argv
@@ -45,6 +54,9 @@ def main():
branch = 'branches/%s-stable' % version
tag = 'tags/%s-release' % version
+ svn_branch_url = '%s/%s' % (svn_base_url, branch)
+ svn_tag_url = '%s/%s' % (svn_base_url, tag)
+
if os.path.isdir(tag):
raise Exception(
"Tag path %s already exists. Can't release same version twice!"
@@ -52,46 +64,47 @@ def main():
# make branch, if needed
if not os.path.isdir(os.path.join(svnroot, branch)):
- # update trunk
- cd(svntrunk)
- runcmd('svn up')
- cd(svnroot)
- runcmd('svn copy %s %s' % (svn_trunk_url, branch))
-
- # clean up setup.cfg and check in branch
- cd(branch)
-
- # remove dev tag from setup
- runcmd('cp setup.cfg.release setup.cfg')
- runcmd('svn rm setup.cfg.release --force')
-
+ # make branch
+ runcmd("svn copy %s %s -m 'Release branch for %s'"
+ % (svn_trunk_url, svn_branch_url, version))
+ # clean up setup.cfg and check in tag
cd(branchroot)
- runcmd("svn ci -m 'Release branch for %s'" % version)
-
+ runcmd('svn co %s' % svn_branch_url)
else:
# re-releasing branch
cd(branch)
runcmd('svn up')
# make tag from branch
- cd(svnroot)
- runcmd('svn copy %s %s' % (branch, tag))
+ runcmd('svn copy %s %s -m "Release tag for %s"'
+ % (svn_branch_url, svn_tag_url, version))
- # check in tag
+ # check out tag
cd(tagroot)
- runcmd("svn ci -m 'Release tag for %s'" % version)
-
- # make docs
- cd(svnroot)
+ runcmd('svn co %s' % svn_tag_url)
cd(tag)
- runcmd('scripts/mkindex.py')
- runcmd('scripts/mkdocs.py')
+ # remove dev tag from setup
+ runcmd('cp setup.cfg.release setup.cfg')
+ runcmd('svn rm setup.cfg.release --force')
+ runcmd("svn ci -m 'Updated setup.cfg to release status'")
+
+ # wiki pages must be built from tag checkout
runcmd('scripts/mkwiki.py')
- # FIXME need to do this from an *export* to limit files included
+ # need to build dist from an *export* to limit files included
# (setuptools includes too many files when run under a checkout)
- # setup sdist
+
+ # export tag
+ cd('/tmp')
+ runcmd('svn export %s nose_rel_%s' % (svn_tag_url, version))
+ cd('nose_rel_%s' % version)
+
+ # make docs
+ runcmd('scripts/mkindex.py')
+ runcmd('scripts/mkdocs.py')
+
+ # make sdist
runcmd('python setup.py sdist')
# upload docs and distribution
diff --git a/scripts/rst2wiki.py b/scripts/rst2wiki.py
deleted file mode 100644
index 1a55223..0000000
--- a/scripts/rst2wiki.py
+++ /dev/null
@@ -1,125 +0,0 @@
-#!/usr/bin/env python
-
-import sys
-from docutils.nodes import SparseNodeVisitor, paragraph, title_reference, \
- emphasis
-from docutils.writers import Writer
-from docutils.core import publish_string
-
-class WikiWriter(Writer):
- def translate(self):
- visitor = WikiVisitor(self.document)
- self.document.walkabout(visitor)
- self.output = visitor.astext()
-
-class WikiVisitor(SparseNodeVisitor):
-
- def __init__(self, document):
- SparseNodeVisitor.__init__(self, document)
- self.list_depth = 0
- self.list_item_prefix = None
- self.indent = self.old_indent = ''
- self.output = []
- self.preformat = False
-
- def astext(self):
- return '\n>>>\n\n'+ ''.join(self.output) + '\n\n<<<\n'
-
- def visit_Text(self, node):
- #print "Text", node
- data = node.astext()
- if not self.preformat:
- data = data.lstrip('\n\r')
- data = data.replace('\r', '')
- data = data.replace('\n', ' ')
- self.output.append(data)
-
- def visit_bullet_list(self, node):
- self.list_depth += 1
- self.list_item_prefix = (' ' * self.list_depth) + '* '
-
- def depart_bullet_list(self, node):
- self.list_depth -= 1
- if self.list_depth == 0:
- self.list_item_prefix = None
- else:
- (' ' * self.list_depth) + '* '
- self.output.append('\n\n')
-
- def visit_list_item(self, node):
- self.old_indent = self.indent
- self.indent = self.list_item_prefix
-
- def depart_list_item(self, node):
- self.indent = self.old_indent
-
- def visit_literal_block(self, node):
- self.output.extend(['{{{', '\n'])
- self.preformat = True
-
- def depart_literal_block(self, node):
- self.output.extend(['\n', '}}}', '\n\n'])
- self.preformat = False
-
- def visit_paragraph(self, node):
- self.output.append(self.indent)
-
- def depart_paragraph(self, node):
- self.output.append('\n\n')
- if self.indent == self.list_item_prefix:
- # we're in a sub paragraph of a list item
- self.indent = ' ' * self.list_depth
-
- def visit_reference(self, node):
- if node.has_key('refuri'):
- href = node['refuri']
- elif node.has_key('refid'):
- href = '#' + node['refid']
- else:
- href = None
- self.output.append('[' + href + ' ')
-
- def depart_reference(self, node):
- self.output.append(']')
-
- def visit_subtitle(self, node):
- self.output.append('=== ')
-
- def depart_subtitle(self, node):
- self.output.append(' ===\n\n')
- self.list_depth = 0
- self.indent = ''
-
- def visit_title(self, node):
- self.output.append('== ')
-
- def depart_title(self, node):
- self.output.append(' ==\n\n')
- self.list_depth = 0
- self.indent = ''
-
- def visit_title_reference(self, node):
- self.output.append("`")
-
- def depart_title_reference(self, node):
- self.output.append("`")
-
- def visit_emphasis(self, node):
- self.output.append('*')
-
- def depart_emphasis(self, node):
- self.output.append('*')
-
- def visit_literal(self, node):
- self.output.append('`')
-
- def depart_literal(self, node):
- self.output.append('`')
-
-
-def main(source):
- output = publish_string(source, writer=WikiWriter())
- print output
-
-if __name__ == '__main__':
- main(sys.stdin.read())
diff --git a/unit_tests/test_capture_plugin.py b/unit_tests/test_capture_plugin.py
index 091b0bd..8988665 100644
--- a/unit_tests/test_capture_plugin.py
+++ b/unit_tests/test_capture_plugin.py
@@ -73,7 +73,7 @@ class TestCapturePlugin(unittest.TestCase):
self.assertEqual(ec, fec)
self.assertEqual(tb, ftb)
assert 'Oh my!' in fev, "Output not found in error message"
- assert 'Oh my!' in d.captured_output, "Output not attached to test"
+ assert 'Oh my!' in d.capturedOutput, "Output not attached to test"
if __name__ == '__main__':
unittest.main()
diff --git a/unit_tests/test_result_proxy.py b/unit_tests/test_result_proxy.py
index 6233ca8..9ed1e11 100644
--- a/unit_tests/test_result_proxy.py
+++ b/unit_tests/test_result_proxy.py
@@ -63,6 +63,29 @@ class TestResultProxy(unittest.TestCase):
assert method in res.called, "%s was not proxied"
self.assertEqual(res.shouldStop, 'yes please')
+ def test_attributes_are_proxied(self):
+ res = unittest.TestResult()
+ proxy = ResultProxy(res, test=None)
+ proxy.errors
+ proxy.failures
+ proxy.shouldStop
+ proxy.testsRun
+
+ def test_test_cases_can_access_result_attributes(self):
+ from nose.case import Test
+ class TC(unittest.TestCase):
+ def run(self, result):
+ unittest.TestCase.run(self, result)
+ print "errors", result.errors
+ print "failures", result.failures
+ def runTest(self):
+ pass
+ test = TC()
+ case = Test(test)
+ res = unittest.TestResult()
+ proxy = ResultProxy(res, test=case)
+ case(proxy)
+
def test_proxy_handles_missing_methods(self):
from nose.case import Test
class TC(unittest.TestCase):
diff --git a/unit_tests/test_utils.py b/unit_tests/test_utils.py
index ad01e25..f982cc2 100644
--- a/unit_tests/test_utils.py
+++ b/unit_tests/test_utils.py
@@ -1,9 +1,12 @@
+import os
import unittest
import nose
from nose import case
# don't import * -- some util functions look testlike
from nose import util
+np = os.path.normpath
+
class TestUtils(unittest.TestCase):
def test_file_like(self):
@@ -23,29 +26,31 @@ class TestUtils(unittest.TestCase):
assert split_test_name('some.module') == \
(None, 'some.module', None)
assert split_test_name('this/file.py:func') == \
- ('this/file.py', None, 'func')
+ (np('this/file.py'), None, 'func')
assert split_test_name('some/file.py') == \
- ('some/file.py', None, None)
+ (np('some/file.py'), None, None)
assert split_test_name(':Baz') == \
(None, None, 'Baz')
+ assert split_test_name('foo:bar/baz.py') == \
+ (np('foo:bar/baz.py'), None, None)
def test_split_test_name_windows(self):
# convenience
stn = util.split_test_name
self.assertEqual(stn(r'c:\some\path.py:a_test'),
- (r'c:\some\path.py', None, 'a_test'))
+ (np(r'c:\some\path.py'), None, 'a_test'))
self.assertEqual(stn(r'c:\some\path.py'),
- (r'c:\some\path.py', None, None))
+ (np(r'c:\some\path.py'), None, None))
self.assertEqual(stn(r'c:/some/other/path.py'),
- (r'c:/some/other/path.py', None, None))
+ (np(r'c:/some/other/path.py'), None, None))
self.assertEqual(stn(r'c:/some/other/path.py:Class.test'),
- (r'c:/some/other/path.py', None, 'Class.test'))
+ (np(r'c:/some/other/path.py'), None, 'Class.test'))
try:
- stn('c:something')
+ stn('cat:dog:something')
except ValueError:
pass
else:
- self.fail("Ambiguous test name should throw ValueError")
+ self.fail("Nonsense test name should throw ValueError")
def test_test_address(self):
# test addresses are specified as