boxed / mutmut

Mutation testing system
https://mutmut.readthedocs.io
BSD 3-Clause "New" or "Revised" License
931 stars 112 forks source link

mutmut run failed to collect stats #336

Closed 15r10nk closed 2 weeks ago

15r10nk commented 1 month ago

I tried to test inline-snapshot with mutmut but got the following error:

❯ hatch run mutmut run
generating mutants
    done in 2062ms
⠇ running stats
    done
failed to collect stats, no active tests found
⠸ running clean tests........................................................,..,,,,,....
............. [ 19%]
........F
======================================= FAILURES ========================================
_____________________________________ test_persist ______________________________________

project = <tests.conftest.project.<locals>.Project object at 0x7f63af037050>

    def test_persist(project):

        project.setup(
            """\
    from inline_snapshot import external

    def test_something():
        assert "hello" == snapshot(external("bbbbb*.txt"))
        assert 2 == snapshot(1+1)
    """
        )

        result = project.run("--inline-snapshot=update")

>       assert project.storage() == snapshot([])

/home/frank/projects/inline-snapshot/mutants/tests/test_external.py:76: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/home/frank/projects/inline-snapshot/src/inline_snapshot/_inline_snapshot.py:529: in __ca
ll__
    return self.func(*args, **kwargs)
/home/frank/projects/inline-snapshot/src/inline_snapshot/_inline_snapshot.py:593: in snap
shot
    snapshots[key]._re_eval(obj)
/home/frank/projects/inline-snapshot/src/inline_snapshot/_inline_snapshot.py:642: in _re_
eval
    self._value._re_eval(obj)
/home/frank/projects/inline-snapshot/src/inline_snapshot/_inline_snapshot.py:111: in _re_
eval
    re_eval(self._old_value, self._ast_node, value)
/home/frank/projects/inline-snapshot/src/inline_snapshot/_inline_snapshot.py:95: in re_ev
al
    old_items = adapter.items(old_value, node)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <inline_snapshot._adapter.sequence_adapter.ListAdapter object at 0x7f63a2881c40>
value = [], node = None

    def items(self, value, node):

>       assert isinstance(node, self.node_type), (node, self)
E       AssertionError: (None, <inline_snapshot._adapter.sequence_adapter.ListAdapter obj
ect at 0x7f63a2881c40>)

/home/frank/projects/inline-snapshot/src/inline_snapshot/_adapter/sequence_adapter.py:22:
 AssertionError
--------------------------------- Captured stdout call ----------------------------------
write code:
# äöß 🐍
from inline_snapshot import snapshot
from inline_snapshot import outsource
from inline_snapshot import external

def test_something():
    assert "hello" == snapshot(external("bbbbb*.txt"))
    assert 2 == snapshot(1+1)

running: pytest --inline-snapshot=update
     in: /tmp/pytest-of-frank/pytest-396/test_persist1
============================= test session starts ==============================
platform linux -- Python 3.12.6, pytest-8.3.3, pluggy-1.5.0
rootdir: /tmp/pytest-of-frank/pytest-396/test_persist1
plugins: inline-snapshot-0.13.3, time-machine-2.16.0, hypothesis-6.115.3, subtests-0.13.1
, xdist-3.6.1
collected 1 item

test_file.py .                                                           [100%]
=============================== inline snapshot ================================
──────────────────────────────── Fix snapshots ─────────────────────────────────
+-------------------------------- test_file.py --------------------------------+
| @@ -4,5 +4,5 @@                                                              |
|                                                                              |
|  from inline_snapshot import external                                        |
|                                                                              |
|  def test_something():                                                       |
| -    assert "hello" == snapshot(external("bbbbb*.txt"))                      |
| +    assert "hello" == snapshot("hello")                                     |
|      assert 2 == snapshot(1+1)                                               |
+------------------------------------------------------------------------------+
These changes are not applied.
Use --inline-snapshot=fix to apply them, or use the interactive mode with 
--inline-snapshot=review

─────────────────────────────── Update snapshots ───────────────────────────────
+-------------------------------- test_file.py --------------------------------+
| @@ -5,4 +5,4 @@                                                              |
|                                                                              |
|                                                                              |
|  def test_something():                                                       |
|      assert "hello" == snapshot(external("bbbbb*.txt"))                      |
| -    assert 2 == snapshot(1+1)                                               |
| +    assert 2 == snapshot(2)                                                 |
+------------------------------------------------------------------------------+
These changes will be applied, because you used --inline-snapshot=update

============================== 1 passed in 0.13s ===============================
==================================== inline snapshot ====================================
=========================== short test summary info ============================
FAILED tests/test_external.py::test_persist - AssertionError: (None, <inline_...
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 83 passed, 6 subtests passed in 8.93s
failed to run clean test

I don't know if this problem is only caused by mutmut, because inline-snapshot does also some special things with pytest/assert-rewriting and some ast analysis which can have problems when the source code is modified before it is compiled.

I hope you can reproduce the problem in this branch with.

> hatch run mutmut run
boxed commented 1 month ago

failed to collect stats

Hmm. I guess mutmut should error out there already. Without that step working, running the tests clean is kinda useless.

These changes will be applied, because you used --inline-snapshot=update

Are you running tests that themselves modify tests in-place on disk?

15r10nk commented 1 month ago

No, I'm not changing any test files in the tests/ folder on disk.

I create tests for pytest in temporary folders and use pytest to run these tests. The update you see in the log is from one of these pytest runs (rootdir is a temporary dir).

My assumption is that my tests fail for some reason when you try to collect your stats. But I have currently no idea why.

15r10nk commented 1 month ago

I added a requirements.txt to my branch and tested it in a normal venv. I got the same problem with mutmut run, but I was able to execute python -m pytest tests --import-mode=append -x -q inside the mutants directory successfully.

My best guess at the moment is that your stats collector plugin causes some problems in combination with my tests. I will later take a look at it again.

boxed commented 1 month ago

Getting closer... I discovered that when I go into mutants and execute pytest it still imports from the original src dir, and not from the mutants dir.

Yea.. as I write that I get why. I don't use a src dir for my projects, so after mutation generation I would get mutants/iommi for example, while you get mutants/src/inline_snapshot, but mutants is added to the python path, while you need mutants/src to be added!

boxed commented 1 month ago
    src_path = (Path('mutants') / 'src')
    source_path = (Path('mutants') / 'source')
    if src_path.exists():
        sys.path.insert(0, str(src_path.absolute()))
    elif source_path.exists():
        sys.path.insert(0, str(source_path.absolute))
    else:
        sys.path.insert(0, os.path.abspath('mutants'))

instead of just inserting mutants works. Now it crashes with SyntaxError: from __future__ imports must occur at the beginning of the file. Huge progress!

boxed commented 1 month ago

Now I discovered that my trampolines don't preserve generator semantics. So that's where I'm at right now.

boxed commented 1 month ago

I released 3.1.0 with fixes for from __future__, stat hard exit, and generator handling. Still no luck in running your project though, but I'm getting a lot further.

15r10nk commented 1 month ago

my generators return also values.

I think you should change your trampoline code to something like this:

    if not mutant_under_test.startswith(prefix):
        result=yield from orig(*args, **kwargs)
        return result # for the yield case
    mutant_name = mutant_under_test.rpartition('.')[-1]
    result = yield from mutants[mutant_name](*args, **kwargs)
    return result
boxed commented 1 month ago

Ah. Good point. I pushed a fix for that. Even with that fix I crash here:

  File "/Users/boxed/Projects/inline-snapshot/mutants/src/inline_snapshot/_inline_snapshot.py", line 2297, in _get_changes
    yield from _mutmut_yield_from_trampoline(object.__getattribute__(self, "xǁUndecidedValueǁ_get_changes__mutmut_orig"), object.__getattribute__(self, "xǁUndecidedValueǁ_get_changes__mutmut_mutants"), *args, **kwargs) 
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/boxed/Projects/inline-snapshot/mutants/src/inline_snapshot/_inline_snapshot.py", line 35, in _mutmut_yield_from_trampoline
    result = yield from orig(*args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/boxed/Projects/inline-snapshot/mutants/src/inline_snapshot/_inline_snapshot.py", line 1541, in xǁUndecidedValueǁ_get_changes__mutmut_orig
    yield from handle(self._ast_node, self._old_value)
  File "/Users/boxed/Projects/inline-snapshot/mutants/src/inline_snapshot/_inline_snapshot.py", line 1522, in handle
    for item in adapter.items(obj, node):
                ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/boxed/Projects/inline-snapshot/mutants/src/inline_snapshot/_adapter/dict_adapter.py", line 294, in items
    return _mutmut_trampoline(object.__getattribute__(self, "xǁDictAdapterǁitems__mutmut_orig"), object.__getattribute__(self, "xǁDictAdapterǁitems__mutmut_mutants"), *args, **kwargs) 
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/boxed/Projects/inline-snapshot/mutants/src/inline_snapshot/_adapter/dict_adapter.py", line 14, in _mutmut_trampoline
    result = orig(*args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^
  File "/Users/boxed/Projects/inline-snapshot/mutants/src/inline_snapshot/_adapter/dict_adapter.py", line 60, in xǁDictAdapterǁitems__mutmut_orig
    assert isinstance(node, ast.Dict), f'{type(node)}, {ast.unparse(node.parent.parent)}'
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: <class 'ast.Starred'>, result = orig(*args, **kwargs)

I don't really understand how this happens.

15r10nk commented 1 month ago

I asked me while reading your trampoline code if it could be possible to remove the trampoline indirection and replace it with if-else constructs in the functions, like:

def add(a,b):
   if __mutmut_use_orig(...):
       return a+b
   elif __mutmut_use_mutant(1,...):
       return a-b
   elif __mutmut_use_mutant(2,...):
       return ... # some other mutant

This would allow it to use inspect.currentframe() like I do in snapshot(). The global and class namespaces would also stay clean, which can be important for code that uses globals() or cls.__dict__

boxed commented 1 month ago

Hm. Yea I guess that could work too. The function bodies would be rather huge which can have other problems I guess. It would also make it more annoying to implement the code to get the diff for a mutant.

I tried adding

    while 'mutmut' not in frame.f_code.co_name:
        frame = frame.f_back

into snapshot(), which maybe improves things as far as mutmut is concerned, but still it fails on that AST traversal thing.

15r10nk commented 1 month ago

are you not mutating snapshot() because it uses inspect.currentframe or because it has a decorator?

boxed commented 1 month ago

I didn't realize until you said so right now that snapshot() isn't mutated! I don't know why. It should be!

15r10nk commented 1 month ago

I removed the decorator and got mutated.

I think the problem is that snapshot() has problems finding the correct frame. I will look at it later again. Maybe this helps you.

boxed commented 1 month ago

Yea ok, so decorators stop mutation! I wrote a small test for that and yea. That should just result in fewer mutants though, not a fail like the traceback above I think.

I think the problem is that snapshot() has problems finding the correct frame. I will look at it later again. Maybe this helps you. Yea, the added while loop I wrote about above will work around that issue.

15r10nk commented 1 month ago
== 60 failed, 321 passed, 28 skipped, 874 subtests passed in 76.08s (0:01:16) ==

made some progress. skipping the mutmut frames was a good idea, but I had to use:

while "mutmut" in frame.f_code.co_name:
    frame = frame.f_back

in vs not in is important.

https://github.com/15r10nk/inline-snapshot/pull/120

boxed commented 1 month ago

Doh! haha, yea my bad. Coding at night when you are sleep deprived because you're sick doesn't give you the best code :P

15r10nk commented 1 month ago

That is maybe an other issue but I found it could be caused by the failing mutmut run. I tried to apply an mutation.

❯ mutmut show src.inline_snapshot._adapter.value_adapter.xǁValueAdapterǁassign__mutmut_29
# src.inline_snapshot._adapter.value_adapter.xǁValueAdapterǁassign__mutmut_29: not checked
--- src/inline_snapshot/_adapter/value_adapter.py
+++ src/inline_snapshot/_adapter/value_adapter.py
@@ -31,7 +31,6 @@
             new_code=new_code,
             flag=flag,
             old_value=old_value,
-            new_value=new_value,
         )

         return new_value

inline-snapshot on  mutmut3 [?] is 📦 v0.13.3 via 🐍 v3.10.14 (venv_mutmut) 
❯ mutmut apply src.inline_snapshot._adapter.value_adapter.xǁValueAdapterǁassign__mutmut_29
Traceback (most recent call last):
  File "/home/frank/projects/inline-snapshot/venv_mutmut/bin/mutmut", line 8, in <module>
    sys.exit(cli())
  File "/home/frank/projects/inline-snapshot/venv_mutmut/lib/python3.10/site-packages/click/core.py", li
ne 1157, in __call__
    return self.main(*args, **kwargs)
  File "/home/frank/projects/inline-snapshot/venv_mutmut/lib/python3.10/site-packages/click/core.py", li
ne 1078, in main
    rv = self.invoke(ctx)
  File "/home/frank/projects/inline-snapshot/venv_mutmut/lib/python3.10/site-packages/click/core.py", li
ne 1688, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/home/frank/projects/inline-snapshot/venv_mutmut/lib/python3.10/site-packages/click/core.py", li
ne 1434, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/home/frank/projects/inline-snapshot/venv_mutmut/lib/python3.10/site-packages/click/core.py", li
ne 783, in invoke
    return __callback(*args, **kwargs)
  File "/home/frank/projects/mutmut/mutmut/__main__.py", line 1393, in apply
    apply_mutant(mutant_name)
  File "/home/frank/projects/mutmut/mutmut/__main__.py", line 1416, in apply_mutant
    raise FileNotFoundError(f'Could not apply mutant {mutant_name}')
FileNotFoundError: Could not apply mutant src.inline_snapshot._adapter.value_adapter.xǁValueAdapterǁassi
gn__mutmut_29
15r10nk commented 1 month ago

I found the problem:

    def assign(self, *args, **kwargs):
        yield from _mutmut_yield_from_trampoline(object.__getattribute__(self, "xǁValueAdapterǁassign__mutmut_orig"), object.__getattribute__(self, "xǁValueAdapterǁassign__mutmut_mutants"), *args, **kwargs) 

This example is from my ValueAdapter. The problem is that this generated function does not return the value from the generator The code should look like this:

    def assign(self, *args, **kwargs):
        result=yield from _mutmut_yield_from_trampoline(object.__getattribute__(self, "xǁValueAdapterǁassign__mutmut_orig"), object.__getattribute__(self, "xǁValueAdapterǁassign__mutmut_mutants"), *args, **kwargs) 
        return result
boxed commented 1 month ago

Ah. Fixed on main now.

Now I can get mutmut to collect stats on your project, but running the clean tests fail on test_persist.

15r10nk commented 1 month ago

yes, I know. I will look into it.

15r10nk commented 1 month ago

This is what I get when I run the following command in mutants/.

❯ MUTANT_UNDER_TEST="."  python -m pytest --import-mode=append -k test_outsource
========================================= test session starts ==========================================
platform linux -- Python 3.10.14, pytest-8.3.3, pluggy-1.5.0
rootdir: /home/frank/projects/inline-snapshot/mutants
configfile: pyproject.toml
plugins: xdist-3.6.1, time-machine-2.16.0, hypothesis-6.115.3, subtests-0.13.1, inline-snapshot-0.13.3
collected 381 items / 380 deselected / 1 selected                                                      

tests/test_external.py E                                                                         [100%]

================================================ ERRORS ================================================
___________________________________ ERROR at setup of test_outsource ___________________________________
file /home/frank/projects/inline-snapshot/tests/test_external.py, line 32
  def test_outsource(storage):
E       fixture 'storage' not found
>       available fixtures: LineMatcher, _config_for_test, _pytest, _sys_snapshot, cache, capfd, capfdbi
nary, caplog, capsys, capsysbinary, check_update, doctest_namespace, executing_used, linecomp, monkeypat
ch, project, pytestconfig, pytester, record_property, record_testsuite_property, record_xml_attribute, r
ecwarn, snapshot_check, source, subtests, testdir, testrun_uid, time_machine, tmp_path, tmp_path_factory
, tmpdir, tmpdir_factory, worker_id
>       use 'pytest --fixtures [testpath]' for help on them.

/home/frank/projects/inline-snapshot/tests/test_external.py:32
=========================================== inline snapshot ============================================
=========================== short test summary info ============================
ERROR tests/test_external.py::test_outsource
======================= 380 deselected, 1 error in 0.20s =======================

rootdir is mutants/ (which is correct) but the test is from the project root.

It should be from /home/frank/projects/inline-snapshot/mutants/tests/test_external.py:32

I have the same problem when I run it with mutmut run (the might look different because I changed some mutmut code.)

❯ mutmut run
generating mutants
    done in 1982ms
⠏ running stats> pytest -x -q --import-mode=append {'plugins': [<mutmut.__main__.PytestRunner.run_stats.
<locals>.StatsCollector object at 0x7fdd331bf010>]}
...........F
=============================================== FAILURES ===============================================
___________________________________ test_snapshot_generates_hasrepr ____________________________________

    def test_snapshot_generates_hasrepr():

>       Example(
            """\
    from inline_snapshot import snapshot

    class Thing:
        def __repr__(self):
            return "<something>"

        def __eq__(self,other):
            if not isinstance(other,Thing):
                return NotImplemented
            return True

    def test_thing():
        assert Thing() == snapshot()

        """
        ).run_pytest(
            ["--inline-snapshot=create"],
            returncode=snapshot(0),
            changed_files=snapshot(
                {
                    "test_something.py": """\
    from inline_snapshot import snapshot

    from inline_snapshot import HasRepr

    class Thing:
        def __repr__(self):
            return "<something>"

        def __eq__(self,other):
            if not isinstance(other,Thing):
                return NotImplemented
            return True

    def test_thing():
        assert Thing() == snapshot(HasRepr(Thing, "<something>"))

        \
    """
                }
            ),
        ).run_pytest(
            ["--inline-snapshot=disable"], returncode=0
        ).run_pytest(
            returncode=0
        )

../tests/test_code_repr.py:46:  

the test should be from tests/test_code_repr.py:46: and not from ../tests/test_code_repr.py:46:

15r10nk commented 1 month ago

I made some progress by setting the --rootdir

M mutmut/__main__.py
@@ -683,7 +683,7 @@ class ListAllTestsResult:
 class PytestRunner(TestRunner):
     def execute_pytest(self, params, **kwargs):
         import pytest
-        exit_code = int(pytest.main(params, **kwargs))
+        exit_code = int(pytest.main(["--rootdir=.",*params], **kwargs))
         if exit_code == 4:
             raise BadTestExecutionCommandsException(params)
         return exit_code

I use now the correct inline-snapshot version in every case for all of my tests. I get now a different error:

❯ mutmut run
generating mutants
    done in 1971ms
⠧ running stats...........F
=============================================== FAILURES ===============================================
___________________________________ test_snapshot_generates_hasrepr ____________________________________

    def test_snapshot_generates_hasrepr():

>       Example(
            """\
    from inline_snapshot import snapshot

    class Thing:
        def __repr__(self):
            return "<something>"

        def __eq__(self,other):
            if not isinstance(other,Thing):
                return NotImplemented
            return True

    def test_thing():
        assert Thing() == snapshot()

        """
        ).run_pytest(
            ["--inline-snapshot=create"],
            returncode=snapshot(0),
            changed_files=snapshot(
                {
                    "test_something.py": """\
    from inline_snapshot import snapshot

    from inline_snapshot import HasRepr

    class Thing:
        def __repr__(self):
            return "<something>"

        def __eq__(self,other):
            if not isinstance(other,Thing):
                return NotImplemented
            return True

    def test_thing():
        assert Thing() == snapshot(HasRepr(Thing, "<something>"))

        \
    """
                }
            ),
        ).run_pytest(
            ["--inline-snapshot=disable"], returncode=0
        ).run_pytest(
            returncode=0
        )

../tests/test_code_repr.py:46: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
src/inline_snapshot/testing/_example.py:14784: in run_pytest
    result =  _mutmut_trampoline(object.__getattribute__(self, "xǁExampleǁrun_pytest__mutmut_orig"), obj
ect.__getattribute__(self, "xǁExampleǁrun_pytest__mutmut_mutants"), *args, **kwargs)
src/inline_snapshot/testing/_example.py:14: in _mutmut_trampoline
    result = orig(*args, **kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <inline_snapshot.testing._example.Example object at 0x7f261b99dc90>
args = ['--inline-snapshot=create']

    def xǁExampleǁrun_pytest__mutmut_orig(
        self,
        args: list[str] = [],
        *,
        env: dict[str, str] = {},
        changed_files: Snapshot[dict[str, str]] | None = None,
        report: Snapshot[str] | None = None,
        returncode: Snapshot[int] | None = None,
    ) -> Example:
        """Run pytest with the given args and env variables in an seperate
        process.

        It can be used to test the interaction between your code and pytest, but it is a bit slower than
 `run_inline`

        Parameters:
            args: pytest arguments like "--inline-snapshot=fix"
            env: dict of environment variables
            changed_files: snapshot of files which are changed by this run.
            report: snapshot of the report at the end of the pytest run.
            returncode: snapshot of the pytest returncode.

        Returns:
            A new Example instance which contains the changed files.
        """

        with TemporaryDirectory() as dir:
            tmp_path = Path(dir)
            self._write_files(tmp_path)

            cmd = ["pytest", *args]

            term_columns = 80

            command_env = dict(os.environ)
            command_env["TERM"] = "unknown"
            command_env["COLUMNS"] = str(
                term_columns + 1 if platform.system() == "Windows" else term_columns
            )
            command_env.pop("CI", None)

            command_env.update(env)

            result = sp.run(cmd, cwd=tmp_path, capture_output=True, env=command_env)

            print("run>", *cmd)
            print("stdout:")
            print(result.stdout.decode())
            print("stderr:")
            print(result.stderr.decode())

            if returncode is not None:
>               assert result.returncode == returncode
E               AssertionError

src/inline_snapshot/testing/_example.py:7544: AssertionError
----------------------------------------- Captured stdout call -----------------------------------------
file: test_something.py
from inline_snapshot import snapshot

class Thing:
    def __repr__(self):
        return "<something>"

    def __eq__(self,other):
        if not isinstance(other,Thing):
            return NotImplemented
        return True

def test_thing():
    assert Thing() == snapshot()

run> pytest --inline-snapshot=create
stdout:

stderr:
Traceback (most recent call last):
  File "/home/frank/projects/inline-snapshot/venv_mutmut/bin/pytest", line 8, in <module>
    sys.exit(console_main())
  File "/home/frank/projects/inline-snapshot/venv_mutmut/lib/python3.10/site-packages/_pytest/config/__i
nit__.py", line 201, in console_main
    code = main()
  File "/home/frank/projects/inline-snapshot/venv_mutmut/lib/python3.10/site-packages/_pytest/config/__i
nit__.py", line 156, in main
    config = _prepareconfig(args, plugins)
  File "/home/frank/projects/inline-snapshot/venv_mutmut/lib/python3.10/site-packages/_pytest/config/__i
nit__.py", line 341, in _prepareconfig
    config = pluginmanager.hook.pytest_cmdline_parse(
  File "/home/frank/projects/inline-snapshot/venv_mutmut/lib/python3.10/site-packages/pluggy/_hooks.py",
 line 513, in __call__
    return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
  File "/home/frank/projects/inline-snapshot/venv_mutmut/lib/python3.10/site-packages/pluggy/_manager.py
", line 120, in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
  File "/home/frank/projects/inline-snapshot/venv_mutmut/lib/python3.10/site-packages/pluggy/_callers.py
", line 139, in _multicall
    raise exception.with_traceback(exception.__traceback__)
  File "/home/frank/projects/inline-snapshot/venv_mutmut/lib/python3.10/site-packages/pluggy/_callers.py
", line 122, in _multicall
    teardown.throw(exception)  # type: ignore[union-attr]
  File "/home/frank/projects/inline-snapshot/venv_mutmut/lib/python3.10/site-packages/_pytest/helpconfig
.py", line 105, in pytest_cmdline_parse
    config = yield
  File "/home/frank/projects/inline-snapshot/venv_mutmut/lib/python3.10/site-packages/pluggy/_callers.py
", line 103, in _multicall
    res = hook_impl.function(*args)
  File "/home/frank/projects/inline-snapshot/venv_mutmut/lib/python3.10/site-packages/_pytest/config/__i
nit__.py", line 1140, in pytest_cmdline_parse
    self.parse(args)
  File "/home/frank/projects/inline-snapshot/venv_mutmut/lib/python3.10/site-packages/_pytest/config/__i
nit__.py", line 1494, in parse
    self._preparse(args, addopts=addopts)
  File "/home/frank/projects/inline-snapshot/venv_mutmut/lib/python3.10/site-packages/_pytest/config/__i
nit__.py", line 1381, in _preparse
    self.pluginmanager.load_setuptools_entrypoints("pytest11")
  File "/home/frank/projects/inline-snapshot/venv_mutmut/lib/python3.10/site-packages/pluggy/_manager.py
", line 421, in load_setuptools_entrypoints
    plugin = ep.load()
  File "/home/frank/.local/share/hatch/pythons/3.10/python/lib/python3.10/importlib/metadata/__init__.py
", line 171, in load
    module = import_module(match.group('module'))
  File "/home/frank/.local/share/hatch/pythons/3.10/python/lib/python3.10/importlib/__init__.py", line 1
26, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
  File "<frozen importlib._bootstrap>", line 992, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1006, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 688, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 883, in exec_module
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "/home/frank/projects/inline-snapshot/mutants/src/inline_snapshot/__init__.py", line 46, in <modu
le>
    from ._code_repr import customize_repr
  File "/home/frank/projects/inline-snapshot/mutants/src/inline_snapshot/_code_repr.py", line 562, in <m
odule>
    def _(value: Enum):
  File "/home/frank/projects/inline-snapshot/mutants/src/inline_snapshot/_code_repr.py", line 410, in cu
stomize_repr
    result =  _mutmut_trampoline(x_customize_repr__mutmut_orig, x_customize_repr__mutmut_mutants, *args,
 **kwargs)
  File "/home/frank/projects/inline-snapshot/mutants/src/inline_snapshot/_code_repr.py", line 12, in _mu
tmut_trampoline
    record_trampoline_hit(orig.__module__ + '.' + orig.__name__)
  File "/home/frank/projects/mutmut/mutmut/__main__.py", line 135, in record_trampoline_hit
    if mutmut.config.max_stack_depth != -1:
AttributeError: module 'mutmut' has no attribute 'config'

=========================================== inline snapshot ============================================
Error: one snapshot has incorrect values (--inline-snapshot=fix)

You can also use --inline-snapshot=review to approve the changes interactively
=========================== short test summary info ============================
FAILED tests/test_code_repr.py::test_snapshot_generates_hasrepr - AssertionError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 11 passed in 1.16s
failed to collect stats. runner returned 1

The reason is that I write source files in temporary folders and execute them with pytest in a subprocess. But mutmut is not initialized in this case. Is it possible that mutmut3 can work in subprocesses?

boxed commented 1 month ago

The reason is that I write source files in temporary folders and execute them with pytest in a subprocess.

And you want those subprocesses to have mutants turned on?

But mutmut is not initialized in this case. Is it possible that mutmut3 can work in subprocesses?

Not in this case I think. Even if it survives the conf (by say loading it), the registration of the trampoline hit is communicated in-process. I guess that could be changed to be a network call or a local pipe or something...

With this conf in setup.cfg i get a bit further

[mutmut]
also_copy=
    conftest.py
    pyproject.toml
do_not_mutate=
    src/inline_snapshot/pytest_plugin.py
15r10nk commented 1 month ago

And you want those subprocesses to have mutants turned on?

I would be nice if it is possible, but it is not a must have. I fixed the subprocess problem for now:

def record_trampoline_hit(name):
    if not hasattr(mutmut,"config"):
        return

This causes no crash and I hope it is ok for mutmut.

I think the only open problem is that mutmut does not copy my py.typed file from src/inline-snapshot

boxed commented 1 month ago

You can add it to the also_copy config.

boxed commented 2 weeks ago

This issue turned into a pile of issues, many (all?) of which are solved, so I'm closing this. If there are problems still, it's better to create new issues for that.