Skip to content

Guard NINJA_ENV overrides with mockable Env#109

Closed
leynos wants to merge 5 commits intomainfrom
codex/add-envlock-for-ninja_env-modifications
Closed

Guard NINJA_ENV overrides with mockable Env#109
leynos wants to merge 5 commits intomainfrom
codex/add-envlock-for-ninja_env-modifications

Conversation

@leynos
Copy link
Copy Markdown
Owner

@leynos leynos commented Aug 12, 2025

Summary

  • add NinjaEnvGuard helper to override and restore NETSUKE_NINJA
  • use EnvLock when mutating NINJA_ENV during runner execution
  • test NINJA_ENV restoration using mockable::MockEnv

Testing

  • make lint
  • make test

https://chatgpt.com/codex/tasks/task_e_689a8cb37e3c83229e25c6278d6128fb

Summary by Sourcery

Introduce a mockable environment guard to safely override and restore the NINJA_ENV variable in tests.

New Features:

  • Add NinjaEnvGuard and override_ninja_env helper for safely setting and restoring NINJA_ENV in tests
  • Integrate EnvLock to serialize unsafe environment mutations during runner execution

Enhancements:

  • Refactor runner tests to use override_ninja_env and EnvLock instead of manual environment variable manipulation

Tests:

  • Add a test validating that override_ninja_env restores the original NINJA_ENV using MockEnv

@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Aug 12, 2025

Reviewer's Guide

This PR adds a NinjaEnvGuard for safely overriding NINJA_ENV with guaranteed restoration via EnvLock and mockable::Env, refactors existing runner tests to use this guard instead of manual unsafe mutations, and introduces dedicated tests to verify environment restoration.

File-Level Changes

Change Details Files
Encapsulate NINJA_ENV override logic in a reusable guard
  • Create tests/support/ninja_env.rs with NinjaEnvGuard and override_ninja_env using EnvLock and mockable::Env
  • Add pub mod ninja_env entry in tests/support/mod.rs
tests/support/ninja_env.rs
tests/support/mod.rs
Refactor runner tests to use EnvLock and override_ninja_env
  • Replace unsafe std::env::set_var and manual restore with DefaultEnv, EnvLock acquisition, and override_ninja_env guard in run_respects_env_override_for_ninja
tests/runner_tests.rs
Add unit test to verify override_ninja_env restores original environment
  • Introduce tests/ninja_env_tests.rs with MockEnv and EnvLock to assert NINJA_ENV is reset on guard drop
tests/ninja_env_tests.rs

Possibly linked issues


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 Aug 12, 2025

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

  • Tests
    • Added parameterised tests covering temporary Ninja environment overrides and restoration.
    • Switched to a guard-based approach in tests to safely override and auto-restore environment state.
    • Introduced shared test helpers for environment override and synchronisation.
    • Suppressed unused warnings in select test modules for cleaner builds.
    • Enabled Debug formatting for the test environment lock.
  • Chores
    • Applied conditional attributes to test utilities to reduce noise in non-test builds.

Walkthrough

Summarise the test helpers and tests that add a guard-based mechanism to temporarily override and restore the NINJA_ENV environment variable during tests, plus small test-support annotation and Debug derive changes.

Changes

Cohort / File(s) Summary
Ninja env guard
tests/support/ninja_env.rs
Add pub struct NinjaEnvGuard { _lock: EnvLock, original: Option<String> }, pub fn override_ninja_env(lock: EnvLock, env: &impl Env, value: &str) -> NinjaEnvGuard, and impl Drop to restore or remove NINJA_ENV on drop; rely on mockable::Env and netsuke::runner::NINJA_ENV.
Tests using guard
tests/ninja_env_tests.rs, tests/runner_tests.rs
Add parameterised test covering original present/absent/empty cases using override_ninja_env; refactor runner tests to use guard-based override instead of manual mutation/restoration.
Support module exports & attributes
tests/support/mod.rs, tests/support/ninja_env.rs
Export new pub mod ninja_env; add #[cfg_attr(not(test), expect(unused_code, reason = "..."))] to pub fn fake_ninja(...) to silence dead-code in non-test builds; annotate support imports in some tests with #[expect(unused, reason = "...")].
Env lock debug
tests/support/env_lock.rs
Add #[derive(Debug)] to pub struct EnvLock(MutexGuard<'static, ()>);.
Test annotations
tests/assert_cmd_tests.rs, tests/cucumber.rs
Annotate mod support; with #[expect(unused, reason = "...")] to silence unused-export warnings in those tests.

Sequence Diagram(s)

sequenceDiagram
  participant Test
  participant EnvLock
  participant Env as MockEnv
  participant NinjaEnvGuard

  Test->>EnvLock: acquire()
  Test->>Env: raw(NINJA_ENV) -> original?
  Test->>NinjaEnvGuard: override_ninja_env(lock, env, "new")
  NinjaEnvGuard->>Env: unsafe set NINJA_ENV="new"
  Test->>Env: observe "new"
  Note over Test,NinjaEnvGuard: scope exit / guard dropped
  NinjaEnvGuard->>Env: restore original or remove NINJA_ENV
  Test->>Env: observe restored state
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Possibly related issues

Possibly related PRs

Poem

Acquire the lock, set ninja’s mark,
Let the guard hold fast through test and dark.
On drop return what used to be,
Keep the world clean, let tests run free.
🥷✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/add-envlock-for-ninja_env-modifications

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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 @leynos - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments

### Comment 1
<location> `tests/ninja_env_tests.rs:9` </location>
<code_context>
+mod support;
+
+#[test]
+fn override_ninja_env_restores_original() {
+    let _lock = EnvLock::acquire();
+    let mut env = MockEnv::new();
+    env.expect_raw()
+        .withf(|k| k == NINJA_ENV)
+        .returning(|_| Ok("orig".to_string()));
+
+    {
+        let _guard = override_ninja_env(&env, "new");
+        assert_eq!(std::env::var(NINJA_ENV).as_deref(), Ok("new"));
+    }
+    assert_eq!(std::env::var(NINJA_ENV).as_deref(), Ok("orig"));
+    // Clean up to avoid leaking environment state. `remove_var` is `unsafe`
+    // on Rust 2024; the lock above serialises this mutation.
+    unsafe { std::env::remove_var(NINJA_ENV) };
+}
</code_context>

<issue_to_address>
Consider adding tests for edge cases: when NINJA_ENV is unset and when it is set to an empty string.

The current test only checks when NINJA_ENV has a value. Please add cases for when NINJA_ENV is unset and when it is set to an empty string to fully verify guard behavior.
</issue_to_address>

### Comment 2
<location> `tests/support/ninja_env.rs:30` </location>
<code_context>
+/// let env = DefaultEnv::new();
+/// let _guard = override_ninja_env(&env, "/usr/bin/ninja");
+/// ```
+#[allow(dead_code, reason = "only some tests override NINJA_ENV")]
+pub fn override_ninja_env(env: &impl Env, value: &str) -> NinjaEnvGuard {
+    let original = env.raw(NINJA_ENV).ok();
+    // Callers must hold [`EnvLock`](super::env_lock::EnvLock) to serialise this
</code_context>

<issue_to_address>
Consider documenting thread-safety expectations for override_ninja_env.

Explicitly stating the thread-safety requirement in the documentation will help prevent future misuse, especially in tests where EnvLock may not be held.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
/// Set `NINJA_ENV` to `value`, returning a guard that restores the previous
/// value when dropped.
///
/// # Examples
/// ```ignore
/// use mockable::DefaultEnv;
/// use tests::support::ninja_env::override_ninja_env;
/// let env = DefaultEnv::new();
/// let _guard = override_ninja_env(&env, "/usr/bin/ninja");
/// ```
=======
/// Set `NINJA_ENV` to `value`, returning a guard that restores the previous
/// value when dropped.
///
/// # Thread Safety
/// 
/// This function is **not thread-safe**. Callers must hold [`EnvLock`](super::env_lock::EnvLock)
/// to serialize this mutation. Failing to do so may result in undefined behavior or
/// race conditions, especially in tests or concurrent environments.
///
/// # Examples
/// ```ignore
/// use mockable::DefaultEnv;
/// use tests::support::ninja_env::override_ninja_env;
/// let env = DefaultEnv::new();
/// let _guard = override_ninja_env(&env, "/usr/bin/ninja");
/// ```
>>>>>>> REPLACE

</suggested_fix>

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/ninja_env_tests.rs Outdated
Comment on lines +9 to +18
fn override_ninja_env_restores_original() {
let _lock = EnvLock::acquire();
let mut env = MockEnv::new();
env.expect_raw()
.withf(|k| k == NINJA_ENV)
.returning(|_| Ok("orig".to_string()));

{
let _guard = override_ninja_env(&env, "new");
assert_eq!(std::env::var(NINJA_ENV).as_deref(), Ok("new"));
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.

suggestion (testing): Consider adding tests for edge cases: when NINJA_ENV is unset and when it is set to an empty string.

The current test only checks when NINJA_ENV has a value. Please add cases for when NINJA_ENV is unset and when it is set to an empty string to fully verify guard behavior.

Comment thread tests/support/ninja_env.rs Outdated
Comment on lines +20 to +29
/// Set `NINJA_ENV` to `value`, returning a guard that restores the previous
/// value when dropped.
///
/// # Examples
/// ```ignore
/// use mockable::DefaultEnv;
/// use tests::support::ninja_env::override_ninja_env;
/// let env = DefaultEnv::new();
/// let _guard = override_ninja_env(&env, "/usr/bin/ninja");
/// ```
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.

suggestion: Consider documenting thread-safety expectations for override_ninja_env.

Explicitly stating the thread-safety requirement in the documentation will help prevent future misuse, especially in tests where EnvLock may not be held.

Suggested change
/// Set `NINJA_ENV` to `value`, returning a guard that restores the previous
/// value when dropped.
///
/// # Examples
/// ```ignore
/// use mockable::DefaultEnv;
/// use tests::support::ninja_env::override_ninja_env;
/// let env = DefaultEnv::new();
/// let _guard = override_ninja_env(&env, "/usr/bin/ninja");
/// ```
/// Set `NINJA_ENV` to `value`, returning a guard that restores the previous
/// value when dropped.
///
/// # Thread Safety
///
/// This function is **not thread-safe**. Callers must hold [`EnvLock`](super::env_lock::EnvLock)
/// to serialize this mutation. Failing to do so may result in undefined behavior or
/// race conditions, especially in tests or concurrent environments.
///
/// # Examples
/// ```ignore
/// use mockable::DefaultEnv;
/// use tests::support::ninja_env::override_ninja_env;
/// let env = DefaultEnv::new();
/// let _guard = override_ninja_env(&env, "/usr/bin/ninja");
/// ```

Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

4 files reviewed, 2 comments

Edit Code Review Bot Settings | Greptile

Comment thread tests/support/ninja_env.rs Outdated
@@ -0,0 +1,50 @@
//! Override and restore `NINJA_ENV` for tests.
//!
//! Provides a helper that sets the `NETSUKE_NINJA` environment variable while
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

syntax: Documentation references NETSUKE_NINJA but the actual constant is NINJA_ENV. Should be consistent with the variable name used throughout the codebase.

Suggested change
//! Provides a helper that sets the `NETSUKE_NINJA` environment variable while
//! Provides a helper that sets the `NINJA_ENV` environment variable while

Comment thread tests/ninja_env_tests.rs Outdated
Comment on lines +12 to +14
env.expect_raw()
.withf(|k| k == NINJA_ENV)
.returning(|_| Ok("orig".to_string()));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

style: Mock expectation is set but the guard implementation calls env.raw() only once during construction. Consider verifying the mock was called exactly once.

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: 7

♻️ Duplicate comments (1)
tests/support/ninja_env.rs (1)

8-10: Confirm the NINJA_ENV symbol path.

This module depends on netsuke::runner::NINJA_ENV. If that symbol moved, adjust the import or add a re-export. See the verification requested in tests/ninja_env_tests.rs Line 2.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fed9793 and f929fb9.

📒 Files selected for processing (4)
  • tests/ninja_env_tests.rs (1 hunks)
  • tests/runner_tests.rs (3 hunks)
  • tests/support/mod.rs (2 hunks)
  • tests/support/ninja_env.rs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit Inference Engine (AGENTS.md)

**/*.rs: Comment why, not what. Explain assumptions, edge cases, trade-offs, or complexity. Don't echo the obvious.
Use functions and composition. Avoid repetition by extracting reusable logic. Prefer generators or comprehensions, and declarative code to imperative repetition when readable.
Small, meaningful functions. Functions must be small, clear in purpose, single responsibility, and obey command/query segregation.
Name things precisely. Use clear, descriptive variable and function names. For booleans, prefer names with is, has, or should.
Every module must begin with a module level (//! ) comment explaining the module's purpose and utility.
Document public APIs using Rustdoc comments (///) so documentation can be generated with cargo doc.
Place function attributes after doc comments.
Do not use return in single-line functions.
Prefer immutable data and avoid unnecessary mut bindings.
Handle errors with the Result type instead of panicking where feasible.
Prefer .expect() over .unwrap().
Use concat!() to combine long string literals rather than escaping newlines with a backslash.
Prefer single line versions of functions where appropriate.
Clippy warnings MUST be disallowed.
Lints must not be silenced except as a last resort.
Lint rule suppressions must be tightly scoped and include a clear reason.
Prefer expect over allow.
Keep file size manageable. No single code file may be longer than 400 lines. Long switch statements or dispatch tables should be broken up by feature and constituents colocated with targets. Large blocks of test data should be moved to external data files.
Illustrate with clear examples. Function documentation must include clear examples demonstrating the usage and outcome of the function. Test documentation should omit examples where the example serves only to reiterate the test logic.
Prefer semantic error enums. Derive std::error::Error (via the thiserror crate) for any condition the caller might inspect, retry, or map to an HTTP status.
...

Files:

  • tests/ninja_env_tests.rs
  • tests/support/mod.rs
  • tests/support/ninja_env.rs
  • tests/runner_tests.rs

⚙️ CodeRabbit Configuration File

**/*.rs: * Seek to keep the cyclomatic complexity of functions no more than 12.

  • 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 / -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()

  • 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.

Files:

  • tests/ninja_env_tests.rs
  • tests/support/mod.rs
  • tests/support/ninja_env.rs
  • tests/runner_tests.rs
🧬 Code Graph Analysis (2)
tests/ninja_env_tests.rs (2)
tests/support/ninja_env.rs (1)
  • override_ninja_env (31-38)
tests/support/env_lock.rs (1)
  • acquire (19-21)
tests/runner_tests.rs (4)
tests/steps/process_steps.rs (1)
  • run (88-129)
src/runner.rs (5)
  • run (105-126)
  • run_ninja (348-421)
  • new (57-59)
  • new (74-76)
  • new (87-89)
tests/support/ninja_env.rs (1)
  • override_ninja_env (31-38)
tests/support/env_lock.rs (1)
  • acquire (19-21)
🔍 MCP Research (1 server)

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). (1)
  • GitHub Check: build-test
🔇 Additional comments (6)
tests/support/mod.rs (1)

7-7: Export the ninja_env test helper.

Expose the new test helper module to call-sites. This aligns with the PR objective of centralising env override logic behind a guard.

tests/runner_tests.rs (2)

1-1: Import the guard and Env for controlled overrides.

Importing DefaultEnv, run, and the new override_ninja_env helper achieves the goal of mockable, lock-guarded env overrides in tests.

Also applies to: 3-3, 12-12


225-231: Hold EnvLock while the guard drops; drop order is correct.

Create _lock before _guard so that _guard drops first, while the lock is still held. This satisfies the safety requirement for the unsafe env mutation in the guard’s Drop.

tests/ninja_env_tests.rs (2)

10-24: Exercise guard-based override with proper locking.

Acquire EnvLock before overriding and rely on the guard to restore on scope exit; then clean up under lock. This matches the documented safety model.


2-2: Retain import—NINJA_ENV remains publicly exported at netsuke::runner::NINJA_ENV
rg confirms pub const NINJA_ENV: &str = "NETSUKE_NINJA"; in src/runner.rs (line 23). The existing use netsuke::runner::NINJA_ENV; in tests will continue to work.

tests/support/ninja_env.rs (1)

1-7: Provide a focused doc for the test-only NINJA_ENV guard.

Document the safety model (EnvLock) and the dependency on a mockable Env. This aligns with the Deepwiki guidance to abstract environment access behind a trait.

Comment thread tests/ninja_env_tests.rs Outdated
Comment thread tests/runner_tests.rs
Comment thread tests/support/mod.rs Outdated
Comment thread tests/support/ninja_env.rs
Comment thread tests/support/ninja_env.rs Outdated
Comment thread tests/support/ninja_env.rs Outdated
Comment thread tests/support/ninja_env.rs
Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

2 files reviewed, no comments

Edit Code Review Bot Settings | Greptile

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: 7

♻️ Duplicate comments (8)
tests/ninja_env_tests.rs (4)

1-8: Serialise these tests to avoid cross-test environment races.

Even with EnvLock, other tests might mutate the environment without taking the lock. Add serial_test::serial and mark these tests serial to be defensive.

Apply this diff:

 use mockable::MockEnv;
 use netsuke::runner::NINJA_ENV;
 use std::env::VarError;
 use support::env_lock::EnvLock;
 use support::ninja_env::override_ninja_env;
 
+use serial_test::serial;
+
 mod support;
 

9-10: Mark this test serial to prevent collisions with non-locked env mutations.

Apply this diff:

-#[test]
+#[test]
+#[serial]
 fn override_ninja_env_restores_original() {

28-29: Mark this test serial to prevent collisions with non-locked env mutations.

Apply this diff:

-#[test]
+#[test]
+#[serial]
 fn override_ninja_env_removes_when_unset() {

45-46: Mark this test serial to prevent collisions with non-locked env mutations.

Apply this diff:

-#[test]
+#[test]
+#[serial]
 fn override_ninja_env_restores_empty() {
tests/support/ninja_env.rs (4)

31-33: Fix the example import path.

Use the test crate’s support module path to avoid confusion.

Apply this diff:

 /// ```ignore
 /// use mockable::DefaultEnv;
-/// use tests::support::ninja_env::override_ninja_env;
+/// use support::ninja_env::override_ninja_env;
 /// let env = DefaultEnv::new();
 /// let _guard = override_ninja_env(&env, "/usr/bin/ninja");
 /// ```

35-36: Replace forbidden #[allow(dead_code)] with a narrowly scoped #[expect(dead_code)].

Do not silence lints with allow. Use expect with a clear reason.

Apply this diff:

-#[allow(dead_code, reason = "only some tests override NINJA_ENV")]
+#[expect(dead_code, reason = "unused in some test configurations; helper is intentionally provided for tests")]
 pub fn override_ninja_env(env: &impl Env, value: &str) -> NinjaEnvGuard {

38-41: Justify the unsafe set_var with a clear SAFETY contract.

State precisely why this is sound while EnvLock is held.

Apply this diff:

-    // Callers must hold [`EnvLock`](super::env_lock::EnvLock) to serialise this
-    // mutation. `set_var` is `unsafe` on Rust 2024 and the guard restores the
-    // prior value on drop.
+    // SAFETY: Callers hold [`EnvLock`](super::env_lock::EnvLock), which serialises
+    // process-global environment mutation across tests (Rust 2024 marks env mutation
+    // unsafe). With the lock held, setting `NINJA_ENV` here is safe; the guard restores
+    // the prior value on Drop.
     unsafe { std::env::set_var(NINJA_ENV, value) };

47-50: Use the conventional SAFETY: heading and reference the lock explicitly.

Make the safety contract explicit at the restoration site as well.

Apply this diff:

-        // Safety: callers hold [`EnvLock`] for the guard's lifetime, so these
-        // `set_var`/`remove_var` calls are serialised. Both functions are
-        // `unsafe` on Rust 2024.
+        // SAFETY: The guard is intended to be dropped while
+        // [`EnvLock`](super::env_lock::EnvLock) is still held, serialising these
+        // process-wide mutations. Both functions are `unsafe` on Rust 2024.
📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f929fb9 and f432ca2.

📒 Files selected for processing (2)
  • tests/ninja_env_tests.rs (1 hunks)
  • tests/support/ninja_env.rs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit Inference Engine (AGENTS.md)

**/*.rs: Comment why, not what. Explain assumptions, edge cases, trade-offs, or complexity. Don't echo the obvious.
Use functions and composition. Avoid repetition by extracting reusable logic. Prefer generators or comprehensions, and declarative code to imperative repetition when readable.
Small, meaningful functions. Functions must be small, clear in purpose, single responsibility, and obey command/query segregation.
Name things precisely. Use clear, descriptive variable and function names. For booleans, prefer names with is, has, or should.
Every module must begin with a module level (//! ) comment explaining the module's purpose and utility.
Document public APIs using Rustdoc comments (///) so documentation can be generated with cargo doc.
Place function attributes after doc comments.
Do not use return in single-line functions.
Prefer immutable data and avoid unnecessary mut bindings.
Handle errors with the Result type instead of panicking where feasible.
Prefer .expect() over .unwrap().
Use concat!() to combine long string literals rather than escaping newlines with a backslash.
Prefer single line versions of functions where appropriate.
Clippy warnings MUST be disallowed.
Lints must not be silenced except as a last resort.
Lint rule suppressions must be tightly scoped and include a clear reason.
Prefer expect over allow.
Keep file size manageable. No single code file may be longer than 400 lines. Long switch statements or dispatch tables should be broken up by feature and constituents colocated with targets. Large blocks of test data should be moved to external data files.
Illustrate with clear examples. Function documentation must include clear examples demonstrating the usage and outcome of the function. Test documentation should omit examples where the example serves only to reiterate the test logic.
Prefer semantic error enums. Derive std::error::Error (via the thiserror crate) for any condition the caller might inspect, retry, or map to an HTTP status.
...

Files:

  • tests/support/ninja_env.rs
  • tests/ninja_env_tests.rs

⚙️ CodeRabbit Configuration File

**/*.rs: * Seek to keep the cyclomatic complexity of functions no more than 12.

  • 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 / -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()

  • 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.

Files:

  • tests/support/ninja_env.rs
  • tests/ninja_env_tests.rs
🧬 Code Graph Analysis (1)
tests/ninja_env_tests.rs (2)
tests/support/ninja_env.rs (1)
  • override_ninja_env (36-43)
tests/support/env_lock.rs (1)
  • acquire (19-21)
🔍 MCP Research (1 server)

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). (1)
  • GitHub Check: build-test
🔇 Additional comments (3)
tests/ninja_env_tests.rs (2)

13-16: Verify the mock is called exactly once — LGTM.

The expectation .times(1) aligns with the guard’s behaviour (single raw() read during construction).


2-2: Resolve the inconsistency with the PR summary regarding NINJA_ENV imports.

The summary claims tests no longer import netsuke::runner::NINJA_ENV, but this file does. Either adjust the summary or re-export/use a local constant to match the claim.

Would you like me to switch these tests to use a local constant or a test-support re-export to align with the summary?

tests/support/ninja_env.rs (1)

8-10: Comply with the injectable environment standard — LGTM.

Using mockable::Env to obtain the prior value aligns with the organisational requirement to make environment access injectable and testable.

Comment thread tests/ninja_env_tests.rs Outdated
Comment thread tests/ninja_env_tests.rs Outdated
Comment thread tests/ninja_env_tests.rs Outdated
Comment thread tests/ninja_env_tests.rs
Comment thread tests/support/ninja_env.rs
Comment thread tests/support/ninja_env.rs
Comment thread tests/support/ninja_env.rs Outdated
Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

5 files reviewed, no comments

Edit Code Review Bot Settings | Greptile

Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

4 files reviewed, no comments

Edit Code Review Bot Settings | Greptile

Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

1 file reviewed, no comments

Edit Code Review Bot Settings | Greptile

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

♻️ Duplicate comments (1)
tests/ninja_env_tests.rs (1)

14-18: Serialise this test to prevent process-wide env races

Environment is process-global. Even with EnvLock, other tests might mutate env without the lock. Serialise this test to eliminate flakiness.

Apply this diff:

 #[rstest]
+#[serial]
 #[case(Some("orig"))]
 #[case(None)]
 #[case(Some(""))]
 fn override_ninja_env_restores(#[case] original: Option<&'static str>) {

Ensure serial_test is in [dev-dependencies].

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d8ed0bf and ebf596e.

📒 Files selected for processing (1)
  • tests/ninja_env_tests.rs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit Inference Engine (AGENTS.md)

**/*.rs: Comment why, not what. Explain assumptions, edge cases, trade-offs, or complexity. Don't echo the obvious.
Use functions and composition. Avoid repetition by extracting reusable logic. Prefer generators or comprehensions, and declarative code to imperative repetition when readable.
Small, meaningful functions. Functions must be small, clear in purpose, single responsibility, and obey command/query segregation.
Name things precisely. Use clear, descriptive variable and function names. For booleans, prefer names with is, has, or should.
Every module must begin with a module level (//! ) comment explaining the module's purpose and utility.
Document public APIs using Rustdoc comments (///) so documentation can be generated with cargo doc.
Place function attributes after doc comments.
Do not use return in single-line functions.
Prefer immutable data and avoid unnecessary mut bindings.
Handle errors with the Result type instead of panicking where feasible.
Prefer .expect() over .unwrap().
Use concat!() to combine long string literals rather than escaping newlines with a backslash.
Prefer single line versions of functions where appropriate.
Clippy warnings MUST be disallowed.
Lints must not be silenced except as a last resort.
Lint rule suppressions must be tightly scoped and include a clear reason.
Prefer expect over allow.
Keep file size manageable. No single code file may be longer than 400 lines. Long switch statements or dispatch tables should be broken up by feature and constituents colocated with targets. Large blocks of test data should be moved to external data files.
Illustrate with clear examples. Function documentation must include clear examples demonstrating the usage and outcome of the function. Test documentation should omit examples where the example serves only to reiterate the test logic.
Prefer semantic error enums. Derive std::error::Error (via the thiserror crate) for any condition the caller might inspect, retry, or map to an HTTP status.
...

Files:

  • tests/ninja_env_tests.rs

⚙️ CodeRabbit Configuration File

**/*.rs: * Seek to keep the cyclomatic complexity of functions no more than 12.

  • 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 / -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()

  • 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.

Files:

  • tests/ninja_env_tests.rs
🔍 MCP Research (1 server)

Deepwiki:

  • docs/rust-testing-with-rstest-fixtures.md — Recommends using rstest fixtures to provide mock implementations for external dependencies (example shows mocking a database) and states the same pattern can be applied to mock an Env used by minijinja's env(var_name) function for tests (DeepWiki search result linked: https://deepwiki.com/search/guard-ninjaenv-overrides-with_aa8c5b07-0b4a-4e36-8b86-a9eb3f2c0b81)
  • Developer Guide (leynos/netsuke) — Mentions "Mock Fixtures" and using rstest fixtures to inject controlled test doubles; notes there is no direct code example for making the minijinja env function's underlying environment access mockable, only conceptual guidance
⏰ 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). (1)
  • GitHub Check: build-test
🔇 Additional comments (2)
tests/ninja_env_tests.rs (2)

19-34: LGTM: Mock interaction is precise and matches the guard behaviour

Mock env.raw() is asserted with times(1) and keyed correctly to NINJA_ENV. This aligns with the implementation that calls env.raw() exactly once during construction. Using return_once avoids accidental reuse.


36-39: LGTM: Hold the lock while overriding and assert the in-scope value

The guard holds EnvLock while setting the var to "new", and you assert via std::env::var immediately. Using .as_deref() keeps the assertion concise.

Comment thread tests/ninja_env_tests.rs
Comment on lines +1 to +3
use mockable::MockEnv;
use netsuke::runner::NINJA_ENV;
use rstest::rstest;
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.

🛠️ Refactor suggestion

Add module-level docs and import serial_test

Add a module-level //! doc comment per guidelines and import the serial attribute used below.

Apply this diff:

+//! Tests verifying guard-based override of NINJA_ENV. Exercises override_ninja_env and NinjaEnvGuard
+//! semantics with EnvLock and a mock Env, ensuring restoration to the prior state.
 use mockable::MockEnv;
 use netsuke::runner::NINJA_ENV;
 use rstest::rstest;
+use serial_test::serial;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
use mockable::MockEnv;
use netsuke::runner::NINJA_ENV;
use rstest::rstest;
//! Tests verifying guard-based override of NINJA_ENV. Exercises override_ninja_env and NinjaEnvGuard
//! semantics with EnvLock and a mock Env, ensuring restoration to the prior state.
use mockable::MockEnv;
use netsuke::runner::NINJA_ENV;
use rstest::rstest;
use serial_test::serial;
🤖 Prompt for AI Agents
In tests/ninja_env_tests.rs around lines 1 to 3, the file is missing a
module-level doc comment and the `serial` attribute import used by tests; add a
top-of-file `//!` module doc comment (briefly describing the test module's
purpose) and add the import `use serial_test::serial;` alongside the existing
imports so the `serial` attribute is available to the tests.

Comment thread tests/ninja_env_tests.rs
Comment on lines +8 to +13
#[expect(
unused,
reason = "support module exports helpers unused in these tests"
)]
mod support;

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.

⚠️ Potential issue

Replace lint-group unused with a narrowly scoped lint

Using the lint group unused violates the “no lint groups” rule. Scope this to the specific lint you need (here: dead_code) and keep the scope tight.

Apply this diff:

-#[expect(
-    unused,
-    reason = "support module exports helpers unused in these tests"
-)]
+#[expect(
+    dead_code,
+    reason = "support module exposes helpers not used in this test crate"
+)]
 mod support;

If some submodules export additional helpers, prefer placing the narrow #[expect(dead_code, reason = "...")] directly on those specific items in tests/support/*.rs instead of suppressing at the module import site.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#[expect(
unused,
reason = "support module exports helpers unused in these tests"
)]
mod support;
#[expect(
dead_code,
reason = "support module exposes helpers not used in this test crate"
)]
mod support;
🤖 Prompt for AI Agents
In tests/ninja_env_tests.rs around lines 8 to 13, the module import uses the
lint group `unused`; replace that with a narrowly scoped lint by changing the
attribute to expect `dead_code` instead of `unused`, and if possible move that
`#[expect(dead_code, reason = "...")]` onto the specific unused items in
tests/support/*.rs rather than suppressing at the module level so only the exact
dead code is allowed.

Comment thread tests/ninja_env_tests.rs
Comment on lines +41 to +49
match original {
Some(val) => assert_eq!(std::env::var(NINJA_ENV).as_deref(), Ok(val)),
None => assert!(std::env::var(NINJA_ENV).is_err()),
}

let _cleanup = EnvLock::acquire();
// SAFETY: `EnvLock` serialises this mutation; see above for details.
unsafe { std::env::remove_var(NINJA_ENV) };
}
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.

⚠️ Potential issue

Hold EnvLock for restoration assertion and cleanup to eliminate a race window

Acquire EnvLock before asserting the post-guard state and keep it through cleanup. This removes a race window between guard drop and the assertion/cleanup where other tests could mutate env.

Apply this diff:

-    match original {
+    let _lock = EnvLock::acquire();
+    match original {
         Some(val) => assert_eq!(std::env::var(NINJA_ENV).as_deref(), Ok(val)),
         None => assert!(std::env::var(NINJA_ENV).is_err()),
     }
 
-    let _cleanup = EnvLock::acquire();
-    // SAFETY: `EnvLock` serialises this mutation; see above for details.
+    // SAFETY: `EnvLock` is held via `_lock`, serialising this mutation; see above for details.
     unsafe { std::env::remove_var(NINJA_ENV) };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
match original {
Some(val) => assert_eq!(std::env::var(NINJA_ENV).as_deref(), Ok(val)),
None => assert!(std::env::var(NINJA_ENV).is_err()),
}
let _cleanup = EnvLock::acquire();
// SAFETY: `EnvLock` serialises this mutation; see above for details.
unsafe { std::env::remove_var(NINJA_ENV) };
}
let _lock = EnvLock::acquire();
match original {
Some(val) => assert_eq!(std::env::var(NINJA_ENV).as_deref(), Ok(val)),
None => assert!(std::env::var(NINJA_ENV).is_err()),
}
// SAFETY: `EnvLock` is held via `_lock`, serialising this mutation; see above for details.
unsafe { std::env::remove_var(NINJA_ENV) };
}
🤖 Prompt for AI Agents
In tests/ninja_env_tests.rs around lines 41 to 49, the test drops the EnvLock
before asserting/restoring the environment which creates a race; acquire EnvLock
before making the post-guard assertion and keep the returned guard stored (e.g.,
let _cleanup = EnvLock::acquire();) until after the assertion and cleanup code
so the lock serialises both the check and the removal/restoration of NINJA_ENV,
and perform the unsafe std::env::remove_var(NINJA_ENV) while the lock is still
held.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Aug 13, 2025

Superseded

@leynos leynos closed this Aug 13, 2025
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