Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Race condition inunittest.mock #98624

Closed
Labels
type-bugAn unexpected behavior, bug, or error
@noah-weingarden

Description

@noah-weingarden

Bug report

Concurrent calls toNonCallableMock.__getattr__() result in a race condition.

NonCallableMock implements__getattr__(), which instantiates a new mock object for an attribute if it doesn't exist yet when it's accessed. However, if the code under test is multi-threaded and multiple threads try to access the same attribute, each thread may call__getattr__() separately and instantiate adifferent mock object representing the same attribute.

I created a minimal reproducible example here:https://github.com/noah-weingarden/unittest.mock-race-condition

The test and "code-under-test" are both inmre.py.events.py contains twothreading.Event objects used to synchronize access to the mock objects. I copy-pasted the Python 3.10unittest.mock module intomock.py and added code which forces a specific order of events. I also added logging so that it's clear what's going on. Search "CHANGES START" to see where my modifications are. Even though the code-under-test callssendall(), this specific order of eventsalways creates two differentMagicMock instances forsendall(), causing the test to fail because it doesn't see thatsendall() was called.

Runpython3 mre.py to observe the logs and test failure. If you remove my synchronization, the test will fail non-deterministically as opposed to every time.

Here's the order of events:

  1. test_message_sent() tries to accesssocket().__enter__().sendall, which doesn't exist, so__getattr__() is called on the mock forsocket().__enter__().
  2. __getattr__() sees that no mock object forsocket().__enter__().sendall exists, so it calls_get_child_mock().
  3. Before_get_child_mock() runs, the context switches to the thread runningmain().
  4. main() callssendall().
  5. socket().__enter__() still doesn't have asendall attribute, so__getattr__() and then_get_child_mock() are called.
  6. _get_child_mock() creates a newMagicMock and sets it as the mock object forsendall(). Nowmain() has successfully calledsendall() and its call is recorded for this mock object.
  7. The context switches back totest_message_sent(), which continues running_get_child_mock(). It creates a newMagicMock and overwrites the earlier one.
  8. Now when the test retrievessocket().__enter__().sendall , it's accessing a different mock object than the onemain() used. It has no way to tell thatmain() actually made a call.

Your environment

At a minimum, this race condition can occur on any version of Python between 3.6 and 3.10.

Motivation for solving

Mocking is frequently used to test multi-threaded programs, so it's important thatunittest.mock be thread-safe. It's not intuitive that I would need to protect code for mocking in tests from race conditions. I spent months trying to hunt down a race condition within my organization's code before I realized that the issue was our tests' interactions withunittest.mock.

Suggested solution

Synchronize access toNonCallableMock.__getattr__() with a mutex. Either create one lock shared by allNonCallableMock objects and acquire it during each call to__getattr__(), or use fine-grained locking by creating one for each top-levelNonCallableMock object or even everyNonCallableMock object. Mocking is used almost exclusively in unit tests, so I believe the performance hit from synchronization would be worth it. I have a solution along these lines and would be happy to contribute it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    type-bugAn unexpected behavior, bug, or error

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions


      [8]ページ先頭

      ©2009-2025 Movatter.jp