From d7be039f1b6ed1f3aaf04283a50e4601c25d1115 Mon Sep 17 00:00:00 2001 From: Jose Carlos Menezes Date: Sun, 14 Oct 2018 12:18:49 -0300 Subject: [PATCH 01/12] Add returncode argument to pytest.exit If the argument is not None, it'll raise a SystemExit exception to cleanly exit pytest. --- src/_pytest/outcomes.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/_pytest/outcomes.py b/src/_pytest/outcomes.py index f6093ef76..a220b2cd1 100644 --- a/src/_pytest/outcomes.py +++ b/src/_pytest/outcomes.py @@ -57,10 +57,13 @@ class Exit(KeyboardInterrupt): # exposed helper methods -def exit(msg): +def exit(msg, returncode=None): """ exit testing process as if KeyboardInterrupt was triggered. """ __tracebackhide__ = True - raise Exit(msg) + if returncode: + raise SystemExit(returncode) + else: + raise Exit(msg) exit.Exception = Exit From ce55dcf64c31272ed8fff4c6f118af1956f3d0b3 Mon Sep 17 00:00:00 2001 From: Jose Carlos Menezes Date: Sun, 14 Oct 2018 12:20:10 -0300 Subject: [PATCH 02/12] Add test for calling pytest.exit with statuscode It checks that a SystemError was raised and the SystemError code is the same as the returncode argument. --- testing/test_runner.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/testing/test_runner.py b/testing/test_runner.py index b9538cfad..f9b7f8180 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -570,6 +570,15 @@ def test_pytest_exit_msg(testdir): result.stderr.fnmatch_lines(["Exit: oh noes"]) +def test_pytest_exit_returncode(): + try: + pytest.exit("hello", returncode=2) + except SystemExit as exc: + excinfo = _pytest._code.ExceptionInfo() + assert excinfo.errisinstance(SystemExit) + assert excinfo.value.code == 2 + + def test_pytest_fail_notrace_runtest(testdir): """Test pytest.fail(..., pytrace=False) does not show tracebacks during test run.""" testdir.makepyfile( From a0666354dd77b85919eee232ddd20a122bbff2d7 Mon Sep 17 00:00:00 2001 From: Jose Carlos Menezes Date: Sun, 14 Oct 2018 12:25:07 -0300 Subject: [PATCH 03/12] Update changelog --- changelog/4098.feature.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/4098.feature.rst diff --git a/changelog/4098.feature.rst b/changelog/4098.feature.rst new file mode 100644 index 000000000..d7a97ff62 --- /dev/null +++ b/changelog/4098.feature.rst @@ -0,0 +1 @@ +Add to pytest.exit a returncode argument to cleanly exit pytest. From 46d6a3fc27c41ad04b7658a6d454521635fbac9a Mon Sep 17 00:00:00 2001 From: Jose Carlos Menezes Date: Sun, 14 Oct 2018 16:36:53 -0300 Subject: [PATCH 04/12] Pass returncode to Error exception when creating instance --- src/_pytest/outcomes.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/_pytest/outcomes.py b/src/_pytest/outcomes.py index a220b2cd1..92b6824b6 100644 --- a/src/_pytest/outcomes.py +++ b/src/_pytest/outcomes.py @@ -49,8 +49,9 @@ class Failed(OutcomeException): class Exit(KeyboardInterrupt): """ raised for immediate program exits (no tracebacks/summaries)""" - def __init__(self, msg="unknown reason"): + def __init__(self, returncode=None, msg="unknown reason"): self.msg = msg + self.returncode = returncode KeyboardInterrupt.__init__(self, msg) @@ -60,10 +61,7 @@ class Exit(KeyboardInterrupt): def exit(msg, returncode=None): """ exit testing process as if KeyboardInterrupt was triggered. """ __tracebackhide__ = True - if returncode: - raise SystemExit(returncode) - else: - raise Exit(msg) + raise Exit(returncode, msg) exit.Exception = Exit From 836c9f82f12b8d2558d2b3a70edb6b53a6d2d5ac Mon Sep 17 00:00:00 2001 From: Jose Carlos Menezes Date: Sun, 14 Oct 2018 16:39:43 -0300 Subject: [PATCH 05/12] Set test session exitstatus value from Exit excetion return code --- src/_pytest/main.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/_pytest/main.py b/src/_pytest/main.py index 0171bda0c..c1896cdfa 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -185,10 +185,12 @@ def wrap_session(config, doit): session.exitstatus = EXIT_TESTSFAILED except KeyboardInterrupt: excinfo = _pytest._code.ExceptionInfo() - if initstate < 2 and isinstance(excinfo.value, exit.Exception): + exitstatus = EXIT_INTERRUPTED + if initstate <= 2 and isinstance(excinfo.value, exit.Exception): sys.stderr.write("{}: {}\n".format(excinfo.typename, excinfo.value.msg)) + exitstatus = excinfo.value.returncode config.hook.pytest_keyboard_interrupt(excinfo=excinfo) - session.exitstatus = EXIT_INTERRUPTED + session.exitstatus = exitstatus except: # noqa excinfo = _pytest._code.ExceptionInfo() config.notify_exception(excinfo, config.option) From 766d2daa06509839779cd2f33728535d2b428485 Mon Sep 17 00:00:00 2001 From: Jose Carlos Menezes Date: Sun, 14 Oct 2018 16:41:16 -0300 Subject: [PATCH 06/12] Update returncode exit test to check exitstatus returrned from test session --- testing/test_runner.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/testing/test_runner.py b/testing/test_runner.py index f9b7f8180..a3fffe81a 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -570,13 +570,16 @@ def test_pytest_exit_msg(testdir): result.stderr.fnmatch_lines(["Exit: oh noes"]) -def test_pytest_exit_returncode(): - try: - pytest.exit("hello", returncode=2) - except SystemExit as exc: - excinfo = _pytest._code.ExceptionInfo() - assert excinfo.errisinstance(SystemExit) - assert excinfo.value.code == 2 +def test_pytest_exit_returncode(testdir): + testdir.makepyfile( + """ + import pytest + def test_foo(): + pytest.exit("some exit msg", 99) + """ + ) + result = testdir.runpytest() + assert result.ret == 99 def test_pytest_fail_notrace_runtest(testdir): From d4dfd526c11d610e3b2314a8d77ad990623b5db0 Mon Sep 17 00:00:00 2001 From: Jose Carlos Menezes Date: Sun, 14 Oct 2018 18:01:47 -0300 Subject: [PATCH 07/12] Update pytest.exit docstring --- src/_pytest/outcomes.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/_pytest/outcomes.py b/src/_pytest/outcomes.py index 92b6824b6..ca251c0e8 100644 --- a/src/_pytest/outcomes.py +++ b/src/_pytest/outcomes.py @@ -59,7 +59,11 @@ class Exit(KeyboardInterrupt): def exit(msg, returncode=None): - """ exit testing process as if KeyboardInterrupt was triggered. """ + """ + Exit testing process as if KeyboardInterrupt was triggered. + + :param int returncode: return code to be used when exiting pytest.. + """ __tracebackhide__ = True raise Exit(returncode, msg) From bbd1cbb0b3be15543b04674a8f358d2d1ff16a4a Mon Sep 17 00:00:00 2001 From: Jose Carlos Menezes Date: Sun, 14 Oct 2018 18:03:37 -0300 Subject: [PATCH 08/12] Update changelog to better reading --- changelog/4098.feature.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/4098.feature.rst b/changelog/4098.feature.rst index d7a97ff62..1a53de759 100644 --- a/changelog/4098.feature.rst +++ b/changelog/4098.feature.rst @@ -1 +1 @@ -Add to pytest.exit a returncode argument to cleanly exit pytest. +Add returncode argument to pytest.exit() to exit pytest with a specific return code. From d32f2c5c142b71b9fca55860c4d00652ab7a9aa5 Mon Sep 17 00:00:00 2001 From: Jose Carlos Menezes Date: Sun, 14 Oct 2018 18:42:55 -0300 Subject: [PATCH 09/12] Change Exit.__init__ params order to keep backward compatibility --- src/_pytest/outcomes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_pytest/outcomes.py b/src/_pytest/outcomes.py index ca251c0e8..d12e78266 100644 --- a/src/_pytest/outcomes.py +++ b/src/_pytest/outcomes.py @@ -49,7 +49,7 @@ class Failed(OutcomeException): class Exit(KeyboardInterrupt): """ raised for immediate program exits (no tracebacks/summaries)""" - def __init__(self, returncode=None, msg="unknown reason"): + def __init__(self, msg="unknown reason", returncode=None): self.msg = msg self.returncode = returncode KeyboardInterrupt.__init__(self, msg) From 76fb9970c8cc561e1ed6ee21fc10f46c60fc8637 Mon Sep 17 00:00:00 2001 From: Jose Carlos Menezes Date: Sun, 14 Oct 2018 18:43:48 -0300 Subject: [PATCH 10/12] Check if returncode is not None before assigning test return code --- src/_pytest/main.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/_pytest/main.py b/src/_pytest/main.py index c1896cdfa..2d6cea668 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -188,7 +188,8 @@ def wrap_session(config, doit): exitstatus = EXIT_INTERRUPTED if initstate <= 2 and isinstance(excinfo.value, exit.Exception): sys.stderr.write("{}: {}\n".format(excinfo.typename, excinfo.value.msg)) - exitstatus = excinfo.value.returncode + if excinfo.value.returncode is not None: + exitstatus = excinfo.value.returncode config.hook.pytest_keyboard_interrupt(excinfo=excinfo) session.exitstatus = exitstatus except: # noqa From 40091ec2c733331b45ec7ee68538aab6afbcdec1 Mon Sep 17 00:00:00 2001 From: Jose Carlos Menezes Date: Sun, 14 Oct 2018 18:44:53 -0300 Subject: [PATCH 11/12] Update pytest.exit docstring --- src/_pytest/outcomes.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/_pytest/outcomes.py b/src/_pytest/outcomes.py index d12e78266..35b3bfb9f 100644 --- a/src/_pytest/outcomes.py +++ b/src/_pytest/outcomes.py @@ -62,7 +62,8 @@ def exit(msg, returncode=None): """ Exit testing process as if KeyboardInterrupt was triggered. - :param int returncode: return code to be used when exiting pytest.. + :param str msg: message to display upon exit. + :param int returncode: return code to be used when exiting pytest. """ __tracebackhide__ = True raise Exit(returncode, msg) From 27d932e882152c828352ec4855ac2e16d23cc115 Mon Sep 17 00:00:00 2001 From: Jose Carlos Menezes Date: Sun, 14 Oct 2018 18:48:32 -0300 Subject: [PATCH 12/12] Fix order of parameters when raising Exit exception --- src/_pytest/outcomes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_pytest/outcomes.py b/src/_pytest/outcomes.py index 35b3bfb9f..4c7958384 100644 --- a/src/_pytest/outcomes.py +++ b/src/_pytest/outcomes.py @@ -66,7 +66,7 @@ def exit(msg, returncode=None): :param int returncode: return code to be used when exiting pytest. """ __tracebackhide__ = True - raise Exit(returncode, msg) + raise Exit(msg, returncode) exit.Exception = Exit