`AttributeError: 'EqValue' object has no attribute '_changes'` when using `pytest-run-parallel`
In pydantic-core I've started testing free-threaded Python . I'm using the pytest-run-parallel plugin to try to help weed out threading bugs. There's a crash at the end of the test run inside inline-snapshot code:
File "/home/runner/work/pydantic-core/pydantic-core/.venv/lib/python3.13t/site-packages/inline_snapshot/pytest_plugin.py", line 271, in pytest_terminal_summary
for change in snapshot._changes():
~~~~~~~~~~~~~~~~~^^
File "/home/runner/work/pydantic-core/pydantic-core/.venv/lib/python3.13t/site-packages/inline_snapshot/_inline_snapshot.py", line 671, in _changes
yield from self._value._get_changes()
~~~~~~~~~~~~~~~~~~~~~~~~^^
File "/home/runner/work/pydantic-core/pydantic-core/.venv/lib/python3.13t/site-packages/inline_snapshot/_inline_snapshot.py", line 294, in _get_changes
return iter(self._changes)
For context, pytest-run-parallel takes each test and runs it in parallel with itself N times (currently 2 in the pydantic-core CI). I suspect that it is this multiple (in parallel) evaluations somehow breaking an assumption in inline-snapshot.
inline-snapshot can currently not create/fix snapshot in parallel.
You can try to use --inline-snapshot=disable which disables most of the snapshot logic. It makes snapshot() basically a noop.
I will try to make inline-snapshot thread-save.
Thanks, looks like --inline-snapshot=disable makes the whole test suite green.
If it's possible to make inline-snapshot thread-safe, I'm sure that would be useful for pushing free-threaded testing across the ecosystem.
I'm already working on it. It looks like a lock that protects the internal state will do the trick.
Seeing this when trying to run the test suite on #313 I'm afraid, looks like it's the end of the test run again.
Click to show full traceback
tests/test_xfail.py FFF [100%]Traceback (most recent call last):
File "/home/louis/.local/share/hatch/env/virtual/inline-snapshot/vvw5-dmI/hatch-test.py3.13-low/bin/pytest", line 10, in <module>
sys.exit(console_main())
~~~~~~~~~~~~^^
File "/home/louis/.local/share/hatch/env/virtual/inline-snapshot/vvw5-dmI/hatch-test.py3.13-low/lib/python3.13/site-packages/_pytest/config/__init__.py", line 201, in console_main
code = main()
File "/home/louis/.local/share/hatch/env/virtual/inline-snapshot/vvw5-dmI/hatch-test.py3.13-low/lib/python3.13/site-packages/_pytest/config/__init__.py", line 175, in main
ret: ExitCode | int = config.hook.pytest_cmdline_main(config=config)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^
File "/home/louis/.local/share/hatch/env/virtual/inline-snapshot/vvw5-dmI/hatch-test.py3.13-low/lib/python3.13/site-packages/pluggy/_hooks.py", line 512, in __call__
return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/louis/.local/share/hatch/env/virtual/inline-snapshot/vvw5-dmI/hatch-test.py3.13-low/lib/python3.13/site-packages/pluggy/_manager.py", line 120, in _hookexec
return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/louis/.local/share/hatch/env/virtual/inline-snapshot/vvw5-dmI/hatch-test.py3.13-low/lib/python3.13/site-packages/pluggy/_callers.py", line 167, in _multicall
raise exception
File "/home/louis/.local/share/hatch/env/virtual/inline-snapshot/vvw5-dmI/hatch-test.py3.13-low/lib/python3.13/site-packages/pluggy/_callers.py", line 121, in _multicall
res = hook_impl.function(*args)
File "/home/louis/.local/share/hatch/env/virtual/inline-snapshot/vvw5-dmI/hatch-test.py3.13-low/lib/python3.13/site-packages/_pytest/main.py", line 336, in pytest_cmdline_main
return wrap_session(config, _main)
File "/home/louis/.local/share/hatch/env/virtual/inline-snapshot/vvw5-dmI/hatch-test.py3.13-low/lib/python3.13/site-packages/_pytest/main.py", line 324, in wrap_session
config.hook.pytest_sessionfinish(
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
session=session, exitstatus=session.exitstatus
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/louis/.local/share/hatch/env/virtual/inline-snapshot/vvw5-dmI/hatch-test.py3.13-low/lib/python3.13/site-packages/pluggy/_hooks.py", line 512, in __call__
return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/louis/.local/share/hatch/env/virtual/inline-snapshot/vvw5-dmI/hatch-test.py3.13-low/lib/python3.13/site-packages/pluggy/_manager.py", line 120, in _hookexec
return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/louis/.local/share/hatch/env/virtual/inline-snapshot/vvw5-dmI/hatch-test.py3.13-low/lib/python3.13/site-packages/pluggy/_callers.py", line 167, in _multicall
raise exception
File "/home/louis/.local/share/hatch/env/virtual/inline-snapshot/vvw5-dmI/hatch-test.py3.13-low/lib/python3.13/site-packages/pluggy/_callers.py", line 139, in _multicall
teardown.throw(exception)
~~~~~~~~~~~~~~^^^^^^^^^^^
File "/home/louis/.local/share/hatch/env/virtual/inline-snapshot/vvw5-dmI/hatch-test.py3.13-low/lib/python3.13/site-packages/_pytest/logging.py", line 873, in pytest_sessionfinish
return (yield)
^^^^^
File "/home/louis/.local/share/hatch/env/virtual/inline-snapshot/vvw5-dmI/hatch-test.py3.13-low/lib/python3.13/site-packages/pluggy/_callers.py", line 139, in _multicall
teardown.throw(exception)
~~~~~~~~~~~~~~^^^^^^^^^^^
File "/home/louis/.local/share/hatch/env/virtual/inline-snapshot/vvw5-dmI/hatch-test.py3.13-low/lib/python3.13/site-packages/_pytest/terminal.py", line 936, in pytest_sessionfinish
result = yield
^^^^^
File "/home/louis/.local/share/hatch/env/virtual/inline-snapshot/vvw5-dmI/hatch-test.py3.13-low/lib/python3.13/site-packages/pluggy/_callers.py", line 139, in _multicall
teardown.throw(exception)
~~~~~~~~~~~~~~^^^^^^^^^^^
File "/home/louis/.local/share/hatch/env/virtual/inline-snapshot/vvw5-dmI/hatch-test.py3.13-low/lib/python3.13/site-packages/_pytest/warnings.py", line 119, in pytest_sessionfinish
return (yield)
^^^^^
File "/home/louis/.local/share/hatch/env/virtual/inline-snapshot/vvw5-dmI/hatch-test.py3.13-low/lib/python3.13/site-packages/pluggy/_callers.py", line 121, in _multicall
res = hook_impl.function(*args)
File "/home/louis/dev/inline-snapshot/src/inline_snapshot/pytest_plugin.py", line 180, in pytest_sessionfinish
self.session.show_report(Console(highlight=False))
~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/louis/dev/inline-snapshot/src/inline_snapshot/_snapshot_session.py", line 409, in show_report
for change in snapshot._changes():
~~~~~~~~~~~~~~~~~^^
File "/home/louis/dev/inline-snapshot/src/inline_snapshot/_inline_snapshot.py", line 151, in _changes
yield from self._value._get_changes()
~~~~~~~~~~~~~~~~~~~~~~~~^^
File "/home/louis/dev/inline-snapshot/src/inline_snapshot/_snapshot/eq_value.py", line 39, in _get_changes
return iter(self._changes)
^^^^^^^^^^^^^
AttributeError: 'EqValue' object has no attribute '_changes'
AttributeError: 'EqValue' object has no attribute '_changes'
Setting --inline-snapshot=disable allows it to finish (it registers the crashes as SUBFAIL instead)
[{0: 3, 1: 2+2, 2: 3} -> {0: 3, 1: 4, 2: 3} <update>] SUBFAIL tests/test_preserve_values.py::test_generic - AttributeError: 'EqValue' object has no attribute '_changes'
[{0: 3, 1: 3, 2: 5} -> {0: 3, 1: 3} <fix>] SUBFAIL tests/test_preserve_values.py::test_generic - AttributeError: 'EqValue' object has no attribute '_changes'
[{0: 3, 1: 3} -> {0: 3, 1: 3, 2: 8} <fix>] SUBFAIL tests/test_preserve_values.py::test_generic - AttributeError: 'EqValue' object has no attribute '_changes'
[{0: 3, 1: 3, 2: 2+2} -> {0: 3, 1: 3, 2: 4} <update>] SUBFAIL tests/test_preserve_values.py::test_generic - AttributeError: 'EqValue' object has no attribute '_changes'
[{0: 3, 1: 3, 2: 3} -> {0: 3, 1: 3, 2: 3} <>] SUBFAIL tests/test_preserve_values.py::test_generic - AttributeError: 'EqValue' object has no attribute '_changes'