Skip to content

Custom plugin options lose their value in subprocesses when passed space-separated (--opt value) #54

Description

@pcrespov

Summary

When an isolated test runs, pytest-isolated reconstructs the child process argv. For options passed in the space-separated form (--opt value), it only forwards the value if the option name is in a hardcoded allowlist. Any custom option registered by a third-party plugin or conftest.py is not in that allowlist, so the flag is forwarded without its value. The child's argparse then aborts:

__main__.py: error: argument --eval-solution-path: expected one argument

reported by the parent as:

Subprocess group='...' exited with code 4 and produced no per-test report.
The subprocess may have crashed during collection.

This contradicts the README, which states under "CLI Option Compatibility": "All remaining options (including custom plugin options) are forwarded to children."

Environment

  • pytest-isolated: 0.5.0 (also present in the 0.4.x line; logic unchanged)
  • pytest: 9.0.2
  • Python: 3.11 / 3.12

Minimal reproducer

conftest.py:

def pytest_addoption(parser):
    parser.addoption("--my-opt", action="store", default=None)

test_x.py:

import pytest

@pytest.mark.isolated
def test_iso(request):
    assert request.config.getoption("--my-opt") == "HELLO"

Behavior

invocation result
pytest --my-opt HELLO test_x.py FAIL ("expected one argument")
pytest --my-opt=HELLO test_x.py pass

The value's content is irrelevant (plain string, .py, .json, .txt all fail the same way). Only the space-vs-= form matters.

Root cause

pytest_isolated/execution.py, child-argv builder:

options_with_value = (                      # all hardcoded sets
    _INCOMPATIBLE_OPTIONS_WITH_VALUE
    | _PARENT_HANDLED_WITH_VALUE
    | _FORWARDED_OPTIONS_WITH_VALUE
)
...
# Forward all other options
forwarded_args.append(arg)
if (
    arg in options_with_value               # custom opt is NOT in the allowlist
    and i + 1 < len(args)
    and not args[i + 1].startswith("-")
):
    forwarded_args.append(args[i + 1])      # <- value forwarded only for allowlisted opts
    i += 2
else:
    i += 1                                   # <- custom opt: value silently skipped

Arity is inferred from a static set instead of from the parser. The next loop iteration sees the orphaned value token, finds it does not start with -, and drops it as a "positional selector." The --opt=value form survives only because it is a single token handled by the earlier if "=" in arg branch, which needs no arity knowledge.

Suggested fix

Determine option arity from the actual parser rather than a hardcoded allowlist, so custom/third-party options forward their values. pytest exposes the registered options; arity (nargs / whether the action stores a value) can be read from the argparse parser built during pytest_addoption. A targeted option lookup keyed on the option string would cover built-in and plugin-registered options uniformly.

Lower-effort interim mitigation: when an unknown option is followed by a non-dash token, forward that token too (treat unknown --opt value as value-taking). This is heuristic (breaks for genuinely flag-only unknown options immediately followed by a positional), so the parser-driven approach is preferred.

Workaround

Pass custom options as --opt=value instead of --opt value.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions