Skip to content

pyutils-unusedcode crashes when git grep finds no matches for pytest fixtures #186

@myakove

Description

@myakove

Description

The pyutils-unusedcode tool crashes with a subprocess.CalledProcessError when it encounters pytest fixtures that are used as parameters rather than called directly.

Environment

  • Tool: pyutils-unusedcode from python-utility-scripts
  • Python: 3.12
  • OS: Linux

Steps to Reproduce

  1. Create a pytest fixture in a test file:

    @pytest.fixture
    def sample_clusters() -> list[ClusterInfo]:
        return [...]
  2. Use the fixture as a parameter in test functions:

    def test_something(sample_clusters: list[ClusterInfo]) -> None:
        # Use the fixture
        assert len(sample_clusters) == 2
  3. Run pyutils-unusedcode on the codebase

Expected Behavior

The tool should recognize that pytest fixtures are used as parameters and either:

  • Skip them automatically
  • Handle the git grep exit code gracefully
  • Continue processing other functions

Actual Behavior

The tool crashes with:

subprocess.CalledProcessError: Command '['git', 'grep', '-wE', 'sample_clusters(.*)']' returned non-zero exit status 1.

Full Stack Trace:

Traceback (most recent call last):
  File "/path/to/pyutils-unusedcode", line 10, in <module>
    sys.exit(get_unused_functions())
  File "/path/to/click/core.py", line 1442, in __call__
    return self.main(*args, **kwargs)
  [... stack trace continues ...]
  File "/path/to/unused_code.py", line 78, in process_file
    _func_grep_found = subprocess.check_output(["git", "grep", "-wE", f"{func.name}(.*)"], shell=False)
  File "/path/to/subprocess.py", line 466, in check_output
    return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
  File "/path/to/subprocess.py", line 571, in run
    raise CalledProcessError(retcode, process.args,
subprocess.CalledProcessError: Command '['git', 'grep', '-wE', 'sample_clusters(.*)']' returned non-zero exit status 1.

Root Cause

  1. The tool uses git grep -wE 'function_name(.*)' to find function usage
  2. Pytest fixtures are used as parameters (e.g., def test_foo(sample_clusters):), not called with parentheses
  3. When git grep finds no matches, it returns exit code 1
  4. The tool uses subprocess.check_output() which raises an exception on non-zero exit codes

Suggested Fix

In unused_code.py line 78, handle the case where git grep returns no matches:

try:
    _func_grep_found = subprocess.check_output(
        ["git", "grep", "-wE", f"{func.name}(.*)"], 
        shell=False,
        stderr=subprocess.DEVNULL
    )
except subprocess.CalledProcessError as e:
    if e.returncode == 1:  # No matches found
        _func_grep_found = b""  # Treat as empty result
    else:
        raise  # Re-raise for other errors

Workaround

Add pytest fixture names to the exclusion list:

pyutils-unusedcode --exclude-function-prefixes "fixture_name1,fixture_name2"

Additional Context

This issue commonly occurs with pytest fixtures, which are a standard testing pattern. The tool should handle this case gracefully rather than requiring manual exclusions for every fixture.

The tool should ideally:

  1. Detect pytest fixtures automatically (functions decorated with @pytest.fixture)
  2. Use different search patterns for fixtures (look for parameter usage instead of function calls)
  3. Handle git grep exit codes gracefully for any function that might not have matches

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions