diff --git a/pytest/main.py b/pytest/main.py index 7212f087e..3d8e08a00 100644 --- a/pytest/main.py +++ b/pytest/main.py @@ -403,7 +403,11 @@ def main(args=None, plugins=None): if args is None: args = sys.argv[1:] elif not isinstance(args, (tuple, list)): - args = py.std.shlex.split(str(args)) + if isinstance(args, py.path.local): + args = str(args) + if not isinstance(args, str): + raise ValueError("not a string or argument list: %r" % (args,)) + args = py.std.shlex.split(args) if _preinit: _pluginmanager = _preinit.pop(0) else: # subsequent calls to main will create a fresh instance diff --git a/pytest/plugin/assertion.py b/pytest/plugin/assertion.py index 33e6a1706..e0c17b015 100644 --- a/pytest/plugin/assertion.py +++ b/pytest/plugin/assertion.py @@ -3,6 +3,7 @@ support for presented detailed information in failing assertions. """ import py import sys +from pytest.plugin.monkeypatch import monkeypatch def pytest_addoption(parser): group = parser.getgroup("debugconfig") @@ -15,25 +16,21 @@ def pytest_configure(config): # py._code._assertionnew to detect this plugin was loaded and in # turn call the hooks defined here as part of the # DebugInterpreter. + config._monkeypatch = m = monkeypatch() if not config.getvalue("noassert") and not config.getvalue("nomagic"): warn_about_missing_assertion() - config._oldassertion = py.builtin.builtins.AssertionError - config._oldbinrepr = py.code._reprcompare - py.builtin.builtins.AssertionError = py.code._AssertionError def callbinrepr(op, left, right): hook_result = config.hook.pytest_assertrepr_compare( config=config, op=op, left=left, right=right) for new_expl in hook_result: if new_expl: return '\n~'.join(new_expl) - py.code._reprcompare = callbinrepr + m.setattr(py.builtin.builtins, + 'AssertionError', py.code._AssertionError) + m.setattr(py.code, '_reprcompare', callbinrepr) def pytest_unconfigure(config): - if hasattr(config, '_oldassertion'): - py.builtin.builtins.AssertionError = config._oldassertion - py.code._reprcompare = config._oldbinrepr - del config._oldassertion - del config._oldbinrepr + config._monkeypatch.undo() def warn_about_missing_assertion(): try: diff --git a/pytest/plugin/python.py b/pytest/plugin/python.py index 17d94a9c9..6648d0b64 100644 --- a/pytest/plugin/python.py +++ b/pytest/plugin/python.py @@ -59,6 +59,8 @@ def pytest_pycollect_makeitem(__multicall__, collector, name, obj): if res is not None: return res if collector._istestclasscandidate(name, obj): + if hasattr(collector.obj, 'unittest'): + return # we assume it's a mixin class for a TestCase derived one return Class(name, parent=collector) elif collector.funcnamefilter(name) and hasattr(obj, '__call__'): if is_generator(obj): @@ -124,6 +126,7 @@ class PyobjMixin(object): return self._fslineno def reportinfo(self): + # XXX caching? obj = self.obj if hasattr(obj, 'compat_co_firstlineno'): # nose compatibility diff --git a/pytest/plugin/unittest.py b/pytest/plugin/unittest.py index ce2d0efd0..476fcb325 100644 --- a/pytest/plugin/unittest.py +++ b/pytest/plugin/unittest.py @@ -1,12 +1,13 @@ -""" support discovery and running of traditional "unittest.py" style tests. """ +""" discovery and running of std-library "unittest" style tests. """ import py import sys def pytest_pycollect_makeitem(collector, name, obj): - if 'unittest' not in sys.modules: - return # nobody derived unittest.TestCase + unittest = sys.modules.get('unittest') + if unittest is None: + return # nobody can have derived unittest.TestCase try: - isunit = issubclass(obj, py.std.unittest.TestCase) + isunit = issubclass(obj, unittest.TestCase) except KeyboardInterrupt: raise except Exception: diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index 4d00517a3..12afa28bd 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -294,6 +294,7 @@ class TestInvocationVariants: assert not retcode out, err = capsys.readouterr() assert "--help" in out + pytest.raises(ValueError, lambda: pytest.main(retcode)) def test_invoke_with_path(self, testdir, capsys): retcode = testdir.pytestmain(testdir.tmpdir) @@ -330,3 +331,18 @@ class TestInvocationVariants: result.stderr.fnmatch_lines([ "ERROR*file*or*package*not*found*", ]) + + + def test_noclass_discovery_if_not_testcase(self, testdir): + testpath = testdir.makepyfile(""" + import unittest + import py + class TestHello(object): + def test_hello(self): + assert self.attr + + class RealTest(TestHello, unittest.TestCase): + attr = 42 + """) + reprec = testdir.inline_run(testpath) + reprec.assertoutcome(passed=1) diff --git a/testing/plugin/test_assertion.py b/testing/plugin/test_assertion.py index d1a531a7c..59c36ca0b 100644 --- a/testing/plugin/test_assertion.py +++ b/testing/plugin/test_assertion.py @@ -161,6 +161,15 @@ def test_functional(testdir): result = testdir.runpytest("--no-assert") assert "3 == 4" not in result.stdout.str() +def test_AssertionErrorIdentity(testdir): + testdir.makepyfile(""" + def test_hello(): + import exceptions + assert AssertionError is exceptions.AssertionError + """) + result = testdir.runpytest() + result.stdout.fnmatch_lines(["*1 passed*"]) + def test_triple_quoted_string_issue113(testdir): testdir.makepyfile(""" def test_hello(): diff --git a/tox.ini b/tox.ini index 1844826c7..6427d100a 100644 --- a/tox.ini +++ b/tox.ini @@ -52,5 +52,6 @@ commands= [pytest] minversion=2.0 plugins=pytester -addargs=-rfx +addopts=-rfx --pyargs rsyncdirs=pytest testing +