From d0bd01beca0308cfb263ec7f44c9acf0933a1fc0 Mon Sep 17 00:00:00 2001 From: turturica Date: Thu, 9 Aug 2018 18:06:38 -0700 Subject: [PATCH 01/62] Collect any tests from a package's __init__.py --- src/_pytest/python.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 2657bff638a..ad8a5f2525d 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -596,6 +596,7 @@ def isinitpath(self, path): def collect(self): this_path = self.fspath.dirpath() pkg_prefix = None + yield Module(this_path.join("__init__.py"), self) for path in this_path.visit(rec=self._recurse, bf=True, sort=True): # we will visit our own __init__.py file, in which case we skip it if path.basename == "__init__.py" and path.dirpath() == this_path: From 273670b2a204f950df93d87baff26b04a47c0400 Mon Sep 17 00:00:00 2001 From: Tyler Richard Date: Wed, 20 Dec 2017 10:28:50 -0800 Subject: [PATCH 02/62] Fixes capfd so data is available after teardown. --- src/_pytest/capture.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/_pytest/capture.py b/src/_pytest/capture.py index c84ba825eee..b2ab4e57abb 100644 --- a/src/_pytest/capture.py +++ b/src/_pytest/capture.py @@ -188,7 +188,6 @@ def pytest_runtest_call(self, item): def pytest_runtest_teardown(self, item): self._current_item = item self.resume_global_capture() - self.activate_fixture(item) yield self.suspend_capture_item(item, "teardown") self._current_item = None From c24c7e75e26516e032c750d49cec2b840fb128cf Mon Sep 17 00:00:00 2001 From: Tyler Richard Date: Tue, 26 Dec 2017 10:25:25 -0800 Subject: [PATCH 03/62] Added regression test for capfd in a fixture --- testing/test_capture.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/testing/test_capture.py b/testing/test_capture.py index 782971af04a..da0fb5369f5 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -1318,6 +1318,26 @@ def test_capattr(): reprec = testdir.inline_run() reprec.assertoutcome(passed=1) +def test_capfd_after_test(testdir): + testdir.makepyfile(""" + import sys + import pytest + import os + + @pytest.fixture() + def fix(capfd): + yield + out, err = capfd.readouterr() + assert out == 'lolcatz' + os.linesep + assert err == 'err' + + def test_a(fix): + print("lolcatz") + sys.stderr.write("err") + """) + reprec = testdir.inline_run() + reprec.assertoutcome(passed=1) + @pytest.mark.skipif( not sys.platform.startswith("win") and sys.version_info[:2] >= (3, 6), From f4c5994d27287e1ac2440a4ae17032f272208cb3 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 18 Aug 2018 14:32:10 -0300 Subject: [PATCH 04/62] Fixtures during teardown can use capsys and capfd to get output from tests Fix #3033 --- changelog/3033.bugfix.rst | 1 + src/_pytest/capture.py | 36 ++++++++++++++++++++++------- testing/test_capture.py | 48 +++++++++++++++++++++++---------------- 3 files changed, 57 insertions(+), 28 deletions(-) create mode 100644 changelog/3033.bugfix.rst diff --git a/changelog/3033.bugfix.rst b/changelog/3033.bugfix.rst new file mode 100644 index 00000000000..3fcd9dd116c --- /dev/null +++ b/changelog/3033.bugfix.rst @@ -0,0 +1 @@ +Fixtures during teardown can again use ``capsys`` and ``cafd`` to inspect output captured during tests. diff --git a/src/_pytest/capture.py b/src/_pytest/capture.py index b2ab4e57abb..deabcac8de4 100644 --- a/src/_pytest/capture.py +++ b/src/_pytest/capture.py @@ -188,6 +188,7 @@ def pytest_runtest_call(self, item): def pytest_runtest_teardown(self, item): self._current_item = item self.resume_global_capture() + self.activate_fixture(item) yield self.suspend_capture_item(item, "teardown") self._current_item = None @@ -308,6 +309,9 @@ class CaptureFixture(object): def __init__(self, captureclass, request): self.captureclass = captureclass self.request = request + self._capture = None + self._captured_out = self.captureclass.EMPTY_BUFFER + self._captured_err = self.captureclass.EMPTY_BUFFER def _start(self): self._capture = MultiCapture( @@ -316,20 +320,26 @@ def _start(self): self._capture.start_capturing() def close(self): - cap = self.__dict__.pop("_capture", None) - if cap is not None: - self._outerr = cap.pop_outerr_to_orig() - cap.stop_capturing() + if self._capture is not None: + out, err = self._capture.pop_outerr_to_orig() + self._captured_out += out + self._captured_err += err + self._capture.stop_capturing() + self._capture = None def readouterr(self): """Read and return the captured output so far, resetting the internal buffer. :return: captured content as a namedtuple with ``out`` and ``err`` string attributes """ - try: - return self._capture.readouterr() - except AttributeError: - return self._outerr + captured_out, captured_err = self._captured_out, self._captured_err + if self._capture is not None: + out, err = self._capture.readouterr() + captured_out += out + captured_err += err + self._captured_out = self.captureclass.EMPTY_BUFFER + self._captured_err = self.captureclass.EMPTY_BUFFER + return CaptureResult(captured_out, captured_err) @contextlib.contextmanager def _suspend(self): @@ -462,6 +472,7 @@ def readouterr(self): class NoCapture(object): + EMPTY_BUFFER = None __init__ = start = done = suspend = resume = lambda *args: None @@ -471,6 +482,8 @@ class FDCaptureBinary(object): snap() produces `bytes` """ + EMPTY_BUFFER = bytes() + def __init__(self, targetfd, tmpfile=None): self.targetfd = targetfd try: @@ -544,6 +557,8 @@ class FDCapture(FDCaptureBinary): snap() produces text """ + EMPTY_BUFFER = str() + def snap(self): res = FDCaptureBinary.snap(self) enc = getattr(self.tmpfile, "encoding", None) @@ -553,6 +568,9 @@ def snap(self): class SysCapture(object): + + EMPTY_BUFFER = str() + def __init__(self, fd, tmpfile=None): name = patchsysdict[fd] self._old = getattr(sys, name) @@ -590,6 +608,8 @@ def writeorg(self, data): class SysCaptureBinary(SysCapture): + EMPTY_BUFFER = bytes() + def snap(self): res = self.tmpfile.buffer.getvalue() self.tmpfile.seek(0) diff --git a/testing/test_capture.py b/testing/test_capture.py index da0fb5369f5..be3385dfbcc 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -647,6 +647,34 @@ def test_captured_print(captured_print): assert "stdout contents begin" not in result.stdout.str() assert "stderr contents begin" not in result.stdout.str() + @pytest.mark.parametrize("cap", ["capsys", "capfd"]) + def test_fixture_use_by_other_fixtures_teardown(self, testdir, cap): + """Ensure we can access setup and teardown buffers from teardown when using capsys/capfd (##3033)""" + testdir.makepyfile( + """ + import sys + import pytest + import os + + @pytest.fixture() + def fix({cap}): + print("setup out") + sys.stderr.write("setup err\\n") + yield + out, err = {cap}.readouterr() + assert out == 'setup out\\ncall out\\n' + assert err == 'setup err\\ncall err\\n' + + def test_a(fix): + print("call out") + sys.stderr.write("call err\\n") + """.format( + cap=cap + ) + ) + reprec = testdir.inline_run() + reprec.assertoutcome(passed=1) + def test_setup_failure_does_not_kill_capturing(testdir): sub1 = testdir.mkpydir("sub1") @@ -1318,26 +1346,6 @@ def test_capattr(): reprec = testdir.inline_run() reprec.assertoutcome(passed=1) -def test_capfd_after_test(testdir): - testdir.makepyfile(""" - import sys - import pytest - import os - - @pytest.fixture() - def fix(capfd): - yield - out, err = capfd.readouterr() - assert out == 'lolcatz' + os.linesep - assert err == 'err' - - def test_a(fix): - print("lolcatz") - sys.stderr.write("err") - """) - reprec = testdir.inline_run() - reprec.assertoutcome(passed=1) - @pytest.mark.skipif( not sys.platform.startswith("win") and sys.version_info[:2] >= (3, 6), From 7d9b198f734f7d1968c88476545da29fb90c1040 Mon Sep 17 00:00:00 2001 From: victor Date: Sun, 19 Aug 2018 02:32:36 +0200 Subject: [PATCH 05/62] Refactoring: Separated suspend from snapping (stopped always snapping when suspending - solves bug but still missing tests), reorganized functions and context managers. --- src/_pytest/capture.py | 135 +++++++++++++++++++++++------------------ 1 file changed, 77 insertions(+), 58 deletions(-) diff --git a/src/_pytest/capture.py b/src/_pytest/capture.py index c84ba825eee..f10a13a1fe4 100644 --- a/src/_pytest/capture.py +++ b/src/_pytest/capture.py @@ -62,8 +62,9 @@ def silence_logging_at_shutdown(): # finally trigger conftest loading but while capturing (issue93) capman.start_global_capturing() outcome = yield - out, err = capman.suspend_global_capture() + capman.suspend_global_capture() if outcome.excinfo is not None: + out, err = capman.snap_global_capture() sys.stdout.write(out) sys.stderr.write(err) @@ -96,6 +97,8 @@ def _getcapture(self, method): else: raise ValueError("unknown capturing method: %r" % method) + # Global capturing control + def start_global_capturing(self): assert self._global_capturing is None self._global_capturing = self._getcapture(self._method) @@ -110,29 +113,15 @@ def stop_global_capturing(self): def resume_global_capture(self): self._global_capturing.resume_capturing() - def suspend_global_capture(self, item=None, in_=False): - if item is not None: - self.deactivate_fixture(item) + def suspend_global_capture(self, in_=False): cap = getattr(self, "_global_capturing", None) if cap is not None: - try: - outerr = cap.readouterr() - finally: - cap.suspend_capturing(in_=in_) - return outerr + cap.suspend_capturing(in_=in_) - @contextlib.contextmanager - def global_and_fixture_disabled(self): - """Context manager to temporarily disables global and current fixture capturing.""" - # Need to undo local capsys-et-al if exists before disabling global capture - fixture = getattr(self._current_item, "_capture_fixture", None) - ctx_manager = fixture._suspend() if fixture else dummy_context_manager() - with ctx_manager: - self.suspend_global_capture(item=None, in_=False) - try: - yield - finally: - self.resume_global_capture() + def snap_global_capture(self): + return self._global_capturing.readouterr() + + # Fixture Control (its just forwarding, think about removing this later) def activate_fixture(self, item): """If the current item is using ``capsys`` or ``capfd``, activate them so they take precedence over @@ -148,12 +137,53 @@ def deactivate_fixture(self, item): if fixture is not None: fixture.close() + def suspend_fixture(self, item): + fixture = getattr(item, "_capture_fixture", None) + if fixture is not None: + fixture._suspend() + + def resume_fixture(self, item): + fixture = getattr(item, "_capture_fixture", None) + if fixture is not None: + fixture._resume() + + # Helper context managers + + @contextlib.contextmanager + def global_and_fixture_disabled(self): + """Context manager to temporarily disables global and current fixture capturing.""" + # Need to undo local capsys-et-al if exists before disabling global capture + self.suspend_fixture(self._current_item) + self.suspend_global_capture(in_=False) + try: + yield + finally: + self.resume_global_capture() + self.resume_fixture(self._current_item) + + @contextlib.contextmanager + def item_capture(self, when, item): + self.resume_global_capture() + self.activate_fixture(item) + try: + yield + finally: + self.deactivate_fixture(item) + self.suspend_global_capture(in_=False) + + out, err = self.snap_global_capture() + item.add_report_section(when, "stdout", out) + item.add_report_section(when, "stderr", err) + + # Hooks + @pytest.hookimpl(hookwrapper=True) def pytest_make_collect_report(self, collector): if isinstance(collector, pytest.File): self.resume_global_capture() outcome = yield - out, err = self.suspend_global_capture() + self.suspend_global_capture() + out, err = self.snap_global_capture() rep = outcome.get_result() if out: rep.sections.append(("Captured stdout", out)) @@ -163,35 +193,27 @@ def pytest_make_collect_report(self, collector): yield @pytest.hookimpl(hookwrapper=True) - def pytest_runtest_setup(self, item): + def pytest_runtest_logstart(self, item): self._current_item = item - self.resume_global_capture() - # no need to activate a capture fixture because they activate themselves during creation; this - # only makes sense when a fixture uses a capture fixture, otherwise the capture fixture will - # be activated during pytest_runtest_call - yield - self.suspend_capture_item(item, "setup") - self._current_item = None @pytest.hookimpl(hookwrapper=True) - def pytest_runtest_call(self, item): + def pytest_runtest_logfinish(self, item): self._current_item = item - self.resume_global_capture() - # it is important to activate this fixture during the call phase so it overwrites the "global" - # capture - self.activate_fixture(item) - yield - self.suspend_capture_item(item, "call") - self._current_item = None + + @pytest.hookimpl(hookwrapper=True) + def pytest_runtest_setup(self, item): + with self.item_capture("setup", item): + yield + + @pytest.hookimpl(hookwrapper=True) + def pytest_runtest_call(self, item): + with self.item_capture("call", item): + yield @pytest.hookimpl(hookwrapper=True) def pytest_runtest_teardown(self, item): - self._current_item = item - self.resume_global_capture() - self.activate_fixture(item) - yield - self.suspend_capture_item(item, "teardown") - self._current_item = None + with self.item_capture("teardown", item): + yield @pytest.hookimpl(tryfirst=True) def pytest_keyboard_interrupt(self, excinfo): @@ -201,11 +223,6 @@ def pytest_keyboard_interrupt(self, excinfo): def pytest_internalerror(self, excinfo): self.stop_global_capturing() - def suspend_capture_item(self, item, when, in_=False): - out, err = self.suspend_global_capture(item, in_=in_) - item.add_report_section(when, "stdout", out) - item.add_report_section(when, "stderr", err) - capture_fixtures = {"capfd", "capfdbinary", "capsys", "capsysbinary"} @@ -311,10 +328,12 @@ def __init__(self, captureclass, request): self.request = request def _start(self): - self._capture = MultiCapture( - out=True, err=True, in_=False, Capture=self.captureclass - ) - self._capture.start_capturing() + # Start if not started yet + if getattr(self, "_capture", None) is not None: + self._capture = MultiCapture( + out=True, err=True, in_=False, Capture=self.captureclass + ) + self._capture.start_capturing() def close(self): cap = self.__dict__.pop("_capture", None) @@ -332,14 +351,13 @@ def readouterr(self): except AttributeError: return self._outerr - @contextlib.contextmanager def _suspend(self): """Suspends this fixture's own capturing temporarily.""" self._capture.suspend_capturing() - try: - yield - finally: - self._capture.resume_capturing() + + def _resume(self): + """Resumes this fixture's own capturing temporarily.""" + self._capture.resume_capturing() @contextlib.contextmanager def disabled(self): @@ -743,3 +761,4 @@ def _attempt_to_close_capture_file(f): pass else: f.close() + From 2255892d65810cc4db2b82e2621ed313a0c47f7f Mon Sep 17 00:00:00 2001 From: victor Date: Sun, 19 Aug 2018 13:44:12 +0200 Subject: [PATCH 06/62] Improved test to cover more cases. --- testing/test_capture.py | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/testing/test_capture.py b/testing/test_capture.py index 782971af04a..e47689c9c48 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -1387,17 +1387,46 @@ def test_pickling_and_unpickling_encoded_file(): pickle.loads(ef_as_str) -def test_capsys_with_cli_logging(testdir): +def test_capture_with_live_logging(testdir): # Issue 3819 - # capsys should work with real-time cli logging + # capture should work with live cli logging + + # Teardown report seems to have the capture for the whole process (setup, capture, teardown) + testdir.makeconftest(""" + def pytest_runtest_logreport(report): + if "test_global" in report.nodeid: + if report.when == "teardown": + assert "fix setup" in report.caplog + assert "something in test" in report.caplog + assert "fix teardown" in report.caplog + + assert "fix setup" in report.capstdout + assert "begin test" in report.capstdout + assert "end test" in report.capstdout + assert "fix teardown" in report.capstdout + """) + testdir.makepyfile( """ import logging import sys logger = logging.getLogger(__name__) - - def test_myoutput(capsys): # or use "capfd" for fd-level + + @pytest.fixture + def fix1(): + print("fix setup") + logging("fix setup") + yield + logging("fix teardown") + print("fix teardown") + + def test_global(): + print("begin test") + logging.info("something in test") + print("end test") + + def test_capsys(capsys): # or use "capfd" for fd-level print("hello") sys.stderr.write("world\\n") captured = capsys.readouterr() From 9e382e8d29f0563925eedb3dc5139aaee72a1e1c Mon Sep 17 00:00:00 2001 From: victor Date: Sun, 19 Aug 2018 14:29:57 +0200 Subject: [PATCH 07/62] Fixed test. --- testing/test_capture.py | 38 ++++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/testing/test_capture.py b/testing/test_capture.py index e47689c9c48..b2185822afe 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -1396,32 +1396,29 @@ def test_capture_with_live_logging(testdir): def pytest_runtest_logreport(report): if "test_global" in report.nodeid: if report.when == "teardown": - assert "fix setup" in report.caplog - assert "something in test" in report.caplog - assert "fix teardown" in report.caplog - - assert "fix setup" in report.capstdout - assert "begin test" in report.capstdout - assert "end test" in report.capstdout - assert "fix teardown" in report.capstdout + with open("caplog", "w") as f: + f.write(report.caplog) + with open("capstdout", "w") as f: + f.write(report.capstdout) """) testdir.makepyfile( """ import logging import sys + import pytest logger = logging.getLogger(__name__) @pytest.fixture def fix1(): print("fix setup") - logging("fix setup") + logging.info("fix setup") yield - logging("fix teardown") + logging.info("fix teardown") print("fix teardown") - - def test_global(): + + def test_global(fix1): print("begin test") logging.info("something in test") print("end test") @@ -1434,9 +1431,7 @@ def test_capsys(capsys): # or use "capfd" for fd-level assert captured.err == "world\\n" logging.info("something") - print("next") - logging.info("something") captured = capsys.readouterr() @@ -1445,3 +1440,18 @@ def test_capsys(capsys): # or use "capfd" for fd-level ) result = testdir.runpytest_subprocess("--log-cli-level=INFO") assert result.ret == 0 + + with open("caplog", "r") as f: + caplog = f.read() + + assert "fix setup" in caplog + assert "something in test" in caplog + assert "fix teardown" in caplog + + with open("capstdout", "r") as f: + capstdout = f.read() + + assert "fix setup" in capstdout + assert "begin test" in capstdout + assert "end test" in capstdout + assert "fix teardown" in capstdout From 8b2c91836b507727141b12b1f47c8d29ef744d1e Mon Sep 17 00:00:00 2001 From: victor Date: Sun, 19 Aug 2018 14:30:50 +0200 Subject: [PATCH 08/62] Fixed activation and used just runtest_protocol hook --- src/_pytest/capture.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/_pytest/capture.py b/src/_pytest/capture.py index f10a13a1fe4..658632e007d 100644 --- a/src/_pytest/capture.py +++ b/src/_pytest/capture.py @@ -193,12 +193,10 @@ def pytest_make_collect_report(self, collector): yield @pytest.hookimpl(hookwrapper=True) - def pytest_runtest_logstart(self, item): - self._current_item = item - - @pytest.hookimpl(hookwrapper=True) - def pytest_runtest_logfinish(self, item): + def pytest_runtest_protocol(self, item): self._current_item = item + yield + self._current_item = None @pytest.hookimpl(hookwrapper=True) def pytest_runtest_setup(self, item): @@ -329,7 +327,7 @@ def __init__(self, captureclass, request): def _start(self): # Start if not started yet - if getattr(self, "_capture", None) is not None: + if getattr(self, "_capture", None) is None: self._capture = MultiCapture( out=True, err=True, in_=False, Capture=self.captureclass ) From 0564b52c0e1ecdce87b98f902091f3e06a01cc13 Mon Sep 17 00:00:00 2001 From: victor Date: Sun, 19 Aug 2018 15:26:57 +0200 Subject: [PATCH 09/62] Fixed integration with other modules/tests --- src/_pytest/debugging.py | 3 ++- src/_pytest/setuponly.py | 3 ++- testing/logging/test_reporting.py | 4 ---- testing/test_capture.py | 12 ++++++++---- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/_pytest/debugging.py b/src/_pytest/debugging.py index 9991307d006..a0594e3e811 100644 --- a/src/_pytest/debugging.py +++ b/src/_pytest/debugging.py @@ -102,7 +102,8 @@ class PdbInvoke(object): def pytest_exception_interact(self, node, call, report): capman = node.config.pluginmanager.getplugin("capturemanager") if capman: - out, err = capman.suspend_global_capture(in_=True) + capman.suspend_global_capture(in_=True) + out, err = capman.snap_global_capture() sys.stdout.write(out) sys.stdout.write(err) _enter_pdb(node, call.excinfo, report) diff --git a/src/_pytest/setuponly.py b/src/_pytest/setuponly.py index 81240d9d055..721b0a9421c 100644 --- a/src/_pytest/setuponly.py +++ b/src/_pytest/setuponly.py @@ -51,7 +51,8 @@ def _show_fixture_action(fixturedef, msg): config = fixturedef._fixturemanager.config capman = config.pluginmanager.getplugin("capturemanager") if capman: - out, err = capman.suspend_global_capture() + capman.suspend_global_capture() + out, err = capman.snap_global_capture() tw = config.get_terminal_writer() tw.line() diff --git a/testing/logging/test_reporting.py b/testing/logging/test_reporting.py index 820295886f4..b8fc371d4b6 100644 --- a/testing/logging/test_reporting.py +++ b/testing/logging/test_reporting.py @@ -890,10 +890,6 @@ def global_and_fixture_disabled(self): yield self.calls.append("exit disabled") - # sanity check - assert CaptureManager.suspend_capture_item - assert CaptureManager.resume_global_capture - class DummyTerminal(six.StringIO): def section(self, *args, **kwargs): pass diff --git a/testing/test_capture.py b/testing/test_capture.py index b2185822afe..626c4414ac1 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -70,19 +70,23 @@ def test_capturing_basic_api(self, method): try: capman = CaptureManager(method) capman.start_global_capturing() - outerr = capman.suspend_global_capture() + capman.suspend_global_capture() + outerr = capman.snap_global_capture() assert outerr == ("", "") - outerr = capman.suspend_global_capture() + capman.suspend_global_capture() + outerr = capman.snap_global_capture() assert outerr == ("", "") print("hello") - out, err = capman.suspend_global_capture() + capman.suspend_global_capture() + out, err = capman.snap_global_capture() if method == "no": assert old == (sys.stdout, sys.stderr, sys.stdin) else: assert not out capman.resume_global_capture() print("hello") - out, err = capman.suspend_global_capture() + capman.suspend_global_capture() + out, err = capman.snap_global_capture() if method != "no": assert out == "hello\n" capman.stop_global_capturing() From 7ea4992f169edf84eec10735be688523759d4543 Mon Sep 17 00:00:00 2001 From: victor Date: Sun, 19 Aug 2018 15:46:02 +0200 Subject: [PATCH 10/62] Fixed linting. --- src/_pytest/capture.py | 3 +-- testing/logging/test_reporting.py | 1 - testing/test_capture.py | 8 +++++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/_pytest/capture.py b/src/_pytest/capture.py index 658632e007d..3147d9728fc 100644 --- a/src/_pytest/capture.py +++ b/src/_pytest/capture.py @@ -14,7 +14,7 @@ import six import pytest -from _pytest.compat import CaptureIO, dummy_context_manager +from _pytest.compat import CaptureIO patchsysdict = {0: "stdin", 1: "stdout", 2: "stderr"} @@ -759,4 +759,3 @@ def _attempt_to_close_capture_file(f): pass else: f.close() - diff --git a/testing/logging/test_reporting.py b/testing/logging/test_reporting.py index b8fc371d4b6..363982cf915 100644 --- a/testing/logging/test_reporting.py +++ b/testing/logging/test_reporting.py @@ -878,7 +878,6 @@ def test_live_logging_suspends_capture(has_capture_manager, request): import logging import contextlib from functools import partial - from _pytest.capture import CaptureManager from _pytest.logging import _LiveLoggingStreamHandler class MockCaptureManager: diff --git a/testing/test_capture.py b/testing/test_capture.py index 626c4414ac1..ec8c682e294 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -1396,7 +1396,8 @@ def test_capture_with_live_logging(testdir): # capture should work with live cli logging # Teardown report seems to have the capture for the whole process (setup, capture, teardown) - testdir.makeconftest(""" + testdir.makeconftest( + """ def pytest_runtest_logreport(report): if "test_global" in report.nodeid: if report.when == "teardown": @@ -1404,7 +1405,8 @@ def pytest_runtest_logreport(report): f.write(report.caplog) with open("capstdout", "w") as f: f.write(report.capstdout) - """) + """ + ) testdir.makepyfile( """ @@ -1413,7 +1415,7 @@ def pytest_runtest_logreport(report): import pytest logger = logging.getLogger(__name__) - + @pytest.fixture def fix1(): print("fix setup") From e620798d33346187005b44ab0ff96decd5b5e95a Mon Sep 17 00:00:00 2001 From: wim glenn Date: Sun, 19 Aug 2018 23:11:31 -0500 Subject: [PATCH 11/62] more autodocs for pytester --- changelog/3833.doc.rst | 1 + doc/en/reference.rst | 2 +- src/_pytest/pytester.py | 20 ++++++++++++-------- 3 files changed, 14 insertions(+), 9 deletions(-) create mode 100644 changelog/3833.doc.rst diff --git a/changelog/3833.doc.rst b/changelog/3833.doc.rst new file mode 100644 index 00000000000..d74ee10b2a2 --- /dev/null +++ b/changelog/3833.doc.rst @@ -0,0 +1 @@ +Added missing docs for ``pytester.Testdir`` \ No newline at end of file diff --git a/doc/en/reference.rst b/doc/en/reference.rst index 86d92cf07bf..484c755da01 100644 --- a/doc/en/reference.rst +++ b/doc/en/reference.rst @@ -460,7 +460,7 @@ To use it, include in your top-most ``conftest.py`` file:: .. autoclass:: Testdir() - :members: runpytest,runpytest_subprocess,runpytest_inprocess,makeconftest,makepyfile + :members: .. autoclass:: RunResult() :members: diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index 5b42b81eedf..b40a9e267b3 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -550,18 +550,22 @@ def to_text(s): return ret def makefile(self, ext, *args, **kwargs): - """Create a new file in the testdir. + r"""Create new file(s) in the testdir. - ext: The extension the file should use, including the dot, e.g. `.py`. - - args: All args will be treated as strings and joined using newlines. + :param str ext: The extension the file(s) should use, including the dot, e.g. `.py`. + :param list[str] args: All args will be treated as strings and joined using newlines. The result will be written as contents to the file. The name of the file will be based on the test function requesting this fixture. - E.g. "testdir.makefile('.txt', 'line1', 'line2')" - - kwargs: Each keyword is the name of a file, while the value of it will + :param kwargs: Each keyword is the name of a file, while the value of it will be written as contents of the file. - E.g. "testdir.makefile('.ini', pytest='[pytest]\naddopts=-rs\n')" + + Examples: + + .. code-block:: python + + testdir.makefile(".txt", "line1", "line2") + + testdir.makefile(".ini", pytest="[pytest]\naddopts=-rs\n") """ return self._makefile(ext, args, kwargs) From e4bea9068bf800f1610b228457232cbdfd949343 Mon Sep 17 00:00:00 2001 From: wim glenn Date: Sun, 19 Aug 2018 23:39:10 -0500 Subject: [PATCH 12/62] end of line for this file, perhaps? --- changelog/3833.doc.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/3833.doc.rst b/changelog/3833.doc.rst index d74ee10b2a2..254e2e4b6ef 100644 --- a/changelog/3833.doc.rst +++ b/changelog/3833.doc.rst @@ -1 +1 @@ -Added missing docs for ``pytester.Testdir`` \ No newline at end of file +Added missing docs for ``pytester.Testdir`` From d611b035891f481570721d105d392db642412368 Mon Sep 17 00:00:00 2001 From: Victor Date: Mon, 20 Aug 2018 12:23:59 +0200 Subject: [PATCH 13/62] Parametrized tests for capfd as well. Separated global capture test. --- testing/test_capture.py | 52 ++++++++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/testing/test_capture.py b/testing/test_capture.py index ec8c682e294..c029a21f967 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -1391,7 +1391,7 @@ def test_pickling_and_unpickling_encoded_file(): pickle.loads(ef_as_str) -def test_capture_with_live_logging(testdir): +def test_global_capture_with_live_logging(testdir): # Issue 3819 # capture should work with live cli logging @@ -1405,7 +1405,7 @@ def pytest_runtest_logreport(report): f.write(report.caplog) with open("capstdout", "w") as f: f.write(report.capstdout) - """ + """ ) testdir.makepyfile( @@ -1428,20 +1428,6 @@ def test_global(fix1): print("begin test") logging.info("something in test") print("end test") - - def test_capsys(capsys): # or use "capfd" for fd-level - print("hello") - sys.stderr.write("world\\n") - captured = capsys.readouterr() - assert captured.out == "hello\\n" - assert captured.err == "world\\n" - - logging.info("something") - print("next") - logging.info("something") - - captured = capsys.readouterr() - assert captured.out == "next\\n" """ ) result = testdir.runpytest_subprocess("--log-cli-level=INFO") @@ -1461,3 +1447,37 @@ def test_capsys(capsys): # or use "capfd" for fd-level assert "begin test" in capstdout assert "end test" in capstdout assert "fix teardown" in capstdout + + +@pytest.mark.parametrize("capture_fixture", ["capsys", "capfd"]) +def test_capture_with_live_logging(testdir, capture_fixture): + # Issue 3819 + # capture should work with live cli logging + + testdir.makepyfile( + """ + import logging + import sys + + logger = logging.getLogger(__name__) + + def test_capture({0}): + print("hello") + sys.stderr.write("world\\n") + captured = {0}.readouterr() + assert captured.out == "hello\\n" + assert captured.err == "world\\n" + + logging.info("something") + print("next") + logging.info("something") + + captured = {0}.readouterr() + assert captured.out == "next\\n" + """.format( + capture_fixture + ) + ) + + result = testdir.runpytest_subprocess("--log-cli-level=INFO") + assert result.ret == 0 From 4de247cfa0e5067d7270ae2de6fe1f2dac9bd592 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 20 Aug 2018 06:27:35 -0700 Subject: [PATCH 14/62] Use more flexible `language_version: python3` --- .pre-commit-config.yaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bac8bb6e209..faae8237297 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,13 +5,13 @@ repos: hooks: - id: black args: [--safe, --quiet] - language_version: python3.6 + language_version: python3 - repo: https://github.com/asottile/blacken-docs rev: v0.2.0 hooks: - id: blacken-docs additional_dependencies: [black==18.6b4] - language_version: python3.6 + language_version: python3 - repo: https://github.com/pre-commit/pre-commit-hooks rev: v1.3.0 hooks: @@ -37,7 +37,6 @@ repos: files: ^(CHANGELOG.rst|HOWTORELEASE.rst|README.rst|changelog/.*)$ language: python additional_dependencies: [pygments, restructuredtext_lint] - python_version: python3.6 - id: changelogs-rst name: changelog files must end in .rst entry: ./scripts/fail From 70ebab3537f52163989df6ca6832d6dbb77f41bf Mon Sep 17 00:00:00 2001 From: Victor Date: Mon, 20 Aug 2018 17:48:14 +0200 Subject: [PATCH 15/62] Renamed snap_global_capture to read_global_capture. --- src/_pytest/capture.py | 8 ++++---- src/_pytest/debugging.py | 2 +- src/_pytest/setuponly.py | 2 +- testing/test_capture.py | 8 ++++---- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/_pytest/capture.py b/src/_pytest/capture.py index 3147d9728fc..8e9715cd801 100644 --- a/src/_pytest/capture.py +++ b/src/_pytest/capture.py @@ -64,7 +64,7 @@ def silence_logging_at_shutdown(): outcome = yield capman.suspend_global_capture() if outcome.excinfo is not None: - out, err = capman.snap_global_capture() + out, err = capman.read_global_capture() sys.stdout.write(out) sys.stderr.write(err) @@ -118,7 +118,7 @@ def suspend_global_capture(self, in_=False): if cap is not None: cap.suspend_capturing(in_=in_) - def snap_global_capture(self): + def read_global_capture(self): return self._global_capturing.readouterr() # Fixture Control (its just forwarding, think about removing this later) @@ -171,7 +171,7 @@ def item_capture(self, when, item): self.deactivate_fixture(item) self.suspend_global_capture(in_=False) - out, err = self.snap_global_capture() + out, err = self.read_global_capture() item.add_report_section(when, "stdout", out) item.add_report_section(when, "stderr", err) @@ -183,7 +183,7 @@ def pytest_make_collect_report(self, collector): self.resume_global_capture() outcome = yield self.suspend_global_capture() - out, err = self.snap_global_capture() + out, err = self.read_global_capture() rep = outcome.get_result() if out: rep.sections.append(("Captured stdout", out)) diff --git a/src/_pytest/debugging.py b/src/_pytest/debugging.py index a0594e3e811..f51dff373c9 100644 --- a/src/_pytest/debugging.py +++ b/src/_pytest/debugging.py @@ -103,7 +103,7 @@ def pytest_exception_interact(self, node, call, report): capman = node.config.pluginmanager.getplugin("capturemanager") if capman: capman.suspend_global_capture(in_=True) - out, err = capman.snap_global_capture() + out, err = capman.read_global_capture() sys.stdout.write(out) sys.stdout.write(err) _enter_pdb(node, call.excinfo, report) diff --git a/src/_pytest/setuponly.py b/src/_pytest/setuponly.py index 721b0a9421c..c3edc5f8169 100644 --- a/src/_pytest/setuponly.py +++ b/src/_pytest/setuponly.py @@ -52,7 +52,7 @@ def _show_fixture_action(fixturedef, msg): capman = config.pluginmanager.getplugin("capturemanager") if capman: capman.suspend_global_capture() - out, err = capman.snap_global_capture() + out, err = capman.read_global_capture() tw = config.get_terminal_writer() tw.line() diff --git a/testing/test_capture.py b/testing/test_capture.py index c029a21f967..fb0e14b97e8 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -71,14 +71,14 @@ def test_capturing_basic_api(self, method): capman = CaptureManager(method) capman.start_global_capturing() capman.suspend_global_capture() - outerr = capman.snap_global_capture() + outerr = capman.read_global_capture() assert outerr == ("", "") capman.suspend_global_capture() - outerr = capman.snap_global_capture() + outerr = capman.read_global_capture() assert outerr == ("", "") print("hello") capman.suspend_global_capture() - out, err = capman.snap_global_capture() + out, err = capman.read_global_capture() if method == "no": assert old == (sys.stdout, sys.stderr, sys.stdin) else: @@ -86,7 +86,7 @@ def test_capturing_basic_api(self, method): capman.resume_global_capture() print("hello") capman.suspend_global_capture() - out, err = capman.snap_global_capture() + out, err = capman.read_global_capture() if method != "no": assert out == "hello\n" capman.stop_global_capturing() From 223eef6261ef31393452694fcec257f8a389fd94 Mon Sep 17 00:00:00 2001 From: Sankt Petersbug Date: Mon, 20 Aug 2018 14:25:01 -0500 Subject: [PATCH 16/62] Fix '--show-capture=no' capture teardown logs Add a check before printing teardown logs. 'print_teardown_sections' method does not check '--show-capture' option value, and teardown logs are always printed. Resolves: #3816 --- changelog/3816.bugfix.rst | 1 + src/_pytest/terminal.py | 5 +++++ testing/test_terminal.py | 40 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+) create mode 100644 changelog/3816.bugfix.rst diff --git a/changelog/3816.bugfix.rst b/changelog/3816.bugfix.rst new file mode 100644 index 00000000000..6a399d5986f --- /dev/null +++ b/changelog/3816.bugfix.rst @@ -0,0 +1 @@ +Fix ``--show-capture=no`` option still capture teardown logs. diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index 7dd2edd6f9d..f7962498912 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -706,7 +706,12 @@ def summary_passes(self): self._outrep_summary(rep) def print_teardown_sections(self, rep): + showcapture = self.config.option.showcapture + if showcapture == "no": + return for secname, content in rep.sections: + if showcapture != "all" and showcapture not in secname: + continue if "teardown" in secname: self._tw.sep("-", secname) if content[-1:] == "\n": diff --git a/testing/test_terminal.py b/testing/test_terminal.py index a9da27980c0..88e5287e81f 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -948,6 +948,46 @@ def test_one(): assert "!This is stderr!" not in stdout assert "!This is a warning log msg!" not in stdout + def test_show_capture_with_teardown_logs(self, testdir): + """Ensure that the capturing of teardown logs honor --show-capture setting""" + testdir.makepyfile( + """ + import logging + import sys + import pytest + + @pytest.fixture(scope="function", autouse="True") + def hook_each_test(request): + yield + sys.stdout.write("!stdout!") + sys.stderr.write("!stderr!") + logging.warning("!log!") + + def test_func(): + assert False + """ + ) + + result = testdir.runpytest("--show-capture=stdout", "--tb=short").stdout.str() + assert "!stdout!" in result + assert "!stderr!" not in result + assert "!log!" not in result + + result = testdir.runpytest("--show-capture=stderr", "--tb=short").stdout.str() + assert "!stdout!" not in result + assert "!stderr!" in result + assert "!log!" not in result + + result = testdir.runpytest("--show-capture=log", "--tb=short").stdout.str() + assert "!stdout!" not in result + assert "!stderr!" not in result + assert "!log!" in result + + result = testdir.runpytest("--show-capture=no", "--tb=short").stdout.str() + assert "!stdout!" not in result + assert "!stderr!" not in result + assert "!log!" not in result + @pytest.mark.xfail("not hasattr(os, 'dup')") def test_fdopen_kept_alive_issue124(testdir): From 672f4bb5aa9fdb7303a9144507b2aea19c99b534 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 21 Aug 2018 20:19:48 -0300 Subject: [PATCH 17/62] Improve CHANGELOG --- changelog/3816.bugfix.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/3816.bugfix.rst b/changelog/3816.bugfix.rst index 6a399d5986f..a50c8f7291d 100644 --- a/changelog/3816.bugfix.rst +++ b/changelog/3816.bugfix.rst @@ -1 +1 @@ -Fix ``--show-capture=no`` option still capture teardown logs. +Fix bug where ``--show-capture=no`` option would still show logs printed during fixture teardown. From 717775a1c69e21fff65c73d2a33e4bdc6a15c50e Mon Sep 17 00:00:00 2001 From: Natan Lao Date: Tue, 21 Aug 2018 16:57:33 -0700 Subject: [PATCH 18/62] Remove warning about #568 from documentation The documentation (https://docs.pytest.org/en/latest/skipping.html) references issue #568, which has since been fixed. --- doc/en/skipping.rst | 6 ------ 1 file changed, 6 deletions(-) diff --git a/doc/en/skipping.rst b/doc/en/skipping.rst index cda67554d58..efdf008fb69 100644 --- a/doc/en/skipping.rst +++ b/doc/en/skipping.rst @@ -136,12 +136,6 @@ You can use the ``skipif`` marker (as any other marker) on classes:: If the condition is ``True``, this marker will produce a skip result for each of the test methods of that class. -.. warning:: - - The use of ``skipif`` on classes that use inheritance is strongly - discouraged. `A Known bug `_ - in pytest's markers may cause unexpected behavior in super classes. - If you want to skip all test functions of a module, you may use the ``pytestmark`` name on the global level: From 07a560ff246a28e4ab8567404d7f55afb40a3c36 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 21 Aug 2018 20:55:45 -0300 Subject: [PATCH 19/62] Fix collection error when tests is specified with --doctest-modules The problem was that _matchnodes would receive two items: [DoctestModule, Module]. It would then collect the first one, *cache it*, and fail to match against the name in the command line. Next, it would reuse the cached item (DoctestModule) instead of collecting the Module which would eventually find the "test" name on it. Added the type of the node to the cache key to avoid this problem, although I'm not a big fan of caches that have different key types. Fix #3843 --- changelog/3843.bugfix.rst | 1 + src/_pytest/main.py | 7 ++++--- testing/acceptance_test.py | 10 ++++++++++ 3 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 changelog/3843.bugfix.rst diff --git a/changelog/3843.bugfix.rst b/changelog/3843.bugfix.rst new file mode 100644 index 00000000000..3186c3fc5d6 --- /dev/null +++ b/changelog/3843.bugfix.rst @@ -0,0 +1 @@ +Fix collection error when specifying test functions directly in the command line using ``test.py::test`` syntax together with ``--doctest-module``. diff --git a/src/_pytest/main.py b/src/_pytest/main.py index eae0bb25548..947c6aa4b7b 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -625,11 +625,12 @@ def _matchnodes(self, matching, names): resultnodes.append(node) continue assert isinstance(node, nodes.Collector) - if node.nodeid in self._node_cache: - rep = self._node_cache[node.nodeid] + key = (type(node), node.nodeid) + if key in self._node_cache: + rep = self._node_cache[key] else: rep = collect_one_node(node) - self._node_cache[node.nodeid] = rep + self._node_cache[key] = rep if rep.passed: has_matched = False for x in rep.result: diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index bc4e3bed85f..5d6baf12158 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -660,6 +660,16 @@ def join_pythonpath(*dirs): ["*test_world.py::test_other*PASSED*", "*1 passed*"] ) + def test_invoke_test_and_doctestmodules(self, testdir): + p = testdir.makepyfile( + """ + def test(): + pass + """ + ) + result = testdir.runpytest(str(p) + "::test", "--doctest-modules") + result.stdout.fnmatch_lines(["*1 passed*"]) + @pytest.mark.skipif(not hasattr(os, "symlink"), reason="requires symlinks") def test_cmdline_python_package_symlink(self, testdir, monkeypatch): """ From 80bea79512b28d266dc8b9b9be919426990fc7a3 Mon Sep 17 00:00:00 2001 From: Natan Lao Date: Tue, 21 Aug 2018 17:03:59 -0700 Subject: [PATCH 20/62] Add changelog entry --- changelog/3845.trivial.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 changelog/3845.trivial.rst diff --git a/changelog/3845.trivial.rst b/changelog/3845.trivial.rst new file mode 100644 index 00000000000..8b32abaca53 --- /dev/null +++ b/changelog/3845.trivial.rst @@ -0,0 +1,2 @@ +Remove a reference to issue #568 from the documentation, which has since been +fixed. \ No newline at end of file From eb8d14519527dc3469c1663ecc1c5bbd9d73d454 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 21 Aug 2018 21:08:21 -0300 Subject: [PATCH 21/62] Add link to issue in the CHANGELOG entry --- changelog/3845.trivial.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/changelog/3845.trivial.rst b/changelog/3845.trivial.rst index 8b32abaca53..29c45ab56f2 100644 --- a/changelog/3845.trivial.rst +++ b/changelog/3845.trivial.rst @@ -1,2 +1,2 @@ -Remove a reference to issue #568 from the documentation, which has since been -fixed. \ No newline at end of file +Remove a reference to issue `#568 `_ from the documentation, which has since been +fixed. From 89446af51ec723cb628f6d2cf16f10ae2628e25d Mon Sep 17 00:00:00 2001 From: wim glenn Date: Wed, 22 Aug 2018 00:42:10 -0500 Subject: [PATCH 22/62] fixed a bunch of unicode bugs in pytester.py --- changelog/3848.bugfix.rst | 1 + src/_pytest/pytester.py | 26 ++++++++++++-------------- 2 files changed, 13 insertions(+), 14 deletions(-) create mode 100644 changelog/3848.bugfix.rst diff --git a/changelog/3848.bugfix.rst b/changelog/3848.bugfix.rst new file mode 100644 index 00000000000..a2456477a44 --- /dev/null +++ b/changelog/3848.bugfix.rst @@ -0,0 +1 @@ +Fix bugs where unicode arguments could not be passed to testdir.runpytest on Python 2.x diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index b40a9e267b3..2372ea66384 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -22,6 +22,7 @@ from _pytest.main import Session, EXIT_OK from _pytest.assertion.rewrite import AssertionRewritingHook from _pytest.compat import Path +from _pytest.compat import safe_str IGNORE_PAM = [ # filenames added when obtaining details about the current user u"/var/lib/sss/mc/passwd" @@ -34,7 +35,7 @@ def pytest_addoption(parser): action="store_true", dest="lsof", default=False, - help=("run FD checks if lsof is available"), + help="run FD checks if lsof is available", ) parser.addoption( @@ -273,7 +274,7 @@ def popcall(self, name): del self.calls[i] return call lines = ["could not find call %r, in:" % (name,)] - lines.extend([" %s" % str(x) for x in self.calls]) + lines.extend([" %s" % x for x in self.calls]) pytest.fail("\n".join(lines)) def getcall(self, name): @@ -885,14 +886,12 @@ def runpytest(self, *args, **kwargs): return self._runpytest_method(*args, **kwargs) def _ensure_basetemp(self, args): - args = [str(x) for x in args] + args = list(args) for x in args: - if str(x).startswith("--basetemp"): - # print("basedtemp exists: %s" %(args,)) + if safe_str(x).startswith("--basetemp"): break else: args.append("--basetemp=%s" % self.tmpdir.dirpath("basetemp")) - # print("added basetemp: %s" %(args,)) return args def parseconfig(self, *args): @@ -1018,7 +1017,7 @@ def popen(self, cmdargs, stdout, stderr, **kw): """ env = os.environ.copy() env["PYTHONPATH"] = os.pathsep.join( - filter(None, [str(os.getcwd()), env.get("PYTHONPATH", "")]) + filter(None, [os.getcwd(), env.get("PYTHONPATH", "")]) ) kw["env"] = env @@ -1037,14 +1036,13 @@ def run(self, *cmdargs): Returns a :py:class:`RunResult`. """ - return self._run(*cmdargs) - - def _run(self, *cmdargs): - cmdargs = [str(x) for x in cmdargs] + cmdargs = [ + str(arg) if isinstance(arg, py.path.local) else arg for arg in cmdargs + ] p1 = self.tmpdir.join("stdout") p2 = self.tmpdir.join("stderr") - print("running:", " ".join(cmdargs)) - print(" in:", str(py.path.local())) + print("running:", *cmdargs) + print(" in:", py.path.local()) f1 = codecs.open(str(p1), "w", encoding="utf8") f2 = codecs.open(str(p2), "w", encoding="utf8") try: @@ -1076,7 +1074,7 @@ def _dump_lines(self, lines, fp): print("couldn't print to %s because of encoding" % (fp,)) def _getpytestargs(self): - return (sys.executable, "-mpytest") + return sys.executable, "-mpytest" def runpython(self, script): """Run a python script using sys.executable as interpreter. From a12eadd9ef32a8312d49669d98aa533247db2fec Mon Sep 17 00:00:00 2001 From: Jennifer Rinker Date: Wed, 22 Aug 2018 15:37:35 +0200 Subject: [PATCH 23/62] resolving Issue #3824 - expanding docs --- AUTHORS | 1 + changelog/3824.doc.rst | 1 + doc/en/example/pythoncollection.rst | 16 +++++++++++++--- doc/en/reference.rst | 19 ++++++++++++++----- 4 files changed, 29 insertions(+), 8 deletions(-) create mode 100644 changelog/3824.doc.rst diff --git a/AUTHORS b/AUTHORS index 9c3cb6a1266..1641ea15e6f 100644 --- a/AUTHORS +++ b/AUTHORS @@ -98,6 +98,7 @@ Javier Domingo Cansino Javier Romero Jeff Rackauckas Jeff Widman +Jenni Rinker John Eddie Ayson John Towler Jon Sonesen diff --git a/changelog/3824.doc.rst b/changelog/3824.doc.rst new file mode 100644 index 00000000000..01606512065 --- /dev/null +++ b/changelog/3824.doc.rst @@ -0,0 +1 @@ +Added example for multiple glob pattern matches in ``python_files``. diff --git a/doc/en/example/pythoncollection.rst b/doc/en/example/pythoncollection.rst index 8e9d3ae62a7..6c86b8a638f 100644 --- a/doc/en/example/pythoncollection.rst +++ b/doc/en/example/pythoncollection.rst @@ -100,8 +100,10 @@ Changing naming conventions You can configure different naming conventions by setting the :confval:`python_files`, :confval:`python_classes` and -:confval:`python_functions` configuration options. Example:: +:confval:`python_functions` configuration options. +Here is an example:: + # Example 1: have pytest look for "check" instead of "test" # content of pytest.ini # can also be defined in tox.ini or setup.cfg file, although the section # name in setup.cfg files should be "tool:pytest" @@ -112,7 +114,7 @@ the :confval:`python_files`, :confval:`python_classes` and This would make ``pytest`` look for tests in files that match the ``check_* .py`` glob-pattern, ``Check`` prefixes in classes, and functions and methods -that match ``*_check``. For example, if we have:: +that match ``*_check``. For example, if we have:: # content of check_myapp.py class CheckMyApp(object): @@ -121,7 +123,7 @@ that match ``*_check``. For example, if we have:: def complex_check(self): pass -then the test collection looks like this:: +The test collection would look like this:: $ pytest --collect-only =========================== test session starts ============================ @@ -136,6 +138,14 @@ then the test collection looks like this:: ======================= no tests ran in 0.12 seconds ======================= +You can check for multiple glob patterns by adding a space between the patterns:: + + # Example 2: have pytest look for files with "test" and "example" + # content of pytest.ini, tox.ini, or setup.cfg file (replace "pytest" + # with "tool:pytest" for setup.cfg) + [pytest] + python_files=test_*.py example_*.py + .. note:: the ``python_functions`` and ``python_classes`` options has no effect diff --git a/doc/en/reference.rst b/doc/en/reference.rst index 484c755da01..28fc6805ab3 100644 --- a/doc/en/reference.rst +++ b/doc/en/reference.rst @@ -1229,7 +1229,8 @@ passed multiple times. The expected format is ``name=value``. For example:: .. confval:: python_classes One or more name prefixes or glob-style patterns determining which classes - are considered for test collection. By default, pytest will consider any + are considered for test collection. Search for multiple glob patterns by + adding a space between patterns. By default, pytest will consider any class prefixed with ``Test`` as a test collection. Here is an example of how to collect tests from classes that end in ``Suite``: @@ -1246,15 +1247,23 @@ passed multiple times. The expected format is ``name=value``. For example:: .. confval:: python_files One or more Glob-style file patterns determining which python files - are considered as test modules. By default, pytest will consider - any file matching with ``test_*.py`` and ``*_test.py`` globs as a test - module. + are considered as test modules. Search for multiple glob patterns by + adding a space between patterns:: + + .. code-block:: ini + + [pytest] + python_files = test_*.py check_*.py example_*.py + + By default, pytest will consider any file matching with ``test_*.py`` + and ``*_test.py`` globs as a test module. .. confval:: python_functions One or more name prefixes or glob-patterns determining which test functions - and methods are considered tests. By default, pytest will consider any + and methods are considered tests. Search for multiple glob patterns by + adding a space between patterns. By default, pytest will consider any function prefixed with ``test`` as a test. Here is an example of how to collect test functions and methods that end in ``_test``: From 5a7aa123ea29987287a4e01b8e61aacce282cd40 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 22 Aug 2018 11:22:30 -0300 Subject: [PATCH 24/62] Improve docs formatting --- doc/en/example/pythoncollection.rst | 10 +++++----- doc/en/reference.rst | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/en/example/pythoncollection.rst b/doc/en/example/pythoncollection.rst index 6c86b8a638f..b4950a75c18 100644 --- a/doc/en/example/pythoncollection.rst +++ b/doc/en/example/pythoncollection.rst @@ -108,9 +108,9 @@ Here is an example:: # can also be defined in tox.ini or setup.cfg file, although the section # name in setup.cfg files should be "tool:pytest" [pytest] - python_files=check_*.py - python_classes=Check - python_functions=*_check + python_files = check_*.py + python_classes = Check + python_functions = *_check This would make ``pytest`` look for tests in files that match the ``check_* .py`` glob-pattern, ``Check`` prefixes in classes, and functions and methods @@ -144,13 +144,13 @@ You can check for multiple glob patterns by adding a space between the patterns: # content of pytest.ini, tox.ini, or setup.cfg file (replace "pytest" # with "tool:pytest" for setup.cfg) [pytest] - python_files=test_*.py example_*.py + python_files = test_*.py example_*.py .. note:: the ``python_functions`` and ``python_classes`` options has no effect for ``unittest.TestCase`` test discovery because pytest delegates - detection of test case methods to unittest code. + discovery of test case methods to unittest code. Interpreting cmdline arguments as Python packages ----------------------------------------------------- diff --git a/doc/en/reference.rst b/doc/en/reference.rst index 28fc6805ab3..042df9687d0 100644 --- a/doc/en/reference.rst +++ b/doc/en/reference.rst @@ -1255,7 +1255,7 @@ passed multiple times. The expected format is ``name=value``. For example:: [pytest] python_files = test_*.py check_*.py example_*.py - By default, pytest will consider any file matching with ``test_*.py`` + By default, pytest will consider any file matching with ``test_*.py`` and ``*_test.py`` globs as a test module. From 8e2c7b4979e12429641e91acd46b3357ceecddf2 Mon Sep 17 00:00:00 2001 From: wim glenn Date: Wed, 22 Aug 2018 11:00:51 -0500 Subject: [PATCH 25/62] Add a failing testcase for PR #3848 --- testing/test_pytester.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/testing/test_pytester.py b/testing/test_pytester.py index 86dc35796b3..cab9d8e9760 100644 --- a/testing/test_pytester.py +++ b/testing/test_pytester.py @@ -8,7 +8,7 @@ from _pytest.pytester import HookRecorder from _pytest.pytester import CwdSnapshot, SysModulesSnapshot, SysPathsSnapshot from _pytest.config import PytestPluginManager -from _pytest.main import EXIT_OK, EXIT_TESTSFAILED +from _pytest.main import EXIT_OK, EXIT_TESTSFAILED, EXIT_NOTESTSCOLLECTED def test_make_hook_recorder(testdir): @@ -396,3 +396,8 @@ def test_preserve_container(self, monkeypatch, path_type): def test_testdir_subprocess(testdir): testfile = testdir.makepyfile("def test_one(): pass") assert testdir.runpytest_subprocess(testfile).ret == 0 + + +def test_unicode_args(testdir): + result = testdir.runpytest("-k", u"πŸ’©") + assert result.ret == EXIT_NOTESTSCOLLECTED From b08e156b7943db70a94f96943312b912730504aa Mon Sep 17 00:00:00 2001 From: wim glenn Date: Wed, 22 Aug 2018 11:27:36 -0500 Subject: [PATCH 26/62] strip trailing whitespace --- testing/test_pytester.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/test_pytester.py b/testing/test_pytester.py index cab9d8e9760..99e62e5bc09 100644 --- a/testing/test_pytester.py +++ b/testing/test_pytester.py @@ -397,7 +397,7 @@ def test_testdir_subprocess(testdir): testfile = testdir.makepyfile("def test_one(): pass") assert testdir.runpytest_subprocess(testfile).ret == 0 - + def test_unicode_args(testdir): result = testdir.runpytest("-k", u"πŸ’©") assert result.ret == EXIT_NOTESTSCOLLECTED From 917b99e4382626505296e94201dd7f6b253cdd78 Mon Sep 17 00:00:00 2001 From: wim glenn Date: Wed, 22 Aug 2018 13:40:21 -0500 Subject: [PATCH 27/62] More unicode whack-a-mole It seems pytest's very comprehensive CI sniffed out a few other places with similar bugs. Ideally we should find all the places where args are not stringy and solve it at the source, but who knows how many people are relying on the implicit string conversion. See [here](https://github.com/pytest-dev/pytest/blob/master/src/_pytest/config/__init__.py#L160-L166) for one such problem area (args with a single py.path.local instance is converted here, but a list or tuple containing some are not). --- src/_pytest/config/argparsing.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/_pytest/config/argparsing.py b/src/_pytest/config/argparsing.py index 5a4e35b8813..3a2a11af44a 100644 --- a/src/_pytest/config/argparsing.py +++ b/src/_pytest/config/argparsing.py @@ -2,6 +2,8 @@ import warnings import argparse +import py + FILE_OR_DIR = "file_or_dir" @@ -70,7 +72,8 @@ def parse(self, args, namespace=None): self.optparser = self._getparser() try_argcomplete(self.optparser) - return self.optparser.parse_args([str(x) for x in args], namespace=namespace) + args = [str(x) if isinstance(x, py.path.local) else x for x in args] + return self.optparser.parse_args(args, namespace=namespace) def _getparser(self): from _pytest._argcomplete import filescompleter @@ -106,7 +109,7 @@ def parse_known_and_unknown_args(self, args, namespace=None): the remaining arguments unknown at this point. """ optparser = self._getparser() - args = [str(x) for x in args] + args = [str(x) if isinstance(x, py.path.local) else x for x in args] return optparser.parse_known_args(args, namespace=namespace) def addini(self, name, help, type=None, default=None): From cd07c4d4ffad1c9a6fa1c1d8857e7aee1290ccf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20S=C3=BAkup?= Date: Wed, 22 Aug 2018 23:49:40 +0200 Subject: [PATCH 28/62] Use unittest.mock if is only aviable from Python 3.3 is mock part of python standard library in unittest namespace --- .../example_scripts/acceptance/fixture_mock_integration.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/testing/example_scripts/acceptance/fixture_mock_integration.py b/testing/example_scripts/acceptance/fixture_mock_integration.py index 51f46f82cae..c005c919349 100644 --- a/testing/example_scripts/acceptance/fixture_mock_integration.py +++ b/testing/example_scripts/acceptance/fixture_mock_integration.py @@ -1,6 +1,9 @@ """Reproduces issue #3774""" -import mock +try: + import mock +except ImportError: + import unittest.mock as mock import pytest From 8804c7333a9bc26956950443b3d75c657762ba28 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 22 Aug 2018 20:06:13 -0300 Subject: [PATCH 29/62] Fix CHANGELOG formatting --- changelog/3848.bugfix.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/3848.bugfix.rst b/changelog/3848.bugfix.rst index a2456477a44..4442d7a89f9 100644 --- a/changelog/3848.bugfix.rst +++ b/changelog/3848.bugfix.rst @@ -1 +1 @@ -Fix bugs where unicode arguments could not be passed to testdir.runpytest on Python 2.x +Fix bugs where unicode arguments could not be passed to ``testdir.runpytest`` on Python 2. From 8bb8b913570f215a9bff505799418ef9034609ec Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 22 Aug 2018 18:30:42 -0700 Subject: [PATCH 30/62] pyupgrade 1.4: tests --- .../global_testmodule_config/conftest.py | 2 +- doc/en/example/multipython.py | 49 ++++++++++--------- testing/acceptance_test.py | 6 +-- testing/code/test_excinfo.py | 2 +- testing/test_argcomplete.py | 2 +- testing/test_assertrewrite.py | 2 +- testing/test_helpconfig.py | 4 +- testing/test_parseopt.py | 2 +- testing/test_pdb.py | 6 ++- testing/test_terminal.py | 4 +- 10 files changed, 43 insertions(+), 36 deletions(-) diff --git a/doc/en/example/assertion/global_testmodule_config/conftest.py b/doc/en/example/assertion/global_testmodule_config/conftest.py index 4859bea78e6..3597ec06da8 100644 --- a/doc/en/example/assertion/global_testmodule_config/conftest.py +++ b/doc/en/example/assertion/global_testmodule_config/conftest.py @@ -10,4 +10,4 @@ def pytest_runtest_setup(item): return mod = item.getparent(pytest.Module).obj if hasattr(mod, "hello"): - print("mod.hello %r" % (mod.hello,)) + print("mod.hello {!r}".format(mod.hello)) diff --git a/doc/en/example/multipython.py b/doc/en/example/multipython.py index 299833f7175..5e12c9b0363 100644 --- a/doc/en/example/multipython.py +++ b/doc/en/example/multipython.py @@ -2,9 +2,10 @@ module containing a parametrized tests testing cross-python serialization via the pickle module. """ +import textwrap + import py import pytest -import _pytest._code pythonlist = ["python2.7", "python3.4", "python3.5"] @@ -24,42 +25,44 @@ class Python(object): def __init__(self, version, picklefile): self.pythonpath = py.path.local.sysfind(version) if not self.pythonpath: - pytest.skip("%r not found" % (version,)) + pytest.skip("{!r} not found".format(version)) self.picklefile = picklefile def dumps(self, obj): dumpfile = self.picklefile.dirpath("dump.py") dumpfile.write( - _pytest._code.Source( - """ - import pickle - f = open(%r, 'wb') - s = pickle.dump(%r, f, protocol=2) - f.close() - """ - % (str(self.picklefile), obj) + textwrap.dedent( + """\ + import pickle + f = open({!r}, 'wb') + s = pickle.dump({!r}, f, protocol=2) + f.close() + """.format( + str(self.picklefile), obj + ) ) ) - py.process.cmdexec("%s %s" % (self.pythonpath, dumpfile)) + py.process.cmdexec("{} {}".format(self.pythonpath, dumpfile)) def load_and_is_true(self, expression): loadfile = self.picklefile.dirpath("load.py") loadfile.write( - _pytest._code.Source( - """ - import pickle - f = open(%r, 'rb') - obj = pickle.load(f) - f.close() - res = eval(%r) - if not res: - raise SystemExit(1) - """ - % (str(self.picklefile), expression) + textwrap.dedent( + """\ + import pickle + f = open({!r}, 'rb') + obj = pickle.load(f) + f.close() + res = eval({!r}) + if not res: + raise SystemExit(1) + """.format( + str(self.picklefile), expression + ) ) ) print(loadfile) - py.process.cmdexec("%s %s" % (self.pythonpath, loadfile)) + py.process.cmdexec("{} {}".format(self.pythonpath, loadfile)) @pytest.mark.parametrize("obj", [42, {}, {1: 3}]) diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index 5d6baf12158..20f74855513 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -131,7 +131,7 @@ def test_not_collectable_arguments(self, testdir): p2 = testdir.makefile(".pyc", "123") result = testdir.runpytest(p1, p2) assert result.ret - result.stderr.fnmatch_lines(["*ERROR: not found:*%s" % (p2.basename,)]) + result.stderr.fnmatch_lines(["*ERROR: not found:*{}".format(p2.basename)]) def test_issue486_better_reporting_on_conftest_load_failure(self, testdir): testdir.makepyfile("") @@ -453,7 +453,7 @@ def test_earlyinit(self, testdir): @pytest.mark.xfail("sys.platform.startswith('java')") def test_pydoc(self, testdir): for name in ("py.test", "pytest"): - result = testdir.runpython_c("import %s;help(%s)" % (name, name)) + result = testdir.runpython_c("import {};help({})".format(name, name)) assert result.ret == 0 s = result.stdout.str() assert "MarkGenerator" in s @@ -836,7 +836,7 @@ def test_calls_showall(self, testdir): if ("test_%s" % x) in line and y in line: break else: - raise AssertionError("not found %s %s" % (x, y)) + raise AssertionError("not found {} {}".format(x, y)) def test_with_deselected(self, testdir): testdir.makepyfile(self.source) diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py index fbdaeacf753..350dc26fb98 100644 --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -269,7 +269,7 @@ def test_traceback_messy_recursion(self): decorator = pytest.importorskip("decorator").decorator def log(f, *k, **kw): - print("%s %s" % (k, kw)) + print("{} {}".format(k, kw)) f(*k, **kw) log = decorator(log) diff --git a/testing/test_argcomplete.py b/testing/test_argcomplete.py index 9e6b711a25d..fc2306b0008 100644 --- a/testing/test_argcomplete.py +++ b/testing/test_argcomplete.py @@ -11,7 +11,7 @@ def equal_with_bash(prefix, ffc, fc, out=None): res_bash = set(fc(prefix)) retval = set(res) == res_bash if out: - out.write("equal_with_bash %s %s\n" % (retval, res)) + out.write("equal_with_bash {} {}\n".format(retval, res)) if not retval: out.write(" python - bash: %s\n" % (set(res) - res_bash)) out.write(" bash - python: %s\n" % (res_bash - set(res))) diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py index 6cec7f0039e..e3ea3ccfbfb 100644 --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -560,7 +560,7 @@ def f(): assert getmsg(f) == "assert 42" def my_reprcompare(op, left, right): - return "%s %s %s" % (left, op, right) + return "{} {} {}".format(left, op, right) monkeypatch.setattr(util, "_reprcompare", my_reprcompare) diff --git a/testing/test_helpconfig.py b/testing/test_helpconfig.py index b5424235b16..ceea56ccc34 100644 --- a/testing/test_helpconfig.py +++ b/testing/test_helpconfig.py @@ -7,7 +7,9 @@ def test_version(testdir, pytestconfig): result = testdir.runpytest("--version") assert result.ret == 0 # p = py.path.local(py.__file__).dirpath() - result.stderr.fnmatch_lines(["*pytest*%s*imported from*" % (pytest.__version__,)]) + result.stderr.fnmatch_lines( + ["*pytest*{}*imported from*".format(pytest.__version__)] + ) if pytestconfig.pluginmanager.list_plugin_distinfo(): result.stderr.fnmatch_lines(["*setuptools registered plugins:", "*at*"]) diff --git a/testing/test_parseopt.py b/testing/test_parseopt.py index 3870ad419e5..fab288e7fd4 100644 --- a/testing/test_parseopt.py +++ b/testing/test_parseopt.py @@ -294,7 +294,7 @@ def test_argcomplete(testdir, monkeypatch): script = str(testdir.tmpdir.join("test_argcomplete")) pytest_bin = sys.argv[0] if "pytest" not in os.path.basename(pytest_bin): - pytest.skip("need to be run with pytest executable, not %s" % (pytest_bin,)) + pytest.skip("need to be run with pytest executable, not {}".format(pytest_bin)) with open(str(script), "w") as fp: # redirect output from argcomplete to stdin and stderr is not trivial diff --git a/testing/test_pdb.py b/testing/test_pdb.py index 43a78908c25..ed1c49a1acf 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -260,7 +260,9 @@ def test_1(): assert False """ ) - child = testdir.spawn_pytest("--show-capture=%s --pdb %s" % (showcapture, p1)) + child = testdir.spawn_pytest( + "--show-capture={} --pdb {}".format(showcapture, p1) + ) if showcapture in ("all", "log"): child.expect("captured log") child.expect("get rekt") @@ -473,7 +475,7 @@ def test_pdb_used_outside_test(self, testdir): x = 5 """ ) - child = testdir.spawn("%s %s" % (sys.executable, p1)) + child = testdir.spawn("{} {}".format(sys.executable, p1)) child.expect("x = 5") child.sendeof() self.flush(child) diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 88e5287e81f..ac29c3d3015 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -1118,9 +1118,9 @@ def pytest_terminal_summary(terminalreporter): ) def test_summary_stats(exp_line, exp_color, stats_arg): print("Based on stats: %s" % stats_arg) - print('Expect summary: "%s"; with color "%s"' % (exp_line, exp_color)) + print('Expect summary: "{}"; with color "{}"'.format(exp_line, exp_color)) (line, color) = build_summary_stats_line(stats_arg) - print('Actually got: "%s"; with color "%s"' % (line, color)) + print('Actually got: "{}"; with color "{}"'.format(line, color)) assert line == exp_line assert color == exp_color From 0d65783dce057dbfb276d1d7867037a8467dd20f Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 22 Aug 2018 19:00:43 -0700 Subject: [PATCH 31/62] Fix unicode errors when changing to .format(...) --- src/_pytest/_code/_py2traceback.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/_pytest/_code/_py2traceback.py b/src/_pytest/_code/_py2traceback.py index 2dd100c33bd..cceed40edf0 100644 --- a/src/_pytest/_code/_py2traceback.py +++ b/src/_pytest/_code/_py2traceback.py @@ -2,7 +2,7 @@ # CHANGES: # - some_str is replaced, trying to create unicode strings # -from __future__ import absolute_import, division, print_function +from __future__ import absolute_import, division, print_function, unicode_literals import types from six import text_type @@ -51,17 +51,17 @@ def format_exception_only(etype, value): pass else: filename = filename or "" - lines.append(' File "%s", line %d\n' % (filename, lineno)) + lines.append(' File "{}", line {}\n'.format(filename, lineno)) if badline is not None: if isinstance(badline, bytes): # python 2 only badline = badline.decode("utf-8", "replace") - lines.append(u" %s\n" % badline.strip()) + lines.append(" {}\n".format(badline.strip())) if offset is not None: caretspace = badline.rstrip("\n")[:offset].lstrip() # non-space whitespace (likes tabs) must be kept for alignment caretspace = ((c.isspace() and c or " ") for c in caretspace) # only three spaces to account for offset1 == pos 0 - lines.append(" %s^\n" % "".join(caretspace)) + lines.append(" {}^\n".format("".join(caretspace))) value = msg lines.append(_format_final_exc_line(stype, value)) @@ -72,9 +72,9 @@ def _format_final_exc_line(etype, value): """Return a list of a single line -- normal case for format_exception_only""" valuestr = _some_str(value) if value is None or not valuestr: - line = "%s\n" % etype + line = "{}\n".format(etype) else: - line = "%s: %s\n" % (etype, valuestr) + line = "{}: {}\n".format(etype, valuestr) return line @@ -83,7 +83,7 @@ def _some_str(value): return text_type(value) except Exception: try: - return str(value) + return bytes(value).decode("UTF-8", "replace") except Exception: pass - return "" % type(value).__name__ + return "".format(type(value).__name__) From 4d3c1ab4f09ad352b9b0a274f0d802826771e4ed Mon Sep 17 00:00:00 2001 From: turturica Date: Wed, 22 Aug 2018 21:42:59 -0700 Subject: [PATCH 32/62] Fixes #3854 --- src/_pytest/python.py | 15 ++++++++++----- testing/python/collect.py | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 51bc28fe5a2..891e071c6b2 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -590,18 +590,23 @@ def collect(self): self.session.config.pluginmanager._duplicatepaths.remove(path) this_path = self.fspath.dirpath() - pkg_prefix = None - yield Module(this_path.join("__init__.py"), self) + pkg_prefixes = set() for path in this_path.visit(rec=self._recurse, bf=True, sort=True): # we will visit our own __init__.py file, in which case we skip it + skip = False if path.basename == "__init__.py" and path.dirpath() == this_path: continue - if pkg_prefix and pkg_prefix in path.parts(): + + if path.isdir() and path.join('__init__.py').check(file=1): + pkg_prefixes.add(path) + + for pkg_prefix in pkg_prefixes: + if pkg_prefix in path.parts() and pkg_prefix.join('__init__.py') == path: + skip = True + if skip: continue for x in self._collectfile(path): yield x - if isinstance(x, Package): - pkg_prefix = path.dirpath() def _get_xunit_setup_teardown(holder, attr_name, param_obj=None): diff --git a/testing/python/collect.py b/testing/python/collect.py index c040cc09e68..7356597cb7a 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -1623,3 +1623,40 @@ def test_package_with_modules(testdir): root.chdir() result = testdir.runpytest("-v", "-s") result.assert_outcomes(passed=2) + + +def test_package_ordering(testdir): + """ + . + └── root + β”œβ”€β”€ TestRoot.py + β”œβ”€β”€ __init__.py + β”œβ”€β”€ sub1 + β”‚ β”œβ”€β”€ TestSub1.py + β”‚ └── __init__.py + └── sub2 + └── test + β”œβ”€β”€ TestSub2.py + └── test_in_sub2.py + + """ + testdir.makeini( + """ + [pytest] + python_files=Test*.py + """ + ) + root = testdir.mkpydir("root") + sub1 = root.mkdir("sub1") + sub1.ensure("__init__.py") + sub2 = root.mkdir("sub2") + sub2_test = sub2.mkdir("sub2") + + root.join("TestRoot.py").write("def test_1(): pass") + sub1.join("TestSub1.py").write("def test_2(): pass") + sub2_test.join("TestSub2.py").write("def test_3(): pass") + sub2_test.join("test_in_sub2.py").write("def test_4(): pass") + + # Execute from . + result = testdir.runpytest("-v", "-s") + result.assert_outcomes(passed=3) From 0fc4a806e517ed64717df107a9dca3995326a06f Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 22 Aug 2018 19:21:00 -0700 Subject: [PATCH 33/62] py.builtins._totext -> string literals or six.text_type --- src/_pytest/outcomes.py | 3 +-- src/_pytest/python.py | 2 +- testing/code/test_code.py | 5 ++--- testing/code/test_excinfo.py | 7 ++++--- testing/python/metafunc.py | 8 ++------ testing/test_assertion.py | 12 ++++++------ testing/test_assertrewrite.py | 6 ++---- testing/test_capture.py | 7 ++++--- testing/test_junitxml.py | 2 +- 9 files changed, 23 insertions(+), 29 deletions(-) diff --git a/src/_pytest/outcomes.py b/src/_pytest/outcomes.py index 63b8453b7b2..0a66fcab4e9 100644 --- a/src/_pytest/outcomes.py +++ b/src/_pytest/outcomes.py @@ -3,7 +3,6 @@ as well as functions creating them """ from __future__ import absolute_import, division, print_function -import py import sys @@ -21,7 +20,7 @@ def __repr__(self): if self.msg: val = self.msg if isinstance(val, bytes): - val = py._builtin._totext(val, errors="replace") + val = val.decode("UTF-8", errors="replace") return val return "<%s instance>" % (self.__class__.__name__,) diff --git a/src/_pytest/python.py b/src/_pytest/python.py index e269b3bb4c3..45ef3be6165 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -741,7 +741,7 @@ def _prunetraceback(self, excinfo): def _repr_failure_py(self, excinfo, style="long"): if excinfo.errisinstance(fail.Exception): if not excinfo.value.pytrace: - return py._builtin._totext(excinfo.value) + return six.text_type(excinfo.value) return super(FunctionMixin, self)._repr_failure_py(excinfo, style=style) def repr_failure(self, excinfo, outerr=None): diff --git a/testing/code/test_code.py b/testing/code/test_code.py index e098f136df8..27916d64fbc 100644 --- a/testing/code/test_code.py +++ b/testing/code/test_code.py @@ -3,7 +3,6 @@ import sys import _pytest._code -import py import pytest from test_excinfo import TWMock from six import text_type @@ -83,7 +82,7 @@ def test_code_from_func(): def test_unicode_handling(): - value = py.builtin._totext("\xc4\x85\xc4\x87\n", "utf-8").encode("utf8") + value = u"Δ…Δ‡".encode("UTF-8") def f(): raise Exception(value) @@ -96,7 +95,7 @@ def f(): @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") + value = u"Δ…Δ‡".encode("UTF-8") def f(): raise SyntaxError("invalid syntax", (None, 1, 3, value)) diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py index fbdaeacf753..f4fe9bd5395 100644 --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -8,6 +8,7 @@ import _pytest import py import pytest +import six from _pytest._code.code import ( ExceptionInfo, FormattedExcinfo, @@ -884,10 +885,10 @@ def test_reprexcinfo_unicode(self): class MyRepr(TerminalRepr): def toterminal(self, tw): - tw.line(py.builtin._totext("я", "utf-8")) + tw.line(u"я") - x = py.builtin._totext(MyRepr()) - assert x == py.builtin._totext("я", "utf-8") + x = six.text_type(MyRepr()) + assert x == u"я" def test_toterminal_long(self, importasmod): mod = importasmod( diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index 7ef34678c9c..db1cd77c557 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -3,7 +3,6 @@ import sys import attr import _pytest._code -import py import pytest from _pytest import python, fixtures @@ -295,9 +294,7 @@ def test_idmaker_autoname(self): ) assert result == ["a0-1.0", "a1-b1"] # unicode mixing, issue250 - result = idmaker( - (py.builtin._totext("a"), "b"), [pytest.param({}, b"\xc3\xb4")] - ) + result = idmaker((u"a", "b"), [pytest.param({}, b"\xc3\xb4")]) assert result == ["a0-\\xc3\\xb4"] def test_idmaker_with_bytes_regex(self): @@ -309,7 +306,6 @@ def test_idmaker_with_bytes_regex(self): def test_idmaker_native_strings(self): from _pytest.python import idmaker - totext = py.builtin._totext result = idmaker( ("a", "b"), [ @@ -324,7 +320,7 @@ def test_idmaker_native_strings(self): pytest.param({7}, set("seven")), pytest.param(tuple("eight"), (8, -8, 8)), pytest.param(b"\xc3\xb4", b"name"), - pytest.param(b"\xc3\xb4", totext("other")), + pytest.param(b"\xc3\xb4", u"other"), ], ) assert result == [ diff --git a/testing/test_assertion.py b/testing/test_assertion.py index 23763f07886..501477810ff 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -509,12 +509,12 @@ def test_repr_no_exc(self): assert "raised in repr()" not in expl def test_unicode(self): - left = py.builtin._totext("£€", "utf-8") - right = py.builtin._totext("Β£", "utf-8") + left = u"£€" + right = u"Β£" expl = callequal(left, right) - assert expl[0] == py.builtin._totext("'£€' == 'Β£'", "utf-8") - assert expl[1] == py.builtin._totext("- £€", "utf-8") - assert expl[2] == py.builtin._totext("+ Β£", "utf-8") + assert expl[0] == u"'£€' == 'Β£'" + assert expl[1] == u"- £€" + assert expl[2] == u"+ Β£" def test_nonascii_text(self): """ @@ -542,7 +542,7 @@ def test_mojibake(self): expl = callequal(left, right) for line in expl: assert isinstance(line, py.builtin.text) - msg = py.builtin._totext("\n").join(expl) + msg = u"\n".join(expl) assert msg diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py index 6cec7f0039e..c5cabf79b7a 100644 --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -654,12 +654,10 @@ def test_zipfile(self, testdir): def test_readonly(self, testdir): sub = testdir.mkdir("testing") sub.join("test_readonly.py").write( - py.builtin._totext( - """ + b""" def test_rewritten(): assert "@py_builtins" in globals() - """ - ).encode("utf-8"), + """, "wb", ) old_mode = sub.stat().mode diff --git a/testing/test_capture.py b/testing/test_capture.py index 93eaaa85cb7..475f619244c 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- from __future__ import absolute_import, division, print_function # note: py.io capture tests where copied from @@ -1083,9 +1084,9 @@ def test_capture_results_accessible_by_attribute(self): def test_capturing_readouterr_unicode(self): with self.getcapture() as cap: - print("hx\xc4\x85\xc4\x87") + print("hxΔ…Δ‡") out, err = cap.readouterr() - assert out == py.builtin._totext("hx\xc4\x85\xc4\x87\n", "utf8") + assert out == u"hxΔ…Δ‡\n" @pytest.mark.skipif( "sys.version_info >= (3,)", reason="text output different for bytes on python3" @@ -1095,7 +1096,7 @@ def test_capturing_readouterr_decode_error_handling(self): # triggered an internal error in pytest print("\xa6") out, err = cap.readouterr() - assert out == py.builtin._totext("\ufffd\n", "unicode-escape") + assert out == u"\ufffd\n" def test_reset_twice_error(self): with self.getcapture() as cap: diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index ae2b4ea764f..0678d59e80d 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -941,7 +941,7 @@ def test_func(self, param): def test_unicode_issue368(testdir): path = testdir.tmpdir.join("test.xml") log = LogXML(str(path), None) - ustr = py.builtin._totext("Π’ΠΠ˜!", "utf-8") + ustr = u"Π’ΠΠ˜!" class Report(BaseReport): longrepr = ustr From c2cd3378864ba5e3756413e41042b30742030f04 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 22 Aug 2018 19:24:33 -0700 Subject: [PATCH 34/62] py.builtin.exec_ => six.exec_ --- bench/empty.py | 4 ++-- doc/en/example/assertion/failure_demo.py | 4 ++-- src/_pytest/_code/code.py | 3 ++- src/_pytest/assertion/rewrite.py | 2 +- src/_pytest/python_api.py | 6 +++--- src/_pytest/recwarn.py | 6 +++--- testing/code/test_source.py | 6 +++--- testing/test_assertrewrite.py | 3 ++- 8 files changed, 18 insertions(+), 16 deletions(-) diff --git a/bench/empty.py b/bench/empty.py index b90319936b3..338ebf13834 100644 --- a/bench/empty.py +++ b/bench/empty.py @@ -1,4 +1,4 @@ -import py +import six for i in range(1000): - py.builtin.exec_("def test_func_%d(): pass" % i) + six.exec_("def test_func_%d(): pass" % i) diff --git a/doc/en/example/assertion/failure_demo.py b/doc/en/example/assertion/failure_demo.py index 0a104578c50..def6ae2effa 100644 --- a/doc/en/example/assertion/failure_demo.py +++ b/doc/en/example/assertion/failure_demo.py @@ -1,6 +1,6 @@ from pytest import raises import _pytest._code -import py +import six def otherfunc(a, b): @@ -177,7 +177,7 @@ def test_dynamic_compile_shows_nicely(): name = "abc-123" module = imp.new_module(name) code = _pytest._code.compile(src, name, "exec") - py.builtin.exec_(code, module.__dict__) + six.exec_(code, module.__dict__) sys.modules[name] = module module.foo() diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index d6c5cd90edb..2662e432016 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -11,6 +11,7 @@ from _pytest.compat import _PY2, _PY3, PY35, safe_str from six import text_type import py +import six builtin_repr = repr @@ -128,7 +129,7 @@ def exec_(self, code, **vars): """ f_locals = self.f_locals.copy() f_locals.update(vars) - py.builtin.exec_(code, self.f_globals, f_locals) + six.exec_(code, self.f_globals, f_locals) def repr(self, object): """ return a 'safe' (non-recursive, one-line) string repr for 'object' diff --git a/src/_pytest/assertion/rewrite.py b/src/_pytest/assertion/rewrite.py index 4f96b9e8c23..952cfb59441 100644 --- a/src/_pytest/assertion/rewrite.py +++ b/src/_pytest/assertion/rewrite.py @@ -223,7 +223,7 @@ def load_module(self, name): mod.__loader__ = self # Normally, this attribute is 3.4+ mod.__spec__ = spec_from_file_location(name, co.co_filename, loader=self) - py.builtin.exec_(co, mod.__dict__) + six.exec_(co, mod.__dict__) except: # noqa if name in sys.modules: del sys.modules[name] diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py index abc4d1e17b6..6cd17de254b 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -4,7 +4,7 @@ from numbers import Number from decimal import Decimal -import py +import six from six.moves import zip, filterfalse from more_itertools.more import always_iterable @@ -680,8 +680,8 @@ def raises(expected_exception, *args, **kwargs): # print "raises frame scope: %r" % frame.f_locals try: code = _pytest._code.Source(code).compile() - py.builtin.exec_(code, frame.f_globals, loc) - # XXX didn'T mean f_globals == f_locals something special? + six.exec_(code, frame.f_globals, loc) + # XXX didn't mean f_globals == f_locals something special? # this is destroyed here ... except expected_exception: return _pytest._code.ExceptionInfo() diff --git a/src/_pytest/recwarn.py b/src/_pytest/recwarn.py index 177757f27ec..0eee4c84195 100644 --- a/src/_pytest/recwarn.py +++ b/src/_pytest/recwarn.py @@ -4,11 +4,11 @@ import inspect import _pytest._code -import py +import re import sys import warnings -import re +import six from _pytest.fixtures import yield_fixture from _pytest.outcomes import fail @@ -130,7 +130,7 @@ def warns(expected_warning, *args, **kwargs): with WarningsChecker(expected_warning, match_expr=match_expr): code = _pytest._code.Source(code).compile() - py.builtin.exec_(code, frame.f_globals, loc) + six.exec_(code, frame.f_globals, loc) else: func = args[0] with WarningsChecker(expected_warning, match_expr=match_expr): diff --git a/testing/code/test_source.py b/testing/code/test_source.py index 995fabcf42c..d7e8fe42221 100644 --- a/testing/code/test_source.py +++ b/testing/code/test_source.py @@ -6,8 +6,8 @@ import sys import _pytest._code -import py import pytest +import six from _pytest._code import Source from _pytest._code.source import ast @@ -323,7 +323,7 @@ def test_compile_to_ast(self): def test_compile_and_getsource(self): co = self.source.compile() - py.builtin.exec_(co, globals()) + six.exec_(co, globals()) f(7) excinfo = pytest.raises(AssertionError, "f(6)") frame = excinfo.traceback[-1].frame @@ -392,7 +392,7 @@ def f(): def g(): pass """ co = _pytest._code.compile(source) - py.builtin.exec_(co, globals()) + six.exec_(co, globals()) assert str(_pytest._code.Source(f)).strip() == "def f():\n raise ValueError" assert str(_pytest._code.Source(g)).strip() == "def g(): pass" diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py index c5cabf79b7a..1ba3f947311 100644 --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -9,6 +9,7 @@ import zipfile import py import pytest +import six import _pytest._code from _pytest.assertion import util @@ -49,7 +50,7 @@ def getmsg(f, extra_ns=None, must_pass=False): ns = {} if extra_ns is not None: ns.update(extra_ns) - py.builtin.exec_(code, ns) + six.exec_(code, ns) func = ns[f.__name__] try: func() From dccac69d82b5677bcd8ec7775a12b0d13f6e69ac Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 22 Aug 2018 19:26:11 -0700 Subject: [PATCH 35/62] py.builtin.text -> six.text_type --- testing/test_assertion.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/testing/test_assertion.py b/testing/test_assertion.py index 501477810ff..a9e6247139d 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -6,6 +6,7 @@ import _pytest.assertion as plugin import py import pytest +import six from _pytest.assertion import util from _pytest.assertion import truncate @@ -541,7 +542,7 @@ def test_mojibake(self): right = bytes(right, "utf-8") expl = callequal(left, right) for line in expl: - assert isinstance(line, py.builtin.text) + assert isinstance(line, six.text_type) msg = u"\n".join(expl) assert msg From 7099ea9bb0a4fa2b03d3793729bf7cf3254aa04f Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 22 Aug 2018 19:28:42 -0700 Subject: [PATCH 36/62] py.builtin._reraise -> six.reraise --- src/_pytest/fixtures.py | 4 ++-- src/_pytest/runner.py | 8 ++++---- testing/code/test_excinfo.py | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index cc8921e6599..c12691caa59 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -858,7 +858,7 @@ def finish(self, request): if exceptions: e = exceptions[0] del exceptions # ensure we don't keep all frames alive because of the traceback - py.builtin._reraise(*e) + six.reraise(*e) finally: hook = self._fixturemanager.session.gethookproxy(request.node.fspath) @@ -885,7 +885,7 @@ def execute(self, request): result, cache_key, err = cached_result if my_cache_key == cache_key: if err is not None: - py.builtin._reraise(*err) + six.reraise(*err) else: return result # we have a previous but differently parametrized fixture instance diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index 5739bbef055..1ba9ff310b7 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -6,7 +6,7 @@ import sys from time import time -import py +import six from _pytest._code.code import ExceptionInfo from _pytest.outcomes import skip, Skipped, TEST_OUTCOME @@ -317,7 +317,7 @@ def _callfinalizers(self, colitem): if exc is None: exc = sys.exc_info() if exc: - py.builtin._reraise(*exc) + six.reraise(*exc) def _teardown_with_finalization(self, colitem): self._callfinalizers(colitem) @@ -352,7 +352,7 @@ def _teardown_towards(self, needed_collectors): if exc is None: exc = sys.exc_info() if exc: - py.builtin._reraise(*exc) + six.reraise(*exc) def prepare(self, colitem): """ setup objects along the collector chain to the test-method @@ -363,7 +363,7 @@ def prepare(self, colitem): # check if the last collection node has raised an error for col in self.stack: if hasattr(col, "_prepare_exc"): - py.builtin._reraise(*col._prepare_exc) + six.reraise(*col._prepare_exc) for col in needed_collectors[len(self.stack) :]: self.stack.append(col) try: diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py index f4fe9bd5395..f8bf25b68fa 100644 --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -252,7 +252,7 @@ def reraise_me(): import sys exc, val, tb = sys.exc_info() - py.builtin._reraise(exc, val, tb) + six.reraise(exc, val, tb) def f(n): try: From 85482d575e6a868c829fe6f9b9d9fe3a6cab4a53 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Thu, 23 Aug 2018 09:06:17 -0700 Subject: [PATCH 37/62] Replace Source with dedent where possible --- testing/acceptance_test.py | 20 +- testing/code/test_code.py | 9 +- testing/code/test_excinfo.py | 8 +- testing/python/collect.py | 137 +++++------ testing/python/fixture.py | 436 +++++++++++++++++----------------- testing/python/metafunc.py | 18 +- testing/test_assertrewrite.py | 14 +- testing/test_cacheprovider.py | 72 +++--- testing/test_capture.py | 95 ++++---- testing/test_collection.py | 14 +- testing/test_config.py | 36 +-- testing/test_conftest.py | 189 ++++++++------- testing/test_doctest.py | 48 ++-- testing/test_terminal.py | 24 +- 14 files changed, 557 insertions(+), 563 deletions(-) diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index 5d6baf12158..52879f15c0d 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -2,11 +2,11 @@ from __future__ import absolute_import, division, print_function import os import sys +import textwrap import types import six -import _pytest._code import py import pytest from _pytest.main import EXIT_NOTESTSCOLLECTED, EXIT_USAGEERROR @@ -201,16 +201,16 @@ def test_chdir(self, testdir): testdir.tmpdir.join("py").mksymlinkto(py._pydir) p = testdir.tmpdir.join("main.py") p.write( - _pytest._code.Source( + textwrap.dedent( + """\ + import sys, os + sys.path.insert(0, '') + import py + print(py.__file__) + print(py.__path__) + os.chdir(os.path.dirname(os.getcwd())) + print(py.log) """ - import sys, os - sys.path.insert(0, '') - import py - print (py.__file__) - print (py.__path__) - os.chdir(os.path.dirname(os.getcwd())) - print (py.log) - """ ) ) result = testdir.runpython(p) diff --git a/testing/code/test_code.py b/testing/code/test_code.py index 27916d64fbc..f7a8a4dbd55 100644 --- a/testing/code/test_code.py +++ b/testing/code/test_code.py @@ -4,6 +4,7 @@ import _pytest._code import pytest +import mock from test_excinfo import TWMock from six import text_type @@ -67,12 +68,8 @@ def func(): f = func() f = _pytest._code.Frame(f) - prop = f.code.__class__.fullsource - try: - f.code.__class__.fullsource = None - assert f.statement == _pytest._code.Source("") - finally: - f.code.__class__.fullsource = prop + with mock.patch.object(f.code.__class__, "fullsource", None): + assert f.statement == "" def test_code_from_func(): diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py index f8bf25b68fa..52cad6a3ea3 100644 --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -149,7 +149,7 @@ def xyz(): except somenoname: pass xyz() - """ + """ ) try: exec(source.compile()) @@ -426,7 +426,7 @@ class TestFormattedExcinfo(object): @pytest.fixture def importasmod(self, request): def importasmod(source): - source = _pytest._code.Source(source) + source = textwrap.dedent(source) tmpdir = request.getfixturevalue("tmpdir") modpath = tmpdir.join("mod.py") tmpdir.ensure("__init__.py") @@ -450,10 +450,10 @@ def excinfo_from_exec(self, source): def test_repr_source(self): pr = FormattedExcinfo() source = _pytest._code.Source( - """ + """\ def f(x): pass - """ + """ ).strip() pr.flow_marker = "|" lines = pr.get_source(source, 0) diff --git a/testing/python/collect.py b/testing/python/collect.py index c040cc09e68..b3b19802a74 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- import os import sys -from textwrap import dedent +import textwrap import _pytest._code import pytest @@ -47,13 +47,14 @@ def test_import_prepend_append(self, testdir, monkeypatch): p = root2.join("test_x456.py") monkeypatch.syspath_prepend(str(root1)) p.write( - dedent( + textwrap.dedent( """\ - import x456 - def test(): - assert x456.__file__.startswith(%r) - """ - % str(root2) + import x456 + def test(): + assert x456.__file__.startswith({!r}) + """.format( + str(root2) + ) ) ) with root2.as_cwd(): @@ -929,23 +930,23 @@ def pytest_pycollect_makemodule(path, parent): def test_customized_pymakemodule_issue205_subdir(self, testdir): b = testdir.mkdir("a").mkdir("b") b.join("conftest.py").write( - _pytest._code.Source( + textwrap.dedent( + """\ + import pytest + @pytest.hookimpl(hookwrapper=True) + def pytest_pycollect_makemodule(): + outcome = yield + mod = outcome.get_result() + mod.obj.hello = "world" """ - import pytest - @pytest.hookimpl(hookwrapper=True) - def pytest_pycollect_makemodule(): - outcome = yield - mod = outcome.get_result() - mod.obj.hello = "world" - """ ) ) b.join("test_module.py").write( - _pytest._code.Source( + textwrap.dedent( + """\ + def test_hello(): + assert hello == "world" """ - def test_hello(): - assert hello == "world" - """ ) ) reprec = testdir.inline_run() @@ -954,31 +955,31 @@ def test_hello(): def test_customized_pymakeitem(self, testdir): b = testdir.mkdir("a").mkdir("b") b.join("conftest.py").write( - _pytest._code.Source( + textwrap.dedent( + """\ + import pytest + @pytest.hookimpl(hookwrapper=True) + def pytest_pycollect_makeitem(): + outcome = yield + if outcome.excinfo is None: + result = outcome.get_result() + if result: + for func in result: + func._some123 = "world" """ - import pytest - @pytest.hookimpl(hookwrapper=True) - def pytest_pycollect_makeitem(): - outcome = yield - if outcome.excinfo is None: - result = outcome.get_result() - if result: - for func in result: - func._some123 = "world" - """ ) ) b.join("test_module.py").write( - _pytest._code.Source( - """ - import pytest + textwrap.dedent( + """\ + import pytest - @pytest.fixture() - def obj(request): - return request.node._some123 - def test_hello(obj): - assert obj == "world" - """ + @pytest.fixture() + def obj(request): + return request.node._some123 + def test_hello(obj): + assert obj == "world" + """ ) ) reprec = testdir.inline_run() @@ -1033,7 +1034,7 @@ def pytest_collect_file(path, parent): ) testdir.makefile( ".narf", - """ + """\ def test_something(): assert 1 + 1 == 2""", ) @@ -1046,29 +1047,29 @@ def test_setup_only_available_in_subdir(testdir): sub1 = testdir.mkpydir("sub1") sub2 = testdir.mkpydir("sub2") sub1.join("conftest.py").write( - _pytest._code.Source( + textwrap.dedent( + """\ + import pytest + def pytest_runtest_setup(item): + assert item.fspath.purebasename == "test_in_sub1" + def pytest_runtest_call(item): + assert item.fspath.purebasename == "test_in_sub1" + def pytest_runtest_teardown(item): + assert item.fspath.purebasename == "test_in_sub1" """ - import pytest - def pytest_runtest_setup(item): - assert item.fspath.purebasename == "test_in_sub1" - def pytest_runtest_call(item): - assert item.fspath.purebasename == "test_in_sub1" - def pytest_runtest_teardown(item): - assert item.fspath.purebasename == "test_in_sub1" - """ ) ) sub2.join("conftest.py").write( - _pytest._code.Source( + textwrap.dedent( + """\ + import pytest + def pytest_runtest_setup(item): + assert item.fspath.purebasename == "test_in_sub2" + def pytest_runtest_call(item): + assert item.fspath.purebasename == "test_in_sub2" + def pytest_runtest_teardown(item): + assert item.fspath.purebasename == "test_in_sub2" """ - import pytest - def pytest_runtest_setup(item): - assert item.fspath.purebasename == "test_in_sub2" - def pytest_runtest_call(item): - assert item.fspath.purebasename == "test_in_sub2" - def pytest_runtest_teardown(item): - assert item.fspath.purebasename == "test_in_sub2" - """ ) ) sub1.join("test_in_sub1.py").write("def test_1(): pass") @@ -1547,12 +1548,12 @@ def test_skip_duplicates_by_default(testdir): a = testdir.mkdir("a") fh = a.join("test_a.py") fh.write( - _pytest._code.Source( + textwrap.dedent( + """\ + import pytest + def test_real(): + pass """ - import pytest - def test_real(): - pass - """ ) ) result = testdir.runpytest(a.strpath, a.strpath) @@ -1567,12 +1568,12 @@ def test_keep_duplicates(testdir): a = testdir.mkdir("a") fh = a.join("test_a.py") fh.write( - _pytest._code.Source( + textwrap.dedent( + """\ + import pytest + def test_real(): + pass """ - import pytest - def test_real(): - pass - """ ) ) result = testdir.runpytest("--keep-duplicates", a.strpath, a.strpath) diff --git a/testing/python/fixture.py b/testing/python/fixture.py index bbfcf775be4..47be7800c50 100644 --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -1,6 +1,5 @@ -from textwrap import dedent +import textwrap -import _pytest._code import pytest from _pytest.pytester import get_public_names from _pytest.fixtures import FixtureLookupError, FixtureRequest @@ -208,23 +207,23 @@ def spam(request): ) subdir = testdir.mkpydir("subdir") subdir.join("conftest.py").write( - _pytest._code.Source( - """ - import pytest + textwrap.dedent( + """\ + import pytest - @pytest.fixture - def spam(): - return 'spam' - """ + @pytest.fixture + def spam(): + return 'spam' + """ ) ) testfile = subdir.join("test_spam.py") testfile.write( - _pytest._code.Source( + textwrap.dedent( + """\ + def test_spam(spam): + assert spam == "spam" """ - def test_spam(spam): - assert spam == "spam" - """ ) ) result = testdir.runpytest() @@ -276,26 +275,26 @@ def spam(): ) subdir = testdir.mkpydir("subdir") subdir.join("conftest.py").write( - _pytest._code.Source( - """ - import pytest + textwrap.dedent( + """\ + import pytest - @pytest.fixture(params=[1, 2, 3]) - def spam(request): - return request.param - """ + @pytest.fixture(params=[1, 2, 3]) + def spam(request): + return request.param + """ ) ) testfile = subdir.join("test_spam.py") testfile.write( - _pytest._code.Source( - """ - params = {'spam': 1} + textwrap.dedent( + """\ + params = {'spam': 1} - def test_spam(spam): - assert spam == params['spam'] - params['spam'] += 1 - """ + def test_spam(spam): + assert spam == params['spam'] + params['spam'] += 1 + """ ) ) result = testdir.runpytest() @@ -320,26 +319,26 @@ def spam(): ) subdir = testdir.mkpydir("subdir") subdir.join("conftest.py").write( - _pytest._code.Source( - """ - import pytest + textwrap.dedent( + """\ + import pytest - @pytest.fixture(params=[1, 2, 3]) - def spam(request): - return request.param - """ + @pytest.fixture(params=[1, 2, 3]) + def spam(request): + return request.param + """ ) ) testfile = subdir.join("test_spam.py") testfile.write( - _pytest._code.Source( - """ - params = {'spam': 1} + textwrap.dedent( + """\ + params = {'spam': 1} - def test_spam(spam): - assert spam == params['spam'] - params['spam'] += 1 - """ + def test_spam(spam): + assert spam == params['spam'] + params['spam'] += 1 + """ ) ) result = testdir.runpytest() @@ -807,13 +806,13 @@ def test_fixtures_sub_subdir_normalize_sep(self, testdir): # this tests that normalization of nodeids takes place b = testdir.mkdir("tests").mkdir("unit") b.join("conftest.py").write( - _pytest._code.Source( + textwrap.dedent( + """\ + import pytest + @pytest.fixture + def arg1(): + pass """ - import pytest - @pytest.fixture - def arg1(): - pass - """ ) ) p = b.join("test_module.py") @@ -1484,41 +1483,41 @@ def test_parsefactories_relative_node_ids(self, testdir): runner = testdir.mkdir("runner") package = testdir.mkdir("package") package.join("conftest.py").write( - dedent( + textwrap.dedent( """\ import pytest @pytest.fixture def one(): return 1 - """ + """ ) ) package.join("test_x.py").write( - dedent( + textwrap.dedent( """\ - def test_x(one): - assert one == 1 - """ + def test_x(one): + assert one == 1 + """ ) ) sub = package.mkdir("sub") sub.join("__init__.py").ensure() sub.join("conftest.py").write( - dedent( + textwrap.dedent( """\ - import pytest - @pytest.fixture - def one(): - return 2 - """ + import pytest + @pytest.fixture + def one(): + return 2 + """ ) ) sub.join("test_y.py").write( - dedent( + textwrap.dedent( """\ - def test_x(one): - assert one == 2 - """ + def test_x(one): + assert one == 2 + """ ) ) reprec = testdir.inline_run() @@ -1535,44 +1534,44 @@ def test_package_xunit_fixture(self, testdir): ) package = testdir.mkdir("package") package.join("__init__.py").write( - dedent( + textwrap.dedent( """\ - from .. import values - def setup_module(): - values.append("package") - def teardown_module(): - values[:] = [] - """ + from .. import values + def setup_module(): + values.append("package") + def teardown_module(): + values[:] = [] + """ ) ) package.join("test_x.py").write( - dedent( + textwrap.dedent( """\ - from .. import values - def test_x(): - assert values == ["package"] - """ + from .. import values + def test_x(): + assert values == ["package"] + """ ) ) package = testdir.mkdir("package2") package.join("__init__.py").write( - dedent( + textwrap.dedent( """\ - from .. import values - def setup_module(): - values.append("package2") - def teardown_module(): - values[:] = [] - """ + from .. import values + def setup_module(): + values.append("package2") + def teardown_module(): + values[:] = [] + """ ) ) package.join("test_x.py").write( - dedent( + textwrap.dedent( """\ - from .. import values - def test_x(): - assert values == ["package2"] - """ + from .. import values + def test_x(): + assert values == ["package2"] + """ ) ) reprec = testdir.inline_run() @@ -1587,32 +1586,32 @@ def test_package_fixture_complex(self, testdir): package = testdir.mkdir("package") package.join("__init__.py").write("") package.join("conftest.py").write( - dedent( + textwrap.dedent( """\ - import pytest - from .. import values - @pytest.fixture(scope="package") - def one(): - values.append("package") - yield values - values.pop() - @pytest.fixture(scope="package", autouse=True) - def two(): - values.append("package-auto") - yield values - values.pop() - """ + import pytest + from .. import values + @pytest.fixture(scope="package") + def one(): + values.append("package") + yield values + values.pop() + @pytest.fixture(scope="package", autouse=True) + def two(): + values.append("package-auto") + yield values + values.pop() + """ ) ) package.join("test_x.py").write( - dedent( + textwrap.dedent( """\ - from .. import values - def test_package_autouse(): - assert values == ["package-auto"] - def test_package(one): - assert values == ["package-auto", "package"] - """ + from .. import values + def test_package_autouse(): + assert values == ["package-auto"] + def test_package(one): + assert values == ["package-auto", "package"] + """ ) ) reprec = testdir.inline_run() @@ -1804,24 +1803,24 @@ class TestAutouseManagement(object): def test_autouse_conftest_mid_directory(self, testdir): pkgdir = testdir.mkpydir("xyz123") pkgdir.join("conftest.py").write( - _pytest._code.Source( + textwrap.dedent( + """\ + import pytest + @pytest.fixture(autouse=True) + def app(): + import sys + sys._myapp = "hello" """ - import pytest - @pytest.fixture(autouse=True) - def app(): - import sys - sys._myapp = "hello" - """ ) ) t = pkgdir.ensure("tests", "test_app.py") t.write( - _pytest._code.Source( + textwrap.dedent( + """\ + import sys + def test_app(): + assert sys._myapp == "hello" """ - import sys - def test_app(): - assert sys._myapp == "hello" - """ ) ) reprec = testdir.inline_run("-s") @@ -2715,17 +2714,17 @@ def finalize(): ) b = testdir.mkdir("subdir") b.join("test_overridden_fixture_finalizer.py").write( - dedent( - """ - import pytest - @pytest.fixture - def browser(browser): - browser['visited'] = True - return browser + textwrap.dedent( + """\ + import pytest + @pytest.fixture + def browser(browser): + browser['visited'] = True + return browser - def test_browser(browser): - assert browser['visited'] is True - """ + def test_browser(browser): + assert browser['visited'] is True + """ ) ) reprec = testdir.runpytest("-s") @@ -3217,120 +3216,119 @@ def test_hello(): def test_show_fixtures_trimmed_doc(self, testdir): p = testdir.makepyfile( - dedent( - ''' - import pytest - @pytest.fixture - def arg1(): - """ - line1 - line2 + textwrap.dedent( + '''\ + import pytest + @pytest.fixture + def arg1(): + """ + line1 + line2 - """ - @pytest.fixture - def arg2(): - """ - line1 - line2 + """ + @pytest.fixture + def arg2(): + """ + line1 + line2 - """ - ''' + """ + ''' ) ) result = testdir.runpytest("--fixtures", p) result.stdout.fnmatch_lines( - dedent( + textwrap.dedent( + """\ + * fixtures defined from test_show_fixtures_trimmed_doc * + arg2 + line1 + line2 + arg1 + line1 + line2 """ - * fixtures defined from test_show_fixtures_trimmed_doc * - arg2 - line1 - line2 - arg1 - line1 - line2 - - """ ) ) def test_show_fixtures_indented_doc(self, testdir): p = testdir.makepyfile( - dedent( + textwrap.dedent( + '''\ + import pytest + @pytest.fixture + def fixture1(): + """ + line1 + indented line + """ ''' - import pytest - @pytest.fixture - def fixture1(): - """ - line1 - indented line - """ - ''' ) ) result = testdir.runpytest("--fixtures", p) result.stdout.fnmatch_lines( - dedent( + textwrap.dedent( + """\ + * fixtures defined from test_show_fixtures_indented_doc * + fixture1 + line1 + indented line """ - * fixtures defined from test_show_fixtures_indented_doc * - fixture1 - line1 - indented line - """ ) ) def test_show_fixtures_indented_doc_first_line_unindented(self, testdir): p = testdir.makepyfile( - dedent( + textwrap.dedent( + '''\ + import pytest + @pytest.fixture + def fixture1(): + """line1 + line2 + indented line + """ ''' - import pytest - @pytest.fixture - def fixture1(): - """line1 - line2 - indented line - """ - ''' ) ) result = testdir.runpytest("--fixtures", p) result.stdout.fnmatch_lines( - dedent( + textwrap.dedent( + """\ + * fixtures defined from test_show_fixtures_indented_doc_first_line_unindented * + fixture1 + line1 + line2 + indented line """ - * fixtures defined from test_show_fixtures_indented_doc_first_line_unindented * - fixture1 - line1 - line2 - indented line - """ ) ) def test_show_fixtures_indented_in_class(self, testdir): p = testdir.makepyfile( - dedent( + textwrap.dedent( + '''\ + import pytest + class TestClass(object): + @pytest.fixture + def fixture1(self): + """line1 + line2 + indented line + """ ''' - import pytest - class TestClass(object): - @pytest.fixture - def fixture1(self): - """line1 - line2 - indented line - """ - ''' ) ) result = testdir.runpytest("--fixtures", p) result.stdout.fnmatch_lines( - dedent( + textwrap.dedent( + """\ + * fixtures defined from test_show_fixtures_indented_in_class * + fixture1 + line1 + line2 + indented line """ - * fixtures defined from test_show_fixtures_indented_in_class * - fixture1 - line1 - line2 - indented line - """ ) ) @@ -3667,26 +3665,26 @@ def test_non_relative_path(self, testdir): fixdir = testdir.mkdir("fixtures") fixfile = fixdir.join("fix.py") fixfile.write( - _pytest._code.Source( - """ - import pytest + textwrap.dedent( + """\ + import pytest - @pytest.fixture(params=[0, 1, 2]) - def fix_with_param(request): - return request.param - """ + @pytest.fixture(params=[0, 1, 2]) + def fix_with_param(request): + return request.param + """ ) ) testfile = tests_dir.join("test_foos.py") testfile.write( - _pytest._code.Source( - """ - from fix import fix_with_param + textwrap.dedent( + """\ + from fix import fix_with_param - def test_foo(request): - request.getfixturevalue('fix_with_param') - """ + def test_foo(request): + request.getfixturevalue('fix_with_param') + """ ) ) @@ -3698,9 +3696,9 @@ def test_foo(request): E*Failed: The requested fixture has no parameter defined for the current test. E* E*Requested fixture 'fix_with_param' defined in: - E*fix.py:5 + E*fix.py:4 E*Requested here: - E*test_foos.py:5 + E*test_foos.py:4 *1 failed* """ ) diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index db1cd77c557..f5d839f086e 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -2,7 +2,7 @@ import re import sys import attr -import _pytest._code +import textwrap import pytest from _pytest import python, fixtures @@ -1271,19 +1271,19 @@ def test_generate_tests_only_done_in_subdir(self, testdir): sub1 = testdir.mkpydir("sub1") sub2 = testdir.mkpydir("sub2") sub1.join("conftest.py").write( - _pytest._code.Source( + textwrap.dedent( + """\ + def pytest_generate_tests(metafunc): + assert metafunc.function.__name__ == "test_1" """ - def pytest_generate_tests(metafunc): - assert metafunc.function.__name__ == "test_1" - """ ) ) sub2.join("conftest.py").write( - _pytest._code.Source( + textwrap.dedent( + """\ + def pytest_generate_tests(metafunc): + assert metafunc.function.__name__ == "test_2" """ - def pytest_generate_tests(metafunc): - assert metafunc.function.__name__ == "test_2" - """ ) ) sub1.join("test_in_sub1.py").write("def test_1(): pass") diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py index 1ba3f947311..4074b80dc3e 100644 --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -1039,14 +1039,14 @@ def test_get_data_support(self, testdir): """ path = testdir.mkpydir("foo") path.join("test_foo.py").write( - _pytest._code.Source( + textwrap.dedent( + """\ + class Test(object): + def test_foo(self): + import pkgutil + data = pkgutil.get_data('foo.test_foo', 'data.txt') + assert data == b'Hey' """ - class Test(object): - def test_foo(self): - import pkgutil - data = pkgutil.get_data('foo.test_foo', 'data.txt') - assert data == b'Hey' - """ ) ) path.join("data.txt").write("Hey") diff --git a/testing/test_cacheprovider.py b/testing/test_cacheprovider.py index 7ec73ec63eb..cfeb4a0cf39 100644 --- a/testing/test_cacheprovider.py +++ b/testing/test_cacheprovider.py @@ -1,9 +1,9 @@ from __future__ import absolute_import, division, print_function import sys +import textwrap import py -import _pytest import pytest import os import shutil @@ -224,17 +224,17 @@ def test_3(): result = testdir.runpytest() result.stdout.fnmatch_lines(["*2 failed*"]) p.write( - _pytest._code.Source( - """ - def test_1(): - assert 1 + textwrap.dedent( + """\ + def test_1(): + assert 1 - def test_2(): - assert 1 + def test_2(): + assert 1 - def test_3(): - assert 0 - """ + def test_3(): + assert 0 + """ ) ) result = testdir.runpytest("--lf") @@ -252,19 +252,19 @@ def test_3(): def test_failedfirst_order(self, testdir): testdir.tmpdir.join("test_a.py").write( - _pytest._code.Source( + textwrap.dedent( + """\ + def test_always_passes(): + assert 1 """ - def test_always_passes(): - assert 1 - """ ) ) testdir.tmpdir.join("test_b.py").write( - _pytest._code.Source( + textwrap.dedent( + """\ + def test_always_fails(): + assert 0 """ - def test_always_fails(): - assert 0 - """ ) ) result = testdir.runpytest() @@ -277,14 +277,14 @@ def test_always_fails(): def test_lastfailed_failedfirst_order(self, testdir): testdir.makepyfile( **{ - "test_a.py": """ + "test_a.py": """\ def test_always_passes(): assert 1 - """, - "test_b.py": """ + """, + "test_b.py": """\ def test_always_fails(): assert 0 - """, + """, } ) result = testdir.runpytest() @@ -298,16 +298,16 @@ def test_always_fails(): def test_lastfailed_difference_invocations(self, testdir, monkeypatch): monkeypatch.setenv("PYTHONDONTWRITEBYTECODE", 1) testdir.makepyfile( - test_a=""" + test_a="""\ def test_a1(): assert 0 def test_a2(): assert 1 - """, - test_b=""" + """, + test_b="""\ def test_b1(): assert 0 - """, + """, ) p = testdir.tmpdir.join("test_a.py") p2 = testdir.tmpdir.join("test_b.py") @@ -317,11 +317,11 @@ def test_b1(): result = testdir.runpytest("--lf", p2) result.stdout.fnmatch_lines(["*1 failed*"]) p2.write( - _pytest._code.Source( + textwrap.dedent( + """\ + def test_b1(): + assert 1 """ - def test_b1(): - assert 1 - """ ) ) result = testdir.runpytest("--lf", p2) @@ -332,18 +332,18 @@ def test_b1(): def test_lastfailed_usecase_splice(self, testdir, monkeypatch): monkeypatch.setenv("PYTHONDONTWRITEBYTECODE", 1) testdir.makepyfile( - """ + """\ def test_1(): assert 0 - """ + """ ) p2 = testdir.tmpdir.join("test_something.py") p2.write( - _pytest._code.Source( + textwrap.dedent( + """\ + def test_2(): + assert 0 """ - def test_2(): - assert 0 - """ ) ) result = testdir.runpytest() diff --git a/testing/test_capture.py b/testing/test_capture.py index 475f619244c..ec08f235fe1 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -6,9 +6,9 @@ import pickle import os import sys +import textwrap from io import UnsupportedOperation -import _pytest._code import py import pytest import contextlib @@ -269,7 +269,7 @@ def test_func(): def test_capturing_outerr(self, testdir): p1 = testdir.makepyfile( - """ + """\ import sys def test_capturing(): print (42) @@ -278,7 +278,7 @@ def test_capturing_error(): print (1) sys.stderr.write(str(2)) raise ValueError - """ + """ ) result = testdir.runpytest(p1) result.stdout.fnmatch_lines( @@ -298,21 +298,21 @@ def test_capturing_error(): class TestLoggingInteraction(object): def test_logging_stream_ownership(self, testdir): p = testdir.makepyfile( - """ + """\ def test_logging(): import logging import pytest stream = capture.CaptureIO() logging.basicConfig(stream=stream) stream.close() # to free memory/release resources - """ + """ ) result = testdir.runpytest_subprocess(p) assert result.stderr.str().find("atexit") == -1 def test_logging_and_immediate_setupteardown(self, testdir): p = testdir.makepyfile( - """ + """\ import logging def setup_function(function): logging.warn("hello1") @@ -324,7 +324,7 @@ def test_logging(): def teardown_function(function): logging.warn("hello3") assert 0 - """ + """ ) for optargs in (("--capture=sys",), ("--capture=fd",)): print(optargs) @@ -338,7 +338,7 @@ def teardown_function(function): def test_logging_and_crossscope_fixtures(self, testdir): p = testdir.makepyfile( - """ + """\ import logging def setup_module(function): logging.warn("hello1") @@ -350,7 +350,7 @@ def test_logging(): def teardown_module(function): logging.warn("hello3") assert 0 - """ + """ ) for optargs in (("--capture=sys",), ("--capture=fd",)): print(optargs) @@ -364,11 +364,11 @@ def teardown_module(function): def test_conftestlogging_is_shown(self, testdir): testdir.makeconftest( - """ + """\ import logging logging.basicConfig() logging.warn("hello435") - """ + """ ) # make sure that logging is still captured in tests result = testdir.runpytest_subprocess("-s", "-p", "no:capturelog") @@ -378,19 +378,19 @@ def test_conftestlogging_is_shown(self, testdir): def test_conftestlogging_and_test_logging(self, testdir): testdir.makeconftest( - """ + """\ import logging logging.basicConfig() - """ + """ ) # make sure that logging is still captured in tests p = testdir.makepyfile( - """ + """\ def test_hello(): import logging logging.warn("hello433") assert 0 - """ + """ ) result = testdir.runpytest_subprocess(p, "-p", "no:capturelog") assert result.ret != 0 @@ -403,24 +403,24 @@ class TestCaptureFixture(object): @pytest.mark.parametrize("opt", [[], ["-s"]]) def test_std_functional(self, testdir, opt): reprec = testdir.inline_runsource( - """ + """\ def test_hello(capsys): print (42) out, err = capsys.readouterr() assert out.startswith("42") - """, + """, *opt ) reprec.assertoutcome(passed=1) def test_capsyscapfd(self, testdir): p = testdir.makepyfile( - """ + """\ def test_one(capsys, capfd): pass def test_two(capfd, capsys): pass - """ + """ ) result = testdir.runpytest(p) result.stdout.fnmatch_lines( @@ -438,12 +438,12 @@ def test_capturing_getfixturevalue(self, testdir): in the same test is an error. """ testdir.makepyfile( - """ + """\ def test_one(capsys, request): request.getfixturevalue("capfd") def test_two(capfd, request): request.getfixturevalue("capsys") - """ + """ ) result = testdir.runpytest() result.stdout.fnmatch_lines( @@ -458,10 +458,10 @@ def test_two(capfd, request): def test_capsyscapfdbinary(self, testdir): p = testdir.makepyfile( - """ + """\ def test_one(capsys, capfdbinary): pass - """ + """ ) result = testdir.runpytest(p) result.stdout.fnmatch_lines( @@ -471,12 +471,13 @@ def test_one(capsys, capfdbinary): @pytest.mark.parametrize("method", ["sys", "fd"]) def test_capture_is_represented_on_failure_issue128(self, testdir, method): p = testdir.makepyfile( - """ - def test_hello(cap%s): + """\ + def test_hello(cap{}): print ("xxx42xxx") assert 0 - """ - % method + """.format( + method + ) ) result = testdir.runpytest(p) result.stdout.fnmatch_lines(["xxx42xxx"]) @@ -484,21 +485,21 @@ def test_hello(cap%s): @needsosdup def test_stdfd_functional(self, testdir): reprec = testdir.inline_runsource( - """ + """\ def test_hello(capfd): import os os.write(1, "42".encode('ascii')) out, err = capfd.readouterr() assert out.startswith("42") capfd.close() - """ + """ ) reprec.assertoutcome(passed=1) @needsosdup def test_capfdbinary(self, testdir): reprec = testdir.inline_runsource( - """ + """\ def test_hello(capfdbinary): import os # some likely un-decodable bytes @@ -506,7 +507,7 @@ def test_hello(capfdbinary): out, err = capfdbinary.readouterr() assert out == b'\\xfe\\x98\\x20' assert err == b'' - """ + """ ) reprec.assertoutcome(passed=1) @@ -515,7 +516,7 @@ def test_hello(capfdbinary): ) def test_capsysbinary(self, testdir): reprec = testdir.inline_runsource( - """ + """\ def test_hello(capsysbinary): import sys # some likely un-decodable bytes @@ -523,7 +524,7 @@ def test_hello(capsysbinary): out, err = capsysbinary.readouterr() assert out == b'\\xfe\\x98\\x20' assert err == b'' - """ + """ ) reprec.assertoutcome(passed=1) @@ -532,10 +533,10 @@ def test_hello(capsysbinary): ) def test_capsysbinary_forbidden_in_python2(self, testdir): testdir.makepyfile( - """ + """\ def test_hello(capsysbinary): pass - """ + """ ) result = testdir.runpytest() result.stdout.fnmatch_lines( @@ -548,10 +549,10 @@ def test_hello(capsysbinary): def test_partial_setup_failure(self, testdir): p = testdir.makepyfile( - """ + """\ def test_hello(capsys, missingarg): pass - """ + """ ) result = testdir.runpytest(p) result.stdout.fnmatch_lines(["*test_partial_setup_failure*", "*1 error*"]) @@ -559,12 +560,12 @@ def test_hello(capsys, missingarg): @needsosdup def test_keyboardinterrupt_disables_capturing(self, testdir): p = testdir.makepyfile( - """ + """\ def test_hello(capfd): import os os.write(1, str(42).encode('ascii')) raise KeyboardInterrupt() - """ + """ ) result = testdir.runpytest_subprocess(p) result.stdout.fnmatch_lines(["*KeyboardInterrupt*"]) @@ -573,7 +574,7 @@ def test_hello(capfd): @pytest.mark.issue14 def test_capture_and_logging(self, testdir): p = testdir.makepyfile( - """ + """\ import logging def test_log(capsys): logging.error('x') @@ -586,7 +587,7 @@ def test_log(capsys): @pytest.mark.parametrize("no_capture", [True, False]) def test_disabled_capture_fixture(self, testdir, fixture, no_capture): testdir.makepyfile( - """ + """\ def test_disabled({fixture}): print('captured before') with {fixture}.disabled(): @@ -620,7 +621,7 @@ def test_fixture_use_by_other_fixtures(self, testdir, fixture): Ensure that capsys and capfd can be used by other fixtures during setup and teardown. """ testdir.makepyfile( - """ + """\ from __future__ import print_function import sys import pytest @@ -656,7 +657,7 @@ def test_captured_print(captured_print): def test_fixture_use_by_other_fixtures_teardown(self, testdir, cap): """Ensure we can access setup and teardown buffers from teardown when using capsys/capfd (##3033)""" testdir.makepyfile( - """ + """\ import sys import pytest import os @@ -684,11 +685,11 @@ def test_a(fix): def test_setup_failure_does_not_kill_capturing(testdir): sub1 = testdir.mkpydir("sub1") sub1.join("conftest.py").write( - _pytest._code.Source( + textwrap.dedent( + """\ + def pytest_runtest_setup(item): + raise ValueError(42) """ - def pytest_runtest_setup(item): - raise ValueError(42) - """ ) ) sub1.join("test_mod.py").write("def test_func1(): pass") diff --git a/testing/test_collection.py b/testing/test_collection.py index 5b494ba31af..ce0e3a920bd 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -1,9 +1,9 @@ from __future__ import absolute_import, division, print_function import pprint import sys +import textwrap import pytest -import _pytest._code from _pytest.main import Session, EXIT_NOTESTSCOLLECTED, _in_venv @@ -913,13 +913,13 @@ def test_fixture_scope_sibling_conftests(testdir): """Regression test case for https://github.com/pytest-dev/pytest/issues/2836""" foo_path = testdir.mkdir("foo") foo_path.join("conftest.py").write( - _pytest._code.Source( + textwrap.dedent( + """\ + import pytest + @pytest.fixture + def fix(): + return 1 """ - import pytest - @pytest.fixture - def fix(): - return 1 - """ ) ) foo_path.join("test_foo.py").write("def test_foo(fix): assert fix == 1") diff --git a/testing/test_config.py b/testing/test_config.py index ef9dacd9c5b..756b51de492 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -17,11 +17,11 @@ def test_getcfg_and_config(self, testdir, tmpdir, section, filename): sub = tmpdir.mkdir("sub") sub.chdir() tmpdir.join(filename).write( - _pytest._code.Source( - """ - [{section}] - name = value - """.format( + textwrap.dedent( + """\ + [{section}] + name = value + """.format( section=section ) ) @@ -38,11 +38,11 @@ def test_getcfg_empty_path(self): def test_append_parse_args(self, testdir, tmpdir, monkeypatch): monkeypatch.setenv("PYTEST_ADDOPTS", '--color no -rs --tb="short"') tmpdir.join("pytest.ini").write( - _pytest._code.Source( + textwrap.dedent( + """\ + [pytest] + addopts = --verbose """ - [pytest] - addopts = --verbose - """ ) ) config = testdir.parseconfig(tmpdir) @@ -438,11 +438,11 @@ def test_origargs(self): def test_inifilename(self, tmpdir): tmpdir.join("foo/bar.ini").ensure().write( - _pytest._code.Source( + textwrap.dedent( + """\ + [pytest] + name = value """ - [pytest] - name = value - """ ) ) @@ -453,12 +453,12 @@ def test_inifilename(self, tmpdir): cwd = tmpdir.join("a/b") cwd.join("pytest.ini").ensure().write( - _pytest._code.Source( + textwrap.dedent( + """\ + [pytest] + name = wrong-value + should_not_be_set = true """ - [pytest] - name = wrong-value - should_not_be_set = true - """ ) ) with cwd.ensure(dir=True).as_cwd(): diff --git a/testing/test_conftest.py b/testing/test_conftest.py index 449ef528181..f3b5bac38d8 100644 --- a/testing/test_conftest.py +++ b/testing/test_conftest.py @@ -1,7 +1,6 @@ from __future__ import absolute_import, division, print_function -from textwrap import dedent +import textwrap -import _pytest._code import py import pytest from _pytest.config import PytestPluginManager @@ -174,11 +173,11 @@ def test_conftest_confcutdir(testdir): testdir.makeconftest("assert 0") x = testdir.mkdir("x") x.join("conftest.py").write( - _pytest._code.Source( + textwrap.dedent( + """\ + def pytest_addoption(parser): + parser.addoption("--xyz", action="store_true") """ - def pytest_addoption(parser): - parser.addoption("--xyz", action="store_true") - """ ) ) result = testdir.runpytest("-h", "--confcutdir=%s" % x, x) @@ -198,11 +197,11 @@ def test_no_conftest(testdir): def test_conftest_existing_resultlog(testdir): x = testdir.mkdir("tests") x.join("conftest.py").write( - _pytest._code.Source( + textwrap.dedent( + """\ + def pytest_addoption(parser): + parser.addoption("--xyz", action="store_true") """ - def pytest_addoption(parser): - parser.addoption("--xyz", action="store_true") - """ ) ) testdir.makefile(ext=".log", result="") # Writes result.log @@ -213,11 +212,11 @@ def pytest_addoption(parser): def test_conftest_existing_junitxml(testdir): x = testdir.mkdir("tests") x.join("conftest.py").write( - _pytest._code.Source( + textwrap.dedent( + """\ + def pytest_addoption(parser): + parser.addoption("--xyz", action="store_true") """ - def pytest_addoption(parser): - parser.addoption("--xyz", action="store_true") - """ ) ) testdir.makefile(ext=".xml", junit="") # Writes junit.xml @@ -247,38 +246,38 @@ def test_fixture_dependency(testdir, monkeypatch): sub = testdir.mkdir("sub") sub.join("__init__.py").write("") sub.join("conftest.py").write( - dedent( - """ - import pytest + textwrap.dedent( + """\ + import pytest - @pytest.fixture - def not_needed(): - assert False, "Should not be called!" + @pytest.fixture + def not_needed(): + assert False, "Should not be called!" - @pytest.fixture - def foo(): - assert False, "Should not be called!" + @pytest.fixture + def foo(): + assert False, "Should not be called!" - @pytest.fixture - def bar(foo): - return 'bar' - """ + @pytest.fixture + def bar(foo): + return 'bar' + """ ) ) subsub = sub.mkdir("subsub") subsub.join("__init__.py").write("") subsub.join("test_bar.py").write( - dedent( - """ - import pytest + textwrap.dedent( + """\ + import pytest - @pytest.fixture - def bar(): - return 'sub bar' + @pytest.fixture + def bar(): + return 'sub bar' - def test_event_fixture(bar): - assert bar == 'sub bar' - """ + def test_event_fixture(bar): + assert bar == 'sub bar' + """ ) ) result = testdir.runpytest("sub") @@ -288,11 +287,11 @@ def test_event_fixture(bar): def test_conftest_found_with_double_dash(testdir): sub = testdir.mkdir("sub") sub.join("conftest.py").write( - dedent( + textwrap.dedent( + """\ + def pytest_addoption(parser): + parser.addoption("--hello-world", action="store_true") """ - def pytest_addoption(parser): - parser.addoption("--hello-world", action="store_true") - """ ) ) p = sub.join("test_hello.py") @@ -313,56 +312,54 @@ def _setup_tree(self, testdir): # for issue616 package = testdir.mkdir("package") package.join("conftest.py").write( - dedent( + textwrap.dedent( """\ - import pytest - @pytest.fixture - def fxtr(): - return "from-package" - """ + import pytest + @pytest.fixture + def fxtr(): + return "from-package" + """ ) ) package.join("test_pkgroot.py").write( - dedent( + textwrap.dedent( """\ - def test_pkgroot(fxtr): - assert fxtr == "from-package" - """ + def test_pkgroot(fxtr): + assert fxtr == "from-package" + """ ) ) swc = package.mkdir("swc") swc.join("__init__.py").ensure() swc.join("conftest.py").write( - dedent( + textwrap.dedent( """\ - import pytest - @pytest.fixture - def fxtr(): - return "from-swc" - """ + import pytest + @pytest.fixture + def fxtr(): + return "from-swc" + """ ) ) swc.join("test_with_conftest.py").write( - dedent( + textwrap.dedent( """\ - def test_with_conftest(fxtr): - assert fxtr == "from-swc" - - """ + def test_with_conftest(fxtr): + assert fxtr == "from-swc" + """ ) ) snc = package.mkdir("snc") snc.join("__init__.py").ensure() snc.join("test_no_conftest.py").write( - dedent( + textwrap.dedent( """\ - def test_no_conftest(fxtr): - assert fxtr == "from-package" # No local conftest.py, so should - # use value from parent dir's - - """ + def test_no_conftest(fxtr): + assert fxtr == "from-package" # No local conftest.py, so should + # use value from parent dir's + """ ) ) print("created directory structure:") @@ -422,31 +419,31 @@ def test_search_conftest_up_to_inifile(testdir, confcutdir, passed, error): src = root.join("src").ensure(dir=1) src.join("pytest.ini").write("[pytest]") src.join("conftest.py").write( - _pytest._code.Source( + textwrap.dedent( + """\ + import pytest + @pytest.fixture + def fix1(): pass """ - import pytest - @pytest.fixture - def fix1(): pass - """ ) ) src.join("test_foo.py").write( - _pytest._code.Source( + textwrap.dedent( + """\ + def test_1(fix1): + pass + def test_2(out_of_reach): + pass """ - def test_1(fix1): - pass - def test_2(out_of_reach): - pass - """ ) ) root.join("conftest.py").write( - _pytest._code.Source( + textwrap.dedent( + """\ + import pytest + @pytest.fixture + def out_of_reach(): pass """ - import pytest - @pytest.fixture - def out_of_reach(): pass - """ ) ) @@ -464,19 +461,19 @@ def out_of_reach(): pass def test_issue1073_conftest_special_objects(testdir): testdir.makeconftest( - """ + """\ class DontTouchMe(object): def __getattr__(self, x): raise Exception('cant touch me') x = DontTouchMe() - """ + """ ) testdir.makepyfile( - """ + """\ def test_some(): pass - """ + """ ) res = testdir.runpytest() assert res.ret == 0 @@ -484,15 +481,15 @@ def test_some(): def test_conftest_exception_handling(testdir): testdir.makeconftest( - """ + """\ raise ValueError() - """ + """ ) testdir.makepyfile( - """ + """\ def test_some(): pass - """ + """ ) res = testdir.runpytest() assert res.ret == 4 @@ -507,7 +504,7 @@ def test_hook_proxy(testdir): **{ "root/demo-0/test_foo1.py": "def test1(): pass", "root/demo-a/test_foo2.py": "def test1(): pass", - "root/demo-a/conftest.py": """ + "root/demo-a/conftest.py": """\ def pytest_ignore_collect(path, config): return True """, @@ -525,11 +522,11 @@ def test_required_option_help(testdir): testdir.makeconftest("assert 0") x = testdir.mkdir("x") x.join("conftest.py").write( - _pytest._code.Source( + textwrap.dedent( + """\ + def pytest_addoption(parser): + parser.addoption("--xyz", action="store_true", required=True) """ - def pytest_addoption(parser): - parser.addoption("--xyz", action="store_true", required=True) - """ ) ) result = testdir.runpytest("-h", x) diff --git a/testing/test_doctest.py b/testing/test_doctest.py index 6a84c5febc9..d7815b1cf94 100644 --- a/testing/test_doctest.py +++ b/testing/test_doctest.py @@ -1,7 +1,7 @@ # encoding: utf-8 from __future__ import absolute_import, division, print_function import sys -import _pytest._code +import textwrap from _pytest.compat import MODULE_NOT_FOUND_ERROR from _pytest.doctest import DoctestItem, DoctestModule, DoctestTextfile import pytest @@ -258,16 +258,16 @@ def foo(): def test_doctest_linedata_missing(self, testdir): testdir.tmpdir.join("hello.py").write( - _pytest._code.Source( + textwrap.dedent( + """\ + class Fun(object): + @property + def test(self): + ''' + >>> a = 1 + >>> 1/0 + ''' """ - class Fun(object): - @property - def test(self): - ''' - >>> a = 1 - >>> 1/0 - ''' - """ ) ) result = testdir.runpytest("--doctest-modules") @@ -300,10 +300,10 @@ def test_doctest_unex_importerror_only_txt(self, testdir): def test_doctest_unex_importerror_with_module(self, testdir): testdir.tmpdir.join("hello.py").write( - _pytest._code.Source( + textwrap.dedent( + """\ + import asdalsdkjaslkdjasd """ - import asdalsdkjaslkdjasd - """ ) ) testdir.maketxtfile( @@ -339,27 +339,27 @@ def test_doctestmodule(self, testdir): def test_doctestmodule_external_and_issue116(self, testdir): p = testdir.mkpydir("hello") p.join("__init__.py").write( - _pytest._code.Source( + textwrap.dedent( + """\ + def somefunc(): + ''' + >>> i = 0 + >>> i + 1 + 2 + ''' """ - def somefunc(): - ''' - >>> i = 0 - >>> i + 1 - 2 - ''' - """ ) ) result = testdir.runpytest(p, "--doctest-modules") result.stdout.fnmatch_lines( [ - "004 *>>> i = 0", - "005 *>>> i + 1", + "003 *>>> i = 0", + "004 *>>> i + 1", "*Expected:", "* 2", "*Got:", "* 1", - "*:5: DocTestFailure", + "*:4: DocTestFailure", ] ) diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 88e5287e81f..0f674a34fcc 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -4,9 +4,9 @@ from __future__ import absolute_import, division, print_function import collections import sys +import textwrap import pluggy -import _pytest._code import py import pytest from _pytest.main import EXIT_NOTESTSCOLLECTED @@ -161,12 +161,12 @@ class TestMore(BaseTests): def test_itemreport_directclasses_not_shown_as_subclasses(self, testdir): a = testdir.mkpydir("a123") a.join("test_hello123.py").write( - _pytest._code.Source( + textwrap.dedent( + """\ + class TestClass(object): + def test_method(self): + pass """ - class TestClass(object): - def test_method(self): - pass - """ ) ) result = testdir.runpytest("-v") @@ -312,13 +312,13 @@ def test_collectonly_error(self, testdir): result = testdir.runpytest("--collect-only", p) assert result.ret == 2 result.stdout.fnmatch_lines( - _pytest._code.Source( + textwrap.dedent( + """\ + *ERROR* + *ImportError* + *No module named *Errlk* + *1 error* """ - *ERROR* - *ImportError* - *No module named *Errlk* - *1 error* - """ ).strip() ) From 3f336869e2e632c6f66432065073a45046f39ccc Mon Sep 17 00:00:00 2001 From: Gandalf Saxe Date: Thu, 23 Aug 2018 18:07:28 +0200 Subject: [PATCH 38/62] Move information on `pip install -e` to the top Should fix complaints in #2421. --- doc/en/goodpractices.rst | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/doc/en/goodpractices.rst b/doc/en/goodpractices.rst index d9c68529941..b1bb0e81aa9 100644 --- a/doc/en/goodpractices.rst +++ b/doc/en/goodpractices.rst @@ -4,6 +4,27 @@ Good Integration Practices ================================================= +Install package with pip +------------------------------------------------- + +For development, we recommend to use virtualenv_ environments and pip_ +for installing your application and any dependencies +as well as the ``pytest`` package itself. This ensures your code and +dependencies are isolated from the system Python installation. + +First you need to place a `setup.py` file in the root of your package with the following minimum content: + + from setuptools import setup, find_packages + + setup(name="PACKAGENAME", packages=find_packages()) + +Where `PACKAGENAME` is the name of your package. You can then install your package in "editable" mode by running from the same directory:: + + pip install -e . + +which lets you change your source code (both tests and application) and rerun tests at will. +This is similar to running ``python setup.py develop`` or ``conda develop`` in that it installs +your package using a symlink to your development code. .. _`test discovery`: .. _`Python test discovery`: @@ -177,19 +198,6 @@ Note that this layout also works in conjunction with the ``src`` layout mentione tox ------ -For development, we recommend to use virtualenv_ environments and pip_ -for installing your application and any dependencies -as well as the ``pytest`` package itself. This ensures your code and -dependencies are isolated from the system Python installation. - -You can then install your package in "editable" mode:: - - pip install -e . - -which lets you change your source code (both tests and application) and rerun tests at will. -This is similar to running ``python setup.py develop`` or ``conda develop`` in that it installs -your package using a symlink to your development code. - Once you are done with your work and want to make sure that your actual package passes all tests you may want to look into `tox`_, the virtualenv test automation tool and its `pytest support From 40b4fe64af7446f56b612d0ea26bb2e0a5b4705a Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 23 Aug 2018 22:11:17 -0300 Subject: [PATCH 39/62] Fix linting --- doc/en/goodpractices.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/en/goodpractices.rst b/doc/en/goodpractices.rst index b1bb0e81aa9..87d446d0d8d 100644 --- a/doc/en/goodpractices.rst +++ b/doc/en/goodpractices.rst @@ -12,13 +12,14 @@ for installing your application and any dependencies as well as the ``pytest`` package itself. This ensures your code and dependencies are isolated from the system Python installation. -First you need to place a `setup.py` file in the root of your package with the following minimum content: +First you need to place a ``setup.py`` file in the root of your package with the following minimum content: from setuptools import setup, find_packages - + + setup(name="PACKAGENAME", packages=find_packages()) -Where `PACKAGENAME` is the name of your package. You can then install your package in "editable" mode by running from the same directory:: +Where ``PACKAGENAME`` is the name of your package. You can then install your package in "editable" mode by running from the same directory:: pip install -e . From 99e31f6fb17177b60046dd2fc44c0a313cf4b8aa Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Thu, 23 Aug 2018 18:55:21 -0700 Subject: [PATCH 40/62] Use `bytes` directly instead of `binary_type` `bytes` is an alias for `str` in python2.6+ --- src/_pytest/assertion/rewrite.py | 14 ++++++-------- src/_pytest/assertion/util.py | 4 ++-- testing/test_capture.py | 6 +++--- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/_pytest/assertion/rewrite.py b/src/_pytest/assertion/rewrite.py index 952cfb59441..5cf63a0639a 100644 --- a/src/_pytest/assertion/rewrite.py +++ b/src/_pytest/assertion/rewrite.py @@ -402,12 +402,11 @@ def _saferepr(obj): JSON reprs. """ - repr = py.io.saferepr(obj) - if isinstance(repr, six.text_type): - t = six.text_type + r = py.io.saferepr(obj) + if isinstance(r, six.text_type): + return r.replace(u"\n", u"\\n") else: - t = six.binary_type - return repr.replace(t("\n"), t("\\n")) + return r.replace(b"\n", b"\\n") from _pytest.assertion.util import format_explanation as _format_explanation # noqa @@ -446,10 +445,9 @@ def _should_repr_global_name(obj): def _format_boolop(explanations, is_or): explanation = "(" + (is_or and " or " or " and ").join(explanations) + ")" if isinstance(explanation, six.text_type): - t = six.text_type + return explanation.replace(u"%", u"%%") else: - t = six.binary_type - return explanation.replace(t("%"), t("%%")) + return explanation.replace(b"%", b"%%") def _call_reprcompare(ops, results, expls, each_obj): diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index 08213c80e01..a3013cb9838 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -187,9 +187,9 @@ def escape_for_readable_diff(binary_text): r = r.replace(r"\r", "\r") return r - if isinstance(left, six.binary_type): + if isinstance(left, bytes): left = escape_for_readable_diff(left) - if isinstance(right, six.binary_type): + if isinstance(right, bytes): right = escape_for_readable_diff(right) if not verbose: i = 0 # just in case left or right has zero length diff --git a/testing/test_capture.py b/testing/test_capture.py index ec08f235fe1..75d82ecde56 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -12,7 +12,7 @@ import py import pytest import contextlib -from six import binary_type, text_type +from six import text_type from _pytest import capture from _pytest.capture import CaptureManager from _pytest.main import EXIT_NOTESTSCOLLECTED @@ -24,12 +24,12 @@ def tobytes(obj): if isinstance(obj, text_type): obj = obj.encode("UTF-8") - assert isinstance(obj, binary_type) + assert isinstance(obj, bytes) return obj def totext(obj): - if isinstance(obj, binary_type): + if isinstance(obj, bytes): obj = text_type(obj, "UTF-8") assert isinstance(obj, text_type) return obj From 5f8b50c0946b17d5ee5054066cd2ee6dd75e187b Mon Sep 17 00:00:00 2001 From: turturica Date: Thu, 23 Aug 2018 22:48:44 -0700 Subject: [PATCH 41/62] Address #3796 and add a test for it. --- src/_pytest/python.py | 10 +++--- testing/python/fixture.py | 68 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 4 deletions(-) diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 891e071c6b2..1ecd9310ed4 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -597,14 +597,16 @@ def collect(self): if path.basename == "__init__.py" and path.dirpath() == this_path: continue - if path.isdir() and path.join('__init__.py').check(file=1): - pkg_prefixes.add(path) - for pkg_prefix in pkg_prefixes: - if pkg_prefix in path.parts() and pkg_prefix.join('__init__.py') == path: + if pkg_prefix in path.parts() and pkg_prefix.join('__init__.py') != path: skip = True + if skip: continue + + if path.isdir() and path.join('__init__.py').check(file=1): + pkg_prefixes.add(path) + for x in self._collectfile(path): yield x diff --git a/testing/python/fixture.py b/testing/python/fixture.py index bbfcf775be4..4170f8f3732 100644 --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -3979,3 +3979,71 @@ def test_func(self, f2, f1, m2): items, _ = testdir.inline_genitems() request = FixtureRequest(items[0]) assert request.fixturenames == "s1 p1 m1 m2 c1 f2 f1".split() + + def test_multiple_packages(self, testdir): + """Complex test involving multiple package fixtures. Make sure teardowns + are executed in order. + . + └── root + β”œβ”€β”€ __init__.py + β”œβ”€β”€ sub1 + β”‚ β”œβ”€β”€ __init__.py + β”‚ β”œβ”€β”€ conftest.py + β”‚ └── test_1.py + └── sub2 + β”œβ”€β”€ __init__.py + β”œβ”€β”€ conftest.py + └── test_2.py + """ + root = testdir.mkdir("root") + root.join("__init__.py").write("values = []") + sub1 = root.mkdir("sub1") + sub1.ensure("__init__.py") + sub1.join("conftest.py").write( + dedent( + """\ + import pytest + from .. import values + @pytest.fixture(scope="package") + def fix(): + values.append("pre-sub1") + yield values + values.pop() + """ + ) + ) + sub1.join("test_1.py").write( + dedent( + """\ + from .. import values + def test_1(fix): + assert values == ["pre-sub1"] + """ + ) + ) + sub2 = root.mkdir("sub2") + sub2.ensure("__init__.py") + sub2.join("conftest.py").write( + dedent( + """\ + import pytest + from .. import values + @pytest.fixture(scope="package") + def fix(): + values.append("pre-sub2") + yield values + values.pop() + """ + ) + ) + sub2.join("test_2.py").write( + dedent( + """\ + from .. import values + def test_2(fix): + assert values == ["pre-sub2"] + """ + ) + ) + reprec = testdir.inline_run() + reprec.assertoutcome(passed=2) From 72e6482994974741ddc2058807cf4d3f7b771ced Mon Sep 17 00:00:00 2001 From: turturica Date: Thu, 23 Aug 2018 22:58:36 -0700 Subject: [PATCH 42/62] Make linting happy. --- src/_pytest/python.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 1ecd9310ed4..d694ba67640 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -598,13 +598,16 @@ def collect(self): continue for pkg_prefix in pkg_prefixes: - if pkg_prefix in path.parts() and pkg_prefix.join('__init__.py') != path: + if ( + pkg_prefix in path.parts() + and pkg_prefix.join("__init__.py") != path + ): skip = True if skip: continue - if path.isdir() and path.join('__init__.py').check(file=1): + if path.isdir() and path.join("__init__.py").check(file=1): pkg_prefixes.add(path) for x in self._collectfile(path): From 459b040d2179ba6af0a8c51c1134a9979331959c Mon Sep 17 00:00:00 2001 From: turturica Date: Fri, 24 Aug 2018 11:54:04 -0700 Subject: [PATCH 43/62] Fix dedent after merge. --- testing/python/fixture.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/testing/python/fixture.py b/testing/python/fixture.py index 8306ae315b2..c36395b54f8 100644 --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -3998,7 +3998,7 @@ def test_multiple_packages(self, testdir): sub1 = root.mkdir("sub1") sub1.ensure("__init__.py") sub1.join("conftest.py").write( - dedent( + textwrap.dedent( """\ import pytest from .. import values @@ -4011,7 +4011,7 @@ def fix(): ) ) sub1.join("test_1.py").write( - dedent( + textwrap.dedent( """\ from .. import values def test_1(fix): @@ -4022,7 +4022,7 @@ def test_1(fix): sub2 = root.mkdir("sub2") sub2.ensure("__init__.py") sub2.join("conftest.py").write( - dedent( + textwrap.dedent( """\ import pytest from .. import values @@ -4035,7 +4035,7 @@ def fix(): ) ) sub2.join("test_2.py").write( - dedent( + textwrap.dedent( """\ from .. import values def test_2(fix): From 14ffadf004f8111338d201dcd7883b714df09a4d Mon Sep 17 00:00:00 2001 From: Andrew Champion Date: Fri, 24 Aug 2018 12:07:22 -0700 Subject: [PATCH 44/62] correct cmdclass --- doc/en/goodpractices.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/en/goodpractices.rst b/doc/en/goodpractices.rst index 87d446d0d8d..b3d903badd2 100644 --- a/doc/en/goodpractices.rst +++ b/doc/en/goodpractices.rst @@ -291,7 +291,7 @@ your own setuptools Test command for invoking pytest. setup( # ..., tests_require=["pytest"], - cmdclass={"test": PyTest}, + cmdclass={"pytest": PyTest}, ) Now if you run:: From e3df1031ca2662763a5d9ad7d9282e62accb8821 Mon Sep 17 00:00:00 2001 From: turturica Date: Fri, 24 Aug 2018 12:26:18 -0700 Subject: [PATCH 45/62] Add encoding: utf8 for python 2.7 --- testing/python/fixture.py | 1 + 1 file changed, 1 insertion(+) diff --git a/testing/python/fixture.py b/testing/python/fixture.py index c36395b54f8..47503b3407a 100644 --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- import textwrap import pytest From f6948597e41e4e8029345d42bffe67f36d797e3a Mon Sep 17 00:00:00 2001 From: Andrew Champion Date: Fri, 24 Aug 2018 12:29:18 -0700 Subject: [PATCH 46/62] add to changelog --- changelog/3870.doc.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/3870.doc.rst diff --git a/changelog/3870.doc.rst b/changelog/3870.doc.rst new file mode 100644 index 00000000000..c56e10d1145 --- /dev/null +++ b/changelog/3870.doc.rst @@ -0,0 +1 @@ +correct documentation for setuptools integration From dce8df45d5b86c7b20802ee790a8c6da2ecd55cb Mon Sep 17 00:00:00 2001 From: turturica Date: Fri, 24 Aug 2018 15:51:42 -0700 Subject: [PATCH 47/62] Added changelog items. --- changelog/3796.bugfix.rst | 2 ++ changelog/3854.bugfix.rst | 1 + testing/python/fixture.py | 4 ++-- 3 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 changelog/3796.bugfix.rst create mode 100644 changelog/3854.bugfix.rst diff --git a/changelog/3796.bugfix.rst b/changelog/3796.bugfix.rst new file mode 100644 index 00000000000..bc590815f55 --- /dev/null +++ b/changelog/3796.bugfix.rst @@ -0,0 +1,2 @@ +Fixed an issue where teardown of fixtures of consecutive sub-packages were executed once, at the end of the outer +package. \ No newline at end of file diff --git a/changelog/3854.bugfix.rst b/changelog/3854.bugfix.rst new file mode 100644 index 00000000000..72af08aba99 --- /dev/null +++ b/changelog/3854.bugfix.rst @@ -0,0 +1 @@ +Fixes double collection of tests within packages when the filename starts with a capital letter. \ No newline at end of file diff --git a/testing/python/fixture.py b/testing/python/fixture.py index 47503b3407a..f8f5eb54e34 100644 --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -4007,7 +4007,7 @@ def test_multiple_packages(self, testdir): def fix(): values.append("pre-sub1") yield values - values.pop() + assert values.pop() == "pre-sub1" """ ) ) @@ -4031,7 +4031,7 @@ def test_1(fix): def fix(): values.append("pre-sub2") yield values - values.pop() + assert values.pop() == "pre-sub2" """ ) ) From f0226e9329d364084be6bc22f0c11a8053ae9d9c Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 24 Aug 2018 20:10:37 -0300 Subject: [PATCH 48/62] Fix test_package_ordering on Windows --- testing/python/collect.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/testing/python/collect.py b/testing/python/collect.py index 52c06a42e1d..5fe85a012bb 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -1630,21 +1630,21 @@ def test_package_ordering(testdir): """ . └── root - β”œβ”€β”€ TestRoot.py + β”œβ”€β”€ check_root.py β”œβ”€β”€ __init__.py β”œβ”€β”€ sub1 - β”‚ β”œβ”€β”€ TestSub1.py + β”‚ β”œβ”€β”€ check_sub1.py β”‚ └── __init__.py └── sub2 └── test - β”œβ”€β”€ TestSub2.py + β”œβ”€β”€ check_sub2.py └── test_in_sub2.py """ testdir.makeini( """ [pytest] - python_files=Test*.py + python_files=check_*.py """ ) root = testdir.mkpydir("root") @@ -1653,9 +1653,9 @@ def test_package_ordering(testdir): sub2 = root.mkdir("sub2") sub2_test = sub2.mkdir("sub2") - root.join("TestRoot.py").write("def test_1(): pass") - sub1.join("TestSub1.py").write("def test_2(): pass") - sub2_test.join("TestSub2.py").write("def test_3(): pass") + root.join("check_root.py").write("def test_1(): pass") + sub1.join("check_sub1.py").write("def test_2(): pass") + sub2_test.join("check_sub2.py").write("def test_3(): pass") sub2_test.join("test_in_sub2.py").write("def test_4(): pass") # Execute from . From 8cf0e46bbf51bcf1d95019ce7c4d501e8cc4b7d7 Mon Sep 17 00:00:00 2001 From: turturica Date: Fri, 24 Aug 2018 16:23:50 -0700 Subject: [PATCH 49/62] test_package_ordering: Collect *.py, but keep a mix of case for filenames. The test doesn't make sense for Windows, because of its case-insensitivity. --- changelog/3796.bugfix.rst | 2 +- changelog/3854.bugfix.rst | 2 +- testing/python/collect.py | 16 +++++++--------- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/changelog/3796.bugfix.rst b/changelog/3796.bugfix.rst index bc590815f55..7a115301a95 100644 --- a/changelog/3796.bugfix.rst +++ b/changelog/3796.bugfix.rst @@ -1,2 +1,2 @@ Fixed an issue where teardown of fixtures of consecutive sub-packages were executed once, at the end of the outer -package. \ No newline at end of file +package. diff --git a/changelog/3854.bugfix.rst b/changelog/3854.bugfix.rst index 72af08aba99..6184f03b66e 100644 --- a/changelog/3854.bugfix.rst +++ b/changelog/3854.bugfix.rst @@ -1 +1 @@ -Fixes double collection of tests within packages when the filename starts with a capital letter. \ No newline at end of file +Fixes double collection of tests within packages when the filename starts with a capital letter. diff --git a/testing/python/collect.py b/testing/python/collect.py index 5fe85a012bb..8f4283e4072 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -1630,21 +1630,20 @@ def test_package_ordering(testdir): """ . └── root - β”œβ”€β”€ check_root.py + β”œβ”€β”€ Test_root.py β”œβ”€β”€ __init__.py β”œβ”€β”€ sub1 - β”‚ β”œβ”€β”€ check_sub1.py + β”‚ β”œβ”€β”€ Test_sub1.py β”‚ └── __init__.py └── sub2 └── test - β”œβ”€β”€ check_sub2.py - └── test_in_sub2.py + └── test_sub2.py """ testdir.makeini( """ [pytest] - python_files=check_*.py + python_files=*.py """ ) root = testdir.mkpydir("root") @@ -1653,10 +1652,9 @@ def test_package_ordering(testdir): sub2 = root.mkdir("sub2") sub2_test = sub2.mkdir("sub2") - root.join("check_root.py").write("def test_1(): pass") - sub1.join("check_sub1.py").write("def test_2(): pass") - sub2_test.join("check_sub2.py").write("def test_3(): pass") - sub2_test.join("test_in_sub2.py").write("def test_4(): pass") + root.join("Test_root.py").write("def test_1(): pass") + sub1.join("Test_sub1.py").write("def test_2(): pass") + sub2_test.join("test_sub2.py").write("def test_3(): pass") # Execute from . result = testdir.runpytest("-v", "-s") From 1e4ecda8845dcf32bdfd395063b797a91d54e80d Mon Sep 17 00:00:00 2001 From: turturica Date: Fri, 24 Aug 2018 18:01:38 -0700 Subject: [PATCH 50/62] Fix the package fixture ordering in Windows. --- src/_pytest/fixtures.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index c12691caa59..66efe0f813b 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -93,7 +93,7 @@ def get_scope_package(node, fixturedef): cls = pytest.Package current = node - fixture_package_name = os.path.join(fixturedef.baseid, "__init__.py") + fixture_package_name = "%s/%s" % (fixturedef.baseid, "__init__.py") while current and ( type(current) is not cls or fixture_package_name != current.nodeid ): From c336449729659ede3f3f2535172f911a8c268b86 Mon Sep 17 00:00:00 2001 From: turturica Date: Fri, 24 Aug 2018 18:05:35 -0700 Subject: [PATCH 51/62] Make linting happy. Argh. --- src/_pytest/fixtures.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 66efe0f813b..bfbf7bb546f 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -2,7 +2,6 @@ import functools import inspect -import os import sys import warnings from collections import OrderedDict, deque, defaultdict From de6f2c0336a03e34ed5b060f13c3932e9710db14 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 25 Aug 2018 11:09:43 -0300 Subject: [PATCH 52/62] Collect tests from __init__.py files if they match 'python_files' Fix #3773 --- changelog/3773.bugfix.rst | 1 + src/_pytest/pytester.py | 4 +++- src/_pytest/python.py | 17 +++++++++++++---- .../collect/collect_init_tests/pytest.ini | 2 ++ .../collect_init_tests/tests/__init__.py | 2 ++ .../collect_init_tests/tests/test_foo.py | 2 ++ testing/test_collection.py | 14 ++++++++++++++ 7 files changed, 37 insertions(+), 5 deletions(-) create mode 100644 changelog/3773.bugfix.rst create mode 100644 testing/example_scripts/collect/collect_init_tests/pytest.ini create mode 100644 testing/example_scripts/collect/collect_init_tests/tests/__init__.py create mode 100644 testing/example_scripts/collect/collect_init_tests/tests/test_foo.py diff --git a/changelog/3773.bugfix.rst b/changelog/3773.bugfix.rst new file mode 100644 index 00000000000..427f6c274e0 --- /dev/null +++ b/changelog/3773.bugfix.rst @@ -0,0 +1 @@ +Fix collection of tests from ``__init__.py`` files if they match the ``python_files`` configuration option. diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index 2372ea66384..4ba428cd841 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -672,7 +672,9 @@ def copy_example(self, name=None): example_path.copy(result) return result else: - raise LookupError("example is not found as a file or directory") + raise LookupError( + 'example "{}" is not found as a file or directory'.format(example_path) + ) Session = Session diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 7fb7ff9ef78..977b07442e2 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -201,15 +201,19 @@ def pytest_collect_file(path, parent): ext = path.ext if ext == ".py": if not parent.session.isinitpath(path): - for pat in parent.config.getini("python_files") + ["__init__.py"]: - if path.fnmatch(pat): - break - else: + if not path_matches_patterns( + path, parent.config.getini("python_files") + ["__init__.py"] + ): return ihook = parent.session.gethookproxy(path) return ihook.pytest_pycollect_makemodule(path=path, parent=parent) +def path_matches_patterns(path, patterns): + """Returns True if the given py.path.local matches one of the patterns in the list of globs given""" + return any(path.fnmatch(pattern) for pattern in patterns) + + def pytest_pycollect_makemodule(path, parent): if path.basename == "__init__.py": return Package(path, parent) @@ -590,6 +594,11 @@ def collect(self): self.session.config.pluginmanager._duplicatepaths.remove(path) this_path = self.fspath.dirpath() + init_module = this_path.join("__init__.py") + if init_module.check(file=1) and path_matches_patterns( + init_module, self.config.getini("python_files") + ): + yield Module(init_module, self) pkg_prefixes = set() for path in this_path.visit(rec=self._recurse, bf=True, sort=True): # we will visit our own __init__.py file, in which case we skip it diff --git a/testing/example_scripts/collect/collect_init_tests/pytest.ini b/testing/example_scripts/collect/collect_init_tests/pytest.ini new file mode 100644 index 00000000000..7c479554025 --- /dev/null +++ b/testing/example_scripts/collect/collect_init_tests/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +python_files = *.py diff --git a/testing/example_scripts/collect/collect_init_tests/tests/__init__.py b/testing/example_scripts/collect/collect_init_tests/tests/__init__.py new file mode 100644 index 00000000000..9cd366295e7 --- /dev/null +++ b/testing/example_scripts/collect/collect_init_tests/tests/__init__.py @@ -0,0 +1,2 @@ +def test_init(): + pass diff --git a/testing/example_scripts/collect/collect_init_tests/tests/test_foo.py b/testing/example_scripts/collect/collect_init_tests/tests/test_foo.py new file mode 100644 index 00000000000..8f2d73cfa4f --- /dev/null +++ b/testing/example_scripts/collect/collect_init_tests/tests/test_foo.py @@ -0,0 +1,2 @@ +def test_foo(): + pass diff --git a/testing/test_collection.py b/testing/test_collection.py index ce0e3a920bd..fb3860f998f 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -938,3 +938,17 @@ def fix(): "*1 passed, 1 error*", ] ) + + +def test_collect_init_tests(testdir): + """Check that we collect files from __init__.py files when they patch the 'python_files' (#3773)""" + p = testdir.copy_example("collect/collect_init_tests") + result = testdir.runpytest(p, "--collect-only") + result.stdout.fnmatch_lines( + [ + "*", + "*", + "*", + "*", + ] + ) From f872fcb5d0ba99bce5b1017c067d3b18ecccf15c Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 25 Aug 2018 17:33:29 -0300 Subject: [PATCH 53/62] Remove dangerous sys.path manipulations in test_pluginmanager Noticed these while working in something else --- testing/test_pluginmanager.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/testing/test_pluginmanager.py b/testing/test_pluginmanager.py index 958dfc650c8..39f5fb91f01 100644 --- a/testing/test_pluginmanager.py +++ b/testing/test_pluginmanager.py @@ -25,7 +25,6 @@ def pytest_myhook(xyz): ) conf = testdir.makeconftest( """ - import sys ; sys.path.insert(0, '.') import newhooks def pytest_addhooks(pluginmanager): pluginmanager.addhooks(newhooks) @@ -263,8 +262,7 @@ def test_consider_module_import_module(self, testdir): mod.pytest_plugins = "pytest_a" aplugin = testdir.makepyfile(pytest_a="#") reprec = testdir.make_hook_recorder(pytestpm) - # syspath.prepend(aplugin.dirpath()) - sys.path.insert(0, str(aplugin.dirpath())) + testdir.syspathinsert(aplugin.dirpath()) pytestpm.consider_module(mod) call = reprec.getcall(pytestpm.hook.pytest_plugin_registered.name) assert call.plugin.__name__ == "pytest_a" From 415fcb912bb7c877a31150c53e42275644e70f46 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sat, 25 Aug 2018 22:56:17 +0200 Subject: [PATCH 54/62] Travis: use TOXENV=linting for linting stage This will run it with `--show-diff-on-failure` then, and helps to keep it in line / in a central place. --- .travis.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index bb80ddd74e2..373b79289b5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -62,12 +62,7 @@ jobs: repo: pytest-dev/pytest - stage: linting python: '3.6' - env: - install: - - pip install pre-commit - - pre-commit install-hooks - script: - - pre-commit run --all-files + env: TOXENV=linting script: tox --recreate From b0541e9d317f40390c9bbc69af2e8a8a426502df Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 25 Aug 2018 18:17:52 -0300 Subject: [PATCH 55/62] Correctly restore sys.path in test and remove dead code in test_pytester The code in test_pytester has been refactored into a class right above the dead code, and the code has been left there by mistake apparently. --- testing/test_monkeypatch.py | 14 ++++++---- testing/test_pytester.py | 51 ------------------------------------- 2 files changed, 9 insertions(+), 56 deletions(-) diff --git a/testing/test_monkeypatch.py b/testing/test_monkeypatch.py index c47d10de2d2..adf10e4d046 100644 --- a/testing/test_monkeypatch.py +++ b/testing/test_monkeypatch.py @@ -228,11 +228,15 @@ def test_syspath_prepend(mp): def test_syspath_prepend_double_undo(mp): - mp.syspath_prepend("hello world") - mp.undo() - sys.path.append("more hello world") - mp.undo() - assert sys.path[-1] == "more hello world" + old_syspath = sys.path[:] + try: + mp.syspath_prepend("hello world") + mp.undo() + sys.path.append("more hello world") + mp.undo() + assert sys.path[-1] == "more hello world" + finally: + sys.path[:] = old_syspath def test_chdir_with_path_local(mp, tmpdir): diff --git a/testing/test_pytester.py b/testing/test_pytester.py index 99e62e5bc09..5b6a6a800d6 100644 --- a/testing/test_pytester.py +++ b/testing/test_pytester.py @@ -215,57 +215,6 @@ def test_foo(): assert imported.data == 42 -def test_inline_run_clean_sys_paths(testdir): - def test_sys_path_change_cleanup(self, testdir): - test_path1 = testdir.tmpdir.join("boink1").strpath - test_path2 = testdir.tmpdir.join("boink2").strpath - test_path3 = testdir.tmpdir.join("boink3").strpath - sys.path.append(test_path1) - sys.meta_path.append(test_path1) - original_path = list(sys.path) - original_meta_path = list(sys.meta_path) - test_mod = testdir.makepyfile( - """ - import sys - sys.path.append({:test_path2}) - sys.meta_path.append({:test_path2}) - def test_foo(): - sys.path.append({:test_path3}) - sys.meta_path.append({:test_path3})""".format( - locals() - ) - ) - testdir.inline_run(str(test_mod)) - assert sys.path == original_path - assert sys.meta_path == original_meta_path - - def spy_factory(self): - class SysPathsSnapshotSpy(object): - instances = [] - - def __init__(self): - SysPathsSnapshotSpy.instances.append(self) - self._spy_restore_count = 0 - self.__snapshot = SysPathsSnapshot() - - def restore(self): - self._spy_restore_count += 1 - return self.__snapshot.restore() - - return SysPathsSnapshotSpy - - def test_inline_run_taking_and_restoring_a_sys_paths_snapshot( - self, testdir, monkeypatch - ): - spy_factory = self.spy_factory() - monkeypatch.setattr(pytester, "SysPathsSnapshot", spy_factory) - test_mod = testdir.makepyfile("def test_foo(): pass") - testdir.inline_run(str(test_mod)) - assert len(spy_factory.instances) == 1 - spy = spy_factory.instances[0] - assert spy._spy_restore_count == 1 - - def test_assert_outcomes_after_pytest_error(testdir): testdir.makepyfile("def test_foo(): assert True") From 3da88d794f8b50b15f2bfc7012fd2ee9d262fd6f Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 25 Aug 2018 16:48:01 -0700 Subject: [PATCH 56/62] Use skip_install for testing tox env --- tox.ini | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 6514421b64a..1fd66f6b20b 100644 --- a/tox.ini +++ b/tox.ini @@ -36,8 +36,7 @@ commands = [testenv:linting] -skipsdist = True -usedevelop = True +skip_install = True basepython = python3.6 deps = pre-commit commands = pre-commit run --all-files --show-diff-on-failure From d3f5324386cf64bb1be2c64b9f885cfaa39a3604 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sun, 26 Aug 2018 01:39:21 +0200 Subject: [PATCH 57/62] tox: coveralls: also report to codecov This is meant to get base coverage on master for codecov. --- tox.ini | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 6514421b64a..7cabfd3d4ff 100644 --- a/tox.ini +++ b/tox.ini @@ -163,16 +163,18 @@ commands = [testenv:coveralls] -passenv = TRAVIS TRAVIS_JOB_ID TRAVIS_BRANCH COVERALLS_REPO_TOKEN +passenv = CI TRAVIS TRAVIS_* COVERALLS_REPO_TOKEN usedevelop = True changedir = . deps = {[testenv]deps} coveralls + codecov commands = coverage run --source=_pytest -m pytest testing coverage report -m coveralls + codecov [testenv:release] decription = do a release, required posarg of the version number From 508774742e3f55caa3d1b2942738ed778a4cb0cb Mon Sep 17 00:00:00 2001 From: Gandalf Saxe Date: Sun, 26 Aug 2018 11:54:08 +0200 Subject: [PATCH 58/62] Code block: :: missing and 4 spaces instead of 5 I just noticed the newly committed code block doesn't format as a code block without `::` in the paragraph before. Perhaps doesn't make a difference, but also corrected 5 spaces to 4 which seems standard. --- doc/en/goodpractices.rst | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/doc/en/goodpractices.rst b/doc/en/goodpractices.rst index b3d903badd2..70c36924452 100644 --- a/doc/en/goodpractices.rst +++ b/doc/en/goodpractices.rst @@ -12,12 +12,11 @@ for installing your application and any dependencies as well as the ``pytest`` package itself. This ensures your code and dependencies are isolated from the system Python installation. -First you need to place a ``setup.py`` file in the root of your package with the following minimum content: +First you need to place a ``setup.py`` file in the root of your package with the following minimum content:: - from setuptools import setup, find_packages + from setuptools import setup, find_packages - - setup(name="PACKAGENAME", packages=find_packages()) + setup(name="PACKAGENAME", packages=find_packages()) Where ``PACKAGENAME`` is the name of your package. You can then install your package in "editable" mode by running from the same directory:: From c31018d9bcf0d206bcb134a376ea0f8e880510f1 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sun, 26 Aug 2018 12:43:43 +0000 Subject: [PATCH 59/62] Preparing release version 3.7.3 --- CHANGELOG.rst | 55 +++++++++++++++++++++++++++++ changelog/3033.bugfix.rst | 1 - changelog/3773.bugfix.rst | 1 - changelog/3796.bugfix.rst | 2 -- changelog/3816.bugfix.rst | 1 - changelog/3819.bugfix.rst | 1 - changelog/3824.doc.rst | 1 - changelog/3826.trivial.rst | 1 - changelog/3833.doc.rst | 1 - changelog/3843.bugfix.rst | 1 - changelog/3845.trivial.rst | 2 -- changelog/3848.bugfix.rst | 1 - changelog/3854.bugfix.rst | 1 - changelog/3870.doc.rst | 1 - doc/en/announce/index.rst | 1 + doc/en/announce/release-3.7.3.rst | 33 +++++++++++++++++ doc/en/example/parametrize.rst | 2 +- doc/en/example/pythoncollection.rst | 7 +--- doc/en/example/reportingdemo.rst | 2 +- 19 files changed, 92 insertions(+), 23 deletions(-) delete mode 100644 changelog/3033.bugfix.rst delete mode 100644 changelog/3773.bugfix.rst delete mode 100644 changelog/3796.bugfix.rst delete mode 100644 changelog/3816.bugfix.rst delete mode 100644 changelog/3819.bugfix.rst delete mode 100644 changelog/3824.doc.rst delete mode 100644 changelog/3826.trivial.rst delete mode 100644 changelog/3833.doc.rst delete mode 100644 changelog/3843.bugfix.rst delete mode 100644 changelog/3845.trivial.rst delete mode 100644 changelog/3848.bugfix.rst delete mode 100644 changelog/3854.bugfix.rst delete mode 100644 changelog/3870.doc.rst create mode 100644 doc/en/announce/release-3.7.3.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 6a864e8a3f9..c433b028352 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -18,6 +18,61 @@ with advance notice in the **Deprecations** section of releases. .. towncrier release notes start +pytest 3.7.3 (2018-08-26) +========================= + +Bug Fixes +--------- + +- `#3033 `_: Fixtures during teardown can again use ``capsys`` and ``cafd`` to inspect output captured during tests. + + +- `#3773 `_: Fix collection of tests from ``__init__.py`` files if they match the ``python_files`` configuration option. + + +- `#3796 `_: Fixed an issue where teardown of fixtures of consecutive sub-packages were executed once, at the end of the outer + package. + + +- `#3816 `_: Fix bug where ``--show-capture=no`` option would still show logs printed during fixture teardown. + + +- `#3819 `_: Fix ``stdout/stderr`` not getting captured when real-time cli logging is active. + + +- `#3843 `_: Fix collection error when specifying test functions directly in the command line using ``test.py::test`` syntax together with ``--doctest-module``. + + +- `#3848 `_: Fix bugs where unicode arguments could not be passed to ``testdir.runpytest`` on Python 2. + + +- `#3854 `_: Fixes double collection of tests within packages when the filename starts with a capital letter. + + + +Improved Documentation +---------------------- + +- `#3824 `_: Added example for multiple glob pattern matches in ``python_files``. + + +- `#3833 `_: Added missing docs for ``pytester.Testdir`` + + +- `#3870 `_: correct documentation for setuptools integration + + + +Trivial/Internal Changes +------------------------ + +- `#3826 `_: Replace broken type annotations with type comments. + + +- `#3845 `_: Remove a reference to issue `#568 `_ from the documentation, which has since been + fixed. + + pytest 3.7.2 (2018-08-16) ========================= diff --git a/changelog/3033.bugfix.rst b/changelog/3033.bugfix.rst deleted file mode 100644 index 3fcd9dd116c..00000000000 --- a/changelog/3033.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixtures during teardown can again use ``capsys`` and ``cafd`` to inspect output captured during tests. diff --git a/changelog/3773.bugfix.rst b/changelog/3773.bugfix.rst deleted file mode 100644 index 427f6c274e0..00000000000 --- a/changelog/3773.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fix collection of tests from ``__init__.py`` files if they match the ``python_files`` configuration option. diff --git a/changelog/3796.bugfix.rst b/changelog/3796.bugfix.rst deleted file mode 100644 index 7a115301a95..00000000000 --- a/changelog/3796.bugfix.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fixed an issue where teardown of fixtures of consecutive sub-packages were executed once, at the end of the outer -package. diff --git a/changelog/3816.bugfix.rst b/changelog/3816.bugfix.rst deleted file mode 100644 index a50c8f7291d..00000000000 --- a/changelog/3816.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fix bug where ``--show-capture=no`` option would still show logs printed during fixture teardown. diff --git a/changelog/3819.bugfix.rst b/changelog/3819.bugfix.rst deleted file mode 100644 index 02b33f9b1b2..00000000000 --- a/changelog/3819.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fix ``stdout/stderr`` not getting captured when real-time cli logging is active. diff --git a/changelog/3824.doc.rst b/changelog/3824.doc.rst deleted file mode 100644 index 01606512065..00000000000 --- a/changelog/3824.doc.rst +++ /dev/null @@ -1 +0,0 @@ -Added example for multiple glob pattern matches in ``python_files``. diff --git a/changelog/3826.trivial.rst b/changelog/3826.trivial.rst deleted file mode 100644 index 5354d0df9c0..00000000000 --- a/changelog/3826.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -Replace broken type annotations with type comments. diff --git a/changelog/3833.doc.rst b/changelog/3833.doc.rst deleted file mode 100644 index 254e2e4b6ef..00000000000 --- a/changelog/3833.doc.rst +++ /dev/null @@ -1 +0,0 @@ -Added missing docs for ``pytester.Testdir`` diff --git a/changelog/3843.bugfix.rst b/changelog/3843.bugfix.rst deleted file mode 100644 index 3186c3fc5d6..00000000000 --- a/changelog/3843.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fix collection error when specifying test functions directly in the command line using ``test.py::test`` syntax together with ``--doctest-module``. diff --git a/changelog/3845.trivial.rst b/changelog/3845.trivial.rst deleted file mode 100644 index 29c45ab56f2..00000000000 --- a/changelog/3845.trivial.rst +++ /dev/null @@ -1,2 +0,0 @@ -Remove a reference to issue `#568 `_ from the documentation, which has since been -fixed. diff --git a/changelog/3848.bugfix.rst b/changelog/3848.bugfix.rst deleted file mode 100644 index 4442d7a89f9..00000000000 --- a/changelog/3848.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fix bugs where unicode arguments could not be passed to ``testdir.runpytest`` on Python 2. diff --git a/changelog/3854.bugfix.rst b/changelog/3854.bugfix.rst deleted file mode 100644 index 6184f03b66e..00000000000 --- a/changelog/3854.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixes double collection of tests within packages when the filename starts with a capital letter. diff --git a/changelog/3870.doc.rst b/changelog/3870.doc.rst deleted file mode 100644 index c56e10d1145..00000000000 --- a/changelog/3870.doc.rst +++ /dev/null @@ -1 +0,0 @@ -correct documentation for setuptools integration diff --git a/doc/en/announce/index.rst b/doc/en/announce/index.rst index 1e7f2ce0f74..8a97baa5a61 100644 --- a/doc/en/announce/index.rst +++ b/doc/en/announce/index.rst @@ -6,6 +6,7 @@ Release announcements :maxdepth: 2 + release-3.7.3 release-3.7.2 release-3.7.1 release-3.7.0 diff --git a/doc/en/announce/release-3.7.3.rst b/doc/en/announce/release-3.7.3.rst new file mode 100644 index 00000000000..479ff7d873d --- /dev/null +++ b/doc/en/announce/release-3.7.3.rst @@ -0,0 +1,33 @@ +pytest-3.7.3 +======================================= + +pytest 3.7.3 has just been released to PyPI. + +This is a bug-fix release, being a drop-in replacement. To upgrade:: + + pip install --upgrade pytest + +The full changelog is available at http://doc.pytest.org/en/latest/changelog.html. + +Thanks to all who contributed to this release, among them: + +* Andrew Champion +* Anthony Sottile +* Bruno Oliveira +* Daniel Hahler +* Gandalf Saxe +* Jennifer Rinker +* Natan Lao +* OndΕ™ej SΓΊkup +* Ronny Pfannschmidt +* Sankt Petersbug +* Tyler Richard +* Victor +* Vlad Shcherbina +* turturica +* victor +* wim glenn + + +Happy testing, +The pytest Development Team diff --git a/doc/en/example/parametrize.rst b/doc/en/example/parametrize.rst index fdc8025545b..7dca5510ac1 100644 --- a/doc/en/example/parametrize.rst +++ b/doc/en/example/parametrize.rst @@ -413,7 +413,7 @@ Running it results in some skips if we don't have all the python interpreters in . $ pytest -rs -q multipython.py ...sss...sssssssss...sss... [100%] ========================= short test summary info ========================== - SKIP [15] $REGENDOC_TMPDIR/CWD/multipython.py:28: 'python3.4' not found + SKIP [15] $REGENDOC_TMPDIR/CWD/multipython.py:29: 'python3.4' not found 12 passed, 15 skipped in 0.12 seconds Indirect parametrization of optional implementations/imports diff --git a/doc/en/example/pythoncollection.rst b/doc/en/example/pythoncollection.rst index b4950a75c18..757e8980ac5 100644 --- a/doc/en/example/pythoncollection.rst +++ b/doc/en/example/pythoncollection.rst @@ -129,12 +129,7 @@ The test collection would look like this:: =========================== test session starts ============================ platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini - collected 2 items - - - - - + collected 0 items ======================= no tests ran in 0.12 seconds ======================= diff --git a/doc/en/example/reportingdemo.rst b/doc/en/example/reportingdemo.rst index e7c01e56188..7e1acea4e73 100644 --- a/doc/en/example/reportingdemo.rst +++ b/doc/en/example/reportingdemo.rst @@ -423,7 +423,7 @@ get on the terminal - we are working on that):: name = "abc-123" module = imp.new_module(name) code = _pytest._code.compile(src, name, "exec") - py.builtin.exec_(code, module.__dict__) + six.exec_(code, module.__dict__) sys.modules[name] = module > module.foo() From b69f853acbc3ff705751b69f8955a0e25a2bf92c Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sun, 26 Aug 2018 09:46:46 -0300 Subject: [PATCH 60/62] Tweak CHANGELOG for 3.7.3 --- CHANGELOG.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c433b028352..66720707648 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -30,7 +30,7 @@ Bug Fixes - `#3773 `_: Fix collection of tests from ``__init__.py`` files if they match the ``python_files`` configuration option. -- `#3796 `_: Fixed an issue where teardown of fixtures of consecutive sub-packages were executed once, at the end of the outer +- `#3796 `_: Fix issue where teardown of fixtures of consecutive sub-packages were executed once, at the end of the outer package. @@ -40,13 +40,13 @@ Bug Fixes - `#3819 `_: Fix ``stdout/stderr`` not getting captured when real-time cli logging is active. -- `#3843 `_: Fix collection error when specifying test functions directly in the command line using ``test.py::test`` syntax together with ``--doctest-module``. +- `#3843 `_: Fix collection error when specifying test functions directly in the command line using ``test.py::test`` syntax together with ``--doctest-modules``. - `#3848 `_: Fix bugs where unicode arguments could not be passed to ``testdir.runpytest`` on Python 2. -- `#3854 `_: Fixes double collection of tests within packages when the filename starts with a capital letter. +- `#3854 `_: Fix double collection of tests within packages when the filename starts with a capital letter. @@ -56,10 +56,10 @@ Improved Documentation - `#3824 `_: Added example for multiple glob pattern matches in ``python_files``. -- `#3833 `_: Added missing docs for ``pytester.Testdir`` +- `#3833 `_: Added missing docs for ``pytester.Testdir``. -- `#3870 `_: correct documentation for setuptools integration +- `#3870 `_: Correct documentation for setuptools integration. From 70bdacf01aa0917d4499c1aff4f9f2061968a655 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sun, 26 Aug 2018 12:58:47 +0000 Subject: [PATCH 61/62] Fix collection example docs --- doc/en/example/pythoncollection.rst | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/doc/en/example/pythoncollection.rst b/doc/en/example/pythoncollection.rst index 757e8980ac5..500873817c0 100644 --- a/doc/en/example/pythoncollection.rst +++ b/doc/en/example/pythoncollection.rst @@ -103,8 +103,8 @@ the :confval:`python_files`, :confval:`python_classes` and :confval:`python_functions` configuration options. Here is an example:: - # Example 1: have pytest look for "check" instead of "test" # content of pytest.ini + # Example 1: have pytest look for "check" instead of "test" # can also be defined in tox.ini or setup.cfg file, although the section # name in setup.cfg files should be "tool:pytest" [pytest] @@ -129,7 +129,12 @@ The test collection would look like this:: =========================== test session starts ============================ platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini - collected 0 items + collected 2 items + + + + + ======================= no tests ran in 0.12 seconds ======================= From e74ad4ff9b89936c2ef9c4e34054ae9d3ba1e31f Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sun, 26 Aug 2018 12:27:02 -0300 Subject: [PATCH 62/62] Fix typo in CHANGELOG --- CHANGELOG.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 66720707648..0d6c9d41238 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -24,7 +24,7 @@ pytest 3.7.3 (2018-08-26) Bug Fixes --------- -- `#3033 `_: Fixtures during teardown can again use ``capsys`` and ``cafd`` to inspect output captured during tests. +- `#3033 `_: Fixtures during teardown can again use ``capsys`` and ``capfd`` to inspect output captured during tests. - `#3773 `_: Fix collection of tests from ``__init__.py`` files if they match the ``python_files`` configuration option.