Skip to content

Introduce NinjaEnvGuard and NINJA_ENV-based test isolation#243

Merged
leynos merged 6 commits intomainfrom
terragon/improve-parallel-test-execution-qangzd
Dec 4, 2025
Merged

Introduce NinjaEnvGuard and NINJA_ENV-based test isolation#243
leynos merged 6 commits intomainfrom
terragon/improve-parallel-test-execution-qangzd

Conversation

@leynos
Copy link
Copy Markdown
Owner

@leynos leynos commented Dec 2, 2025

Summary

  • Rework tests to isolate Ninja resolution using NINJA_ENV and NinjaEnvGuard
  • Introduce fixture-driven approach (ninja_in_env) to enable parallel test execution without cross-test env interference
  • Remove PATH mutations from tests

Changes

Docs

  • Added docs/test-isolation-with-ninja-env.md
    • Explains NINJA_ENV-based Ninja discovery and fixture pattern
    • Provides usage examples and dos/don’ts for parallel tests

Tests

  • Refactor tests/runner_tests.rs to use NinjaEnvGuard and NINJA_ENV instead of PATH mutations
  • Introduce fixture ninja_in_env that returns (tempdir holding ninja, NinjaEnvGuard)
  • Update existing fixtures to use override_ninja_env and remove PATH-based guards
  • Rename and adjust fixtures/macros to reflect environment-based isolation (ninja_in_env, ninja_with_exit_code)
  • Remove serial-based PATH mutation patterns for tests relying on environment changes
  • Ensure tests drop/cleanup uses NINJA_ENV guard rather than PATH-related guards

Rationale

  • Mutating PATH is global and prone to races during parallel test execution
  • Using NINJA_ENV localizes changes to a per-test environment, restored automatically
  • The fixture approach keeps temporary resources alive for the test duration and avoids shell-dependent setups

Migration notes

  • Tests previously mutating PATH now rely on NINJA_ENV; references to PATH guards removed in favor of NinjaEnvGuard usage
  • If you added tests that relied on PATH manipulations for Ninja discovery, update them to use the new ninja_in_env (or ninja_with_exit_code) fixtures

Testing plan

  • Run cargo test across multiple workers to verify parallel compatibility
  • Validate that NINJA_ENV overrides are honored and isolated per test using NinjaEnvGuard
  • Exercise different fake Ninja exit codes to ensure proper test behavior
  • Confirm no cross-test env contamination occurs (no PATH mutations tested)

Additional notes

  • The new docs provide concrete fixture usage and rationale to help maintain parallel-friendly tests going forward
  • No public API changes; impact is limited to test infrastructure and documentation

🌿 Generated by Terry


ℹ️ Tag @terragon-labs to ask questions and address PR feedback

📎 Task: https://www.terragonlabs.com/task/e5fb4018-e860-42f6-ac83-94aa85fff64f

Replace test fixtures that put a fake ninja executable on PATH with fixtures that set NINJA_ENV environment variable instead. This avoids mutating PATH, enabling tests to run in parallel without interference. Add documentation explaining benefits of using NINJA_ENV and provide fixture patterns for controlled ninja execution during tests.

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Dec 2, 2025

Reviewer's Guide

Refactors test infrastructure to resolve the Ninja binary via NINJA_ENV with scoped guards instead of mutating PATH, enabling safe parallel execution, and documents the new test-isolation pattern.

File-Level Changes

Change Details Files
Replace PATH-based fake Ninja fixtures with NINJA_ENV-scoped fixtures and guards.
  • Rename the primary fixture from ninja_in_path to ninja_in_env and change its contract to return a temporary directory plus a NinjaEnvGuard instead of a PathGuard and ninja path.
  • Update the fake-ninja-with-exit-code fixture to ninja_with_exit_code returning a tempdir and NinjaEnvGuard, using override_ninja_env instead of prepend_dir_to_path.
  • Adjust test imports to use NinjaEnvGuard and override_ninja_env, and remove usage of prepend_dir_to_path and PathGuard.
tests/runner_tests.rs
Update tests to rely on NINJA_ENV for Ninja resolution, removing serial constraints and PATH cleanup logic.
  • Remove #[serial] attributes from tests that previously serialized due to PATH mutation and let them run in parallel.
  • Update tests to consume the new ninja_in_env and ninja_with_exit_code fixture signatures, no longer using the ninja_path return value.
  • Eliminate manual drop calls for ninja_path, temp directories, and PATH-related guards, relying on NinjaEnvGuard drop semantics for cleanup.
tests/runner_tests.rs
Document the new NINJA_ENV-based test isolation pattern and best practices.
  • Add a dedicated documentation file explaining why NINJA_ENV is preferred over PATH for Ninja resolution in tests.
  • Describe the fixture pattern that creates a fake Ninja, scopes NINJA_ENV using override_ninja_env, and keeps temp resources alive for the test duration.
  • Provide concrete dos and don'ts to keep tests parallel-friendly and avoid PATH-based test coupling.
docs/test-isolation-with-ninja-env.md

Possibly linked issues

  • #(not specified): PR updates ninja_with_exit_code to use NINJA_ENV and removes PATH manipulation, exactly matching the requested refactor.
  • #0: PR implements NINJA_ENV-based fixtures, removes #[serial], and eliminates no-op drops as requested in issue.

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

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Dec 2, 2025

Warning

Rate limit exceeded

@leynos has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 3 minutes and 4 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 907453c and 275c7fd.

📒 Files selected for processing (3)
  • docs/test-isolation-with-ninja-env.md (1 hunks)
  • test_support/src/env.rs (3 hunks)
  • tests/runner_tests.rs (5 hunks)

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Summary by CodeRabbit

  • Documentation

    • Added guidance on test isolation using the NINJA_ENV environment variable, including fixture patterns and best practices for parallel-friendly testing.
  • Tests

    • Refactored test infrastructure to use environment-based Ninja isolation instead of PATH-based approaches.
    • Enhanced test fixture lifecycle management with improved resource scoping.
    • Enabled additional tests to run in parallel for faster execution.

✏️ Tip: You can customize this high-level summary in your review settings.

Walkthrough

Add new documentation describing isolation of tests from the Ninja binary via NINJA_ENV and refactor test fixtures and test-support code to use a NinjaEnvGuard that holds an EnvLock instead of mutating PATH; update tests to the new guard-based lifecycle and add tests exercising success/failure behaviours.

Changes

Cohort / File(s) Summary
Documentation
docs/test-isolation-with-ninja-env.md
Add new guide showing how to isolate tests with NINJA_ENV, demonstrate EnvGuard-based scoping and fixture patterns (Rust rstest) for creating/managing a fake ninja executable, describe fixture lifecycle, precedence over PATH, and parallel-friendly dos/don'ts.
Tests
tests/runner_tests.rs
Replace PATH-mutation fixtures with NinjaEnvGuard. Rename ninja_in_path()ninja_in_env() and change fixtures (including ninja_with_exit_code) to return (tempfile::TempDir, NinjaEnvGuard); add setup_ninja_env_test() helper; remove PathGuard usage and serial annotations; add tests for success/failing ninja env behaviour.
Test support / Env
test_support/src/env.rs
Modify NinjaEnvGuard struct to include _lock: EnvLock and update override_ninja_env to acquire and hold an EnvLock while constructing the inner EnvGuard via EnvGuard::with_env_and_lock(..., StdEnv::default(), false), documenting the lock's lifetime and re-locking semantics.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Inspect test_support/src/env.rs for correct lock acquisition, held-lifetime semantics and safe interaction with EnvGuard::with_env_and_lock.
  • Verify all test fixtures and callers updated to new return types and that no PATH-based guards remain.
  • Confirm tests/runner_tests.rs compiles after fixture signature changes and that new tests assert expected error messages.
  • Check docs/test-isolation-with-ninja-env.md examples align with actual fixture APIs.

Possibly related issues

Poem

A tiny fake Ninja takes the stage,
NINJA_ENV holds centre, calm and sage,
Guards lock the path and watch the race,
Tests march parallel, finding their place,
CI hums, the night keeps pace.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main objective: introducing NinjaEnvGuard and NINJA_ENV-based test isolation to replace PATH mutations and enable parallel test execution.
Description check ✅ Passed The description comprehensively covers the rationale, changes across docs and tests, migration path, and testing plan—all directly related to the changeset's focus on NINJA_ENV-based test isolation.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

@leynos leynos marked this pull request as ready for review December 3, 2025 09:23
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 there - I've reviewed your changes - here's some feedback:

  • Now that NINJA_ENV is guarded, consider removing the remaining #[serial] on run_respects_env_override_for_ninja if NinjaEnvGuard already handles synchronization, to keep this test consistent with the others that now run in parallel.
  • In run_respects_env_override_for_ninja, the explicit drop(guard) at the end is redundant since the guard will be dropped when it goes out of scope; you can simplify by relying on RAII unless you need the environment restored earlier in the test.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Now that `NINJA_ENV` is guarded, consider removing the remaining `#[serial]` on `run_respects_env_override_for_ninja` if `NinjaEnvGuard` already handles synchronization, to keep this test consistent with the others that now run in parallel.
- In `run_respects_env_override_for_ninja`, the explicit `drop(guard)` at the end is redundant since the guard will be dropped when it goes out of scope; you can simplify by relying on RAII unless you need the environment restored earlier in the test.

## Individual Comments

### Comment 1
<location> `tests/runner_tests.rs:228-232` </location>
<code_context>
 #[test]
 #[serial]
 fn run_respects_env_override_for_ninja() -> Result<()> {
-    let (temp_dir, ninja_path) = fake_ninja(0u8)?;
+    let (_temp_dir, ninja_path) = fake_ninja(0u8)?;
</code_context>

<issue_to_address>
**suggestion (testing):** Consider a test that proves `NINJA_ENV` takes precedence over a conflicting Ninja on `PATH`.

This currently verifies that `NINJA_ENV` is used, but not that it takes precedence over a conflicting `PATH` entry. To cover the documented resolution order, consider adding a variant that:

1. Puts a different (or invalid) fake Ninja on `PATH`.
2. Sets `NINJA_ENV` via `override_ninja_env` to the expected fake Ninja.
3. Asserts that `run` uses the `NINJA_ENV` Ninja (via exit code, output, or another observable effect).

That would more directly exercise the precedence behavior and protect against regressions in resolution order.

Suggested implementation:

```rust
#[test]
#[serial]
fn run_respects_env_override_for_ninja() -> Result<()> {
    // Successful Ninja that we will reference via NINJA_ENV.
    let (_temp_dir_env, ninja_env_path) = fake_ninja(0u8)?;

    // Failing Ninja that we will place on PATH to ensure NINJA_ENV takes precedence.
    let (_temp_dir_path, ninja_path_on_path) = fake_ninja(1u8)?;

    let env = SystemEnv::new();

    // Put the failing Ninja on PATH. If PATH took precedence over NINJA_ENV,
    // `run` would fail due to this Ninja's non‑zero exit code.
    let ninja_dir_on_path = ninja_path_on_path
        .parent()
        .expect("fake_ninja path should have a parent directory");
    std::env::set_var("PATH", ninja_dir_on_path);

    // Override NINJA_ENV to point at the successful fake Ninja.
    let guard = override_ninja_env(&env, &ninja_env_path);

    let (_temp, manifest_path) = create_test_manifest()?;

    run(&cli)
        .context("expected run to prefer NINJA_ENV over conflicting PATH entry")?;
    drop(guard);
    Ok(())
}

```

1. This change assumes that:
   - `fake_ninja(1u8)` produces a Ninja that reliably fails (e.g., exits with status 1) when invoked.
   - The `run(&cli)` invocation and `cli` construction remain valid and unchanged elsewhere in the file.

2. If `std::env::set_var("PATH", ninja_dir_on_path)` conflicts with any existing test helpers (e.g., if there is a dedicated environment abstraction or helper for overriding `PATH`), you may want to replace that line with the appropriate helper call that the rest of the tests use.

3. If your test harness or environment requires restoring `PATH` after the test, consider capturing the previous value before `set_var` and restoring it in a guard similar to `override_ninja_env`. If such a helper already exists (e.g., `override_path_env`), prefer using it for consistency.
</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 tests/runner_tests.rs
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.

ℹ️ 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 tests/runner_tests.rs 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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/runner_tests.rs (1)

226-241: Re-evaluate need for #[serial] on run_respects_env_override_for_ninja

Drop #[serial] here unless another unsynchronised environment mutation justifies it. override_ninja_env already acquires EnvLock and restores NINJA_ENV on drop, which aligns with the new parallel-safe pattern; keeping #[serial] undermines the PR’s goal of parallel test execution.

Run this script to locate any remaining serialised tests that might still rely on unsafe environment patterns:

#!/bin/bash
# Find remaining serial_test usage across Rust tests
rg -n '#\\[serial\\]' --type=rust
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7a272c4 and d9b58b8.

📒 Files selected for processing (2)
  • docs/test-isolation-with-ninja-env.md (1 hunks)
  • tests/runner_tests.rs (5 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
docs/**/*.md

📄 CodeRabbit inference engine (AGENTS.md)

docs/**/*.md: When new decisions are made, requirements change, libraries are added/removed, or architectural patterns evolve, proactively update the relevant file(s) in the docs/ directory to reflect the latest state.
Documentation must use en-GB-oxendict spelling and grammar ("-ize" / "-yse" / "-our"), with exception for the LICENSE filename.

docs/**/*.md: Use British English (en-GB) spelling with -ize endings (realize, organization), -lyse endings (analyse, paralyse, catalyse), -our endings (colour, behaviour), -re endings (calibre, centre, fibre), double 'l' (cancelled, counsellor), maintained 'e' (likeable, liveable), and -ogue endings (analogue, catalogue)
Use the Oxford comma in documentation: 'ships, planes, and hovercraft'
Treat company names as collective nouns in documentation: 'Concordat Industries are expanding'
Write headings in sentence case in documentation
Use Markdown headings (#, ##, ###) in order without skipping levels
Follow markdownlint recommendations for Markdown formatting
Provide code blocks and lists using standard Markdown syntax
Always provide a language identifier for fenced code blocks; use plaintext for non-code text
Use - as the first level bullet and renumber lists when items change in documentation
Prefer inline links using [text](url) or angle brackets around the URL in documentation
Ensure blank lines before and after bulleted lists and fenced blocks in documentation
Ensure tables have a delimiter line below the header row in documentation
Expand any uncommon acronym on first use in documentation (e.g., Continuous Integration (CI))
Wrap paragraphs at 80 columns in documentation
Wrap code at 120 columns in documentation
Do not wrap tables in documentation
Use GitHub-flavoured numeric footnotes referenced as [^1] in documentation
Number footnotes in order of appearance in the document
Caption every table and every diagram in documentation
Use ![alt text](path/to/image) for embedding figures with brief alt te...

Files:

  • docs/test-isolation-with-ninja-env.md
**/*.md

📄 CodeRabbit inference engine (AGENTS.md)

**/*.md: Markdown paragraphs and bullet points must be wrapped at 80 columns.
Code blocks in Markdown must be wrapped at 120 columns.
Tables and headings in Markdown must not be wrapped.
Use dashes (-) for list bullets in Markdown.
Use GitHub-flavoured Markdown footnotes ([^1]) for references and footnotes.

Files:

  • docs/test-isolation-with-ninja-env.md

⚙️ CodeRabbit configuration file

**/*.md: * Avoid 2nd person or 1st person pronouns ("I", "you", "we")

  • Use en-GB-oxendict (-ize / -yse / -our) spelling and grammar
  • Headings must not be wrapped.
  • Documents must start with a level 1 heading
  • Headings must correctly increase or decrease by no more than one level at a time
  • Use GitHub-flavoured Markdown style for footnotes and endnotes.
  • Numbered footnotes must be numbered by order of appearance in the document.

Files:

  • docs/test-isolation-with-ninja-env.md
docs/**/{README,readme,GUIDE,guide,*.md}

📄 CodeRabbit inference engine (docs/adr-001-replace-serde-yml-with-serde-saphyr.md)

Document in Netsuke's README and user guide that the project now supports YAML 1.2 (not YAML 1.1) and that duplicate keys in manifests are no longer permitted

Files:

  • docs/test-isolation-with-ninja-env.md
docs/**/!(README).md

📄 CodeRabbit inference engine (docs/documentation-style-guide.md)

Avoid first and second person personal pronouns outside the README.md file in documentation

Files:

  • docs/test-isolation-with-ninja-env.md
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Clippy warnings MUST be disallowed in Rust code.
Fix any warnings emitted during Rust tests in the code itself rather than silencing them.
In Rust, extract meaningfully named helper functions when a function is too long, adhering to separation of concerns and CQRS.
In Rust, group related parameters in meaningfully named structs when a function has too many parameters.
In Rust, consider using Arc to reduce data returned when a function is returning a large error.
Every Rust module must begin with a module level (//!) comment explaining the module's purpose and utility.
Document public APIs in Rust using Rustdoc comments (///) so documentation can be generated with cargo doc.
In Rust, prefer immutable data and avoid unnecessary mut bindings.
In Rust, handle errors with the Result type instead of panicking where feasible.
Avoid unsafe code in Rust unless absolutely necessary, and document any usage clearly.
In Rust, place function attributes after doc comments.
In Rust, do not use return in single-line functions.
In Rust, use predicate functions for conditional criteria with more than two branches.
Lints in Rust must not be silenced except as a last resort.
Lint rule suppressions in Rust must be tightly scoped and include a clear reason.
In Rust, prefer expect over allow.
In Rust tests, use rstest fixtures for shared setup.
In Rust tests, replace duplicated tests with #[rstest(...)] parameterized cases.
In Rust, prefer mockall for mocks and stubs.
In Rust, use concat!() to combine long string literals rather than escaping newlines with a backslash.
In Rust, prefer single line versions of functions where appropriate (e.g., pub fn new(id: u64) -> Self { Self(id) } instead of multi-line).
Use NewTypes in Rust to model domain values and eliminate 'integer soup'. Reach for newt-hype when introducing many homogeneous wrappers; add small shims for string-backed wrappers. For path-centric wrappers implement AsRef<Path> along...

Files:

  • tests/runner_tests.rs

⚙️ CodeRabbit configuration file

**/*.rs: * Seek to keep the cognitive complexity of functions no more than 9.

  • Adhere to single responsibility and CQRS
  • Place function attributes after doc comments.
  • Do not use return in single-line functions.
  • Move conditionals with >2 branches into a predicate function.
  • Avoid unsafe unless absolutely necessary.
  • Every module must begin with a //! doc comment that explains the module's purpose and utility.
  • Comments and docs must follow en-GB-oxendict (-ize / -yse / -our) spelling and grammar
  • Lints must not be silenced except as a last resort.
    • #[allow] is forbidden.
    • Only narrowly scoped #[expect(lint, reason = "...")] is allowed.
    • No lint groups, no blanket or file-wide suppression.
    • Include FIXME: with link if a fix is expected.
  • Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.
  • Use rstest fixtures for shared setup and to avoid repetition between tests.
  • Replace duplicated tests with #[rstest(...)] parameterised cases.
  • Prefer mockall for mocks/stubs.
  • Prefer .expect() over .unwrap() in tests.
  • .expect() and .unwrap() are forbidden outside of tests. Errors must be propagated.
  • Ensure that any API or behavioural changes are reflected in the documentation in docs/
  • Ensure that any completed roadmap steps are recorded in the appropriate roadmap in docs/
  • Files must not exceed 400 lines in length
    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.
  • Environment access (env::set_var and env::remove_var) are always unsafe in Rust 2024 and MUST be marked as such
    • For testing of functionality depending upon environment variables, dependency injection and...

Files:

  • tests/runner_tests.rs
🧬 Code graph analysis (1)
tests/runner_tests.rs (4)
test_support/src/env.rs (1)
  • override_ninja_env (238-246)
test_support/src/lib.rs (1)
  • fake_ninja (95-127)
test_support/src/check_ninja.rs (1)
  • fake_ninja_check_build_file (13-59)
test_support/src/path_guard.rs (1)
  • new (27-31)
🔍 Remote MCP Deepwiki

Summary of Relevant Context for PR Review

Based on the repository documentation, here are the key facts and context for reviewing this pull request:

NinjaEnvGuard Implementation Details

NinjaEnvGuard is a Rust struct that leverages RAII to ensure NINJA_ENV is restored to its original state when dropped, preventing global environment pollution. The mechanism uses EnvLock to serialize environment mutations across threads, which is crucial because std::env::set_var is unsafe and can cause race conditions in tests.

How NINJA_ENV Works

The netsuke application resolves the path to the Ninja executable by first attempting to read NINJA_ENV from the environment; if set and containing valid UTF-8, that path is used; otherwise it falls back to the default "ninja" program.

Why PATH Mutation is Problematic

PATH mutation is problematic for parallel tests because it modifies global process state, leading to flaky tests, parallel execution conflicts, and potential state corruption if a test panics. The override_ninja_env function with NinjaEnvGuard solves this by providing an RAII guard that restores NINJA_ENV to its original value when it goes out of scope, ensuring changes are localized to a single test.

Testing Framework Context

The project uses rstest for fixture-based unit testing, cucumber-rs for behavior-driven development, and insta for snapshot testing. For rstest, fixtures are called fresh for each test to provide proper test isolation, with regular (per-test) fixtures preferred over #[once] fixtures to ensure proper cleanup.

Design Pattern Validation

The refactoring aligns with the project's established testing patterns: The project emphasizes Dependency Injection to handle external dependencies like environment variables, with the mockable crate providing traits like Env and Clock to prevent global state access in tests.,

⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: release / build-windows (x86_64-pc-windows-msvc, amd64, x64, windows-x86_64) / Build windows artefacts
  • GitHub Check: release / build-macos (x86_64-apple-darwin, macos-13, macos-x86_64, macos-x86_64) / Build macos artefacts
  • GitHub Check: release / build-linux (aarch64-unknown-linux-gnu, linux-arm64, arm64) / Build linux artefacts
  • GitHub Check: release / build-windows (aarch64-pc-windows-msvc, arm64, arm64, windows-aarch64) / Build windows artefacts
  • GitHub Check: Sourcery review
🔇 Additional comments (3)
tests/runner_tests.rs (3)

11-27: Use NinjaEnvGuard-based fixture for NINJA_ENV correctly

Keep this import and fixture; they correctly wrap fake_ninja_check_build_file with SystemEnv and override_ninja_env, and they keep both the temporary directory and NinjaEnvGuard alive for the test duration via RAII. No issues here.


29-40: Keep ninja_with_exit_code aligned with the NINJA_ENV guard pattern

Retain this fixture shape; it mirrors ninja_in_env, exposes only (TempDir, NinjaEnvGuard), and avoids leaking the fake Ninja path or mutating PATH. This fits the parallel-friendly test strategy.


97-115: Use ninja_in_env / ninja_with_exit_code fixtures to isolate Ninja correctly

Use of ninja_in_env() and ninja_with_exit_code(0) in these tests correctly scopes NINJA_ENV, keeps the fake Ninja directory alive, and removes any reliance on PATH or shell configuration. Binding the guard to _guard is sufficient to keep it in scope without triggering clippy.

Also applies to: 119-151, 155-178

Comment thread docs/test-isolation-with-ninja-env.md
@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Dec 3, 2025

@coderabbitai Please suggest a fix for this issue that will expand the lifetime of the environment guard to cover the execution of the CLI and supply a prompt for an AI coding agent to enable it to apply the fix:

Guard NINJA_ENV-based tests against parallel races

The new ninja_in_env/ninja_with_exit_code fixtures still mutate the process-wide NINJA_ENV (they call override_ninja_env and return a guard, but the guard only restores on drop and does not hold the lock while the CLI runs). The tests that consume these fixtures (run_executes_ninja_without_persisting_file, run_build_with_emit_keeps_file, run_build_with_emit_creates_parent_dirs) no longer have #[serial], so running the suite in parallel can interleave these mutations: whichever test sets NINJA_ENV last controls resolve_ninja_program in the others. That makes the suite flaky and can hide the build-file validation from fake_ninja_check_build_file when another test overwrites the variable with the plain fake_ninja fixture.

Add a test that ensures the NINJA_ENV environment variable takes precedence over any ninja executable on PATH. This involves placing a failing fake ninja on PATH and a working fake ninja via NINJA_ENV, verifying that the latter is respected during builds.

Also update documentation to clarify NINJA_ENV precedence behavior.

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
@coderabbitai

This comment was marked as resolved.

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)
docs/test-isolation-with-ninja-env.md (1)

37-43: Expand the CLI acronym on first use in the example

Replace the inline comment so that it spells out “command-line interface (CLI)” on first use, in line with the acronym guidance.

Apply a change along these lines:

-    // run the CLI here; the guard restores NINJA_ENV on drop
+    // run the command-line interface (CLI) here; the guard restores NINJA_ENV on drop
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d9b58b8 and 962d0e8.

📒 Files selected for processing (2)
  • docs/test-isolation-with-ninja-env.md (1 hunks)
  • tests/runner_tests.rs (5 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Clippy warnings MUST be disallowed in Rust code.
Fix any warnings emitted during Rust tests in the code itself rather than silencing them.
In Rust, extract meaningfully named helper functions when a function is too long, adhering to separation of concerns and CQRS.
In Rust, group related parameters in meaningfully named structs when a function has too many parameters.
In Rust, consider using Arc to reduce data returned when a function is returning a large error.
Every Rust module must begin with a module level (//!) comment explaining the module's purpose and utility.
Document public APIs in Rust using Rustdoc comments (///) so documentation can be generated with cargo doc.
In Rust, prefer immutable data and avoid unnecessary mut bindings.
In Rust, handle errors with the Result type instead of panicking where feasible.
Avoid unsafe code in Rust unless absolutely necessary, and document any usage clearly.
In Rust, place function attributes after doc comments.
In Rust, do not use return in single-line functions.
In Rust, use predicate functions for conditional criteria with more than two branches.
Lints in Rust must not be silenced except as a last resort.
Lint rule suppressions in Rust must be tightly scoped and include a clear reason.
In Rust, prefer expect over allow.
In Rust tests, use rstest fixtures for shared setup.
In Rust tests, replace duplicated tests with #[rstest(...)] parameterized cases.
In Rust, prefer mockall for mocks and stubs.
In Rust, use concat!() to combine long string literals rather than escaping newlines with a backslash.
In Rust, prefer single line versions of functions where appropriate (e.g., pub fn new(id: u64) -> Self { Self(id) } instead of multi-line).
Use NewTypes in Rust to model domain values and eliminate 'integer soup'. Reach for newt-hype when introducing many homogeneous wrappers; add small shims for string-backed wrappers. For path-centric wrappers implement AsRef<Path> along...

Files:

  • tests/runner_tests.rs

⚙️ CodeRabbit configuration file

**/*.rs: * Seek to keep the cognitive complexity of functions no more than 9.

  • Adhere to single responsibility and CQRS
  • Place function attributes after doc comments.
  • Do not use return in single-line functions.
  • Move conditionals with >2 branches into a predicate function.
  • Avoid unsafe unless absolutely necessary.
  • Every module must begin with a //! doc comment that explains the module's purpose and utility.
  • Comments and docs must follow en-GB-oxendict (-ize / -yse / -our) spelling and grammar
  • Lints must not be silenced except as a last resort.
    • #[allow] is forbidden.
    • Only narrowly scoped #[expect(lint, reason = "...")] is allowed.
    • No lint groups, no blanket or file-wide suppression.
    • Include FIXME: with link if a fix is expected.
  • Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.
  • Use rstest fixtures for shared setup and to avoid repetition between tests.
  • Replace duplicated tests with #[rstest(...)] parameterised cases.
  • Prefer mockall for mocks/stubs.
  • Prefer .expect() over .unwrap() in tests.
  • .expect() and .unwrap() are forbidden outside of tests. Errors must be propagated.
  • Ensure that any API or behavioural changes are reflected in the documentation in docs/
  • Ensure that any completed roadmap steps are recorded in the appropriate roadmap in docs/
  • Files must not exceed 400 lines in length
    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.
  • Environment access (env::set_var and env::remove_var) are always unsafe in Rust 2024 and MUST be marked as such
    • For testing of functionality depending upon environment variables, dependency injection and...

Files:

  • tests/runner_tests.rs
docs/**/*.md

📄 CodeRabbit inference engine (AGENTS.md)

docs/**/*.md: When new decisions are made, requirements change, libraries are added/removed, or architectural patterns evolve, proactively update the relevant file(s) in the docs/ directory to reflect the latest state.
Documentation must use en-GB-oxendict spelling and grammar ("-ize" / "-yse" / "-our"), with exception for the LICENSE filename.

docs/**/*.md: Use British English (en-GB) spelling with -ize endings (realize, organization), -lyse endings (analyse, paralyse, catalyse), -our endings (colour, behaviour), -re endings (calibre, centre, fibre), double 'l' (cancelled, counsellor), maintained 'e' (likeable, liveable), and -ogue endings (analogue, catalogue)
Use the Oxford comma in documentation: 'ships, planes, and hovercraft'
Treat company names as collective nouns in documentation: 'Concordat Industries are expanding'
Write headings in sentence case in documentation
Use Markdown headings (#, ##, ###) in order without skipping levels
Follow markdownlint recommendations for Markdown formatting
Provide code blocks and lists using standard Markdown syntax
Always provide a language identifier for fenced code blocks; use plaintext for non-code text
Use - as the first level bullet and renumber lists when items change in documentation
Prefer inline links using [text](url) or angle brackets around the URL in documentation
Ensure blank lines before and after bulleted lists and fenced blocks in documentation
Ensure tables have a delimiter line below the header row in documentation
Expand any uncommon acronym on first use in documentation (e.g., Continuous Integration (CI))
Wrap paragraphs at 80 columns in documentation
Wrap code at 120 columns in documentation
Do not wrap tables in documentation
Use GitHub-flavoured numeric footnotes referenced as [^1] in documentation
Number footnotes in order of appearance in the document
Caption every table and every diagram in documentation
Use ![alt text](path/to/image) for embedding figures with brief alt te...

Files:

  • docs/test-isolation-with-ninja-env.md
**/*.md

📄 CodeRabbit inference engine (AGENTS.md)

**/*.md: Markdown paragraphs and bullet points must be wrapped at 80 columns.
Code blocks in Markdown must be wrapped at 120 columns.
Tables and headings in Markdown must not be wrapped.
Use dashes (-) for list bullets in Markdown.
Use GitHub-flavoured Markdown footnotes ([^1]) for references and footnotes.

Files:

  • docs/test-isolation-with-ninja-env.md

⚙️ CodeRabbit configuration file

**/*.md: * Avoid 2nd person or 1st person pronouns ("I", "you", "we")

  • Use en-GB-oxendict (-ize / -yse / -our) spelling and grammar
  • Headings must not be wrapped.
  • Documents must start with a level 1 heading
  • Headings must correctly increase or decrease by no more than one level at a time
  • Use GitHub-flavoured Markdown style for footnotes and endnotes.
  • Numbered footnotes must be numbered by order of appearance in the document.

Files:

  • docs/test-isolation-with-ninja-env.md
docs/**/{README,readme,GUIDE,guide,*.md}

📄 CodeRabbit inference engine (docs/adr-001-replace-serde-yml-with-serde-saphyr.md)

Document in Netsuke's README and user guide that the project now supports YAML 1.2 (not YAML 1.1) and that duplicate keys in manifests are no longer permitted

Files:

  • docs/test-isolation-with-ninja-env.md
docs/**/!(README).md

📄 CodeRabbit inference engine (docs/documentation-style-guide.md)

Avoid first and second person personal pronouns outside the README.md file in documentation

Files:

  • docs/test-isolation-with-ninja-env.md
🧬 Code graph analysis (1)
tests/runner_tests.rs (4)
test_support/src/env.rs (2)
  • override_ninja_env (238-246)
  • prepend_dir_to_path (194-208)
test_support/src/lib.rs (1)
  • fake_ninja (95-127)
test_support/src/check_ninja.rs (1)
  • fake_ninja_check_build_file (13-59)
test_support/src/path_guard.rs (1)
  • new (27-31)
🔍 Remote MCP Deepwiki

Summary of additional findings relevant to the PR review (concise, actionable):

  • Tests and runner resolve Ninja from NINJA_ENV before PATH; the codebase already prefers NINJA_ENV for resolution and uses RAII guards to restore env on Drop (so moving from PATH mutation → NINJA_ENV is appropriate).,

  • Current Env-guard pattern (EnvGuard / NinjaEnvGuard) restores NINJA_ENV on Drop but does not hold a lock across CLI invocation — so returning a guard from a fixture without the test holding it for the entire CLI lifetime allows other tests to overwrite NINJA_ENV while the CLI is running (race). The PR comments already note this exact race.

Recommendation to fix (minimal, test-focused):

  • Ensure the environment guard's lifetime spans the actual CLI/Ninja execution. Two practical options:

    1. Change test fixtures so they return the guard and the test invokes the CLI while the guard is still in scope (i.e., hold the guard variable until after run completes). Example: let (_tmpdir, ninja_env_guard) = ninja_with_exit_code(...); run_cli(...); drop(ninja_env_guard);
    2. Or enhance NinjaEnvGuard to acquire a process-wide EnvLock (existing EnvLock mechanism) for the duration of CLI execution: provide a helper API run_with_ninja_env(ninja_path, closure) that sets NINJA_ENV and holds EnvLock until the closure (which runs the CLI) returns. This guarantees mutual exclusion across threads/tests and prevents interleaving NINJA_ENV mutations.
  • Prefer option 2 for robustness (tests need not remember to hold/drop); option 1 is smaller and quick to land if tests are updated accordingly.

Caveats and verification steps:

  • If adopting option 2, use EnvLock to serialize environment mutations only while the CLI subprocess is executing (avoid holding the lock for longer than necessary).
  • Add a test that runs two fixtures in parallel asserting their fake-ninja behaviors do not interfere (to confirm the race is fixed).

Tools/sources used:

  • Repository documentation and structure via Deepwiki:,
⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: release / build-windows (aarch64-pc-windows-msvc, arm64, arm64, windows-aarch64) / Build windows artefacts
  • GitHub Check: release / build-macos (x86_64-apple-darwin, macos-13, macos-x86_64, macos-x86_64) / Build macos artefacts
  • GitHub Check: release / build-linux (aarch64-unknown-linux-gnu, linux-arm64, arm64) / Build linux artefacts
  • GitHub Check: release / build-windows (x86_64-pc-windows-msvc, amd64, x64, windows-x86_64) / Build windows artefacts
  • GitHub Check: Sourcery review
  • GitHub Check: build-test (ubuntu-latest, stable)
🔇 Additional comments (1)
tests/runner_tests.rs (1)

225-241: Retain this precedence test once the NINJA_ENV guard holds the env lock

This test correctly asserts that NINJA_ENV takes precedence over a conflicting PATH entry by:

  • Putting a failing fake Ninja on PATH via prepend_dir_to_path.
  • Pointing NINJA_ENV at a successful fake Ninja via override_ninja_env.
  • Expecting run(&cli) to succeed.

After updating override_ninja_env so that NinjaEnvGuard holds the environment lock for its lifetime, keep this test as-is; it will continue to validate the documented resolution order while also benefiting from the stronger isolation guarantees.

Run cargo test --tests -- --test-threads=4 after adjusting override_ninja_env to ensure this test remains stable and still passes under parallel execution.

Comment thread tests/runner_tests.rs
@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Dec 3, 2025

@coderabbitai have the following now been resolved?

  • Now that NINJA_ENV is guarded, consider removing the remaining #[serial] on run_respects_env_override_for_ninja if NinjaEnvGuard already handles synchronization, to keep this test consistent with the others that now run in parallel.
  • In run_respects_env_override_for_ninja, the explicit drop(guard) at the end is redundant since the guard will be dropped when it goes out of scope; you can simplify by relying on RAII unless you need the environment restored earlier in the test.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Dec 3, 2025

Tip

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if NinjaEnvGuard now holds the EnvLock
rg -nP "struct NinjaEnvGuard" -A 5 test_support/src/env.rs

Length of output: 163


🏁 Script executed:

#!/bin/bash
# Check the current state of run_respects_env_override_for_ninja test
rg -nP "fn run_respects_env_override_for_ninja" -B 5 -A 30 tests/runner_tests.rs

Length of output: 803


🏁 Script executed:

#!/bin/bash
# Check the override_ninja_env function implementation
rg -nP "pub fn override_ninja_env" -A 15 test_support/src/env.rs

Length of output: 614


@leynos Looking at the current code state:

Status of the two issues:

  1. #[serial] removed: The run_respects_env_override_for_ninja test (line 225) now has only #[test] with no #[serial] attribute.

  2. No explicit drop(guard): The test uses _env_guard and _path_guard variables (lines 230, 232) that automatically drop at the end of scope via RAII—there's no explicit drop() call.

However, there's a critical issue:

The underlying fix has NOT been applied yet. The NinjaEnvGuard struct (line 212-214 in test_support/src/env.rs) still lacks the _lock: EnvLock field, and override_ninja_env (line 239) still discards the lock immediately with let _lock = EnvLock::acquire();.

This means: Removing #[serial] was premature. Without the lock-holding fix, run_respects_env_override_for_ninja and other tests using override_ninja_env can still race on NINJA_ENV mutations when running in parallel.

Recommendation: Apply the proposed fix to NinjaEnvGuard first (adding the _lock field and using EnvGuard::new_unlocked), then the removal of #[serial] and reliance on RAII will be safe and correct.

The NinjaEnvGuard now retains the EnvLock for its entire lifetime, ensuring
that parallel mutations of the NINJA_ENV environment variable are serialized.
This prevents race conditions when tests override NINJA_ENV concurrently.

The guard restores the prior value on drop and holds the lock to prevent
interleaving writes, confining unsafety to the guard's scope.

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
Added integration tests verifying behavior when using the NINJA_ENV environment variable. Tests cover the successful execution of the CLI with a fake ninja binary and failure when the ninja binary exits with a non-zero status.

Also improved safety comments and refined NinjaEnvGuard implementation in env.rs.

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Dec 3, 2025

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

tests/runner_tests.rs

Comment on file

#[serial]
fn run_executes_ninja_without_persisting_file() -> Result<()> {
    let (_ninja_dir, ninja_path, _guard) = ninja_in_path()?;
    let (_ninja_dir, _guard) = ninja_in_env()?;

❌ New issue: Code Duplication
The module contains 2 functions with similar structure: run_executes_ninja_without_persisting_file,run_succeeds_with_checking_ninja_env

@coderabbitai

This comment was marked as resolved.

Refactored repeated setup code in runner_tests.rs into a shared helper function `setup_ninja_env_test`. This new function consolidates the creation of a fake ninja directory, temporary project directory, CLI construction, and environment guard setup for tests that rely on the `NINJA_ENV` environment variable. The change improves code reuse and readability in the test suite.

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
@leynos leynos changed the title Refactor tests to use NINJA_ENV for parallel isolation Introduce NinjaEnvGuard and NINJA_ENV-based test isolation Dec 3, 2025
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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bfd54eb and 907453c.

📒 Files selected for processing (3)
  • docs/test-isolation-with-ninja-env.md (1 hunks)
  • test_support/src/env.rs (3 hunks)
  • tests/runner_tests.rs (5 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
docs/**/*.md

📄 CodeRabbit inference engine (AGENTS.md)

docs/**/*.md: When new decisions are made, requirements change, libraries are added/removed, or architectural patterns evolve, proactively update the relevant file(s) in the docs/ directory to reflect the latest state.
Documentation must use en-GB-oxendict spelling and grammar ("-ize" / "-yse" / "-our"), with exception for the LICENSE filename.

docs/**/*.md: Use British English (en-GB) spelling with -ize endings (realize, organization), -lyse endings (analyse, paralyse, catalyse), -our endings (colour, behaviour), -re endings (calibre, centre, fibre), double 'l' (cancelled, counsellor), maintained 'e' (likeable, liveable), and -ogue endings (analogue, catalogue)
Use the Oxford comma in documentation: 'ships, planes, and hovercraft'
Treat company names as collective nouns in documentation: 'Concordat Industries are expanding'
Write headings in sentence case in documentation
Use Markdown headings (#, ##, ###) in order without skipping levels
Follow markdownlint recommendations for Markdown formatting
Provide code blocks and lists using standard Markdown syntax
Always provide a language identifier for fenced code blocks; use plaintext for non-code text
Use - as the first level bullet and renumber lists when items change in documentation
Prefer inline links using [text](url) or angle brackets around the URL in documentation
Ensure blank lines before and after bulleted lists and fenced blocks in documentation
Ensure tables have a delimiter line below the header row in documentation
Expand any uncommon acronym on first use in documentation (e.g., Continuous Integration (CI))
Wrap paragraphs at 80 columns in documentation
Wrap code at 120 columns in documentation
Do not wrap tables in documentation
Use GitHub-flavoured numeric footnotes referenced as [^1] in documentation
Number footnotes in order of appearance in the document
Caption every table and every diagram in documentation
Use ![alt text](path/to/image) for embedding figures with brief alt te...

Files:

  • docs/test-isolation-with-ninja-env.md
**/*.md

📄 CodeRabbit inference engine (AGENTS.md)

**/*.md: Markdown paragraphs and bullet points must be wrapped at 80 columns.
Code blocks in Markdown must be wrapped at 120 columns.
Tables and headings in Markdown must not be wrapped.
Use dashes (-) for list bullets in Markdown.
Use GitHub-flavoured Markdown footnotes ([^1]) for references and footnotes.

Files:

  • docs/test-isolation-with-ninja-env.md

⚙️ CodeRabbit configuration file

**/*.md: * Avoid 2nd person or 1st person pronouns ("I", "you", "we")

  • Use en-GB-oxendict (-ize / -yse / -our) spelling and grammar
  • Headings must not be wrapped.
  • Documents must start with a level 1 heading
  • Headings must correctly increase or decrease by no more than one level at a time
  • Use GitHub-flavoured Markdown style for footnotes and endnotes.
  • Numbered footnotes must be numbered by order of appearance in the document.

Files:

  • docs/test-isolation-with-ninja-env.md
docs/**/{README,readme,GUIDE,guide,*.md}

📄 CodeRabbit inference engine (docs/adr-001-replace-serde-yml-with-serde-saphyr.md)

Document in Netsuke's README and user guide that the project now supports YAML 1.2 (not YAML 1.1) and that duplicate keys in manifests are no longer permitted

Files:

  • docs/test-isolation-with-ninja-env.md
docs/**/!(README).md

📄 CodeRabbit inference engine (docs/documentation-style-guide.md)

Avoid first and second person personal pronouns outside the README.md file in documentation

Files:

  • docs/test-isolation-with-ninja-env.md
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Clippy warnings MUST be disallowed in Rust code.
Fix any warnings emitted during Rust tests in the code itself rather than silencing them.
In Rust, extract meaningfully named helper functions when a function is too long, adhering to separation of concerns and CQRS.
In Rust, group related parameters in meaningfully named structs when a function has too many parameters.
In Rust, consider using Arc to reduce data returned when a function is returning a large error.
Every Rust module must begin with a module level (//!) comment explaining the module's purpose and utility.
Document public APIs in Rust using Rustdoc comments (///) so documentation can be generated with cargo doc.
In Rust, prefer immutable data and avoid unnecessary mut bindings.
In Rust, handle errors with the Result type instead of panicking where feasible.
Avoid unsafe code in Rust unless absolutely necessary, and document any usage clearly.
In Rust, place function attributes after doc comments.
In Rust, do not use return in single-line functions.
In Rust, use predicate functions for conditional criteria with more than two branches.
Lints in Rust must not be silenced except as a last resort.
Lint rule suppressions in Rust must be tightly scoped and include a clear reason.
In Rust, prefer expect over allow.
In Rust tests, use rstest fixtures for shared setup.
In Rust tests, replace duplicated tests with #[rstest(...)] parameterized cases.
In Rust, prefer mockall for mocks and stubs.
In Rust, use concat!() to combine long string literals rather than escaping newlines with a backslash.
In Rust, prefer single line versions of functions where appropriate (e.g., pub fn new(id: u64) -> Self { Self(id) } instead of multi-line).
Use NewTypes in Rust to model domain values and eliminate 'integer soup'. Reach for newt-hype when introducing many homogeneous wrappers; add small shims for string-backed wrappers. For path-centric wrappers implement AsRef<Path> along...

Files:

  • tests/runner_tests.rs
  • test_support/src/env.rs

⚙️ CodeRabbit configuration file

**/*.rs: * Seek to keep the cognitive complexity of functions no more than 9.

  • Adhere to single responsibility and CQRS
  • Place function attributes after doc comments.
  • Do not use return in single-line functions.
  • Move conditionals with >2 branches into a predicate function.
  • Avoid unsafe unless absolutely necessary.
  • Every module must begin with a //! doc comment that explains the module's purpose and utility.
  • Comments and docs must follow en-GB-oxendict (-ize / -yse / -our) spelling and grammar
  • Lints must not be silenced except as a last resort.
    • #[allow] is forbidden.
    • Only narrowly scoped #[expect(lint, reason = "...")] is allowed.
    • No lint groups, no blanket or file-wide suppression.
    • Include FIXME: with link if a fix is expected.
  • Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.
  • Use rstest fixtures for shared setup and to avoid repetition between tests.
  • Replace duplicated tests with #[rstest(...)] parameterised cases.
  • Prefer mockall for mocks/stubs.
  • Prefer .expect() over .unwrap() in tests.
  • .expect() and .unwrap() are forbidden outside of tests. Errors must be propagated.
  • Ensure that any API or behavioural changes are reflected in the documentation in docs/
  • Ensure that any completed roadmap steps are recorded in the appropriate roadmap in docs/
  • Files must not exceed 400 lines in length
    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.
  • Environment access (env::set_var and env::remove_var) are always unsafe in Rust 2024 and MUST be marked as such
    • For testing of functionality depending upon environment variables, dependency injection and...

Files:

  • tests/runner_tests.rs
  • test_support/src/env.rs
🧬 Code graph analysis (2)
tests/runner_tests.rs (1)
test_support/src/env.rs (2)
  • override_ninja_env (244-255)
  • prepend_dir_to_path (198-212)
test_support/src/env.rs (3)
test_support/src/env_lock.rs (1)
  • acquire (17-22)
test_support/src/env_var_guard.rs (1)
  • original (68-70)
test_support/src/env_guard.rs (1)
  • with_env_and_lock (76-88)
🔍 Remote MCP Deepwiki

Summary of additional repository facts relevant to this PR review (sources cited):

  • Netsuke resolves which ninja executable to run from an environment override before falling back to PATH; tests and runner rely on an environment-based override mechanism (NINJA_ENV) for ninja selection and for CLI subprocess resolution.

  • The repo has a dedicated test_support crate and fixtures pattern; tests use RAII-style guards, tempfile-based resources, and aim for parallel execution where possible (serial_test used only for resource-exclusive tests). Test fixtures and runner expect guards to span the lifetime of any subprocess spawned by the CLI.

  • The test infrastructure and docs emphasize avoiding global PATH mutations in tests and recommend overriding NINJA_ENV for isolated, parallel-friendly tests; manifest/runner code and tests rely on environment guards and temp-file RAII to keep resources alive during subprocess execution.

  • The codebase already enforces strict error/context propagation (anyhow/thiserror/miette) and deterministic test output; changing guard semantics (holding an EnvLock for guard lifetime) is required to make the NINJA_ENV-based isolation actually safe for parallel tests (otherwise "last setter wins" races remain). This aligns with the PR's stated root-cause and proposed fix.

Tools used:

  • Deepwiki_read_wiki_contents (leynos/netsuke)
⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: release / build-macos (aarch64-apple-darwin, macos-14, macos-arm64, macos-aarch64) / Build macos artefacts
  • GitHub Check: release / build-linux (aarch64-unknown-linux-gnu, linux-arm64, arm64) / Build linux artefacts
  • GitHub Check: release / build-macos (x86_64-apple-darwin, macos-13, macos-x86_64, macos-x86_64) / Build macos artefacts
  • GitHub Check: release / build-windows (aarch64-pc-windows-msvc, arm64, arm64, windows-aarch64) / Build windows artefacts
  • GitHub Check: release / build-windows (x86_64-pc-windows-msvc, amd64, x64, windows-x86_64) / Build windows artefacts
  • GitHub Check: Sourcery review
  • GitHub Check: build-test (ubuntu-latest, stable)
🔇 Additional comments (1)
tests/runner_tests.rs (1)

14-54: Keep NINJA_ENV guards in scope to maintain parallel safety

Maintain the current pattern where ninja_in_env, ninja_with_exit_code, and setup_ninja_env_test all return a NinjaEnvGuard that stays in scope for the full duration of each test. This brackets run(&cli) with the guard’s lifetime and matches the override_ninja_env locking semantics, so tests remain parallel-safe without #[serial] or manual drop calls.

Also applies to: 111-186

Comment thread docs/test-isolation-with-ninja-env.md
Comment thread test_support/src/env.rs
Comment thread tests/runner_tests.rs
…NV tests

- Updated ninja_in_env and ninja_with_exit_code fixtures to return PathBuf for ninja_path alongside NinjaEnvGuard.
- Modified setup_ninja_env_test and tests to use the ninja_path for existence checks and improved clarity.
- Enhanced NinjaEnvGuard docs to clarify lock acquisition and restore order.
- Added documentation on EnvLock preventing parallel test NINJA_ENV mutation races.

This refactor clarifies test fixtures and ensures robust handling of environment variables in tests.

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
@leynos leynos merged commit fe4dc58 into main Dec 4, 2025
15 checks passed
@leynos leynos deleted the terragon/improve-parallel-test-execution-qangzd branch December 4, 2025 01:54
@leynos leynos linked an issue Dec 4, 2025 that may be closed by this pull request
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Remove no-op drops and enable parallel test execution by isolating environment

1 participant