Stop the mock environment mutating the real process environment (#490) - #497
Stop the mock environment mutating the real process environment (#490)#497leynos wants to merge 8 commits into
Conversation
Reviewer's GuideThis PR removes a leaky environment-mutation abstraction so that only real-environment helpers can mutate process globals, makes PATH/NINJA helpers explicitly depend on SystemEnv, and rewrites tests to use VarGuard and a deterministic mock environment constructor instead of mocks that silently mutate the process state. File-Level Changes
Assessment against linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
Summary
Verification
WalkthroughChangesExplicit child command environments
Sequence Diagram(s)sequenceDiagram
participant BDDSteps
participant CommandEnv
participant run_ninja_with
participant NinjaProcess
BDDSteps->>CommandEnv: compose child PATH
BDDSteps->>run_ninja_with: submit NinjaBuildRequest
run_ninja_with->>NinjaProcess: spawn with explicit environment
NinjaProcess-->>BDDSteps: return build result
Possibly related issues
Possibly related PRs
Suggested labels: Suggested reviewers: Poem
Important Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional. ❌ Failed checks (1 warning, 7 inconclusive)
✅ Passed checks (12 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4e4aa7661a
ℹ️ 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".
|
@coderabbitai review |
✅ Action performedReview finished.
|
|
@coderabbitai Have the following failed checks now been resolved? If further work is required, please provide an AI agent prompt for the remaining work to be done to address these failures. Do not treat warnings as optional or aspirational. Where a change is out of scope for this PR, propose a GitHub issue unless one exists already. (Treat documentation and validation coverage as in scope). #490 Rework PathGuard and prepend_dir_to_path to compose an Env value instead of mutating the process environment, returning the modified environment for injection, and require explicit PATH value for PathGuard construction. ❌ prepend_dir_to_path continues to mutate the real process environment using SystemEnv and PathGuard; the PR explicitly states that reworking PathGuard to a compositional, injected Env is deferred until runner::run_ninja gains an injected seam and PathGuard is later removed. |
This comment was marked as resolved.
This comment was marked as resolved.
4e4aa76 to
5275359
Compare
`make fmt` reflows every Markdown file in the repository, and successive runs during this branch's work committed that reflow across five documents #497 has no reason to touch. It was also the sole cause of the rebase conflict against main. Reverts docs/ to main. The behavioural change is unaffected. Refs #490. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
dccf4dd to
1871ade
Compare
|
On the failed pre-merge rows:
|
Rebuild PR #497's branch from origin/main (8ac4103) instead of replaying it: main absorbed a parallel environment-isolation migration that removed EnvMut, PathGuard, prepend_dir_to_path, VarGuard, override_ninja_env, and NinjaEnvGuard, so a rebase conflicted on every seam the branch touched. Ported from the old head (a7970ed), adapted to main's names: - runner::process::command_env: the CommandEnv type (inherit, with_var, with_path, is_empty, get, apply) carrying child-process overrides as data, applied additively before spawn. - NinjaBuildRequest and NinjaToolRequest gain an env: &CommandEnv field and move to process/request.rs; command shaping moves to process/configure.rs; both request types and run_ninja_with and run_ninja_tool_with become public alongside the run_ninja and run_ninja_tool wrappers, which supply CommandEnv::inherit(). - BDD: TestWorld gains command_env; install_test_ninja composes the child PATH with test_support::env::prepend_path_value and the run step passes it through run_ninja_with, matching main's forwarded-env world shape. - env_path_tests: the CommandEnv cases (later-override-wins, inherit-empty, verbatim round-trip, parent-PATH-unchanged), the separator-rejection case, two Unix child probes proving the injected environment reaches spawned build and tool processes, and the two property suites (input-splitting composition model; last-write-wins override model), merged with main's existing prepend_path_value coverage; the proptest regression seed carries a provenance comment. - Compile-time coverage: tests/ui/command_env_embedder_pass.rs is type-checked against the netsuke rlib by tests/command_env_ui_tests.rs (Cargo-built rlib plus direct rustc --extern --emit=metadata, the locale-stub harness pattern); the harness documents why removed APIs get no absence test. - Docs: developers-guide gains the runner::process::command_env module section and a corrected TestWorld row; ADR-002's stale PathGuard reference now describes explicit command-environment composition; test-isolation-with-ninja-env states that an injected child PATH governs the commands Ninja launches, not program selection. Dropped as absorbed or superseded by main: - path_with_dir_prepended: main's test_support::env::prepend_path_value is the composition helper, and it stays in test_support; its empty-equals-absent contract is kept and the property model adapted to it. - The old head's test_support/env rework, path_guard removal, ninja_env_tests, path_guard_tests, runner_tests changes, and manifest_command_helpers changes: main's parallel migration already landed equivalents. - The mock_env_with_path case: the helper no longer exists on main. - resolve_ninja_program stays crate-internal: main's tests inject the programme path instead of asserting on resolution. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Round feedback on #497, all three points taken. The guides now state the guarantee precisely: an injected child PATH cannot select Ninja only when the request's program is an absolute or otherwise resolved path, because program is handed to Command::new as given and a bare relative name is looked up in the child's PATH on Unix. The separator test is renamed for the condition it actually exercises — an unrepresentable entry — with the platform rule stated correctly: Windows can represent ';' by quoting and rejects '"', the quoting character itself. The child-PATH propagation proof compares raw bytes through OsString rather than a UTF-8 conversion that would fail on a valid non-UTF-8 PATH before propagation was checked. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
c5a32d5 to
3ee7b41
Compare
|
@coderabbitai review |
✅ Action performedReview finished.
|
|
Note GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer. |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/developers-guide.md`:
- Around line 2636-2643: The executable-resolution rule is inconsistent across
the documentation and test description. In docs/developers-guide.md lines
2636-2643, replace the unconditional “Callers therefore pass resolved paths”
statement with a conditional requirement that callers use an absolute or
otherwise resolved program path when executable-selection isolation from
injected PATH is required; in tests/env_path_tests.rs lines 168-173, update the
Unix behavior description to state that relative programs are resolved through
the child PATH rather than the parent PATH.
In `@src/runner/process/command_env.rs`:
- Around line 64-72: Update the Rustdoc example for
CommandEnv::inherit().with_path to construct the PATH value with
std::env::join_paths using platform-native path components instead of a
colon-delimited literal. Preserve the assertion against the resulting
platform-appropriate value and the existing parent-environment behavior.
In `@tests/command_env_ui_tests.rs`:
- Around line 142-155: Add a tracked-work reference to the item-level
#[expect(clippy::disallowed_methods)] reasons on both cargo() and rustc(),
preserving their narrow explanations; alternatively, replace the ambient
environment lookups with an approved injected source and remove the
suppressions.
In `@tests/env_path_tests.rs`:
- Around line 323-408: Extract the properties module containing entry,
composition_prepends_and_preserves_order, and
overrides_resolve_to_their_last_declaration into a sibling integration-test
file, preserving its imports, helpers, and tests. Remove the original module
from tests/env_path_tests.rs so the file remains within the 400-line limit, and
ensure the extracted module still references the same public APIs and test
support utilities.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 17f53f1c-73a9-4508-bec4-db61994c59b0
📒 Files selected for processing (16)
docs/adr-002-replace-cucumber-with-rstest-bdd.mddocs/developers-guide.mddocs/test-isolation-with-ninja-env.mddocs/users-guide.mdsrc/runner/mod.rssrc/runner/process/command_env.rssrc/runner/process/configure.rssrc/runner/process/mod.rssrc/runner/process/request.rstest_support/src/env.rstests/bdd/fixtures/mod.rstests/bdd/steps/process.rstests/command_env_ui_tests.rstests/env_path_tests.proptest-regressionstests/env_path_tests.rstests/ui/command_env_embedder_pass.rs
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
leynos/rstest-bdd(auto-detected)leynos/ortho-config(auto-detected)leynos/whitaker(auto-detected)leynos/shared-actions(auto-detected)
Rebuild PR #497's branch from origin/main (8ac4103) instead of replaying it: main absorbed a parallel environment-isolation migration that removed EnvMut, PathGuard, prepend_dir_to_path, VarGuard, override_ninja_env, and NinjaEnvGuard, so a rebase conflicted on every seam the branch touched. Ported from the old head (a7970ed), adapted to main's names: - runner::process::command_env: the CommandEnv type (inherit, with_var, with_path, is_empty, get, apply) carrying child-process overrides as data, applied additively before spawn. - NinjaBuildRequest and NinjaToolRequest gain an env: &CommandEnv field and move to process/request.rs; command shaping moves to process/configure.rs; both request types and run_ninja_with and run_ninja_tool_with become public alongside the run_ninja and run_ninja_tool wrappers, which supply CommandEnv::inherit(). - BDD: TestWorld gains command_env; install_test_ninja composes the child PATH with test_support::env::prepend_path_value and the run step passes it through run_ninja_with, matching main's forwarded-env world shape. - env_path_tests: the CommandEnv cases (later-override-wins, inherit-empty, verbatim round-trip, parent-PATH-unchanged), the separator-rejection case, two Unix child probes proving the injected environment reaches spawned build and tool processes, and the two property suites (input-splitting composition model; last-write-wins override model), merged with main's existing prepend_path_value coverage; the proptest regression seed carries a provenance comment. - Compile-time coverage: tests/ui/command_env_embedder_pass.rs is type-checked against the netsuke rlib by tests/command_env_ui_tests.rs (Cargo-built rlib plus direct rustc --extern --emit=metadata, the locale-stub harness pattern); the harness documents why removed APIs get no absence test. - Docs: developers-guide gains the runner::process::command_env module section and a corrected TestWorld row; ADR-002's stale PathGuard reference now describes explicit command-environment composition; test-isolation-with-ninja-env states that an injected child PATH governs the commands Ninja launches, not program selection. Dropped as absorbed or superseded by main: - path_with_dir_prepended: main's test_support::env::prepend_path_value is the composition helper, and it stays in test_support; its empty-equals-absent contract is kept and the property model adapted to it. - The old head's test_support/env rework, path_guard removal, ninja_env_tests, path_guard_tests, runner_tests changes, and manifest_command_helpers changes: main's parallel migration already landed equivalents. - The mock_env_with_path case: the helper no longer exists on main. - resolve_ninja_program stays crate-internal: main's tests inject the programme path instead of asserting on resolution. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The empty- and missing-PATH twins collapse into one parameterized case — the contract is identical and only the spelling of "valueless" differs — and the embedder fixture bundles its five request pieces into a Parts struct, which also reads closer to how an embedder would hold them. Both were fixed rather than suppressed; neither refactor loses information. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Round feedback on #497, all three points taken. The guides now state the guarantee precisely: an injected child PATH cannot select Ninja only when the request's program is an absolute or otherwise resolved path, because program is handed to Command::new as given and a bare relative name is looked up in the child's PATH on Unix. The separator test is renamed for the condition it actually exercises — an unrepresentable entry — with the platform rule stated correctly: Windows can represent ';' by quoting and rejects '"', the quoting character itself. The child-PATH propagation proof compares raw bytes through OsString rather than a UTF-8 conversion that would fail on a valid non-UTF-8 PATH before propagation was checked. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The round's error was a genuine gap: every child-probe test asserted only variables that were set, so an env_clear implementation would have passed. A new probe proves an un-overridden PATH is inherited verbatim by the spawned process. The three probe cases now share one fixture, which also keeps the file under the 400-line ceiling. The users' guide gains an embedder note for CommandEnv and the explicit request forms: overrides are additive, un-named variables are inherited, and the program path should be absolute because a relative name resolves in the child's PATH. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Convert probe_fixture to an rstest fixture with the recording script supplied through #[with], so each subprocess case states only what it configures. The inheritance test no longer reads the parent's PATH: a baseline probe spawned directly outside CommandEnv records what a plainly inherited child sees, and the run through run_ninja_with must match it. That removes the lint expectation while keeping the test's power against an env_clear-based implementation, which would record an empty PATH where the baseline records the real one. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The fixture anchored run_ninja_with and run_ninja_tool_with through function-pointer bindings on the belief that calling them would spawn a process. The harness compiles the fixture with --emit=metadata and never runs it, so genuine calls are safe and prove more: an embedder can drive both boundaries with the composed requests, not merely name their signatures. Also state prepend_path_value's empty-value rule in its doc: an empty prior PATH collapses like an absent one because preserving the empty entry would reintroduce the implicit current-directory lookup it denotes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The guide claimed callers always pass resolved program paths; the rule is conditional — only callers that must not let the injected PATH select the executable need one — and the probe test's doc comment said the opposite of the truth (Unix lookup uses the child's PATH once one is set, not the parent's). Align both statements. Build the with_path doctest's example value with join_paths so Windows readers do not copy a Unix-only literal, and split the property module into a sibling file via #[path] to bring env_path_tests.rs back under the 400-line ceiling. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
992107e to
222bf2d
Compare
|
@coderabbitai review |
✅ Action performedReview finished.
|
|
Note GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer. |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/developers-guide.md`:
- Around line 2659-2666: Update the documentation for
test_support::env::prepend_path_value to state that it errors when a PATH entry
contains an unrepresentable character on the target platform, specifically
reflecting Windows where quotes are unrepresentable and semicolons are supported
through quoting. Match the terminology used in tests/env_path_tests.rs and leave
the remaining composition behavior unchanged.
In `@src/runner/process/mod.rs`:
- Around line 147-149: Update the documentation example for
CommandEnv::with_path to state that it replaces the child PATH rather than
prepending a directory. Clarify that callers must compose the full PATH value
before invoking with_path when prepend behavior is needed.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: ef51c5e3-a4f1-462c-9ae4-430eaef4f9c4
📒 Files selected for processing (17)
docs/adr-002-replace-cucumber-with-rstest-bdd.mddocs/developers-guide.mddocs/test-isolation-with-ninja-env.mddocs/users-guide.mdsrc/runner/mod.rssrc/runner/process/command_env.rssrc/runner/process/configure.rssrc/runner/process/mod.rssrc/runner/process/request.rstest_support/src/env.rstests/bdd/fixtures/mod.rstests/bdd/steps/process.rstests/command_env_ui_tests.rstests/env_path_property_tests.rstests/env_path_tests.proptest-regressionstests/env_path_tests.rstests/ui/command_env_embedder_pass.rs
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
leynos/rstest-bdd(auto-detected)leynos/ortho-config(auto-detected)leynos/whitaker(auto-detected)leynos/shared-actions(auto-detected)
| `PATH` values are composed with `test_support::env::prepend_path_value`, a | ||
| pure function that places a directory ahead of an explicitly supplied prior | ||
| value. It takes the starting value rather than reading the process, so the | ||
| result depends only on its inputs. An absent prior value yields just the new | ||
| directory, and — by the helper's contract, which its tests pin — a wholly | ||
| empty prior value is treated the same way; empty entries inside a non-empty | ||
| value survive composition. It returns an error when an entry contains the | ||
| platform path separator, which `std::env::join_paths` itself reports. |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Correct the Windows join_paths error condition.
Replace “entry contains the platform path separator”. On Windows, ; is
representable through quoting. " is the unrepresentable character. Describe
the error as an unrepresentable PATH entry, as tests/env_path_tests.rs does.
Triage: [type:docstyle]
Proposed documentation update
- It returns an error when an entry contains the platform path separator, which
- `std::env::join_paths` itself reports.
+ It returns an error when an entry is not representable in a PATH value. For
+ example, Unix rejects `:`, while Windows rejects `"`;
+ `std::env::join_paths` reports both cases.As per coding guidelines, keep documentation strategy synchronized with the implementation.
📝 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.
| `PATH` values are composed with `test_support::env::prepend_path_value`, a | |
| pure function that places a directory ahead of an explicitly supplied prior | |
| value. It takes the starting value rather than reading the process, so the | |
| result depends only on its inputs. An absent prior value yields just the new | |
| directory, and — by the helper's contract, which its tests pin — a wholly | |
| empty prior value is treated the same way; empty entries inside a non-empty | |
| value survive composition. It returns an error when an entry contains the | |
| platform path separator, which `std::env::join_paths` itself reports. | |
| `PATH` values are composed with `test_support::env::prepend_path_value`, a | |
| pure function that places a directory ahead of an explicitly supplied prior | |
| value. It takes the starting value rather than reading the process, so the | |
| result depends only on its inputs. An absent prior value yields just the new | |
| directory, and — by the helper's contract, which its tests pin — a wholly | |
| empty prior value is treated the same way; empty entries inside a non-empty | |
| value survive composition. It returns an error when an entry is not | |
| representable in a PATH value. For example, Unix rejects `:`, while Windows | |
| rejects `"`; | |
| `std::env::join_paths` reports both cases. |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/developers-guide.md` around lines 2659 - 2666, Update the documentation
for test_support::env::prepend_path_value to state that it errors when a PATH
entry contains an unrepresentable character on the target platform, specifically
reflecting Windows where quotes are unrepresentable and semicolons are supported
through quoting. Match the terminology used in tests/env_path_tests.rs and leave
the remaining composition behavior unchanged.
Source: Coding guidelines
| /// // `inherit()` reproduces `run_ninja`; `with_path` puts a directory ahead of | ||
| /// // the child's `PATH` without touching the parent process. | ||
| /// let env = CommandEnv::inherit().with_path("/opt/toolchain/bin"); |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Correct the with_path description.
State that CommandEnv::with_path replaces the child PATH. It calls
with_var("PATH", path) and does not prepend a directory. Compose the full
PATH value before calling with_path when prepend behaviour is required.
Proposed correction
-/// // `inherit()` reproduces `run_ninja`; `with_path` puts a directory ahead of
-/// // the child's `PATH` without touching the parent process.
+/// // `inherit()` reproduces `run_ninja`; `with_path` replaces the child's
+/// // `PATH` without touching the parent process.📝 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.
| /// // `inherit()` reproduces `run_ninja`; `with_path` puts a directory ahead of | |
| /// // the child's `PATH` without touching the parent process. | |
| /// let env = CommandEnv::inherit().with_path("/opt/toolchain/bin"); | |
| /// // `inherit()` reproduces `run_ninja`; `with_path` replaces the child's | |
| /// // `PATH` without touching the parent process. | |
| /// let env = CommandEnv::inherit().with_path("/opt/toolchain/bin"); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/runner/process/mod.rs` around lines 147 - 149, Update the documentation
example for CommandEnv::with_path to state that it replaces the child PATH
rather than prepending a directory. Clarify that callers must compose the full
PATH value before invoking with_path when prepend behavior is needed.
Summary
test_support::env::EnvMutwas implemented for bothDefaultEnvandmockable::MockEnv, and both impls calledstd::env::set_var. Setting a variable through the mock mutated the real process environment, while the double's ownexpect_rawexpectations carried on returning their programmed values. Any test believing it operated on an isolated environment was writing global state, and from that point the double and the process disagreed about the value.This deletes the trait rather than wrapping it, per the AGENTS.md testing mandate landed in 3f84545.
The bug, concretely
tests/ninja_env_tests.rsprogrammed a mock to reportNINJA_ENVas absent:The guard captured its "original" value from the mock but wrote to and restored the process. Each assertion happened to consult whichever half of the pair agreed with it, so the tests passed regardless of whether the behaviour was correct. On a host with
NETSUKE_NINJAgenuinely set, the second test would have silently deleted it.Changes
EnvMuttrait and both impls. Mutation now goes through a privateset_process_var, reachable only by the real-environment helpers in the module, so no mock can mutate anything.mocked_path_env()withmock_env_with_path(value). The old constructor seeded a double from the ambientPATH, making its behaviour depend on the environment of whoever ran the suite.prepend_dir_to_pathandoverride_ninja_envnow take&SystemEnvrather than a generic parameter. This is honest about what they do: they mutate the live environment becauserunner::run_ninjaresolves its program from it and has no injected seam yet (Inject an Env seam into Ninja program resolution #488).install_test_ninjadrops itsenvparameter, since every call site passedsystem_env().VarGuardrather than a mock, and add a test pinning thatmock_env_with_pathreports the supplied value rather than the ambient one.Also: the injected command environment
The first revision deferred reworking
PathGuard. That is now done, because the deferral rested on a mistaken assumption — that the fake Ninja had to be discoverable through the parent process.prepend_dir_to_pathmutated the parentPATHunderEnvLockand restored it on drop. Serialising the writers never isolated the readers:test_support/src/netsuke.rsforwards ambientPATHinto child processes without taking the lock, so one scenario could inherit another's temporary executable.Ninja now receives its environment as data:
CommandEnvcarries overrides applied withCommand::env.NinjaBuildRequestandNinjaToolRequestcarry one, andconfigure_ninja_baseapplies it.run_ninjaandrun_ninja_toolkeep their signatures and passCommandEnv::inherit(), so production behaviour is unchanged.run_ninja_with/run_ninja_tool_withtake a request for callers supplying an environment.path_with_dir_prepended(existing, dir)replacesprepend_dir_to_path. It takes the starting value explicitly, so its result depends only on its inputs, and distinguishes absent from empty: absent yields just the directory, empty keeps the empty entry the caller attached meaning to.PathGuard,tests/path_guard_tests.rs, andwith_isolated_pathare deleted. With nothing mutatingPATH, there is nothing to restore.The BDD world carries a composed
CommandEnvin place of the guard.tests/env_path_tests.rsis rewritten around composition — present, empty and absentPATH, later overrides replacing earlier ones, a composed value reaching the command environment verbatim, and that composing leaves the parentPATHunchanged. None of them is#[serial].Two judgement calls
NETSUKE_NINJAstill goes through the process inrun_respects_env_override_for_ninja. That test exercisesresolve_ninja_programreading the live environment, which is the production behaviour under test; only the competingPATHentry moved to the child.process::modpassed the 400-line limit, so command construction moved toprocess::configure. Handled rather than suppressed.Verification
All six commit gates pass:
check-fmt,lint,typecheck,test(1241 nextest),markdownlint,nixie. CodeScene delta: no issues.Closes #490.
Refs #496, #488, #494.
🤖 Generated with Claude Code
Summary by Sourcery
Prevent mocked environments from mutating the real process environment and align PATH/NINJA_ENV helpers and tests with explicit, process-scoped mutation.
Bug Fixes:
Enhancements:
Tests: