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

Created on 2017-02-22 16:22 by kernc, last changed 2020-02-12 12:22 by Maximilian Peters.

Pull Requests
URL Status Linked Edit
PR 4800 open kernc, 2017-12-11 20:25
Messages (4)
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.
History
Date User Action Args
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