Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/opencode-review-dispatch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -931,14 +931,14 @@ jobs:
done <<<"$configured_commands_json"
else
run_and_capture "Python coverage with missing-line report (${project_dir})" \
bash -c 'cd "$1" && PYTHONPATH=. python3 -m coverage run -m pytest tests && python3 -m coverage report --show-missing' bash "$project_dir"
bash -c 'cd "$1" && PYTHONPATH="$([ -d src ] && printf src:. || printf .)" python3 -m coverage run -m pytest tests && python3 -m coverage report --show-missing' bash "$project_dir"
fi
done < <(tracked_python_projects_with_tests)

if [ "$measured_projects" -eq 0 ]; then
if has_tracked_files '*.py'; then
run_and_capture "Python coverage with missing-line report" \
bash -c 'PYTHONPATH=. python3 -m coverage run -m pytest && python3 -m coverage report --show-missing'
bash -c 'PYTHONPATH="$([ -d src ] && printf src:. || printf .)" python3 -m coverage run -m pytest && python3 -m coverage report --show-missing'
elif python3 -I -c 'import pytest_cov' >/dev/null 2>&1; then
run_and_capture "Python pytest-cov coverage" python3 -m pytest --cov=. --cov-report=term-missing
else
Expand Down Expand Up @@ -1057,7 +1057,7 @@ jobs:
if [ -f "${project_dir}/tests/test_docstrings.py" ]; then
measured_projects=1
run_and_capture "Python docstring coverage (${project_dir})" \
bash -c 'cd "$1" && PYTHONPATH=. python3 -m pytest tests/test_docstrings.py' bash "$project_dir"
bash -c 'cd "$1" && PYTHONPATH="$([ -d src ] && printf src:. || printf .)" python3 -m pytest tests/test_docstrings.py' bash "$project_dir"
fi
done < <(tracked_python_projects_with_tests)
[ "$measured_projects" -eq 1 ]
Expand Down
19 changes: 18 additions & 1 deletion scripts/ci/safe_pytest_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,29 @@ def discover_commands(workflow_dir: pathlib.Path) -> list[list[str]]:
return commands


def _project_python_path(project_dir: pathlib.Path) -> str:
"""Return the ``PYTHONPATH`` for a project, honoring a ``src`` package layout.

Repositories that keep their importable package under ``src/`` (a
``src``-layout such as ``src/<package>``) cannot import it with the project
root alone on the path, so an offline coverage run started from the project
root fails at collection with ``ModuleNotFoundError``. When a ``src``
directory exists it is prepended to the path so both ``src``-layout and
flat-layout suites import correctly; otherwise the path is just the project
root, preserving the previous behavior.
"""
entries = ["."]
if (project_dir / "src").is_dir():
entries.insert(0, "src")
return os.pathsep.join(entries)


def execute_command(project_dir: pathlib.Path, argv: Sequence[str]) -> int:
"""Execute validated pytest argv directly in one project directory."""
if not _is_pytest_argv(argv) or any(_has_shell_control(arg) for arg in argv):
raise ValueError("configured command is not a safe direct pytest invocation")
env = os.environ.copy()
env["PYTHONPATH"] = "."
env["PYTHONPATH"] = _project_python_path(project_dir)
virtualenv_bin = project_dir.resolve() / ".venv" / "bin"
if virtualenv_bin.is_dir():
inherited_path = env.get("PATH")
Expand Down
10 changes: 10 additions & 0 deletions tests/test_opencode_agent_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -655,6 +655,16 @@ def test_opencode_python_coverage_never_resolves_pr_dependency_manifests():
assert "python3 -m coverage run -m pytest tests" in measure
assert "python3 -m coverage report --show-missing" in measure
assert "python3 -m pytest tests/test_docstrings.py" in measure
# src-layout packages (e.g. src/<pkg>) must be importable from the project
# root; the coverage and docstring runners prepend src to PYTHONPATH when a
# src directory exists, falling back to the project root otherwise.
assert "PYTHONPATH=. python3 -m coverage run -m pytest tests" not in measure
assert "[ -d src ] && printf src:. || printf ." in measure
Comment thread
coderabbitai[bot] marked this conversation as resolved.
assert "PYTHONPATH=. python3 -m pytest tests/test_docstrings.py" not in measure
assert (
'PYTHONPATH="$([ -d src ] && printf src:. || printf .)" '
"python3 -m pytest tests/test_docstrings.py"
) in measure


def test_opencode_coverage_prefers_preinstalled_declared_pnpm_before_npm():
Expand Down
16 changes: 16 additions & 0 deletions tests/test_opencode_security_boundaries.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,22 @@ def fake_run(argv, *, cwd, env, shell, check):
assert observed["env"]["PATH"].split(os.pathsep)[0] == str(virtualenv_bin)


def test_safe_pytest_executor_adds_src_layout_to_pythonpath(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
"""A ``src``-layout project imports its package: ``src`` is prepended to PYTHONPATH."""
observed: dict[str, object] = {}
(tmp_path / "src").mkdir()

def fake_run(argv, *, cwd, env, shell, check):
observed.update(env=env)
return subprocess.CompletedProcess(argv, 0)

monkeypatch.setattr(safe_pytest.subprocess, "run", fake_run)
assert safe_pytest.execute_command(tmp_path, ["pytest", "tests"]) == 0
assert observed["env"]["PYTHONPATH"] == os.pathsep.join(("src", "."))


def test_configured_pytest_discovery_drops_injected_workflow_command(tmp_path: Path) -> None:
"""Only supported one-line pytest argv are returned from a PR-controlled workflow file."""
workflow_dir = tmp_path / ".github" / "workflows"
Expand Down
Loading