classification
Title: unittest.TestCase.assertWarns raises RuntimeEror if sys.modules changes size
Type: behavior Stage: resolved
Components: Library (Lib) Versions: Python 3.8, Python 3.7, Python 3.6, Python 2.7
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: Nosy List: Arthit Suriyawongkul, Maximilian Peters, William.Schwartz, akuchling, kernc, miss-islington, serhiy.storchaka
Priority: normal Keywords: patch

Created on 2017-02-22 16:22 by kernc, last changed 2020-06-11 18:36 by akuchling. This issue is now closed.

Pull Requests
URL Status Linked Edit
PR 4800 merged kernc, 2017-12-11 20:25
PR 20816 merged miss-islington, 2020-06-11 18:03
PR 20817 merged miss-islington, 2020-06-11 18:03
Messages (8)
msg288370 - (view) Author: kernc (kernc) * Date: 2017-02-22 16:22
If any of the objects in sys.modules is a module-like object that performs some additional imports in its __getattribute__ (as appropriate) handler, the following simple unit test test case:

    import unittest
    import warnings

    ... # Ensure one of the imported modules is a module-like object as above

    class Case(unittest.TestCase):
        def test_assertWarns(self):
            with self.assertWarns(UserWarning):
                warnings.warn('Some warning')

fails with:

    ======================================================================
    ERROR: test_assertWarns (example.Case)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
      File "/tmp/example.py", line 9, in test_assertWarns
        with self.assertWarns(UserWarning):
      File "/usr/lib/python3.4/unittest/case.py", line 205, in __enter__
        for v in sys.modules.values():
    RuntimeError: dictionary changed size during iteration
    ----------------------------------------------------------------------

The problem is in the iteration over sys.modules in unittest.case._AssertWarnsContext.__enter__() and accessing every module's __warningregistry__ attribute. On this access, the module-like objects may perform arbitrary actions including importing of further modules which extends sys.modules.

https://github.com/python/cpython/blob/16ea19fc6653ee4ec1be7cd0206073962119ac08/Lib/unittest/case.py#L226-L227

The simple proposed fix with no foreseen side-effects is to wrap sys.modules.values() call as a tuple().
msg323821 - (view) Author: William Schwartz (William.Schwartz) Date: 2018-08-21 05:12
I am also running into this problem. I'm not 100%, but I'm pretty sure that looping over sys.modules and accessing __warningregistry__ on each module triggers one of my module's __getattr__ functions (PEP 562), which in turn uses setuptools entry points to import an arbitrary set of other modules.

Bizarrely, however, I cannot reproduce on macOS, only Linux. Presumably there's something platform dependent about how the default unittest test runner orders my tests, and hence which modules have already been loaded by the time I call assertWarnsRegex.
msg356671 - (view) Author: Arthit Suriyawongkul (Arthit Suriyawongkul) Date: 2019-11-15 13:03
Confirmed this behavior on
- Python 3.6.8 64-bit on Windows Server 2016 (AppVeyor "Visual Studio 2017" build environment)
- Python 3.6.7 64-bit on Linux
msg361886 - (view) Author: Maximilian Peters (Maximilian Peters) Date: 2020-02-12 12:22
Ran into the same issue using Python 3.6.8 [GCC 5.4.0 20160609] on Linux.

The same code worked on Windows 10 with Python 3.7 32-bit and 64-bit.
msg371315 - (view) Author: A.M. Kuchling (akuchling) * (Python committer) Date: 2020-06-11 18:03
New changeset 46398fba4d66ad342cf2504ef947b5fb857423b2 by kernc in branch 'master':
bpo-29620: iterate over a copy of sys.modules (GH-4800)
https://github.com/python/cpython/commit/46398fba4d66ad342cf2504ef947b5fb857423b2
msg371317 - (view) Author: A.M. Kuchling (akuchling) * (Python committer) Date: 2020-06-11 18:31
New changeset 3e499cda47afe2282ca3f1d04151e2c86f2e7e09 by Miss Islington (bot) in branch '3.8':
bpo-29620: iterate over a copy of sys.modules (GH-4800) (GH-20816)
https://github.com/python/cpython/commit/3e499cda47afe2282ca3f1d04151e2c86f2e7e09
msg371318 - (view) Author: A.M. Kuchling (akuchling) * (Python committer) Date: 2020-06-11 18:34
New changeset f881c869753fb2b1b7aef353416893190251c539 by Miss Islington (bot) in branch '3.7':
bpo-29620: iterate over a copy of sys.modules (GH-4800) (GH-20817)
https://github.com/python/cpython/commit/f881c869753fb2b1b7aef353416893190251c539
msg371319 - (view) Author: A.M. Kuchling (akuchling) * (Python committer) Date: 2020-06-11 18:36
Changes applied to master, 3.8, and 3.7.  Thanks!
History
Date User Action Args
2020-06-11 18:36:06akuchlingsetstatus: open -> closed
resolution: fixed
messages: + msg371319

stage: patch review -> resolved
2020-06-11 18:34:49akuchlingsetmessages: + msg371318
2020-06-11 18:31:55akuchlingsetmessages: + msg371317
2020-06-11 18:03:58miss-islingtonsetpull_requests: + pull_request20015
2020-06-11 18:03:49miss-islingtonsetnosy: + miss-islington
pull_requests: + pull_request20014
2020-06-11 18:03:36akuchlingsetnosy: + akuchling
messages: + msg371315
2020-02-12 12:22:24Maximilian Peterssetnosy: + Maximilian Peters
messages: + msg361886
2019-11-15 13:03:48Arthit Suriyawongkulsetnosy: + Arthit Suriyawongkul
messages: + msg356671
2018-08-21 05:40:04serhiy.storchakasetnosy: + serhiy.storchaka

versions: + Python 2.7, Python 3.8, - Python 3.4, Python 3.5
2018-08-21 05:12:11William.Schwartzsetnosy: + William.Schwartz
messages: + msg323821
2017-12-11 20:25:51kerncsetkeywords: + patch
stage: patch review
pull_requests: + pull_request4699
2017-02-22 16:22:51kernccreate