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.
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 orconftest.pyis not in that allowlist, so the flag is forwarded without its value. The child's argparse then aborts:reported by the parent as:
This contradicts the README, which states under "CLI Option Compatibility": "All remaining options (including custom plugin options) are forwarded to children."
Environment
Minimal reproducer
conftest.py:test_x.py:Behavior
pytest --my-opt HELLO test_x.pypytest --my-opt=HELLO test_x.pyThe value's content is irrelevant (plain string,
.py,.json,.txtall fail the same way). Only the space-vs-=form matters.Root cause
pytest_isolated/execution.py, child-argv builder: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=valueform survives only because it is a single token handled by the earlierif "=" in argbranch, 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 duringpytest_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 valueas 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=valueinstead of--opt value.