inline-snapshot icon indicating copy to clipboard operation
inline-snapshot copied to clipboard

`AttributeError: 'EqValue' object has no attribute '_changes'` when using `pytest-run-parallel`

Open davidhewitt opened this issue 1 year ago • 4 comments

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.

davidhewitt avatar Feb 06 '25 14:02 davidhewitt

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.

15r10nk avatar Feb 06 '25 15:02 15r10nk

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.

davidhewitt avatar Feb 06 '25 20:02 davidhewitt

I'm already working on it. It looks like a lock that protects the internal state will do the trick.

15r10nk avatar Feb 06 '25 20:02 15r10nk

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'

lmmx avatar Oct 22 '25 21:10 lmmx