This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: Exit code 120 returned from Python unit test testing SystemExit
Type: behavior Stage: resolved
Components: IO Versions: Python 3.6
process
Status: closed Resolution: not a bug
Dependencies: Superseder:
Assigned To: Nosy List: John Hagen, martin.panter, r.david.murray, serhiy.storchaka, vstinner, xiang.zhang
Priority: normal Keywords:

Created on 2017-01-02 00:28 by John Hagen, last changed 2022-04-11 14:58 by admin. This issue is now closed.

Messages (7)
msg284437 - (view) Author: John Hagen (John Hagen) * Date: 2017-01-02 00:28
I recently tried to port one of my packages to Python 3.6 and unit tests that worked in Python 2.7, 3.3-3.5 began failing in 3.6.

I originally thought it was a problem with coverage, but it turns out it was not. The full thread is: https://bitbucket.org/ned/coveragepy/issues/545/coverage-fails-on-python-36-travis-build

The highlight is this unit test causes Python to exit with status code 120 (which fails a Travis build):

class ParseArgumentsTestCase(unittest.TestCase):
    def test_no_arguments(self):  # type: () -> None
        with self.assertRaises(SystemExit):
            # Suppress argparse stderr.
            class NullWriter:
                def write(self, s):  # type: (str) -> None
                    pass

            sys.stderr = NullWriter()
            parse_arguments()

Ned found this corresponding note in the Python 3.6 release notes:

Changed in version 3.6: If an error occurs in the cleanup after the Python interpreter has caught SystemExit (such as an error flushing buffered data in the standard streams), the exit status is changed to 120.

If this is indeed, correct behavior and Python 3.6 is catching something incorrect (I agree this is not necessarily the most elegant unit test) then I am happy to close this issue. Just wanted to be sure I at least reported it in case this was a real issue.
msg284438 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2017-01-02 00:40
My guess would be that the problem that your NullWriter doesn't have a flush method.  But I'm not familiar with this change, so I'm just guessing.
msg284534 - (view) Author: Xiang Zhang (xiang.zhang) * (Python committer) Date: 2017-01-03 03:44
Behaviour changed in #5319. Add author Martin.
msg284536 - (view) Author: Martin Panter (martin.panter) * (Python committer) Date: 2017-01-03 04:00
David is right. The 120 code was added in Issue 5319, as a way of indicating a problem in the final stages of the interpreter exiting. The two conditions that trigger this are calling the flush() method on sys.stdout and sys.stderr. If you add a dummy flush() implementation, it no longer exits with 120:

>>> subprocess.call((sys.executable, "-c", """
... class NullWriter:
...     def write(self, s): pass
... import sys; sys.stderr = NullWriter()"""))
120
>>> subprocess.call((sys.executable, "-c", """
... class NullWriter:
...     def write(self, s): pass
...     def flush(self): pass
... import sys; sys.stderr = NullWriter()"""))
0

It does not seem to be explicitly documented what you can set sys.stderr to, but I always thought it is safest to use an io.TextIOBase implementation. I would suggest to derive your NullWriter class from TextIOBase; that way you get a default flush() implementation for free.

Other options are to use StringIO() if you are not expecting too much output, or open(os.devnull, "w"). See also Issue 28864 about adding a predefined class like NullWriter to Python.

Having said all that, perhaps it would be reasonable to tolerate a missing flush() method, and not treat this as an error.

Stepping back a bit, I also suggest restoring sys.stderr after the test. Otherwise, you risk missing an error message. Try contextlib.redirect_stderr().
msg284542 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2017-01-03 06:41
> Having said all that, perhaps it would be reasonable to tolerate a missing flush() method, and not treat this as an error.

In all cases the flush() method is called unconditionally. I think it can be considered as mandatory part of the writer protocol.
msg284551 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2017-01-03 12:17
This issue is not a bug in Python, but a bug in the author's code. I suggest to close the issue, since enough clues have been provided to fix the author's issue.
msg284556 - (view) Author: John Hagen (John Hagen) * Date: 2017-01-03 12:52
I'm completely fine with closing this issue. Thanks for the help everyone.

If someone else doesn't close it in a couple days, I'll do it.
History
Date User Action Args
2022-04-11 14:58:41adminsetgithub: 73316
2017-01-04 02:23:15r.david.murraysetstatus: open -> closed
resolution: not a bug
stage: resolved
2017-01-03 12:52:04John Hagensetmessages: + msg284556
2017-01-03 12:17:30vstinnersetnosy: + vstinner
messages: + msg284551
2017-01-03 06:41:48serhiy.storchakasetnosy: + serhiy.storchaka
messages: + msg284542
2017-01-03 04:00:00martin.pantersetmessages: + msg284536
2017-01-03 03:44:47xiang.zhangsetnosy: + xiang.zhang, martin.panter
messages: + msg284534
2017-01-02 00:40:26r.david.murraysetnosy: + r.david.murray
messages: + msg284438
2017-01-02 00:28:18John Hagencreate