Skip to content

Run Python coverage tooling in a venv#257

Open
leynos wants to merge 28 commits intomainfrom
fix/coverage-venv-fix
Open

Run Python coverage tooling in a venv#257
leynos wants to merge 28 commits intomainfrom
fix/coverage-venv-fix

Conversation

@leynos
Copy link
Copy Markdown
Owner

@leynos leynos commented Apr 26, 2026

Summary

This branch runs the generate-coverage Python coverage phase from a short-lived virtual environment instead of relying on uv run or any system interpreter install path. The change avoids PEP 668 failures on GitHub-hosted Ubuntu runners where Python 3.12 is externally managed, while keeping coverage tooling isolated from the system interpreter.

It also hardens the Makefile check targets so automation with a narrow PATH can still find uv and action-validator from their common user-local install locations.

Review walkthrough

Validation

  • python -c "import ast, sys; ast.parse(open('.github/actions/generate-coverage/scripts/run_python.py').read()); print('parse ok')": passed
  • uv run --with typer --with packaging --with plumbum --with pyyaml pytest .github/actions/generate-coverage/tests/test_scripts.py -k 'coverage_cmd_uses_venv_python or tmp_coveragepy_xml_invokes_venv_python or run_python_cobertura_passes_out_flag' -v: passed, 3 selected tests passed
  • make check-fmt: passed
  • make typecheck: passed
  • make lint: passed
  • make test: passed, 668 passed and 90 skipped
  • make check-fmt lint typecheck: passed
  • env PATH=/usr/bin:/bin make check-fmt lint typecheck: passed

Notes

The branch deliberately does not add --break-system-packages or restore a system install path. Coverage tooling is installed into .venv-coverage and invoked through that venv's Python interpreter.

Summary by Sourcery

Run Python coverage tooling from an isolated, job-local virtual environment instead of via uv run, and make local tooling resolution in the Makefile more robust.

New Features:

  • Introduce a dedicated .venv-coverage virtual environment and helper functions to create it, install coverage tooling into it, and reuse its Python interpreter for all coverage commands.

Bug Fixes:

  • Avoid relying on system Python or uv run --with for coverage, preventing failures on externally managed Python installations such as GitHub-hosted Ubuntu runners.

Enhancements:

  • Refactor coverage command construction to use a cached venv-based Python command while keeping slipcover, pytest, and coverage invocation behaviour consistent.
  • Strengthen tests around coverage command construction and venv lifecycle, including recovery from broken or missing venv Python executables.
  • Improve Makefile commands to resolve uv, action-validator, and markdownlint from common user-local install locations and to lint only changed Markdown files where possible.

Documentation:

  • Document the new Python coverage venv architecture, lifecycle, and concurrency assumptions, and update the generate-coverage README to describe the venv-based installation behaviour.

leynos added 3 commits April 27, 2026 01:44
Create a throwaway coverage virtual environment and install
`slipcover`, `pytest`, and `coverage` into it with `uv pip --python`.
Avoid relying on the separate `uvx` shim being present on PATH when
running formatting and lint targets from automation.
Prefer the common user-local install locations for `uv` and
`action-validator` so automation with a narrow PATH can still run the
format, lint, and typecheck targets.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 26, 2026

Overview

This pull request changes how Python coverage tooling is executed by creating and using a short‑lived, job‑local virtual environment (.venv-coverage) instead of relying on system interpreters or per‑invocation uv run wrappers. The authoritative execplan/design for this work is included in docs/generate-coverage-design.md (added/updated in this PR) and is referenced by the README update in .github/actions/generate-coverage/README.md.

Key technical changes

  • run_python.py

    • Implements full venv lifecycle and robust caching/helpers:
      • _find_coverage_python(): locate candidate Python executables for .venv-coverage.
      • _ensure_coverage_venv(): create or repair .venv-coverage, run uv sync --inexact --python <venv-python> to align project dependencies, and install tooling (slipcover, pytest, coverage) into the venv via uv pip install --python <venv-python> (explicitly disallowing --system installs).
      • _remove_coverage_venv() and _recreate_coverage_venv(): extracted helpers to make repair flows explicit and reduce cyclomatic complexity.
      • _coverage_python_cmd(): cached accessor (functools.lru_cache) returning a plumbum command for the venv Python; initialises and memoises the venv Python once per process to avoid repeated setup.
    • Replaces uv-run-based execution with venv-targeted invocations (e.g. python -m coverage, python -m slipcover, python -m pytest) and removes the old _uv_python_cmd().
    • Implements broken‑venv recovery: detects missing/invalid Python binaries in the venv and recreates/repairs the venv state.
    • Adds NumPy‑style docstrings for main helpers and improves observability via typer.echo() at decision points.
  • Tests

    • Refactors and extends tests to validate venv‑Python invocations rather than uv run command shapes; broad venv lifecycle coverage added: creation, sync/install, reuse, and recovery from broken‑cache scenarios (missing executable, broken symlink, non‑directory placeholder, invalid candidate).
    • Introduces a frozen dataclass CoverageFmtSpec to reduce argument arity and address a static‑analysis finding.
    • Adds VenvTestSetup scaffolding, predicate helpers and shared assertion helpers; introduces snapshot‑style assertions for command structure.
    • Strengthens tests to assert tooling installation excludes --system, verifies caching behaviour (setup only once), and covers venv lifecycle and error paths.
  • Makefile & workflow helper

    • Hardens tool resolution via variables preferring user‑local installs:
      • UV (defaults to ~/.local/bin/uv), ACTION_VALIDATOR (prefers user-local locations), MDLINT (prefers ~/.bun/bin/markdownlint), MARKDOWNLINT_BASE (defaults to origin/main).
    • Uses these variables across targets and prefers linting changed Markdown files relative to MARKDOWNLINT_BASE with a safe fallback when the base is absent.
    • Adds workflow_scripts/markdownlint-check.sh to robustly handle base-missing cases and per-file linting.
  • Documentation & design linkage

    • Updates .github/actions/generate-coverage/README.md to describe the .venv-coverage behaviour and venv‑based installation of slipcover/pytest/coverage.
    • Adds/updates docs/generate-coverage-design.md (the authoritative execplan included in this PR) documenting the venv architecture, lifecycle, _ensure_coverage_venv() behaviour, caching strategy (process‑local lru_cache), concurrency rationale (tied to sequential GitHub Actions step execution), and a small public‑API table.

Commits and notable fixes

  • Addresses a static‑analysis finding (excessive function arguments) by introducing CoverageFmtSpec and refactoring test helpers.
  • Extracts venv recreation/removal helpers to simplify control flow and improve testability.
  • Reworks Makefile markdownlint handling and quotes invocations to avoid brittle shell behaviour.
  • Tests updated to assert command shapes, reject --system installs, and cover venv lifecycle and broken/missing venv Python scenarios.

Code‑quality and API notes

  • No exported/public function signatures were changed; changes are internal and covered by expanded unit tests.
  • Docstrings, observability, recovery paths, and test duplication findings were addressed during review.
  • The branch deliberately avoids --break-system-packages.

Validation

  • Source parsed successfully.
  • make check-fmt, make typecheck and make lint passed (including under a restricted PATH).
  • Full test run reported: 668 passed, 90 skipped.
  • Validations exercised venv lifecycle and recovery tests under altered PATH/uv conditions.

Design choices

  • The caching strategy relies on a process‑local functools.lru_cache and the sequential execution model of GitHub Actions steps; no cross‑process coordination is introduced.
  • docs/generate-coverage-design.md in this PR is the authoritative execplan for the venv architecture, lifecycle and concurrency assumptions.

Walkthrough

Create and reuse a dedicated .venv-coverage, install slipcover, pytest and coverage into it, and run coverage-related commands via the venv Python (memoised per process) instead of using uv run --with. Update Makefile to prefer local tool executables and adjust lint/test targets accordingly.

Changes

Cohort / File(s) Summary
Coverage script refactoring
.github/actions/generate-coverage/scripts/run_python.py
Add _find_coverage_python(), _ensure_coverage_venv(), _coverage_python_cmd(); remove _uv_python_cmd(); switch from uv run to invoking coverage/slipcover/pytest via the venv Python; implement venv discovery, lazy creation/recreation, uv sync --inexact --python, uv pip install --python for tooling, and cache the plumbum Python command with lru_cache.
Tests for venv flow
.github/actions/generate-coverage/tests/test_scripts.py
Replace uv run-centric assertions with venv-Python-targeted assertions; add helpers to compute/patch expected venv Python; add lifecycle tests for create/reuse/rebuild and invalid candidate handling; assert uv pip install --python <venv_python> (no --system) and tooling packages; verify _coverage_python_cmd() caches the command; update exporter and main() tests to expect venv-targeted subprocess invocations and capture run_cmd method.
Makefile tool config
Makefile
Introduce UV, ACTION_VALIDATOR, MDLINT, and MARKDOWNLINT_BASE variables preferring local installs with fallbacks; update targets (test, .venv, lint, fmt, check-fmt, markdownlint) to invoke tools via these variables; change ruff invocation to $(UV) tool run ruff ...; delegate markdownlint file selection to workflow_scripts/markdownlint-check.sh.
Docs & README
.github/actions/generate-coverage/README.md, docs/generate-coverage-design.md
Document venv-based coverage architecture, lifecycle, _coverage_python_cmd() caching and concurrency model; update README to use .venv-coverage and uv commands; adjust markdown-linting notes and MD013 bracket usage.
Markdown lint helper
workflow_scripts/markdownlint-check.sh
Add shell script to compute changed markdown files (against MARKDOWNLINT_BASE) and run markdownlint per-file or fallback to lint-all on no changes or git failure; return aggregated status.

Sequence Diagram(s)

sequenceDiagram
    participant Script as run_python.py
    participant UV as uv
    participant Venv as .venv-coverage
    participant Tooling as slipcover/pytest/coverage

    Script->>Venv: Query cached Python (_coverage_python_cmd/_find_coverage_python)
    alt missing or invalid
        Script->>UV: Create or repair venv (`uv venv .venv-coverage`)
        UV-->>Script: venv created
        Script->>UV: Sync deps (`uv sync --inexact --python <venv_python>`)
        UV-->>Script: deps synced
        Script->>UV: Install tooling (`uv pip install --python <venv_python> slipcover pytest coverage`)
        UV-->>Script: tooling installed
    else cached valid
        Venv-->>Script: Return cached Python
    end
    Script->>Tooling: Run `python -m slipcover` / `python -m pytest` / `python -m coverage` via cached venv Python
    Tooling-->>Script: Emit coverage data / reports
Loading

Poem

Spin the venv, quick and neat 🌀
Install the tools, make the steps complete,
Run the runners with the local Python call,
Gather the coverage, tidy and small,
CI hums smoother from the new venv hall.


Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore

❌ Failed checks (3 errors, 5 warnings)

Check name Status Explanation Resolution
Testing (Overall) ❌ Error Test suite contains strong tests catching major errors but exhibits critical weaknesses: vacuous negative-only assertions, direct mocking of internal functions bypassing venv validation, and extensive use of fake_run_cmd that validates construction but not execution. Merge redundant non-Cobertura format tests; replace mocked _coverage_python_cmd tests with real invocations verifying venv Python paths; add positive flag-presence assertions; extract and independently test _find_coverage_python(); add integration test for _ensure_coverage_venv() idempotency.
Module-Level Documentation ❌ Error The designated Public API functions lack proper NumPy-style documentation with Parameters, Returns, and Raises sections; informal prose is used instead of structured format. Expand docstrings for _ensure_coverage_venv() and _coverage_python_cmd() to NumPy style with Parameters, Returns, and Raises sections; add Parameters and Returns sections to coverage_cmd_for_fmt(), tmp_coveragepy_xml(), main(), and _coverage_args().
Unit Architecture ❌ Error _coverage_python_cmd() violates unit architecture by performing side-effects (venv initialization) while presenting as a query function returning BoundCommand. Refactor _coverage_python_cmd() as pure query function; move _ensure_coverage_venv() call to main() entry point to make initialization boundary and fallibility explicit at call sites.
Developer Documentation ⚠️ Warning Architectural change lacks roadmap item and execution plan; developers-guide.md referenced but does not exist in repository. Add roadmap item for Python coverage venv feature and create corresponding execution plan in docs/execplans/.
Testing (Unit) ⚠️ Warning Two tests with identical setup and overlapping concerns remain unmerged despite explicit review comment requesting consolidation. Merge test_coverage_cmd_default_branch_has_shared_args and test_non_cobertura_formats_do_not_emit_out_flag into a single test with combined assertions.
Domain Architecture ⚠️ Warning Pull request embeds infrastructure concerns into core logic, coupling business code to plumbum, typer, filesystem paths, and vendor-specific exceptions. Introduce adapter boundaries with domain types (CoverageEnvironment, CoverageCommand, CoverageError) and a CoverageEnvironmentRepository to segregate infrastructure from business logic.
Observability ⚠️ Warning Implementation provides partial observability for venv creation, broken-cache recovery, and tooling installation, but lacks logging for venv reuse and LRU cache hits. Add typer.echo() calls to signal venv reuse (after line 111) and LRU cache hits (in _coverage_python_cmd()), plus add capsys verification in venv lifecycle tests.
Concurrency And State ⚠️ Warning Concurrency model incomplete: TOCTOU race in _recreate_coverage_venv(), missing synchronisation for matrix jobs on shared runners, no idempotency safeguards, and insufficient test coverage for multi-process scenarios. Add file locking or atomic directory creation to guard venv setup. Document whether .venv-coverage is job-local or workspace-local. Add success marker file to skip redundant operations. Test concurrent process access.
✅ Passed checks (10 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarises the main change: running Python coverage tooling in a virtual environment instead of via uv run or system interpreter.
Description check ✅ Passed The description comprehensively explains the rationale, implementation details, validation performed, and changes across all modified files, directly relating to the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 84.44% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
User-Facing Documentation ✅ Passed PR does not require user-facing documentation updates; public interface and action inputs/outputs remain unchanged.
Testing (Property / Proof) ✅ Passed PR introduces invariants over a bounded finite state space (5–6 discrete venv scenarios) with comprehensive explicit test coverage; property-based testing is most valuable for unbounded ranges, not fixed enumerations.
Testing (Compile-Time / Ui) ✅ Passed PR adds text-based outputs and structured GitHub Actions file, tested with focused assertions on captured stdout and file content. Direct assertion on simple, deterministic single-line formatted strings is appropriate.
Security And Privacy ✅ Passed Pull request demonstrates strong security practices: safe command construction via plumbum, proper shell variable quoting, isolated virtual environment with whitelisted dependencies, symlink attack prevention, and no secrets or credentials.
Performance And Resource Use ✅ Passed The venv setup's unbounded subprocess operations are acceptable for the intended GitHub Actions job context where the script runs once per job.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/coverage-venv-fix

Comment @coderabbitai help to get the list of available commands and usage tips.

@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Apr 26, 2026

Reviewer's Guide

Run Python coverage generation inside an isolated, lazily-created .venv-coverage virtualenv instead of uv run/system Python, strengthen tests and docs around this behavior, and make the Makefile’s lint/test tooling resolution more robust and configurable in constrained environments.

Sequence diagram for Python coverage venv setup and reuse

sequenceDiagram
    actor GitHubActions
    participant generate_coverage_action
    participant run_python_script
    participant coverage_python_cmd as _coverage_python_cmd
    participant create_venv
    participant install_tools as install_coverage_tools
    participant uv_cli as uv
    participant slipcover_pytest_coverage

    GitHubActions->>generate_coverage_action: run Python coverage step
    generate_coverage_action->>run_python_script: invoke run_python.py

    Note over run_python_script,coverage_python_cmd: First coverage invocation
    run_python_script->>coverage_python_cmd: request coverage Python
    coverage_python_cmd->>create_venv: create_venv()
    alt venv_does_not_exist
        create_venv->>uv_cli: venv .venv-coverage
        uv_cli-->>create_venv: venv created
    else venv_exists
        create_venv-->>create_venv: reuse existing venv
    end
    create_venv-->>coverage_python_cmd: venv_python_path

    coverage_python_cmd->>install_tools: install_coverage_tools(venv_python_path)
    install_tools->>uv_cli: pip install --python venv_python slipcover pytest coverage
    uv_cli-->>install_tools: tools installed
    install_tools-->>coverage_python_cmd: success
    coverage_python_cmd-->>run_python_script: BoundCommand for venv_python

    run_python_script->>slipcover_pytest_coverage: run slipcover/pytest/coverage via venv_python
    slipcover_pytest_coverage-->>run_python_script: coverage results
    run_python_script-->>generate_coverage_action: Cobertura XML
    generate_coverage_action-->>GitHubActions: upload coverage artefact

    Note over run_python_script,coverage_python_cmd: Subsequent coverage invocation
    GitHubActions->>generate_coverage_action: run Python coverage step again
    generate_coverage_action->>run_python_script: invoke run_python.py
    run_python_script->>coverage_python_cmd: request coverage Python
    coverage_python_cmd-->>run_python_script: reuse cached BoundCommand
    run_python_script->>slipcover_pytest_coverage: run tools via cached venv_python
    slipcover_pytest_coverage-->>run_python_script: coverage results
Loading

Flow diagram for coverage venv lifecycle functions

flowchart TD
    start[Start coverage tooling setup]
    cmd[_coverage_python_cmd]
    venv_check[Does .venv-coverage exist]
    create[create_venv]
    install[install_coverage_tools]
    uv_venv[uv venv .venv-coverage]
    detect_python[Locate venv python executable]
    python_missing[Python executable missing]
    recreate[Remove and recreate .venv-coverage]
    uv_pip[uv pip install --python venv_python slipcover pytest coverage]
    cache_cmd[Cache BoundCommand for venv python]
    return_cmd[Return cached BoundCommand]

    start --> cmd
    cmd -->|_COVERAGE_PYTHON_CMD is None| create
    cmd -->|_COVERAGE_PYTHON_CMD is set| return_cmd

    create --> venv_check
    venv_check -->|no| uv_venv
    venv_check -->|yes| detect_python

    uv_venv --> detect_python

    detect_python -->|found| install
    detect_python -->|not found| python_missing

    python_missing --> recreate
    recreate --> uv_venv

    install --> uv_pip
    uv_pip --> cache_cmd
    cache_cmd --> return_cmd

    return_cmd --> end_node[Coverage venv python ready]
Loading

File-Level Changes

Change Details Files
Route all Python coverage execution through a cached venv-scoped Python interpreter created via uv venv and populated with slipcover/pytest/coverage using uv pip, replacing the previous uv run --with approach.
  • Introduce COVERAGE_VENV, TOOLING_PACKAGES, and a module-level _COVERAGE_PYTHON_CMD singleton to manage the dedicated coverage environment.
  • Implement _coverage_python_path(), create_venv(), and install_coverage_tools() to create, recover, or repair .venv-coverage and install slipcover, pytest, and coverage via uv pip install --python without --system.
  • Replace _uv_python_cmd() with _coverage_python_cmd() which lazily creates the venv, installs tools once, and returns a plumbum BoundCommand bound to the venv Python.
  • Update coverage_cmd_for_fmt() and tmp_coveragepy_xml() to use the venv Python command instead of uv run, ensuring slipcover, pytest, and coverage are invoked via the venv interpreter.
.github/actions/generate-coverage/scripts/run_python.py
Expand and adjust tests for the generate-coverage Python tooling to validate venv creation/reuse, tool installation, and use of the venv Python across commands and main flow.
  • Add helpers and dataclasses (VenvTestSetup, CoverageFmtSpec, _coverage_python, _set_fake_coverage_python_cmd) to simulate and assert venv behavior without actually creating environments.
  • Add tests for create_venv() and _coverage_python_path(), covering first-time creation, reuse of existing venvs, recovery from missing/invalid Python executables, and error conditions.
  • Adjust existing coverage command-shape tests to assert that commands start with the venv Python path rather than uv run, and that Cobertura and coveragepy XML flows reuse the venv Python.
  • Update main() integration-style tests to ensure the coveragepy exporter and exit paths capture the venv Python and that commands are executed via run_cmd with the expected method (e.g. run_fg).
.github/actions/generate-coverage/tests/test_scripts.py
Document the new Python coverage venv architecture and update the action README to describe the venv-based installation model.
  • Extend the generate-coverage design doc with a dated entry and a dedicated "Python Coverage Venv Architecture" section covering motivation, lifecycle, concurrency model, and public API (create_venv, install_coverage_tools).
  • Update the generate-coverage README to describe installing slipcover, pytest, and coverage into an isolated .venv-coverage instead of using uv run --with, and tweak markdownlint-related comments for MD013 handling.
docs/generate-coverage-design.md
.github/actions/generate-coverage/README.md
Harden Makefile tooling resolution for uv, action-validator, and markdownlint, and switch Ruff invocations to uv tool run with configurable UV binary.
  • Introduce ACTION_VALIDATOR, MDLINT, MARKDOWNLINT_BASE, and UV variables that prefer common user-local installation paths (e.g. ~/.cargo/bin, ~/.bun/bin, ~/.local/bin) with fallbacks to bare commands.
  • Update test and .venv targets to use $(UV) for uv run, uv venv, and uv sync so constrained PATH environments can still locate uv.
  • Replace direct uvx ruff calls for lint/fmt/check-fmt with $(UV) tool run ruff ... so Ruff is run via uv’s tool mechanism and respects the configured UV path.
  • Change markdownlint target to prefer linting only changed Markdown files based on git diff against MARKDOWNLINT_BASE, falling back to a full tree scan when no changed files are detected.
Makefile

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos leynos marked this pull request as ready for review April 27, 2026 00:50
Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • The coverage venv Python path is hardcoded to .venv-coverage / 'bin' / 'python', which assumes a POSIX layout; if this action might ever run on Windows or non-standard layouts it would be safer to derive the executable path via sysconfig/venv metadata or by invoking python -c within the created venv.
  • In create_venv, you always run uv venv into .venv-coverage without checking for its existence; if this script is called multiple times in the same workspace, a small guard (e.g., if not COVERAGE_VENV.exists()) could avoid redundant work or potential conflicts from re-creating an existing environment.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The coverage venv Python path is hardcoded to `.venv-coverage / 'bin' / 'python'`, which assumes a POSIX layout; if this action might ever run on Windows or non-standard layouts it would be safer to derive the executable path via `sysconfig`/`venv` metadata or by invoking `python -c` within the created venv.
- In `create_venv`, you always run `uv venv` into `.venv-coverage` without checking for its existence; if this script is called multiple times in the same workspace, a small guard (e.g., `if not COVERAGE_VENV.exists()`) could avoid redundant work or potential conflicts from re-creating an existing environment.

## Individual Comments

### Comment 1
<location path=".github/actions/generate-coverage/tests/test_scripts.py" line_range="2023-2024" />
<code_context>
+    return local[python][_coverage_args(fmt, out)]


 @contextlib.contextmanager
-def tmp_coveragepy_xml(out: Path) -> cabc.Generator[Path]:
+def tmp_coveragepy_xml(out: Path, python: str) -> cabc.Generator[Path]:
</code_context>
<issue_to_address>
**suggestion (testing):** Capture and assert the `python` argument passed to `tmp_coveragepy_xml` in the coveragepy path to ensure it matches the venv interpreter.

The tests that monkeypatch `tmp_coveragepy_xml` still ignore the new `python: str` parameter, so they no longer verify that `main()` passes the venv interpreter through. To preserve regression coverage, have the fake `tmp_coveragepy_xml` capture the `_python` argument and assert it matches `_coverage_python(run_python_module)` (or the `create_venv` return value), and optionally assert that non-coveragepy formats never invoke `tmp_coveragepy_xml`. This ensures the coverage XML export remains tied to the throwaway venv interpreter rather than a system Python.
</issue_to_address>

### Comment 2
<location path=".github/actions/generate-coverage/scripts/run_python.py" line_range="67" />
<code_context>


-def coverage_cmd_for_fmt(fmt: str, out: Path) -> BoundCommand:
+def coverage_cmd_for_fmt(fmt: str, out: Path, python: str) -> BoundCommand:
     """Return the slipcover command for the requested format."""
-    python_cmd = _uv_python_cmd()
</code_context>
<issue_to_address>
**issue (complexity):** Consider reintroducing a single helper abstraction for a coverage-capable python command instead of passing a python string through multiple functions.

You can keep the throwaway venv behavior but restore a single, clear abstraction for “python with coverage tooling” and avoid threading `python: str` through unrelated functions.

Concretely:

1. **Introduce a `_coverage_python_cmd()` helper with lazy initialization**

```python
from plumbum import local

_COVERAGE_PYTHON_CMD: BoundCommand | None = None  # module-level cache

def _coverage_python_cmd() -> BoundCommand:
    """Return a python command with coverage tooling available in a throwaway venv."""
    global _COVERAGE_PYTHON_CMD
    if _COVERAGE_PYTHON_CMD is None:
        python = create_venv()
        install_coverage_tools(python)
        _COVERAGE_PYTHON_CMD = local[python]
    return _COVERAGE_PYTHON_CMD
```

- Keeps venv creation + tooling install in one place.
- Ensures a single venv per process without re-creating it for `coveragepy` vs slipcover paths.

2. **Simplify `coverage_cmd_for_fmt` and `tmp_coveragepy_xml` back to “just build commands”**

```python
def coverage_cmd_for_fmt(fmt: str, out: Path) -> BoundCommand:
    """Return the slipcover command for the requested format."""
    python_cmd = _coverage_python_cmd()
    return python_cmd[_coverage_args(fmt, out)]
```

```python
@contextlib.contextmanager
def tmp_coveragepy_xml(out: Path) -> cabc.Generator[Path]:
    """Generate a cobertura XML from coverage.py and clean up afterwards."""
    xml_tmp = out.with_suffix(".xml")
    python_cmd = _coverage_python_cmd()
    try:
        cmd = python_cmd["-m", "coverage", "xml", "-o", str(xml_tmp)]
        run_cmd(cmd)
    except ProcessExecutionError as exc:
        typer.echo(
            f"coverage xml failed with code {exc.retcode}: {exc.stderr}",
            err=True,
        )
        raise typer.Exit(code=exc.retcode or 1) from exc
    try:
        yield xml_tmp
    finally:
        xml_tmp.unlink(missing_ok=True)
```

3. **Keep `main` free of venv protocol knowledge**

With the above, `main` can go back to:

```python
out.parent.mkdir(parents=True, exist_ok=True)
cmd = coverage_cmd_for_fmt(fmt, out)
run_cmd(cmd, method="run_fg")

if fmt == "coveragepy":
    with tmp_coveragepy_xml(out) as xml_tmp:
        percent = get_line_coverage_percent_from_cobertura(xml_tmp)
    Path(".coverage").replace(out)
else:
    percent = get_line_coverage_percent_from_cobertura(out)
```

This preserves the new behavior (throwaway venv for coverage tooling) while:

- Eliminating the fragile `create_venv` + `install_coverage_tools` call sequence from callers.
- Removing the need to thread a `python: str` through multiple functions.
- Restoring a single, cohesive abstraction for “coverage-capable python”.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread .github/actions/generate-coverage/tests/test_scripts.py Outdated
Comment thread .github/actions/generate-coverage/scripts/run_python.py Outdated
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c6669c4d08

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/actions/generate-coverage/scripts/run_python.py Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/actions/generate-coverage/scripts/run_python.py:
- Around line 46-54: The create_venv function currently returns COVERAGE_VENV /
"bin" / "python" which is not cross-platform; update create_venv to detect the
scripts directory for the current platform (e.g., use sys.platform or
sysconfig.get_path("scripts")) and return COVERAGE_VENV / "Scripts" /
"python.exe" on Windows and COVERAGE_VENV / "bin" / "python" on POSIX so the
returned python path works on all OSes; reference the create_venv function and
COVERAGE_VENV constant when making the change.

In @.github/actions/generate-coverage/tests/test_scripts.py:
- Line 2024: The function signature for fake_tmp_coveragepy_xml should mark the
unused parameter as intentionally unused: rename the parameter out to _out in
the def of fake_tmp_coveragepy_xml and update any internal references (if any)
to use _out; leave the function body and behavior unchanged so the stub still
writes to the fixed path but the linter no longer complains.
- Line 2070: The parameter naming doesn't follow the unused-parameter underscore
convention in fake_tmp_coveragepy_xml; rename the parameter out to _out (and
update any internal references) so unused arguments are clearly marked, i.e.,
change def fake_tmp_coveragepy_xml(out: Path, _python: str) to def
fake_tmp_coveragepy_xml(_out: Path, _python: str) and adjust usages within the
function body accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 126101e1-a4ee-429a-b9a7-c7b595beb5bc

📥 Commits

Reviewing files that changed from the base of the PR and between 413dd72 and c6669c4.

📒 Files selected for processing (3)
  • .github/actions/generate-coverage/scripts/run_python.py
  • .github/actions/generate-coverage/tests/test_scripts.py
  • Makefile

Comment thread .github/actions/generate-coverage/scripts/run_python.py Outdated
Comment thread .github/actions/generate-coverage/tests/test_scripts.py Outdated
Comment thread .github/actions/generate-coverage/tests/test_scripts.py Outdated
Cache the coverage-capable Python command behind one helper, reuse an
existing coverage venv when present, and resolve the venv interpreter
across POSIX and Windows layouts.

Extend the run_python tests to cover cached command reuse and the
coverage.py export path using the same venv interpreter.
@lodyai lodyai Bot marked this pull request as draft April 27, 2026 00:59
codescene-delta-analysis[bot]

This comment was marked as outdated.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
.github/actions/generate-coverage/tests/test_scripts.py (1)

2106-2106: ⚠️ Potential issue | 🟡 Minor

Rename intentionally unused stub parameters with an underscore prefix.

Apply the project’s unused-argument convention at both locations. Use _out to silence lint noise and keep intent explicit.

🔧 Proposed fix
-    def fake_tmp_coveragepy_xml(out: Path) -> typ.Iterator[Path]:
+    def fake_tmp_coveragepy_xml(_out: Path) -> typ.Iterator[Path]:
         captured_python.append(
             next(iter(run_python_module._coverage_python_cmd().formulate()))
         )
         xml_path = tmp_path / "coverage.xml"
@@
-    def fake_tmp_coveragepy_xml(out: Path) -> typ.Iterator[Path]:
+    def fake_tmp_coveragepy_xml(_out: Path) -> typ.Iterator[Path]:
         captured_python.append(
             next(iter(run_python_module._coverage_python_cmd().formulate()))
         )
         xml_path = tmp_path / "coverage.xml"

Also applies to: 2160-2160

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/actions/generate-coverage/tests/test_scripts.py at line 2106, Rename
the intentionally unused parameter named out to _out in the test helper function
fake_tmp_coveragepy_xml and in the other test helper stub in this file that
currently accepts an unused out parameter; update the function signatures to use
_out and adjust any internal references accordingly to follow the project's
unused-argument convention and silence lint warnings.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/actions/generate-coverage/scripts/run_python.py:
- Around line 62-67: The create_venv function currently only recreates the
virtualenv when COVERAGE_VENV directory is absent, but fails if the directory
exists without a valid interpreter; update create_venv to detect a missing or
invalid Python at _coverage_python_path() and in that case delete or recreate
COVERAGE_VENV (e.g., remove directory or call the same run_cmd(uv["venv",
str(COVERAGE_VENV)]) flow) before returning the python path, so the function
ensures a working interpreter even when the venv dir is present but broken.

---

Duplicate comments:
In @.github/actions/generate-coverage/tests/test_scripts.py:
- Line 2106: Rename the intentionally unused parameter named out to _out in the
test helper function fake_tmp_coveragepy_xml and in the other test helper stub
in this file that currently accepts an unused out parameter; update the function
signatures to use _out and adjust any internal references accordingly to follow
the project's unused-argument convention and silence lint warnings.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 9cef7b30-7a9d-4570-a36b-3c0e1a320cfd

📥 Commits

Reviewing files that changed from the base of the PR and between c6669c4 and fb502bc.

📒 Files selected for processing (2)
  • .github/actions/generate-coverage/scripts/run_python.py
  • .github/actions/generate-coverage/tests/test_scripts.py

Comment thread .github/actions/generate-coverage/scripts/run_python.py Outdated
@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Apr 27, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response.

.github/actions/generate-coverage/tests/test_scripts.py

Comment on file

def _get_coverage_cmd_parts(
    tmp_path: Path,
    run_python_module: ModuleType,
    monkeypatch: pytest.MonkeyPatch,

❌ New issue: Excess Number of Function Arguments
_get_coverage_cmd_parts has 5 arguments, max arguments = 4

@coderabbitai

This comment was marked as resolved.

@leynos

This comment was marked as resolved.

@coderabbitai

This comment was marked as resolved.

Repair `.venv-coverage` when the directory exists without a usable Python
interpreter, so coverage setup can recover from incomplete or stale venvs.

Reject non-file interpreter placeholders before reusing an existing venv.
@coderabbitai

This comment was marked as resolved.

Introduce `CoverageFmtSpec` so the coverage command test helper stays
within the argument-count limit while preserving the same test behaviour.
codescene-delta-analysis[bot]

This comment was marked as outdated.

codescene-delta-analysis[bot]

This comment was marked as outdated.

Rename unused `fake_tmp_coveragepy_xml` parameters in coveragepy tests so
the stubs clearly follow the unused-argument convention.
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Apr 27, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response. Ensure that this is validated against the current version of the codegraph.

If further refinement to address this finding would be deleterious, please supply a clear explanatory one to two paragraph markdown message I can paste into the CodeScene web ui's diagnostic suppression function so this diagnostic can be silenced.

Code Duplication

.github/actions/generate-coverage/tests/test_scripts.py:

What lead to degradation?

introduced similar code in: test_create_venv_recreates_broken_coverage_venv,test_create_venv_recreates_invalid_python_candidate,test_create_venv_returns_coverage_python

Why does this problem occur?

Duplicated code often leads to code that's harder to change since the same logical change has to be done in multiple functions. More duplication gives lower code health.

How to fix it?

A certain degree of duplicated code might be acceptable. The problems start when it is the same behavior that is duplicated across the functions in the module, ie. a violation of the Don't Repeat Yourself (DRY) principle. DRY violations lead to code that is changed together in predictable patterns, which is both expensive and risky. DRY violations can be identified using CodeScene's X-Ray analysis to detect clusters of change coupled functions with high code similarity. Read More
Once you have identified the similarities across functions, look to extract and encapsulate the concept that varies into its own function(s). These shared abstractions can then be re-used, which minimizes the amount of duplication and simplifies change.

@coderabbitai

This comment was marked as resolved.

Add observable setup and reuse logging around the coverage venv, expand
helper docstrings, and document the venv lifecycle and singleton model.

Cover broken-cache recovery and missing interpreter detection in the
run_python tests.
codescene-delta-analysis[bot]

This comment was marked as outdated.

Resolve `markdownlint` from the Bun install path when automation runs with a
restricted `PATH`, and lint branch-changed Markdown files before falling back
to the full tree.

Keep the coverage design API table under local Markdown lint disables so the
requested table remains readable without surfacing unrelated repository-wide
Markdown debt.
@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Apr 27, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix:

Complex Method

.github/actions/generate-coverage/scripts/run_python.py: _ensure_coverage_venv

What lead to degradation?

_ensure_coverage_venv has a cyclomatic complexity of 9, threshold = 9

Why does this problem occur?

A Complex Method has a high cyclomatic complexity. The recommended threshold for the Python language is a cyclomatic complexity lower than 9.

How to fix it?

There are many reasons for Complex Method. Sometimes, another design approach is beneficial such as a) modeling state using an explicit state machine rather than conditionals, or b) using table lookup rather than long chains of logic. In other scenarios, the function can be split using EXTRACT FUNCTION. Just make sure you extract natural and cohesive functions. Complex Methods can also be addressed by identifying complex conditional expressions and then using the DECOMPOSE CONDITIONAL refactoring.

Helpful refactoring examples

To get a general understanding of what this code health issue looks like - and how it might be addressed - we have prepared some diffs for illustrative purposes.

SAMPLE

# complex_method.js
 function postItem(item) {
   if (!item.id) {
-    if (item.x != null && item.y != null) {
-      post(item);
-    } else {
-      throw Error("Item must have x and y");
-    }
+    // extract a separate function for creating new item
+    postNew(item);
   } else {
-    if (item.x < 10 && item.y > 25) {
-      put(item);
-    } else {
-      throw Error("Item must have an x and y value between 10 and 25");
-    }
+    // and one for updating existing items
+    updateItem(item);
   }
 }
+
+function postNew(item) {
+  validateNew(item);
+  post(item);
+}
+
+function updateItem(item) {
+  validateUpdate(item);
+  put(item);
+}
+

Add regressions for coverage venv creation that leaves no interpreter and for
symlink cache repair, and keep markdownlint changed-file failures from being
masked by the per-file pipeline.

Update the coverage venv design wording to use the requested installation noun
form.
@coderabbitai

This comment was marked as resolved.

codescene-delta-analysis[bot]

This comment was marked as outdated.

Store formulated command arguments before recording them and guard the uv venv
check by argument length so short command lists cannot raise IndexError.
codescene-delta-analysis[bot]

This comment was marked as outdated.

Change the lint target to invoke action-validator once per action YAML file so
its single-path CLI contract is respected.
@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Apr 27, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response. Ensure that this is validated against the current version of the codegraph.

If further refinement to address this finding would be deleterious, please supply a clear explanatory one to two paragraph markdown message I can paste into the CodeScene web ui's diagnostic suppression function so this diagnostic can be silenced.

.github/actions/generate-coverage/tests/test_scripts.py

Comment on file

    captured_python: list[str] = []

    @contextlib.contextmanager
    def fake_tmp_coveragepy_xml(out: Path) -> typ.Iterator[Path]:

❌ Getting worse: Code Duplication
introduced similar code in: test_ensure_coverage_venv_replaces_broken_file_cache,test_ensure_coverage_venv_replaces_broken_symlink_cache

codescene-delta-analysis[bot]

This comment was marked as outdated.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 27, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
grep -n "def test_ensure_coverage_venv_replaces_broken_file_cache\|def test_ensure_coverage_venv_replaces_broken_symlink_cache" \
    .github/actions/generate-coverage/tests/test_scripts.py

echo "=== BROKEN FILE CACHE BODY ==="
sed -n '/def test_ensure_coverage_venv_replaces_broken_file_cache/,/^def /p' \
    .github/actions/generate-coverage/tests/test_scripts.py | head -70

echo "=== BROKEN SYMLINK CACHE BODY ==="
sed -n '/def test_ensure_coverage_venv_replaces_broken_symlink_cache/,/^def /p' \
    .github/actions/generate-coverage/tests/test_scripts.py | head -70

echo "=== EXISTING HELPERS ==="
grep -n "def _assert_venv_rebuild\|def _setup_coverage_venv_test\|class VenvTestSetup\|def _is_uv_venv\|def _assert_venv_recreated" \
    .github/actions/generate-coverage/tests/test_scripts.py

Length of output: 1894


@leynos

File: .github/actions/generate-coverage/tests/test_scripts.py
Symbols: test_ensure_coverage_venv_replaces_broken_file_cache, test_ensure_coverage_venv_replaces_broken_symlink_cache


Analysis

The CodeScene finding references a stale diff commit (a9f3b44c) containing captured_python / fake_tmp_coveragepy_xml; those patterns are no longer present in either function on the current branch. However, a genuine DRY violation still remains — both functions end with the same two-line assertion block:

assert python == str(setup.coverage_venv / "bin" / "python")
_assert_venv_rebuild_commands(
    setup.recorded,
    setup.coverage_venv,
    setup.coverage_venv / "bin" / "python",
)

The path setup.coverage_venv / "bin" / "python" is repeated twice within each call site. Extract a combined assertion helper to eliminate both occurrences.


Fix

Add _assert_venv_default_python_rebuild immediately before test_ensure_coverage_venv_replaces_broken_symlink_cache:

def _assert_venv_default_python_rebuild(
    python: str,
    setup: VenvTestSetup,
) -> None:
    """Assert venv was rebuilt and Python resolved to the default POSIX path."""
    expected = setup.coverage_venv / "bin" / "python"
    assert python == str(expected)
    _assert_venv_rebuild_commands(setup.recorded, setup.coverage_venv, expected)

Rewrite test_ensure_coverage_venv_replaces_broken_symlink_cache:

def test_ensure_coverage_venv_replaces_broken_symlink_cache(
    tmp_path: Path,
    run_python_module: ModuleType,
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    """The helper unlinks a symlink cache placeholder before recreate."""
    setup = _setup_coverage_venv_test(tmp_path, run_python_module, monkeypatch)
    target = tmp_path / "not-a-venv"
    target.write_text("not a directory")
    setup.coverage_venv.symlink_to(target)

    python = run_python_module._ensure_coverage_venv()

    assert not setup.coverage_venv.is_symlink()
    _assert_venv_default_python_rebuild(python, setup)

Rewrite test_ensure_coverage_venv_replaces_broken_file_cache:

def test_ensure_coverage_venv_replaces_broken_file_cache(
    tmp_path: Path,
    run_python_module: ModuleType,
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    """The helper replaces a non-directory cache placeholder before recreate."""
    setup = _setup_coverage_venv_test(tmp_path, run_python_module, monkeypatch)
    setup.coverage_venv.write_text("not a venv")

    python = run_python_module._ensure_coverage_venv()

    _assert_venv_default_python_rebuild(python, setup)

AI coding agent prompt

File: .github/actions/generate-coverage/tests/test_scripts.py
Symbols: test_ensure_coverage_venv_replaces_broken_symlink_cache,
         test_ensure_coverage_venv_replaces_broken_file_cache

Both functions end with an identical two-line assertion block that computes
`setup.coverage_venv / "bin" / "python"` twice. Extract it into a named helper.
Make no other changes to the file.

────────────────────────────────────────────────────────────
STEP 1 — Add _assert_venv_default_python_rebuild helper
────────────────────────────────────────────────────────────

Insert the following function immediately before
`test_ensure_coverage_venv_replaces_broken_symlink_cache`:

    def _assert_venv_default_python_rebuild(
        python: str,
        setup: VenvTestSetup,
    ) -> None:
        """Assert venv was rebuilt and Python resolved to the default POSIX path."""
        expected = setup.coverage_venv / "bin" / "python"
        assert python == str(expected)
        _assert_venv_rebuild_commands(setup.recorded, setup.coverage_venv, expected)

────────────────────────────────────────────────────────────
STEP 2 — Replace test_ensure_coverage_venv_replaces_broken_symlink_cache
────────────────────────────────────────────────────────────

Replace the entire existing function with:

    def test_ensure_coverage_venv_replaces_broken_symlink_cache(
        tmp_path: Path,
        run_python_module: ModuleType,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        """The helper unlinks a symlink cache placeholder before recreate."""
        setup = _setup_coverage_venv_test(tmp_path, run_python_module, monkeypatch)
        target = tmp_path / "not-a-venv"
        target.write_text("not a directory")
        setup.coverage_venv.symlink_to(target)

        python = run_python_module._ensure_coverage_venv()

        assert not setup.coverage_venv.is_symlink()
        _assert_venv_default_python_rebuild(python, setup)

────────────────────────────────────────────────────────────
STEP 3 — Replace test_ensure_coverage_venv_replaces_broken_file_cache
────────────────────────────────────────────────────────────

Replace the entire existing function with:

    def test_ensure_coverage_venv_replaces_broken_file_cache(
        tmp_path: Path,
        run_python_module: ModuleType,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        """The helper replaces a non-directory cache placeholder before recreate."""
        setup = _setup_coverage_venv_test(tmp_path, run_python_module, monkeypatch)
        setup.coverage_venv.write_text("not a venv")

        python = run_python_module._ensure_coverage_venv()

        _assert_venv_default_python_rebuild(python, setup)

────────────────────────────────────────────────────────────
VALIDATION
────────────────────────────────────────────────────────────

Make no other changes. After applying all steps, run:

    make check-fmt
    make typecheck
    make lint
    make test

All four must exit 0. Confirm that both refactored tests pass and no
existing tests are broken.

Split coverage venv removal and recreation out of _ensure_coverage_venv so the
setup flow stays below the complexity threshold while preserving existing
behaviour.
codescene-delta-analysis[bot]

This comment was marked as outdated.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/actions/generate-coverage/tests/test_scripts.py:
- Around line 1855-1856: The assertions comparing the executable name use
Path(...).name which fails on Windows due to a .exe extension; update the checks
in test_scripts.py where venv_parts is asserted (e.g., the assertions
referencing venv_parts[0] and venv_parts[1:]) to compare Path(...).stem == "uv"
(and similarly replace .name with .stem) so the tests normalize the executable
name across platforms; apply the same change at the other occurrences around the
sections mentioned (the groups near lines referenced as 1943 and 2056).

In `@docs/generate-coverage-design.md`:
- Around line 194-195: Update the design note to match the implementation:
change the phrase that says the helper "removes the directory and recreates it"
to describe that it removes the existing path (including unlinking broken
symlinks and removing file placeholders) and then recreates the virtualenv, so
the docs for generate-coverage-design.md accurately reflect the behavior
implemented in run_python.py.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: cb7912d7-6eac-4610-be29-ef9b5587b1a1

📥 Commits

Reviewing files that changed from the base of the PR and between 3d8f177 and a9f3b44.

📒 Files selected for processing (3)
  • .github/actions/generate-coverage/tests/test_scripts.py
  • Makefile
  • docs/generate-coverage-design.md

Comment thread .github/actions/generate-coverage/tests/test_scripts.py Outdated
Comment thread docs/generate-coverage-design.md
@coderabbitai coderabbitai Bot added Roadmap and removed Issue labels Apr 27, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Makefile`:
- Around line 72-81: The markdownlint Makefile target body is too long and
should be moved into a small shell script to satisfy the style rule; create a
script (e.g., scripts/markdownlint-check.sh) that implements the current
conditional using the same variables/logic (use MARKDOWNLINT_BASE, MDLINT and
the git diff fallback and diagnostic echo behavior), make the Makefile
markdownlint target call that script with the same environment variables, and
ensure the script preserves exit codes and the diagnostic echo on fallback so
behavior remains identical.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 01a2c3e5-6ae6-4aa4-9bf7-ac3dda70f774

📥 Commits

Reviewing files that changed from the base of the PR and between a9f3b44 and ee24905.

📒 Files selected for processing (3)
  • .github/actions/generate-coverage/scripts/run_python.py
  • .github/actions/generate-coverage/tests/test_scripts.py
  • Makefile

Comment thread Makefile Outdated
Replace the long Makefile markdownlint recipe with a call to a small shell
script that preserves the existing diff-based linting, fallback warning, and
exit-code behaviour.
codescene-delta-analysis[bot]

This comment was marked as outdated.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Makefile`:
- Line 17: The ACTION_VALIDATOR default selection currently checks ~/.cargo/bin
before the system lookup, which conflicts with CI that installs via Bun; change
the Makefile's ACTION_VALIDATOR assignment to probe ~/.bun/bin first, then
~/.cargo/bin, then fall back to the plain action-validator command (use a
firstword over wildcard results) so the CI-provided Bun-installed binary is
chosen when present. Update the ACTION_VALIDATOR variable assignment
accordingly.

In `@workflow_scripts/markdownlint-check.sh`:
- Around line 11-21: The MDLINT variable is unquoted at each invocation which
can cause word-splitting; update every call to use a quoted variable. Replace
${MDLINT} "${file}" with "${MDLINT}" "${file}", replace -exec ${MDLINT} {} +
with -exec "${MDLINT}" {} +, and likewise quote the MDLINT in the other find ...
-exec invocation so all three usages use "${MDLINT}" to prevent argv drift.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 77a28bdf-f75b-4e4c-ad0f-d66b479ca3dc

📥 Commits

Reviewing files that changed from the base of the PR and between ee24905 and 3c20bbd.

📒 Files selected for processing (2)
  • Makefile
  • workflow_scripts/markdownlint-check.sh

Comment thread Makefile Outdated
Comment thread workflow_scripts/markdownlint-check.sh Outdated
@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Apr 27, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response. Ensure that this is validated against the current version of the codegraph.

If further refinement to address this finding would be deleterious, please supply a clear explanatory one to two paragraph markdown message I can paste into the CodeScene web ui's diagnostic suppression function so this diagnostic can be silenced.

Code Duplication

.github/actions/generate-coverage/tests/test_scripts.py:

What lead to degradation?

introduced similar code in: test_ensure_coverage_venv_replaces_broken_file_cache,test_ensure_coverage_venv_replaces_broken_symlink_cache

Why does this problem occur?

Duplicated code often leads to code that's harder to change since the same logical change has to be done in multiple functions. More duplication gives lower code health.

How to fix it?

A certain degree of duplicated code might be acceptable. The problems start when it is the same behavior that is duplicated across the functions in the module, ie. a violation of the Don't Repeat Yourself (DRY) principle. DRY violations lead to code that is changed together in predictable patterns, which is both expensive and risky. DRY violations can be identified using CodeScene's X-Ray analysis to detect clusters of change coupled functions with high code similarity. Read More
Once you have identified the similarities across functions, look to extract and encapsulate the concept that varies into its own function(s). These shared abstractions can then be re-used, which minimizes the amount of duplication and simplifies change.

@coderabbitai

This comment was marked as resolved.

Choose the Bun-installed action-validator before Cargo or PATH so CI uses its
provided binary, and quote markdownlint invocations in the helper script to
avoid shell word splitting.
codescene-delta-analysis[bot]

This comment was marked as outdated.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
workflow_scripts/markdownlint-check.sh (1)

17-21: ⚠️ Potential issue | 🟠 Major

Propagate markdownlint failures in fallback branches.

Replace the fallback find ... -exec "${MDLINT}" {} + on Line 17 and Line 21 with explicit status aggregation so lint failures cannot be masked when diff detection is empty or fails.

Patch
 MARKDOWNLINT_BASE="${MARKDOWNLINT_BASE:-origin/main}"
 MDLINT="${MDLINT:-markdownlint}"
 
+lint_all_markdown() {
+    status=0
+    while IFS= read -r file; do
+        [ -n "${file}" ] || continue
+        "${MDLINT}" "${file}" || status=1
+    done <<EOF
+$(find . -type f -name '*.md' -not -path './target/*')
+EOF
+    return "${status}"
+}
+
 if files=$(git diff --name-only --diff-filter=ACMRT "${MARKDOWNLINT_BASE}...HEAD" -- '*.md' 2>/dev/null); then
     if [ -n "${files}" ]; then
         status=0
@@
-        find . -type f -name '*.md' -not -path './target/*' -exec "${MDLINT}" {} +
+        lint_all_markdown
     fi
 else
     echo "markdownlint: git diff failed or base '${MARKDOWNLINT_BASE}' not found; linting all .md files" >&2
-    find . -type f -name '*.md' -not -path './target/*' -exec "${MDLINT}" {} +
+    lint_all_markdown
 fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@workflow_scripts/markdownlint-check.sh` around lines 17 - 21, The fallback
branches currently run find ... -exec "${MDLINT}" {} + which hides individual
lint failures; change both fallback blocks to collect file paths (using the same
find criteria) and iterate running "${MDLINT}" on each file, accumulating an
exit status (e.g., keep a global ret=0 and set ret=1 if any run fails) and then
exit with that aggregated status so failures propagate; update the occurrences
referencing MDLINT and MARKDOWNLINT_BASE and replace the inline -exec
invocations with this per-file invocation + status aggregation logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@workflow_scripts/markdownlint-check.sh`:
- Around line 17-21: The fallback branches currently run find ... -exec
"${MDLINT}" {} + which hides individual lint failures; change both fallback
blocks to collect file paths (using the same find criteria) and iterate running
"${MDLINT}" on each file, accumulating an exit status (e.g., keep a global ret=0
and set ret=1 if any run fails) and then exit with that aggregated status so
failures propagate; update the occurrences referencing MDLINT and
MARKDOWNLINT_BASE and replace the inline -exec invocations with this per-file
invocation + status aggregation logic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 2add9514-44e6-41e8-81e0-6835526d6e16

📥 Commits

Reviewing files that changed from the base of the PR and between 3c20bbd and 3588178.

📒 Files selected for processing (2)
  • Makefile
  • workflow_scripts/markdownlint-check.sh

Add a helper for tests that rebuild the coverage venv to the default POSIX
Python path, removing duplicated assertions from file and symlink cache cases.
codescene-delta-analysis[bot]

This comment was marked as outdated.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (3)
.github/actions/generate-coverage/tests/test_scripts.py (3)

1854-1856: ⚠️ Potential issue | 🟡 Minor

Use .stem instead of .name for cross-platform executable assertions.

Line 1855's assertion Path(venv_parts[0]).name == "uv" fails on Windows where the executable is uv.exe.

🛠️ Proposed fix
     venv_parts = setup.recorded[0]
-    assert Path(venv_parts[0]).name == "uv"
+    assert Path(venv_parts[0]).stem == "uv"
     assert venv_parts[1:] == ["venv", str(setup.coverage_venv)]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/actions/generate-coverage/tests/test_scripts.py around lines 1854 -
1856, The assertion using Path(venv_parts[0]).name is brittle on Windows because
executables include extensions; update the check to use Path(venv_parts[0]).stem
instead so the first element comparison (venv_parts[0]) normalizes exe suffixes,
keeping the rest of the assertion venv_parts[1:] == ["venv",
str(setup.coverage_venv)] unchanged; locate this in the test referencing
setup.recorded and venv_parts.

2055-2059: ⚠️ Potential issue | 🟡 Minor

Use .stem instead of .name for cross-platform executable assertions.

Line 2056's assertion Path(parts[0]).name == "uv" fails on Windows.

🛠️ Proposed fix
     parts = setup.recorded[1]
-    assert Path(parts[0]).name == "uv"
+    assert Path(parts[0]).stem == "uv"
     assert parts[1:5] == ["pip", "install", "--python", str(python)]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/actions/generate-coverage/tests/test_scripts.py around lines 2055 -
2059, The assertion that Path(parts[0]).name == "uv" is brittle on Windows;
change it to use Path(parts[0]).stem to compare the executable base name
instead. Update the assertion in the test where parts is taken from
setup.recorded[1] (the block referencing parts[0], parts[1:5], and
run_python_module.TOOLING_PACKAGES) so it asserts Path(parts[0]).stem == "uv"
while leaving the other assertions intact.

1942-1944: ⚠️ Potential issue | 🟡 Minor

Use .stem instead of .name for cross-platform executable assertions.

Line 1943's assertion Path(setup.recorded[0][0]).name == "uv" fails on Windows.

🛠️ Proposed fix
     assert len(setup.recorded) == 1
-    assert Path(setup.recorded[0][0]).name == "uv"
+    assert Path(setup.recorded[0][0]).stem == "uv"
     assert setup.recorded[0][1:] == ["venv", str(setup.coverage_venv)]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/actions/generate-coverage/tests/test_scripts.py around lines 1942 -
1944, Change the cross-platform executable assertion to compare the stem instead
of the filename: instead of using Path(setup.recorded[0][0]).name use
Path(setup.recorded[0][0]).stem when asserting against "uv"; update the
assertion that references setup.recorded (the second assertion) so it uses .stem
to avoid Windows extension differences while keeping the rest of the check
(setup.recorded[0][1:] == ["venv", str(setup.coverage_venv)]) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/actions/generate-coverage/tests/test_scripts.py:
- Around line 1765-1776: Change platform-sensitive filename comparisons to use
Path.stem instead of Path.name: in _assert_python_command_structure replace
Path(parts[0]).name == "python" with checking Path(parts[0]).stem == "python" so
Windows "python.exe" passes; and in _assert_coverage_python_path compare the
final path components by stem (e.g., Path(actual).stem == Path(expected).stem or
compare the last path parts with stems) so the coverage venv python executable
matches across platforms. Ensure these edits target the
_assert_python_command_structure and _assert_coverage_python_path functions.

---

Duplicate comments:
In @.github/actions/generate-coverage/tests/test_scripts.py:
- Around line 1854-1856: The assertion using Path(venv_parts[0]).name is brittle
on Windows because executables include extensions; update the check to use
Path(venv_parts[0]).stem instead so the first element comparison (venv_parts[0])
normalizes exe suffixes, keeping the rest of the assertion venv_parts[1:] ==
["venv", str(setup.coverage_venv)] unchanged; locate this in the test
referencing setup.recorded and venv_parts.
- Around line 2055-2059: The assertion that Path(parts[0]).name == "uv" is
brittle on Windows; change it to use Path(parts[0]).stem to compare the
executable base name instead. Update the assertion in the test where parts is
taken from setup.recorded[1] (the block referencing parts[0], parts[1:5], and
run_python_module.TOOLING_PACKAGES) so it asserts Path(parts[0]).stem == "uv"
while leaving the other assertions intact.
- Around line 1942-1944: Change the cross-platform executable assertion to
compare the stem instead of the filename: instead of using
Path(setup.recorded[0][0]).name use Path(setup.recorded[0][0]).stem when
asserting against "uv"; update the assertion that references setup.recorded (the
second assertion) so it uses .stem to avoid Windows extension differences while
keeping the rest of the check (setup.recorded[0][1:] == ["venv",
str(setup.coverage_venv)]) unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 522aaafd-d20f-436b-9c6f-131abb6e7f43

📥 Commits

Reviewing files that changed from the base of the PR and between 3588178 and ab88554.

📒 Files selected for processing (1)
  • .github/actions/generate-coverage/tests/test_scripts.py

Comment thread .github/actions/generate-coverage/tests/test_scripts.py
Compare executable stems instead of full file names in coverage command tests so
Windows .exe suffixes do not break path assertions.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/actions/generate-coverage/tests/test_scripts.py:
- Around line 2151-2173: Combine the two duplicate tests
test_coverage_cmd_default_branch_has_shared_args and
test_non_cobertura_formats_do_not_emit_out_flag into a single parametrized test
(or a single test) that calls _get_coverage_cmd_parts once with
CoverageFmtSpec("coveragepy", "dat") and asserts all three conditions: "--xml"
not in parts, str(out) not in parts, and "--out" not in parts; update the test
name to something like test_non_cobertura_formats_do_not_emit_cobertura_flags
and remove the redundant test, ensuring you still accept the same fixtures
(tmp_path, run_python_module, monkeypatch) and reference _get_coverage_cmd_parts
and CoverageFmtSpec in the new test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 08cf8053-3367-4eec-bf59-da8f4f6a8f23

📥 Commits

Reviewing files that changed from the base of the PR and between ab88554 and e7b1ce7.

📒 Files selected for processing (1)
  • .github/actions/generate-coverage/tests/test_scripts.py

Comment on lines 2151 to 2173
def test_coverage_cmd_default_branch_has_shared_args(
tmp_path: Path, run_python_module: ModuleType
tmp_path: Path,
run_python_module: ModuleType,
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Non-Cobertura formats reuse the shared slipcover arguments."""
parts, out = _get_coverage_cmd_parts(
tmp_path, run_python_module, "coveragepy", "dat"
tmp_path, run_python_module, monkeypatch, CoverageFmtSpec("coveragepy", "dat")
)
assert "--xml" not in parts
assert str(out) not in parts


def test_non_cobertura_formats_do_not_emit_out_flag(
tmp_path: Path, run_python_module: ModuleType
tmp_path: Path,
run_python_module: ModuleType,
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Ensure slipcover output targeting stays isolated to Cobertura runs."""
parts, _ = _get_coverage_cmd_parts(tmp_path, run_python_module, "coveragepy", "dat")
parts, _ = _get_coverage_cmd_parts(
tmp_path, run_python_module, monkeypatch, CoverageFmtSpec("coveragepy", "dat")
)
assert "--out" not in parts
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Merge overlapping non-Cobertura tests into one parameterized assertion.

Collapse these two tests into one. They execute the same setup path and duplicate intent around non-Cobertura output flags.

♻️ Proposed simplification
 def test_coverage_cmd_default_branch_has_shared_args(
     tmp_path: Path,
     run_python_module: ModuleType,
     monkeypatch: pytest.MonkeyPatch,
 ) -> None:
     """Non-Cobertura formats reuse the shared slipcover arguments."""
     parts, out = _get_coverage_cmd_parts(
         tmp_path, run_python_module, monkeypatch, CoverageFmtSpec("coveragepy", "dat")
     )
     assert "--xml" not in parts
+    assert "--out" not in parts
     assert str(out) not in parts
-
-
-def test_non_cobertura_formats_do_not_emit_out_flag(
-    tmp_path: Path,
-    run_python_module: ModuleType,
-    monkeypatch: pytest.MonkeyPatch,
-) -> None:
-    """Ensure slipcover output targeting stays isolated to Cobertura runs."""
-    parts, _ = _get_coverage_cmd_parts(
-        tmp_path, run_python_module, monkeypatch, CoverageFmtSpec("coveragepy", "dat")
-    )
-    assert "--out" not in parts
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/actions/generate-coverage/tests/test_scripts.py around lines 2151 -
2173, Combine the two duplicate tests
test_coverage_cmd_default_branch_has_shared_args and
test_non_cobertura_formats_do_not_emit_out_flag into a single parametrized test
(or a single test) that calls _get_coverage_cmd_parts once with
CoverageFmtSpec("coveragepy", "dat") and asserts all three conditions: "--xml"
not in parts, str(out) not in parts, and "--out" not in parts; update the test
name to something like test_non_cobertura_formats_do_not_emit_cobertura_flags
and remove the redundant test, ensuring you still accept the same fixtures
(tmp_path, run_python_module, monkeypatch) and reference _get_coverage_cmd_parts
and CoverageFmtSpec in the new test.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant