diff --git a/_pytest/_argcomplete.py b/_pytest/_argcomplete.py index 955855a96..3ab679d8b 100644 --- a/_pytest/_argcomplete.py +++ b/_pytest/_argcomplete.py @@ -87,6 +87,7 @@ class FastFilesCompleter: completion.append(x[prefix_dir:]) return completion + if os.environ.get('_ARGCOMPLETE'): try: import argcomplete.completers diff --git a/_pytest/_code/code.py b/_pytest/_code/code.py index 5b4237939..616d5c431 100644 --- a/_pytest/_code/code.py +++ b/_pytest/_code/code.py @@ -343,6 +343,7 @@ class Traceback(list): l.append(entry.frame.f_locals) return None + co_equal = compile('__recursioncache_locals_1 == __recursioncache_locals_2', '?', 'eval') @@ -846,6 +847,7 @@ def getrawcode(obj, trycall=True): return x return obj + if sys.version_info[:2] >= (3, 5): # RecursionError introduced in 3.5 def is_recursion_error(excinfo): return excinfo.errisinstance(RecursionError) # noqa diff --git a/_pytest/_code/source.py b/_pytest/_code/source.py index 846e3cced..522150b55 100644 --- a/_pytest/_code/source.py +++ b/_pytest/_code/source.py @@ -265,6 +265,7 @@ def findsource(obj): source.lines = [line.rstrip() for line in sourcelines] return source, lineno + def getsource(obj, **kwargs): import _pytest._code obj = _pytest._code.getrawcode(obj) @@ -275,6 +276,7 @@ def getsource(obj, **kwargs): assert isinstance(strsrc, str) return Source(strsrc, **kwargs) + def deindent(lines, offset=None): if offset is None: for line in lines: @@ -288,6 +290,7 @@ def deindent(lines, offset=None): if offset == 0: return list(lines) newlines = [] + def readline_generator(lines): for line in lines: yield line + '\n' diff --git a/_pytest/assertion/__init__.py b/_pytest/assertion/__init__.py index fd1ebe2c1..18cb7d32c 100644 --- a/_pytest/assertion/__init__.py +++ b/_pytest/assertion/__init__.py @@ -80,10 +80,12 @@ def install_importhook(config): config._assertstate.hook = hook = rewrite.AssertionRewritingHook(config) sys.meta_path.insert(0, hook) config._assertstate.trace('installed rewrite import hook') + def undo(): hook = config._assertstate.hook if hook is not None and hook in sys.meta_path: sys.meta_path.remove(hook) + config.add_cleanup(undo) return hook diff --git a/_pytest/assertion/rewrite.py b/_pytest/assertion/rewrite.py index 6b4c1f483..c2098936d 100644 --- a/_pytest/assertion/rewrite.py +++ b/_pytest/assertion/rewrite.py @@ -276,6 +276,7 @@ def _write_pyc(state, co, source_stat, pyc): fp.close() return True + RN = "\r\n".encode("utf-8") N = "\n".encode("utf-8") diff --git a/_pytest/capture.py b/_pytest/capture.py index 9f60db6ac..eea81ca18 100644 --- a/_pytest/capture.py +++ b/_pytest/capture.py @@ -152,6 +152,7 @@ class CaptureManager: item.add_report_section(when, "stdout", out) item.add_report_section(when, "stderr", err) + error_capsysfderror = "cannot use capsys and capfd at the same time" diff --git a/_pytest/config.py b/_pytest/config.py index 5df198e21..bd7a03aa8 100644 --- a/_pytest/config.py +++ b/_pytest/config.py @@ -65,9 +65,11 @@ def main(args=None, plugins=None): class cmdline: # compatibility namespace main = staticmethod(main) + class UsageError(Exception): """ error in pytest usage or invocation""" + _preinit = [] default_plugins = ( @@ -818,9 +820,11 @@ class Notset: def __repr__(self): return "" + notset = Notset() FILE_OR_DIR = 'file_or_dir' + class Config(object): """ access to configuration values, pluginmanager and plugin hooks. """ @@ -843,9 +847,11 @@ class Config(object): self._warn = self.pluginmanager._warn self.pluginmanager.register(self, "pytestconfig") self._configured = False + def do_setns(dic): import pytest setns(pytest, dic) + self.hook.pytest_namespace.call_historic(do_setns, {}) self.hook.pytest_addoption.call_historic(kwargs=dict(parser=self._parser)) diff --git a/_pytest/debugging.py b/_pytest/debugging.py index 299b05028..d96170bd8 100644 --- a/_pytest/debugging.py +++ b/_pytest/debugging.py @@ -31,10 +31,12 @@ def pytest_configure(config): pytestPDB._pdb_cls = pdb_cls old = (pdb.set_trace, pytestPDB._pluginmanager) + def fin(): pdb.set_trace, pytestPDB._pluginmanager = old pytestPDB._config = None pytestPDB._pdb_cls = pdb.Pdb + pdb.set_trace = pytest.set_trace pytestPDB._pluginmanager = config.pluginmanager pytestPDB._config = config diff --git a/_pytest/fixtures.py b/_pytest/fixtures.py index 3f08b7c6d..28bcd4d8d 100644 --- a/_pytest/fixtures.py +++ b/_pytest/fixtures.py @@ -32,11 +32,13 @@ scope2props["function"] = scope2props["instance"] + ("function", "keywords") def scopeproperty(name=None, doc=None): def decoratescope(func): scopename = name or func.__name__ + def provide(self): if func.__name__ in scope2props[self.scope]: return func(self) raise AttributeError("%s not available in %s-scoped context" % ( scopename, self.scope)) + return property(provide, None, None, func.__doc__) return decoratescope diff --git a/_pytest/helpconfig.py b/_pytest/helpconfig.py index 538a763ca..dd161275b 100644 --- a/_pytest/helpconfig.py +++ b/_pytest/helpconfig.py @@ -41,12 +41,14 @@ def pytest_cmdline_parse(): config.trace.root.setwriter(debugfile.write) undo_tracing = config.pluginmanager.enable_tracing() sys.stderr.write("writing pytestdebug information to %s\n" % path) + def unset_tracing(): debugfile.close() sys.stderr.write("wrote pytestdebug information to %s\n" % debugfile.name) config.trace.root.setwriter(None) undo_tracing() + config.add_cleanup(unset_tracing) def pytest_cmdline_main(config): diff --git a/_pytest/junitxml.py b/_pytest/junitxml.py index 97d0675a6..4b9103949 100644 --- a/_pytest/junitxml.py +++ b/_pytest/junitxml.py @@ -27,6 +27,7 @@ else: class Junit(py.xml.Namespace): pass + # We need to get the subset of the invalid unicode ranges according to # XML 1.0 which are valid in this python build. Hence we calculate # this dynamically instead of hardcoding it. The spec range of valid diff --git a/_pytest/mark.py b/_pytest/mark.py index 640c4e61c..357a60492 100644 --- a/_pytest/mark.py +++ b/_pytest/mark.py @@ -54,6 +54,8 @@ def pytest_cmdline_main(config): tw.line() config._ensure_unconfigure() return 0 + + pytest_cmdline_main.tryfirst = True diff --git a/_pytest/pastebin.py b/_pytest/pastebin.py index 4ec62d022..9f1cf9063 100644 --- a/_pytest/pastebin.py +++ b/_pytest/pastebin.py @@ -11,6 +11,7 @@ def pytest_addoption(parser): choices=['failed', 'all'], help="send failed|all info to bpaste.net pastebin service.") + @pytest.hookimpl(trylast=True) def pytest_configure(config): import py @@ -23,13 +24,16 @@ def pytest_configure(config): # pastebin file will be utf-8 encoded binary file config._pastebinfile = tempfile.TemporaryFile('w+b') oldwrite = tr._tw.write + def tee_write(s, **kwargs): oldwrite(s, **kwargs) if py.builtin._istext(s): s = s.encode('utf-8') config._pastebinfile.write(s) + tr._tw.write = tee_write + def pytest_unconfigure(config): if hasattr(config, '_pastebinfile'): # get terminal contents and delete file @@ -45,6 +49,7 @@ def pytest_unconfigure(config): pastebinurl = create_new_paste(sessionlog) tr.write_line("pastebin session-log: %s\n" % pastebinurl) + def create_new_paste(contents): """ Creates a new paste using bpaste.net service. @@ -72,6 +77,7 @@ def create_new_paste(contents): else: return 'bad response: ' + response + def pytest_terminal_summary(terminalreporter): import _pytest.config if terminalreporter.config.option.pastebin != "failed": diff --git a/_pytest/pytester.py b/_pytest/pytester.py index a8bb39794..8aa5c0c4f 100644 --- a/_pytest/pytester.py +++ b/_pytest/pytester.py @@ -479,10 +479,12 @@ class Testdir: for name, value in items: p = self.tmpdir.join(name).new(ext=ext) source = Source(value) + def my_totext(s, encoding="utf-8"): if py.builtin._isbytes(s): s = py.builtin._totext(s, encoding=encoding) return s + source_unicode = "\n".join([my_totext(line) for line in source.lines]) source = py.builtin._totext(source_unicode) content = source.strip().encode("utf-8") # + "\n" @@ -692,12 +694,15 @@ class Testdir: # warning which will trigger to say they can no longer be # re-written, which is fine as they are already re-written. orig_warn = AssertionRewritingHook._warn_already_imported + def revert(): AssertionRewritingHook._warn_already_imported = orig_warn + self.request.addfinalizer(revert) AssertionRewritingHook._warn_already_imported = lambda *a: None rec = [] + class Collect: def pytest_configure(x, config): rec.append(self.make_hook_recorder(config.pluginmanager)) @@ -732,10 +737,13 @@ class Testdir: try: reprec = self.inline_run(*args, **kwargs) except SystemExit as e: + class reprec: ret = e.args[0] + except Exception: traceback.print_exc() + class reprec: ret = 3 finally: diff --git a/_pytest/python.py b/_pytest/python.py index 18432c1e7..88c65b1ce 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -214,9 +214,12 @@ class PyobjMixin(PyobjContext): if obj is None: self._obj = obj = self._getobj() return obj + def fset(self, value): self._obj = value + return property(fget, fset, None, "underlying python object") + obj = obj() def _getobj(self): diff --git a/_pytest/runner.py b/_pytest/runner.py index d1a155415..eb29e7370 100644 --- a/_pytest/runner.py +++ b/_pytest/runner.py @@ -515,8 +515,10 @@ def exit(msg): __tracebackhide__ = True raise Exit(msg) + exit.Exception = Exit + def skip(msg=""): """ skip an executing test with the given message. Note: it's usually better to use the pytest.mark.skipif marker to declare a test to be @@ -525,8 +527,11 @@ def skip(msg=""): """ __tracebackhide__ = True raise Skipped(msg=msg) + + skip.Exception = Skipped + def fail(msg="", pytrace=True): """ explicitly fail an currently-executing test with the given Message. @@ -535,6 +540,8 @@ def fail(msg="", pytrace=True): """ __tracebackhide__ = True raise Failed(msg=msg, pytrace=pytrace) + + fail.Exception = Failed diff --git a/_pytest/skipping.py b/_pytest/skipping.py index 2f000b7b9..a8eaea98a 100644 --- a/_pytest/skipping.py +++ b/_pytest/skipping.py @@ -25,8 +25,10 @@ def pytest_configure(config): if config.option.runxfail: old = pytest.xfail config._cleanup.append(lambda: setattr(pytest, "xfail", old)) + def nop(*args, **kwargs): pass + nop.Exception = XFailed setattr(pytest, "xfail", nop) @@ -65,6 +67,8 @@ def xfail(reason=""): """ xfail an executing test or setup functions with the given reason.""" __tracebackhide__ = True raise XFailed(reason) + + xfail.Exception = XFailed diff --git a/_pytest/tmpdir.py b/_pytest/tmpdir.py index 5c0802508..28a6b0636 100644 --- a/_pytest/tmpdir.py +++ b/_pytest/tmpdir.py @@ -81,6 +81,7 @@ def get_user(): except (ImportError, KeyError): return None + # backward compatibility TempdirHandler = TempdirFactory diff --git a/_pytest/unittest.py b/_pytest/unittest.py index 4355efe70..73224010b 100644 --- a/_pytest/unittest.py +++ b/_pytest/unittest.py @@ -186,6 +186,7 @@ def pytest_runtest_protocol(item): ut = sys.modules['twisted.python.failure'] Failure__init__ = ut.Failure.__init__ check_testcase_implements_trial_reporter() + def excstore(self, exc_value=None, exc_type=None, exc_tb=None, captureVars=None): if exc_value is None: @@ -199,6 +200,7 @@ def pytest_runtest_protocol(item): captureVars=captureVars) except TypeError: Failure__init__(self, exc_value, exc_type, exc_tb) + ut.Failure.__init__ = excstore yield ut.Failure.__init__ = Failure__init__ diff --git a/testing/code/test_code.py b/testing/code/test_code.py index 6f1d9d3cc..ad9db6d2e 100644 --- a/testing/code/test_code.py +++ b/testing/code/test_code.py @@ -24,6 +24,7 @@ def test_code_with_class(): pass pytest.raises(TypeError, "_pytest._code.Code(A)") + if True: def x(): pass @@ -68,8 +69,10 @@ def test_code_from_func(): def test_unicode_handling(): value = py.builtin._totext('\xc4\x85\xc4\x87\n', 'utf-8').encode('utf8') + def f(): raise Exception(value) + excinfo = pytest.raises(Exception, f) str(excinfo) if sys.version_info[0] < 3: @@ -79,8 +82,10 @@ def test_unicode_handling(): @pytest.mark.skipif(sys.version_info[0] >= 3, reason='python 2 only issue') def test_unicode_handling_syntax_error(): value = py.builtin._totext('\xc4\x85\xc4\x87\n', 'utf-8').encode('utf8') + def f(): raise SyntaxError('invalid syntax', (None, 1, 3, value)) + excinfo = pytest.raises(Exception, f) str(excinfo) if sys.version_info[0] < 3: diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py index 3aae9c71c..c72b87428 100644 --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -56,8 +56,10 @@ def test_excinfo_simple(): def test_excinfo_getstatement(): def g(): raise ValueError + def f(): g() + try: f() except ValueError: @@ -168,11 +170,13 @@ class TestTraceback_f_g_h: # raise ValueError # + def g(): # __tracebackhide__ = tracebackhide f() # + def h(): # g() @@ -214,15 +218,18 @@ class TestTraceback_f_g_h: def test_traceback_no_recursion_index(self): def do_stuff(): raise RuntimeError + def reraise_me(): import sys exc, val, tb = sys.exc_info() py.builtin._reraise(exc, val, tb) + def f(n): try: do_stuff() except: reraise_me() + excinfo = pytest.raises(RuntimeError, f, 8) traceback = excinfo.traceback recindex = traceback.recursionindex() @@ -245,17 +252,18 @@ class TestTraceback_f_g_h: excinfo = pytest.raises(ValueError, fail) assert excinfo.traceback.recursionindex() is None - - def test_traceback_getcrashentry(self): def i(): __tracebackhide__ = True raise ValueError + def h(): i() + def g(): __tracebackhide__ = True h() + def f(): g() @@ -271,6 +279,7 @@ class TestTraceback_f_g_h: def g(): __tracebackhide__ = True raise ValueError + def f(): __tracebackhide__ = True g() @@ -465,11 +474,13 @@ raise ValueError() class FakeCode(object): class raw: co_filename = '?' + path = '?' firstlineno = 5 def fullsource(self): return None + fullsource = property(fullsource) class FakeFrame(object): @@ -491,17 +502,21 @@ raise ValueError() class FakeExcinfo(_pytest._code.ExceptionInfo): typename = "Foo" value = Exception() + def __init__(self): pass def exconly(self, tryshort): return "EXC" + def errisinstance(self, cls): return False excinfo = FakeExcinfo() + class FakeRawTB(object): tb_next = None + tb = FakeRawTB() excinfo.traceback = Traceback(tb) @@ -719,8 +734,10 @@ raise ValueError() excinfo = pytest.raises(ValueError, mod.entry) p = FormattedExcinfo() + def raiseos(): raise OSError(2) + monkeypatch.setattr(py.std.os, 'getcwd', raiseos) assert p._makepath(__file__) == __file__ p.repr_traceback(excinfo) @@ -789,9 +806,11 @@ raise ValueError() def test_reprexcinfo_unicode(self): from _pytest._code.code import TerminalRepr + class MyRepr(TerminalRepr): def toterminal(self, tw): tw.line(py.builtin._totext("я", "utf-8")) + x = py.builtin._totext(MyRepr()) assert x == py.builtin._totext("я", "utf-8") diff --git a/testing/python/collect.py b/testing/python/collect.py index 2913b11a4..1e69f2da9 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -383,10 +383,13 @@ class TestFunction: config = testdir.parseconfigure() session = testdir.Session(config) session._fixturemanager = FixtureManager(session) + def func1(): pass + def func2(): pass + f1 = pytest.Function(name="name", parent=session, config=config, args=(1,), callobj=func1) assert f1 == f1 @@ -547,12 +550,15 @@ class TestFunction: def test_pyfunc_call(self, testdir): item = testdir.getitem("def test_func(): raise ValueError") config = item.config + class MyPlugin1: def pytest_pyfunc_call(self, pyfuncitem): raise ValueError + class MyPlugin2: def pytest_pyfunc_call(self, pyfuncitem): return True + config.pluginmanager.register(MyPlugin1()) config.pluginmanager.register(MyPlugin2()) config.hook.pytest_runtest_setup(item=item) diff --git a/testing/python/fixture.py b/testing/python/fixture.py index d6b7840c6..be99ed833 100644 --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -10,15 +10,20 @@ from _pytest import fixtures def test_getfuncargnames(): def f(): pass assert not fixtures.getfuncargnames(f) + def g(arg): pass assert fixtures.getfuncargnames(g) == ('arg',) + def h(arg1, arg2="hello"): pass assert fixtures.getfuncargnames(h) == ('arg1',) + def h(arg1, arg2, arg3="hello"): pass assert fixtures.getfuncargnames(h) == ('arg1', 'arg2') + class A: def f(self, arg1, arg2="hello"): pass + assert fixtures.getfuncargnames(A().f) == ('arg1',) if sys.version_info < (3,0): assert fixtures.getfuncargnames(A.f) == ('arg1',) @@ -869,8 +874,10 @@ class TestRequestCachedSetup: item1 = testdir.getitem("def test_func(): pass") req1 = fixtures.FixtureRequest(item1) l = ["hello", "world"] + def setup(): return l.pop() + ret1 = req1.cached_setup(setup, extrakey=1) ret2 = req1.cached_setup(setup, extrakey=2) assert ret2 == "hello" @@ -884,10 +891,13 @@ class TestRequestCachedSetup: item1 = testdir.getitem("def test_func(): pass") req1 = fixtures.FixtureRequest(item1) l = [] + def setup(): l.append("setup") + def teardown(val): l.append("teardown") + req1.cached_setup(setup, teardown, scope="function") assert l == ['setup'] # artificial call of finalizer diff --git a/testing/python/integration.py b/testing/python/integration.py index 237becd6f..6697342ea 100644 --- a/testing/python/integration.py +++ b/testing/python/integration.py @@ -63,10 +63,12 @@ class TestOEJSKITSpecials: def test_wrapped_getfslineno(): def func(): pass + def wrap(f): func.__wrapped__ = f func.patchings = ["qwe"] return func + @wrap def wrapped_func(x, y, z): pass @@ -77,28 +79,36 @@ def test_wrapped_getfslineno(): class TestMockDecoration: def test_wrapped_getfuncargnames(self): from _pytest.compat import getfuncargnames + def wrap(f): + def func(): pass + func.__wrapped__ = f return func + @wrap def f(x): pass + l = getfuncargnames(f) assert l == ("x",) def test_wrapped_getfuncargnames_patching(self): from _pytest.compat import getfuncargnames + def wrap(f): def func(): pass func.__wrapped__ = f func.patchings = ["qwe"] return func + @wrap def f(x, y, z): pass + l = getfuncargnames(f) assert l == ("y", "z") diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index 9d71df20a..a7e1d5699 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -20,8 +20,10 @@ class TestMetafunc: # initiliazation class FixtureInfo: name2fixturedefs = None + def __init__(self, names): self.names_closure = names + names = fixtures.getfuncargnames(func) fixtureinfo = FixtureInfo(names) return python.Metafunc(func, fixtureinfo, None) @@ -65,7 +67,9 @@ class TestMetafunc: def test_addcall_param(self): def func(arg1): pass metafunc = self.Metafunc(func) + class obj: pass + metafunc.addcall(param=obj) metafunc.addcall(param=obj) metafunc.addcall(param=1) @@ -76,8 +80,11 @@ class TestMetafunc: def test_addcall_funcargs(self): def func(x): pass + metafunc = self.Metafunc(func) + class obj: pass + metafunc.addcall(funcargs={"x": 2}) metafunc.addcall(funcargs={"x": 3}) pytest.raises(pytest.fail.Exception, "metafunc.addcall({'xyz': 0})") @@ -142,8 +149,10 @@ class TestMetafunc: def test_parametrize_with_userobjects(self): def func(x, y): pass metafunc = self.Metafunc(func) + class A: pass + metafunc.parametrize("x", [A(), A()]) metafunc.parametrize("y", list("ab")) assert metafunc._calls[0].id == "x0-a" @@ -254,6 +263,7 @@ class TestMetafunc: @pytest.mark.issue351 def test_idmaker_idfn(self): from _pytest.python import idmaker + def ids(val): if isinstance(val, Exception): return repr(val) @@ -270,6 +280,7 @@ class TestMetafunc: @pytest.mark.issue351 def test_idmaker_idfn_unique_names(self): from _pytest.python import idmaker + def ids(val): return 'a' @@ -285,6 +296,7 @@ class TestMetafunc: @pytest.mark.issue351 def test_idmaker_idfn_exception(self): from _pytest.python import idmaker + def ids(val): raise Exception("bad code") diff --git a/testing/test_assertion.py b/testing/test_assertion.py index 48cd26f02..c63f26b9c 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -12,12 +12,15 @@ PY3 = sys.version_info >= (3, 0) @pytest.fixture def mock_config(): + class Config(object): verbose = False + def getoption(self, name): if name == 'verbose': return self.verbose raise KeyError('Not mocked out: %s' % name) + return Config() diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py index e72266a18..483d80b3d 100644 --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -104,20 +104,29 @@ class TestAssertionRewrite: def f(): assert False assert getmsg(f) == "assert False" + def f(): f = False assert f + assert getmsg(f) == "assert False" + def f(): assert a_global # noqa + assert getmsg(f, {"a_global" : False}) == "assert False" + def f(): assert sys == 42 + assert getmsg(f, {"sys" : sys}) == "assert sys == 42" + def f(): assert cls == 42 # noqa + class X(object): pass + assert getmsg(f, {"cls" : X}) == "assert cls == 42" def test_assert_already_has_message(self): @@ -190,78 +199,110 @@ class TestAssertionRewrite: def f(): f = g = False assert f and g + assert getmsg(f) == "assert (False)" + def f(): f = True g = False assert f and g + assert getmsg(f) == "assert (True and False)" + def f(): f = False g = True assert f and g + assert getmsg(f) == "assert (False)" + def f(): f = g = False assert f or g + assert getmsg(f) == "assert (False or False)" + def f(): f = g = False assert not f and not g + getmsg(f, must_pass=True) + def x(): return False + def f(): assert x() and x() + assert getmsg(f, {"x" : x}) == """assert (False) + where False = x()""" + def f(): assert False or x() + assert getmsg(f, {"x" : x}) == """assert (False or False) + where False = x()""" + def f(): assert 1 in {} and 2 in {} + assert getmsg(f) == "assert (1 in {})" + def f(): x = 1 y = 2 assert x in {1 : None} and y in {} + assert getmsg(f) == "assert (1 in {1: None} and 2 in {})" + def f(): f = True g = False assert f or g + getmsg(f, must_pass=True) + def f(): f = g = h = lambda: True assert f() and g() and h() + getmsg(f, must_pass=True) def test_short_circut_evaluation(self): def f(): assert True or explode # noqa + getmsg(f, must_pass=True) + def f(): x = 1 assert x == 1 or x == 2 + getmsg(f, must_pass=True) def test_unary_op(self): def f(): x = True assert not x + assert getmsg(f) == "assert not True" + def f(): x = 0 assert ~x + 1 + assert getmsg(f) == "assert (~0 + 1)" + def f(): x = 3 assert -x + x + assert getmsg(f) == "assert (-3 + 3)" + def f(): x = 0 assert +x + x + assert getmsg(f) == "assert (+0 + 0)" def test_binary_op(self): @@ -269,7 +310,9 @@ class TestAssertionRewrite: x = 1 y = -1 assert x + y + assert getmsg(f) == "assert (1 + -1)" + def f(): assert not 5 % 4 assert getmsg(f) == "assert not (5 % 4)" @@ -277,7 +320,9 @@ class TestAssertionRewrite: def test_boolop_percent(self): def f(): assert 3 % 2 and False + assert getmsg(f) == "assert ((3 % 2) and False)" + def f(): assert False or 4 % 2 assert getmsg(f) == "assert (False or (4 % 2))" @@ -298,113 +343,159 @@ class TestAssertionRewrite: def test_call(self): def g(a=42, *args, **kwargs): return False + ns = {"g" : g} + def f(): assert g() + assert getmsg(f, ns) == """assert False + where False = g()""" + def f(): assert g(1) + assert getmsg(f, ns) == """assert False + where False = g(1)""" + def f(): assert g(1, 2) + assert getmsg(f, ns) == """assert False + where False = g(1, 2)""" + def f(): assert g(1, g=42) + assert getmsg(f, ns) == """assert False + where False = g(1, g=42)""" + def f(): assert g(1, 3, g=23) + assert getmsg(f, ns) == """assert False + where False = g(1, 3, g=23)""" + def f(): seq = [1, 2, 3] assert g(*seq) + assert getmsg(f, ns) == """assert False + where False = g(*[1, 2, 3])""" + def f(): x = "a" assert g(**{x : 2}) + assert getmsg(f, ns) == """assert False + where False = g(**{'a': 2})""" def test_attribute(self): class X(object): g = 3 + ns = {"x" : X} + def f(): assert not x.g # noqa + assert getmsg(f, ns) == """assert not 3 + where 3 = x.g""" + def f(): x.a = False # noqa assert x.a # noqa + assert getmsg(f, ns) == """assert False + where False = x.a""" def test_comparisons(self): + def f(): a, b = range(2) assert b < a + assert getmsg(f) == """assert 1 < 0""" + def f(): a, b, c = range(3) assert a > b > c + assert getmsg(f) == """assert 0 > 1""" + def f(): a, b, c = range(3) assert a < b > c + assert getmsg(f) == """assert 1 > 2""" + def f(): a, b, c = range(3) assert a < b <= c + getmsg(f, must_pass=True) + def f(): a, b, c = range(3) assert a < b assert b < c + getmsg(f, must_pass=True) def test_len(self): + def f(): l = list(range(10)) assert len(l) == 11 + assert getmsg(f).startswith("""assert 10 == 11 + where 10 = len([""") def test_custom_reprcompare(self, monkeypatch): def my_reprcompare(op, left, right): return "42" + monkeypatch.setattr(util, "_reprcompare", my_reprcompare) + def f(): assert 42 < 3 + assert getmsg(f) == "assert 42" + def my_reprcompare(op, left, right): return "%s %s %s" % (left, op, right) + monkeypatch.setattr(util, "_reprcompare", my_reprcompare) + def f(): assert 1 < 3 < 5 <= 4 < 7 + assert getmsg(f) == "assert 5 <= 4" def test_assert_raising_nonzero_in_comparison(self): def f(): class A(object): + def __nonzero__(self): raise ValueError(42) + def __lt__(self, other): return A() + def __repr__(self): return "" + def myany(x): return False + assert myany(A() < 0) + assert " < 0" in getmsg(f) def test_formatchar(self): def f(): assert "%test" == "test" + assert getmsg(f).startswith("assert '%test' == 'test'") def test_custom_repr(self): @@ -414,8 +505,10 @@ class TestAssertionRewrite: def __repr__(self): return "\n{ \n~ \n}" + f = Foo() assert 0 == f.a + assert r"where 1 = \n{ \n~ \n}.a" in util._format_lines([getmsg(f)])[0] @@ -527,8 +620,10 @@ def test_rewritten(): def test_rewrite_warning(self, pytestconfig, monkeypatch): hook = AssertionRewritingHook(pytestconfig) warnings = [] + def mywarn(code, msg): warnings.append((code, msg)) + monkeypatch.setattr(hook.config, 'warn', mywarn) hook.mark_rewrite('_pytest') assert '_pytest' in warnings[0][1] @@ -642,10 +737,12 @@ class TestAssertionRewriteHookDetails(object): source_path = tmpdir.ensure("source.py") pycpath = tmpdir.join("pyc").strpath assert _write_pyc(state, [1], source_path.stat(), pycpath) + def open(*args): e = IOError() e.errno = 10 raise e + monkeypatch.setattr(b, "open", open) assert not _write_pyc(state, [1], source_path.stat(), pycpath) diff --git a/testing/test_collection.py b/testing/test_collection.py index 71a64c3c9..9cf4de895 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -150,11 +150,13 @@ class TestCollectFS: class TestCollectPluginHookRelay: def test_pytest_collect_file(self, testdir): wascalled = [] + class Plugin: def pytest_collect_file(self, path, parent): if not path.basename.startswith("."): # Ignore hidden files, e.g. .testmondata. wascalled.append(path) + testdir.makefile(".abc", "xyz") pytest.main([testdir.tmpdir], plugins=[Plugin()]) assert len(wascalled) == 1 @@ -162,15 +164,18 @@ class TestCollectPluginHookRelay: def test_pytest_collect_directory(self, testdir): wascalled = [] + class Plugin: def pytest_collect_directory(self, path, parent): wascalled.append(path.basename) + testdir.mkdir("hello") testdir.mkdir("world") pytest.main(testdir.tmpdir, plugins=[Plugin()]) assert "hello" in wascalled assert "world" in wascalled + class TestPrunetraceback: def test_custom_repr_failure(self, testdir): diff --git a/testing/test_config.py b/testing/test_config.py index 83e8f4e6a..75a806c4a 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -373,23 +373,31 @@ def test_options_on_small_file_do_not_blow_up(testdir): ['--traceconfig'], ['-v'], ['-v', '-v']): runfiletest(opts + [path]) + def test_preparse_ordering_with_setuptools(testdir, monkeypatch): pkg_resources = pytest.importorskip("pkg_resources") + def my_iter(name): assert name == "pytest11" + class Dist: project_name = 'spam' version = '1.0' + def _get_metadata(self, name): return ['foo.txt,sha256=abc,123'] + class EntryPoint: name = "mytestplugin" dist = Dist() + def load(self): class PseudoPlugin: x = 42 return PseudoPlugin() + return iter([EntryPoint()]) + monkeypatch.setattr(pkg_resources, 'iter_entry_points', my_iter) testdir.makeconftest(""" pytest_plugins = "mytestplugin", @@ -402,18 +410,24 @@ def test_preparse_ordering_with_setuptools(testdir, monkeypatch): def test_setuptools_importerror_issue1479(testdir, monkeypatch): pkg_resources = pytest.importorskip("pkg_resources") + def my_iter(name): assert name == "pytest11" + class Dist: project_name = 'spam' version = '1.0' + def _get_metadata(self, name): return ['foo.txt,sha256=abc,123'] + class EntryPoint: name = "mytestplugin" dist = Dist() + def load(self): raise ImportError("Don't hide me!") + return iter([EntryPoint()]) monkeypatch.setattr(pkg_resources, 'iter_entry_points', my_iter) @@ -423,19 +437,26 @@ def test_setuptools_importerror_issue1479(testdir, monkeypatch): def test_plugin_preparse_prevents_setuptools_loading(testdir, monkeypatch): pkg_resources = pytest.importorskip("pkg_resources") + def my_iter(name): assert name == "pytest11" + class Dist: project_name = 'spam' version = '1.0' + def _get_metadata(self, name): return ['foo.txt,sha256=abc,123'] + class EntryPoint: name = "mytestplugin" dist = Dist() + def load(self): assert 0, "should not arrive here" + return iter([EntryPoint()]) + monkeypatch.setattr(pkg_resources, 'iter_entry_points', my_iter) config = testdir.parseconfig("-p", "no:mytestplugin") plugin = config.pluginmanager.getplugin("mytestplugin") @@ -503,9 +524,11 @@ def test_notify_exception(testdir, capfd): config.notify_exception(excinfo) out, err = capfd.readouterr() assert "ValueError" in err + class A: def pytest_internalerror(self, excrepr): return True + config.pluginmanager.register(A()) config.notify_exception(excinfo) out, err = capfd.readouterr() @@ -515,9 +538,11 @@ def test_notify_exception(testdir, capfd): def test_load_initial_conftest_last_ordering(testdir): from _pytest.config import get_config pm = get_config().pluginmanager + class My: def pytest_load_initial_conftests(self): pass + m = My() pm.register(m) hc = pm.hook.pytest_load_initial_conftests diff --git a/testing/test_conftest.py b/testing/test_conftest.py index 377283eb9..6dce13859 100644 --- a/testing/test_conftest.py +++ b/testing/test_conftest.py @@ -200,8 +200,10 @@ def test_conftest_import_order(testdir, monkeypatch): sub = testdir.mkdir("sub") ct2 = sub.join("conftest.py") ct2.write("") + def impct(p): return p + conftest = PytestPluginManager() conftest._confcutdir = testdir.tmpdir monkeypatch.setattr(conftest, '_importconftest', impct) diff --git a/testing/test_mark.py b/testing/test_mark.py index e0bf3c3c8..a4430b4c8 100644 --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -23,15 +23,19 @@ class TestMark: def test_pytest_mark_bare(self): mark = Mark() + def f(): pass + mark.hello(f) assert f.hello def test_pytest_mark_keywords(self): mark = Mark() + def f(): pass + mark.world(x=3, y=4)(f) assert f.world assert f.world.kwargs['x'] == 3 @@ -39,8 +43,10 @@ class TestMark: def test_apply_multiple_and_merge(self): mark = Mark() + def f(): pass + mark.world mark.world(x=3)(f) assert f.world.kwargs['x'] == 3 @@ -53,33 +59,43 @@ class TestMark: def test_pytest_mark_positional(self): mark = Mark() + def f(): pass + mark.world("hello")(f) assert f.world.args[0] == "hello" mark.world("world")(f) def test_pytest_mark_positional_func_and_keyword(self): mark = Mark() + def f(): raise Exception + m = mark.world(f, omega="hello") + def g(): pass + assert m(g) == g assert g.world.args[0] is f assert g.world.kwargs["omega"] == "hello" def test_pytest_mark_reuse(self): mark = Mark() + def f(): pass + w = mark.some w("hello", reason="123")(f) assert f.some.args[0] == "hello" assert f.some.kwargs['reason'] == "123" + def g(): pass + w("world", reason2="456")(g) assert g.some.args[0] == "world" assert 'reason' not in g.some.kwargs @@ -610,11 +626,12 @@ class TestFunctional: def test_1(parameter): assert True """) - reprec = testdir.inline_run() reprec.assertoutcome(skipped=1) + class TestKeywordSelection: + def test_select_simple(self, testdir): file_test = testdir.makepyfile(""" def test_one(): @@ -623,6 +640,7 @@ class TestKeywordSelection: def test_method_one(self): assert 42 == 43 """) + def check(keyword, name): reprec = testdir.inline_run("-s", "-k", keyword, file_test) passed, skipped, failed = reprec.listoutcomes() @@ -709,6 +727,7 @@ class TestKeywordSelection: p = testdir.makepyfile(""" def test_one(): assert 1 """) + def assert_test_is_not_selected(keyword): reprec = testdir.inline_run("-k", keyword, p) passed, skipped, failed = reprec.countoutcomes() diff --git a/testing/test_nose.py b/testing/test_nose.py index a5162381e..f54246111 100644 --- a/testing/test_nose.py +++ b/testing/test_nose.py @@ -25,18 +25,22 @@ def test_nose_setup(testdir): def test_setup_func_with_setup_decorator(): from _pytest.nose import call_optional l = [] + class A: @pytest.fixture(autouse=True) def f(self): l.append(1) + call_optional(A(), "f") assert not l def test_setup_func_not_callable(): from _pytest.nose import call_optional + class A: f = 1 + call_optional(A(), "f") def test_nose_setup_func(testdir): diff --git a/testing/test_parseopt.py b/testing/test_parseopt.py index fc9ded488..e933dbb8d 100644 --- a/testing/test_parseopt.py +++ b/testing/test_parseopt.py @@ -138,7 +138,10 @@ class TestParser: def test_parse_setoption(self, parser): parser.addoption("--hello", dest="hello", action="store") parser.addoption("--world", dest="world", default=42) - class A: pass + + class A: + pass + option = A() args = parser.parse_setoption(['--hello', 'world'], option) assert option.hello == "world" diff --git a/testing/test_pastebin.py b/testing/test_pastebin.py index 03570a5c7..8123424ca 100644 --- a/testing/test_pastebin.py +++ b/testing/test_pastebin.py @@ -84,8 +84,10 @@ class TestPaste: function that connects to bpaste service. """ calls = [] + def mocked(url, data): calls.append((url, data)) + class DummyFile: def read(self): # part of html of a normal response diff --git a/testing/test_pdb.py b/testing/test_pdb.py index b22f5128e..df58dad87 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -19,8 +19,10 @@ class TestPDB: def pdblist(self, request): monkeypatch = request.getfixturevalue("monkeypatch") pdblist = [] + def mypdb(*args): pdblist.append(args) + plugin = request.config.pluginmanager.getplugin('debugging') monkeypatch.setattr(plugin, 'post_mortem', mypdb) return pdblist diff --git a/testing/test_pluginmanager.py b/testing/test_pluginmanager.py index e61c84247..d636102f7 100644 --- a/testing/test_pluginmanager.py +++ b/testing/test_pluginmanager.py @@ -83,6 +83,7 @@ class TestPytestPluginInteractions: def test_configure(self, testdir): config = testdir.parseconfig() l = [] + class A: def pytest_configure(self, config): l.append(self) @@ -102,13 +103,16 @@ class TestPytestPluginInteractions: def test_hook_tracing(self): pytestpm = get_config().pluginmanager # fully initialized with plugins saveindent = [] + class api1: def pytest_plugin_registered(self): saveindent.append(pytestpm.trace.root.indent) + class api2: def pytest_plugin_registered(self): saveindent.append(pytestpm.trace.root.indent) raise ValueError() + l = [] pytestpm.trace.root.setwriter(l.append) undo = pytestpm.enable_tracing() diff --git a/testing/test_pytester.py b/testing/test_pytester.py index 65660afdf..14548808c 100644 --- a/testing/test_pytester.py +++ b/testing/test_pytester.py @@ -11,6 +11,7 @@ def test_make_hook_recorder(testdir): assert not recorder.getfailures() pytest.xfail("internal reportrecorder tests need refactoring") + class rep: excinfo = None passed = False @@ -80,10 +81,13 @@ def make_holder(): "x" apimod = type(os)('api') + def pytest_xyz(arg): "x" + def pytest_xyz_noarg(): "x" + apimod.pytest_xyz = pytest_xyz apimod.pytest_xyz_noarg = pytest_xyz_noarg return apiclass, apimod diff --git a/testing/test_runner.py b/testing/test_runner.py index e88a548d7..727defa92 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -38,9 +38,13 @@ class TestSetupState: def test_teardown_multiple_one_fails(self, testdir): r = [] + def fin1(): r.append('fin1') + def fin2(): raise Exception('oops') + def fin3(): r.append('fin3') + item = testdir.getitem("def test_func(): pass") ss = runner.SetupState() ss.addfinalizer(fin1, item) @@ -55,7 +59,9 @@ class TestSetupState: # Ensure the first exception is the one which is re-raised. # Ideally both would be reported however. def fin1(): raise Exception('oops1') + def fin2(): raise Exception('oops2') + item = testdir.getitem("def test_func(): pass") ss = runner.SetupState() ss.addfinalizer(fin1, item) @@ -527,8 +533,10 @@ def test_exception_printing_skip(): def test_importorskip(monkeypatch): importorskip = pytest.importorskip + def f(): importorskip("asdlkj") + try: sys = importorskip("sys") # noqa assert sys == py.std.sys @@ -643,11 +651,13 @@ def test_makereport_getsource_dynamic_code(testdir, monkeypatch): """Test that exception in dynamically generated code doesn't break getting the source line.""" import inspect original_findsource = inspect.findsource + def findsource(obj, *args, **kwargs): # Can be triggered by dynamically created functions if obj.__name__ == 'foo': raise IndexError() return original_findsource(obj, *args, **kwargs) + monkeypatch.setattr(inspect, 'findsource', findsource) testdir.makepyfile(""" diff --git a/tox.ini b/tox.ini index efa3ad2f1..c2f866f26 100644 --- a/tox.ini +++ b/tox.ini @@ -175,3 +175,4 @@ norecursedirs = .tox ja .hg cx_freeze_source [flake8] ignore =E401,E225,E261,E128,E124,E301,E302,E121,E303,W391,E501,E231,E126,E701,E265,E241,E251,E226,E101,W191,E131,E203,E122,E123,E271,E712,E222,E127,E125,E221,W292,E111,E113,E293,E262,W293,E129,E702,E201,E272,E202,E704,E731,E402 +exclude = _pytest/vendored_packages/pluggy.py