Skip to content

Implement graph subcommand with shared Ninja tool invocation and tests#249

Merged
leynos merged 8 commits intomainfrom
terragon/implement-cli-graph-subcommand-6f0ufd
Dec 19, 2025
Merged

Implement graph subcommand with shared Ninja tool invocation and tests#249
leynos merged 8 commits intomainfrom
terragon/implement-cli-graph-subcommand-6f0ufd

Conversation

@leynos
Copy link
Copy Markdown
Owner

@leynos leynos commented Dec 12, 2025

Summary

  • Implements a new graph subcommand that generates a build dependency graph by running ninja -t graph on a temporary build.ninja, and outputs DOT to stdout. Supports -j jobs via Ninja.
  • Refactors Ninja tool invocation into a shared helper used by both the clean and graph subcommands. The clean subcommand now delegates to the shared helper, while graph uses it for invocation.
  • Adds end-to-end tests for the graph subcommand, including success, failure, jobs handling, and missing Ninja scenarios.
  • Updates documentation references to reflect the new graph workflow and shared Ninja tool invocation.

Changes

  • CLI Runner
    • src/runner/mod.rs: Replaced placeholder Graph handling with a concrete handle_graph implementation.
    • Introduced handle_ninja_tool(cli, tool) to share Ninja invocation logic across subcommands, and updated handle_clean to call it with "clean".
    • handle_graph now delegates to handle_ninja_tool(cli, "graph").
  • Tests
    • Added graph feature tests: tests/features_unix/graph.feature with scenarios for invoking ninja with the graph tool, handling failures, respecting jobs, and missing Ninja.
    • tests/runner_tests.rs: Extended with fixtures and tests for graph subcommand, including helpers to ensure behavior with residual files and invalid manifests.
    • tests/steps/process_steps.rs: Added graph-specific fixtures and step implementations, including fixtures for a fake Ninja that expects -t graph (with and without -j), and a run step for the graph subcommand.
  • Documentation
    • Updated netsuke design/docs to reflect the graph subcommand workflow (Stage 6 synthesis, ninja -t graph).
    • Updated roadmap and user guide to reflect that the graph command outputs DOT and uses Ninja graph tool on generated build.ninja.

Why

  • Enables introspection/debugging of Netsuke's build graph via a standard Ninja graph dump, aiding debugging and visualization.
  • Introduces a shared Ninja tool invocation path to keep behavior consistent between clean and graph, and to simplify future command additions.

Test plan

  • Run full test suite: cargo test (or your project’s test command).
  • Specifically run graph-related tests:
    • Features: graph.feature scenarios using a fake Ninja that validates -t graph and optional -j flags.
    • Runner tests: verify success path, Ninja failure propagation, and invalid manifest handling.

Notes

  • The graph command outputs DOT to stdout by design, suitable for piping into Graphviz tools.
  • No public API changes outside the addition of a new subcommand; existing commands remain unaffected.

📎 Task: https://www.terragonlabs.com/task/96b3e9d2-cdfb-4186-8612-3fcacac0f015

Implemented the `graph` command, which generates the Ninja manifest
into a temporary file and runs `ninja -t graph` to output the build
dependency graph in DOT format. Updated CLI help and documentation to
reflect this new command. Added comprehensive tests to verify the
behavior of the graph subcommand under various conditions.

This provides a useful introspection tool for users to visualize and
debug build dependencies using Ninja's graph functionality.

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

coderabbitai Bot commented Dec 12, 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 5 minutes and 22 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 b7c95f0 and bfe336a.

📒 Files selected for processing (2)
  • tests/runner_tests.rs (3 hunks)
  • tests/runner_tool_subcommands_tests.rs (1 hunks)

Walkthrough

Summarise the runner to delegate Ninja tool invocations (e.g., ninja -t graph) via a generic dispatcher, update documentation to reflect graph generation after Ninja synthesis, forbid duplicate manifest keys, and add/adjust tests and test-support utilities to exercise and isolate the new subcommand behaviours.

Changes

Cohort / File(s) Summary
Documentation updates
docs/netsuke-design.md, docs/roadmap.md, docs/users-guide.md
Update design and user docs to state graph generation occurs after Ninja synthesis and is produced via ninja -t graph (DOT to stdout / Graphviz mentioned). Mark graph subcommand as completed in roadmap. Document manifest duplicate-key prohibition and note planned HTML renderer. Minor wording and formatting changes.
Runner refactoring
src/runner/mod.rs
Add generic handle_ninja_tool(cli, tool) to run ninja -t <tool> preserving working dir and job settings; add handle_graph and handle_clean wrappers delegating to it; update logging and error contexts to include explicit tool names.
Test integration & helpers
tests/runner_tests.rs, tests/runner_tool_subcommands_tests.rs, tests/steps/process_steps.rs, tests/cucumber.rs, tests/common/mod.rs
Add shared test helpers and fixtures (create_test_manifest, ninja_with_exit_code). Replace local fixtures with shared ones. Add integration tests for tool subcommands (clean, graph); introduce run_subcommand(world) and run_graph(world); add ninja_env_guard to CliWorld to control NINJA_ENV lifecycle; remove some prior clean-only scaffolding.
Test-support formatting helpers
test_support/src/env.rs, test_support/src/env_lock.rs
Implement Debug for NinjaEnvGuard and EnvLock to provide concise, non-exhaustive debug output; add relevant imports.
Feature tests
tests/features_unix/graph.feature, tests/features_unix/clean.feature, tests/features_unix/ninja_process.feature
Add graph.feature with scenarios for success, failure propagation, job flag handling, and missing ninja; annotate existing features with @unix tag for Unix-specific filtering.
Test edits / cleanup
tests/runner_tests.rs, tests/steps/process_steps.rs
Remove duplicate local fixtures and clean-specific tests; wire tests to shared helpers and the new run_subcommand flow.

Sequence Diagram(s)

sequenceDiagram
    participant User as CLI
    participant Runner as netsuke::runner
    participant FS as Filesystem
    participant Ninja as ninja (external)
    participant GraphViz as Graphviz (dot)

    User->>Runner: invoke `netsuke graph` (with -j, manifest path)
    Runner->>FS: ensure working dir, generate build.ninja via synth (invoke synth stage)
    note right of FS: build.ninja is temporary
    Runner->>Ninja: run `ninja -t graph` (preserve -j)
    Ninja-->>Runner: emit DOT to stdout / exit code
    alt success
        Runner->>GraphViz: optional `dot -Tsvg` (mentioned in docs)
        Runner-->>User: write DOT/SVG to stdout or file
    else failure
        Runner-->>User: propagate ninja exit error with context "ninja -t graph"
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Inspect src/runner/mod.rs::handle_ninja_tool for correct preservation of working directory, job flags (-j), environment propagation and error contexts.
  • Verify handle_graph/handle_clean wrappers and logging use exact tool names and avoid unintended side effects (e.g., whether build.ninja is created/preserved).
  • Review shared test fixtures in tests/common/mod.rs and tests/steps/process_steps.rs for deterministic Ninja resolution and correct use of NinjaEnvGuard.
  • Confirm feature scenarios in tests/features_unix/graph.feature assert expected outputs and failure propagation.

Possibly related PRs

Poem

🔧 Delegate the tool and let Ninja speak,
DOT flows out while docs refine the peak,
Fixtures stand ready, guards held tight,
Tests tread the path from synth to sight,
The graph now hums — the pipeline’s bright! ✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title directly and concisely summarises the primary change: implementing a graph subcommand with shared Ninja tool invocation and tests.
Description check ✅ Passed The description comprehensively documents the graph subcommand implementation, Ninja tool invocation refactoring, test coverage, and documentation updates—all reflected in the changeset.
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.

@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Dec 12, 2025

Reviewer's Guide

Implements a concrete graph subcommand by factoring out a reusable Ninja tool runner, wires it into the CLI, and adds focused tests and docs to validate and describe the new behavior.

Sequence diagram for netsuke graph subcommand invoking ninja -t graph

sequenceDiagram
    actor User
    participant Cli as Cli_args
    participant Runner as Runner_run
    participant Manifest as NinjaManifestGen
    participant Process as Process_utils
    participant Ninja as Ninja_executable

    User->>Cli: netsuke graph [OPTIONS]
    Cli->>Runner: run(cli)
    Runner->>Runner: match Commands::Graph
    Runner->>Runner: handle_graph(cli)
    Runner->>Runner: handle_ninja_tool(cli, tool=graph)

    Runner->>Manifest: generate_ninja(cli)
    Manifest-->>Runner: ninja_manifest_string

    Runner->>Process: create_temp_ninja_file(ninja_manifest_string)
    Process-->>Runner: temp_build_file_path

    Runner->>Process: resolve_ninja_program()
    Process-->>Runner: ninja_program_path

    Runner->>Process: run_ninja_tool(ninja_program_path, cli, temp_build_file_path, graph)
    Process->>Ninja: ninja -t graph -f temp_build_file [jobs]
    Ninja-->>Process: DOT_output_to_stdout
    Process-->>User: DOT_output_streamed

    Process-->>Runner: Ok
    Runner-->>Cli: Ok
    Cli-->>User: exit_code 0
Loading

Class diagram for refactored runner module with shared Ninja tool handler

classDiagram
    class Cli {
    }

    class Runner {
        +run(cli : Cli) Result
        +handle_build(cli : Cli, args : BuildArgs) Result
        +handle_ninja_tool(cli : Cli, tool : str) Result
        +handle_clean(cli : Cli) Result
        +handle_graph(cli : Cli) Result
        +generate_ninja(cli : Cli) Result
    }

    class BuildArgs {
    }

    class Process_utils {
        +create_temp_ninja_file(ninja_manifest : str) Result
        +resolve_ninja_program() PathBuf
        +run_ninja_tool(program_path : Path, cli : Cli, build_file_path : Path, tool : str) Result
    }

    Cli <.. Runner : used_by
    BuildArgs <.. Runner : used_by
    Runner ..> Process_utils : uses
    Runner ..> BuildArgs : uses
    Runner ..> Cli : reads_options_from

    class Commands {
        <<enumeration>>
        Build
        Clean
        Graph
    }

    Commands <.. Runner : dispatches_to_handlers
    Commands : Build -> handle_build
    Commands : Clean -> handle_clean
    Commands : Graph -> handle_graph
Loading

File-Level Changes

Change Details Files
Introduce a shared Ninja tool handler and wire the graph subcommand into the CLI runner.
  • Replace the no-op Graph arm in the main runner dispatch with a real handler
  • Extract common logic for invoking ninja -t <tool> on a temporary manifest into a new handle_ninja_tool helper that preserves CLI directory/jobs settings
  • Refactor handle_clean to delegate to handle_ninja_tool("clean") and implement handle_graph via handle_ninja_tool("graph")
src/runner/mod.rs
Extend runner unit tests to cover the graph subcommand and deduplicate subcommand test helpers.
  • Add assert_subcommand_succeeds_without_persisting_file and assert_subcommand_fails_with_invalid_manifest helpers to share logic between Clean and Graph tests
  • Refactor existing clean tests to use the new helpers instead of inlined logic
  • Introduce a ninja_expecting_graph fixture and new tests for graph success, Ninja failure propagation, and invalid manifest handling
tests/runner_tests.rs
Add Cucumber-style feature coverage and step definitions for the graph subcommand, including fake Ninja integration and shared subcommand execution.
  • Add graph.feature describing graph happy-path, Ninja failure, jobs handling, and missing Ninja scenarios
  • Add fake_ninja_expects_graph and fake_ninja_expects_graph_with_jobs steps that install fake Ninja binaries expecting -t graph and optional -j flags
  • Generalize the clean-only step that runs the process into run_subcommand, and layer run_clean and run_graph on top of it
tests/features/graph.feature
tests/steps/process_steps.rs
Update documentation to describe the graph workflow as running ninja -t graph on a synthesized temporary build file and mark the roadmap item as complete.
  • Clarify in the design doc that netsuke graph now runs through Ninja synthesis (Stage 6) to produce a temporary build.ninja and then calls ninja -t graph
  • Update the user guide’s graph command description to emphasize DOT output from ninja -t graph on the generated manifest
  • Mark the roadmap item for implementing the graph subcommand as completed
docs/netsuke-design.md
docs/users-guide.md
docs/roadmap.md

Tips and commands

Interacting with Sourcery

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

Customizing Your Experience

Access your dashboard to:

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

Getting Help

codescene-delta-analysis[bot]

This comment was marked as outdated.

@macroscopeapp
Copy link
Copy Markdown

macroscopeapp Bot commented Dec 12, 2025

Implement graph CLI subcommand to run Ninja tool via shared handler and add tests across runner and Cucumber

Add Commands::Graph handling that generates a temporary Ninja manifest and invokes ninja -t graph through the new runner::handle_ninja_tool, refactor clean to reuse this path, and update docs and Cucumber/integration tests. Key changes are in mod.rs with supporting test utilities and features.

📍Where to Start

Start with runner::handle_ninja_tool and its callers runner::handle_graph and runner::handle_clean in mod.rs.


Macroscope summarized b7c95f0.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Dec 12, 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:

src/runner/mod.rs

Comment on lines +190 to +206

fn handle_graph(cli: &Cli) -> Result<()> {
    info!(target: "netsuke::subcommand", subcommand = "graph", "Subcommand requested");
    let ninja = generate_ninja(cli)?;

    let tmp = process::create_temp_ninja_file(&ninja)?;
    let build_path = tmp.path();

    let program = process::resolve_ninja_program();
    run_ninja_tool(program.as_path(), cli, build_path, "graph").with_context(|| {
        format!(
            "running {} -t graph with build file {}",
            program.display(),
            build_path.display()
        )
    })?;
    Ok(())
}

❌ New issue: Code Duplication
The module contains 2 functions with similar structure: handle_clean,handle_graph

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Dec 12, 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 lines +423 to +442

fn run_graph_fails_with_invalid_manifest() -> Result<()> {
    let temp = tempfile::tempdir().context("create temp dir for invalid manifest test")?;
    let manifest_path = temp.path().join("Netsukefile");
    std::fs::copy("tests/data/invalid_version.yml", &manifest_path)
        .with_context(|| format!("copy invalid manifest to {}", manifest_path.display()))?;
    let cli = Cli {
        file: manifest_path.clone(),
        command: Some(Commands::Graph),
        ..Cli::default()
    };

    let Err(err) = run(&cli) else {
        bail!("expected graph to fail for invalid manifest");
    };
    ensure!(
        err.to_string().contains("loading manifest at"),
        "error should mention manifest loading, got: {err}"
    );
    Ok(())
}

❌ New issue: Code Duplication
The module contains 6 functions with similar structure: run_clean_fails_with_invalid_manifest,run_clean_subcommand_succeeds,run_graph_fails_with_invalid_manifest,run_graph_subcommand_succeeds and 2 more functions

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Dec 12, 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/steps/process_steps.rs

Comment on lines +248 to +258

fn run_graph(world: &mut CliWorld) -> Result<()> {
    prepare_cli_with_absolute_file(world)?;
    let cli = world
        .cli
        .as_ref()
        .context("CLI configuration has not been initialised")?;
    // Use alternate formatting to capture the full anyhow error chain.
    let result = runner::run(cli).map_err(|e| format!("{e:#}"));
    record_result(world, result);
    Ok(())
}

❌ New issue: Code Duplication
The module contains 2 functions with similar structure: run_clean,run_graph

@coderabbitai

This comment was marked as resolved.

@coderabbitai

This comment was marked as resolved.

@coderabbitai

This comment was marked as resolved.

Refactored the Ninja tool invocations by introducing handle_ninja_tool to
handle generic Ninja tools via temporary build files. Replaced specialized
handle_clean and handle_graph implementations to use this unified handler.

Updated tests to use common helpers for validating subcommand success and
failure cases without leaving temporary build files. Refactored Cucumber test
steps to consolidate subcommand runs through a shared function. This improves
code reuse, clarity, and maintainability.

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
@leynos leynos marked this pull request as ready for review December 12, 2025 20:32
sourcery-ai[bot]

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

Caution

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

⚠️ Outside diff range comments (4)
docs/users-guide.md (2)

496-503: Fix list punctuation and wrap to 80 columns.
Remove the stray punctuation/spacing between the clean and graph bullets and keep the graph description wrapped.

 - `clean`: Removes build artefacts by running `ninja -t clean`. Requires
   rules/targets to be properly configured for cleaning in Ninja (often via
   `phony` targets).

-- `graph`: Generates the build dependency graph by running `ninja -t graph` on
-  the generated `build.ninja`, outputting DOT to stdout (suitable for
-  Graphviz). Future versions may support other formats like `--html`.
+- `graph`: Generates the build dependency graph by running `ninja -t graph` on
+  the generated `build.ninja`, outputting DOT to stdout (suitable for
+  Graphviz). Future versions may support other formats like `--html`.

64-67: Document YAML 1.2 support and reject duplicate keys (required by guidelines).
Add an explicit statement in the user guide that Netsuke targets YAML 1.2 and forbids duplicate keys in manifests.

tests/runner_tests.rs (1)

360-431: Collapse clean/graph subcommand tests into a single #[rstest] table.
Replace the duplicated test functions with one parameterised test over (fixture, command, name) to match the project’s Rust testing guidelines.

tests/steps/process_steps.rs (1)

103-114: Override NINJA_ENV to a non-existent path in the no_ninja step to ensure deterministic test execution.

The current implementation uses mocked_path_env() which only affects PATH lookup. However, resolve_ninja_program() checks NINJA_ENV first before falling back to the "ninja" binary. On hosts with system Ninja installed, the lookup succeeds incorrectly, making the scenario non-deterministic. Use override_ninja_env() with the shared EnvLock guard (already established in test_support) to force resolution to fail.

This applies to both the no_ninja step (lines 103–114) and the subcommand execution paths (lines 223–255) that call runner::run(cli) directly without passing world.ninja.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b9b84ec and fbfe855.

📒 Files selected for processing (7)
  • docs/netsuke-design.md (1 hunks)
  • docs/roadmap.md (1 hunks)
  • docs/users-guide.md (1 hunks)
  • src/runner/mod.rs (2 hunks)
  • tests/features/graph.feature (1 hunks)
  • tests/runner_tests.rs (3 hunks)
  • tests/steps/process_steps.rs (3 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/roadmap.md
  • docs/netsuke-design.md
  • docs/users-guide.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/roadmap.md
  • docs/netsuke-design.md
  • docs/users-guide.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/roadmap.md
  • docs/netsuke-design.md
  • docs/users-guide.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/roadmap.md
  • docs/netsuke-design.md
  • docs/users-guide.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/roadmap.md
  • docs/netsuke-design.md
  • docs/users-guide.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/steps/process_steps.rs
  • tests/runner_tests.rs
  • src/runner/mod.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/steps/process_steps.rs
  • tests/runner_tests.rs
  • src/runner/mod.rs
🧬 Code graph analysis (3)
tests/steps/process_steps.rs (2)
test_support/src/check_ninja.rs (5)
  • fake_ninja_expect_tool (131-133)
  • fake_ninja_expect_tool (292-294)
  • new (15-17)
  • fake_ninja_expect_tool_with_jobs (265-288)
  • fake_ninja_expect_tool_with_jobs (298-304)
test_support/src/env.rs (1)
  • mocked_path_env (53-60)
tests/runner_tests.rs (2)
test_support/src/check_ninja.rs (3)
  • fake_ninja_expect_tool (131-133)
  • fake_ninja_expect_tool (292-294)
  • new (15-17)
test_support/src/env.rs (1)
  • override_ninja_env (249-260)
src/runner/mod.rs (4)
scripts/upload_release_assets.py (1)
  • cli (347-360)
tests/steps/ninja_steps.rs (1)
  • generate_ninja (77-90)
src/runner/process/file_io.rs (1)
  • create_temp_ninja_file (13-29)
src/runner/process/mod.rs (2)
  • resolve_ninja_program (58-60)
  • run_ninja_tool (172-176)
🪛 LanguageTool
docs/users-guide.md

[uncategorized] ~500-~500: Loose punctuation mark.
Context: ...often via phony targets). - graph: Generates the build dependency graph by...

(UNLIKELY_OPENING_PUNCTUATION)

⏰ 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-windows (x86_64-pc-windows-msvc, amd64, x64, windows-x86_64) / Build windows artefacts
  • GitHub Check: release / build-linux (aarch64-unknown-linux-gnu, linux-arm64, arm64) / Build linux artefacts
  • GitHub Check: release / build-macos (aarch64-apple-darwin, macos-15, macos-arm64, macos-aarch64) / Build macos artefacts
  • GitHub Check: release / build-macos (x86_64-apple-darwin, macos-15-intel, 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: Sourcery review
  • GitHub Check: Sourcery review
🔇 Additional comments (5)
docs/netsuke-design.md (1)

2000-2006: Hard-wrap to 80 columns and keep commands in code spans.
Enforce the docs wrapping rule and ensure ninja -t graph / dot -Tsvg remain in backticks.

src/runner/mod.rs (2)

80-100: Route Graph through the shared tool runner exactly as done here.
This dispatch keeps behaviour consistent with Clean and limits duplication.


156-206: Tool dispatch is already appropriately constrained and private.

The handle_ninja_tool() function is private, and tool values ("clean", "graph") flow exclusively through hardcoded enum dispatch in pub fn run(). No arbitrary user input reaches this function. The current pattern already prevents tool injection.

Additionally, ninja_env is a local workspace crate providing only a constant (NETSUKE_NINJA), so external version pinning concerns do not apply.

tests/steps/process_steps.rs (1)

84-101: Keep graph fake-ninja fixtures aligned with Clean fixtures.
The tool-name expectations are consistent with the shared check_ninja::fake_ninja_expect_tool* helpers.

tests/runner_tests.rs (1)

135-186: Keep the shared helper assertions; they lock in the “no persisted build.ninja” contract.
The helper structure and error-context assertions are solid and readable.

Comment thread docs/roadmap.md Outdated
Comment thread tests/features_unix/graph.feature
Renamed tests/features/clean.feature, graph.feature, and ninja_process.feature to tests/features_unix/clean.feature, graph.feature, and ninja_process.feature respectively.
Added @unix tag to each renamed feature file to specify Unix platform.

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
@leynos leynos changed the title Add graph subcommand with tests and ninja graph integration Implement graph subcommand with shared Ninja tool invocation and tests Dec 13, 2025
- Document YAML 1.2 manifest constraints in the user guide.
- Ensure Cucumber process steps override NINJA_ENV so missing-ninja scenarios are deterministic.
- Collapse clean/graph subcommand tests into an rstest table to reduce duplication.
@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Dec 14, 2025

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Dec 14, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
docs/roadmap.md (1)

184-185: Keep the roadmap item formatted and wrapped as-is.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fbfe855 and 9fc4879.

📒 Files selected for processing (10)
  • docs/roadmap.md (1 hunks)
  • docs/users-guide.md (2 hunks)
  • test_support/src/env.rs (1 hunks)
  • test_support/src/env_lock.rs (2 hunks)
  • tests/cucumber.rs (3 hunks)
  • tests/features_unix/clean.feature (1 hunks)
  • tests/features_unix/graph.feature (1 hunks)
  • tests/features_unix/ninja_process.feature (1 hunks)
  • tests/runner_tests.rs (2 hunks)
  • tests/steps/process_steps.rs (4 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:

  • test_support/src/env.rs
  • test_support/src/env_lock.rs
  • tests/steps/process_steps.rs
  • tests/runner_tests.rs
  • tests/cucumber.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:

  • test_support/src/env.rs
  • test_support/src/env_lock.rs
  • tests/steps/process_steps.rs
  • tests/runner_tests.rs
  • tests/cucumber.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/users-guide.md
  • docs/roadmap.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/users-guide.md
  • docs/roadmap.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/users-guide.md
  • docs/roadmap.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/users-guide.md
  • docs/roadmap.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/users-guide.md
  • docs/roadmap.md
🧬 Code graph analysis (4)
test_support/src/env.rs (1)
test_support/src/env_lock.rs (1)
  • fmt (17-19)
test_support/src/env_lock.rs (3)
test_support/src/env.rs (1)
  • fmt (227-229)
test_support/src/lib.rs (1)
  • fmt (64-72)
src/ninja_gen.rs (2)
  • fmt (248-252)
  • fmt (262-277)
tests/steps/process_steps.rs (2)
test_support/src/check_ninja.rs (5)
  • new (15-17)
  • fake_ninja_expect_tool (131-133)
  • fake_ninja_expect_tool (292-294)
  • fake_ninja_expect_tool_with_jobs (265-288)
  • fake_ninja_expect_tool_with_jobs (298-304)
test_support/src/env.rs (2)
  • override_ninja_env (255-266)
  • mocked_path_env (53-60)
tests/cucumber.rs (1)
test_support/src/env.rs (1)
  • restore_many (103-117)
🪛 LanguageTool
docs/users-guide.md

[uncategorized] ~504-~504: Loose punctuation mark.
Context: ...often via phony targets). - graph: Generates the build dependency graph by...

(UNLIKELY_OPENING_PUNCTUATION)

🔍 Remote MCP Ref

Let me search for general information about Ninja's graph tool and project structure patterns:## Summary of Relevant Context for PR Review

Ninja Graph Tool Behavior

Ninja can output GraphViz dependency graphs via the command ninja -t graph, which is what this PR leverages. The tool outputs a file in GraphViz syntax and is typically used via ninja -t graph mytarget | dot -Tpng -ograph.png. This confirms the PR's approach of invoking the Ninja tool to generate DOT format output for further processing or visualization.

Rust CLI Testing Patterns & Code Organization

The review comments correctly identify duplicated patterns. For similar situations, Rust CLI best practices recommend:

  • Helper functions should group tests by common purpose, testing similar behavior, while avoiding helpers created solely to reduce repetitive code
  • Specialized assert functions can be defined and reused repeatedly to verify different test scenarios within file scope
  • When there are many test files with shared helper code, Rust's module system is the recommended approach

Subcommand Architecture

The PR correctly follows Rust CLI patterns where for a program with subcommands, a recommended structure delegates command execution through matched enum variants. The shared handle_ninja_tool helper approach aligns with this pattern by centralizing Ninja invocation logic for both clean and graph subcommands.

Test Fixture and Helper Design

Integration tests should cover all observable user behavior types without needing to cover all edge cases, with unit tests handling edge cases. The PR's feature tests for graph (success, Ninja failure, jobs handling, missing Ninja) and runner tests follow this sensible division.

Key Observations for Reviewers

  1. The refactoring suggestions in the PR comments (extracting handle_ninja_tool, run_subcommand, and assertion helpers) align with established Rust patterns for reducing duplication while maintaining clarity.
  2. The ninja -t graph option outputs dependency graphs in GraphViz format, validating the documentation updates claiming DOT output.
  3. The test structure appropriately separates concerns: feature tests for user-observable behavior, runner tests for command execution, and step implementations for test infrastructure.
⏰ 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: Sourcery review
🔇 Additional comments (14)
test_support/src/env.rs (1)

226-230: Keep the non-exhaustive Debug impl; align it with EnvLock.
Match the established finish_non_exhaustive() pattern so debug output never commits to field layout.

tests/features_unix/clean.feature (1)

1-1: Keep the @unix tag.
Ensure Unix-only features remain trivially filterable in CI and local runs.

test_support/src/env_lock.rs (1)

7-7: Keep the EnvLock Debug impl non-exhaustive.
Expose a stable debug shape without leaking internals.

Also applies to: 16-20

docs/users-guide.md (2)

68-70: Keep the YAML 1.2 + duplicate-key prohibition note explicit.
State the behavioural contract unambiguously at the user-guide level. As per coding guidelines, document YAML 1.2 and duplicate-key rejection.


504-507: Keep the graph command description tied to ninja -t graph and stdout DOT.
Prevent divergence between docs and the actual execution path.

tests/features_unix/ninja_process.feature (1)

1-1: Keep the @unix tag.

tests/features_unix/graph.feature (1)

1-29: Keep the scenario set; cover the full observable contract.
Assert success, propagation of Ninja exit failure, -j forwarding, and missing-executable behaviour.

tests/cucumber.rs (1)

10-14: Enforce Drop ordering: drop NinjaEnvGuard before restoring env vars.
Keep self.ninja_env_guard.take(); ahead of restore_environment() to prevent EnvLock contention while restoring env_vars.

Also applies to: 44-46, 187-193

tests/steps/process_steps.rs (3)

34-48: Avoid env-override lifetime leaks; keep the guard reset and SystemEnv override.

Keep world.ninja_env_guard.take(); before installing the new NINJA_ENV override to prevent scenario-to-scenario bleed and to ensure the old guard drops before the new one is created. This is the right shape for deterministic step execution.


87-105: Keep graph fake-ninja steps Unix-gated and aligned with the tool-validation fixture.

Maintain #[cfg(unix)] on the graph steps because check_ninja::fake_ninja_expect_tool_with_jobs is Unix-only; the ToolName wiring matches the runner’s -t graph contract.


225-258: Keep subcommand execution centralised; preserve full error-chain formatting.

Retain run_subcommand and the clean/graph wrappers to prevent divergence between subcommand steps, and keep format!("{e:#}") to preserve the full anyhow chain for assertions.

tests/runner_tests.rs (3)

135-158: Keep the shared “no persisted build.ninja” assertion.

Keep this helper: it hardens the tool-subcommand contract and prevents regressions where a transient manifest leaks into the project directory.


184-185: Keep the NinjaToolFixture alias to make the rstest table readable.

This alias keeps the case table legible and avoids repeating a noisy function type.


388-399: Keep the graph fixture symmetrical with the clean fixture.

Maintain this mirror fixture; it keeps the clean/graph subcommand tests structurally equivalent and easier to extend.

Comment thread tests/runner_tests.rs Outdated
Comment thread tests/runner_tests.rs Outdated
Move clean/graph tool-subcommand coverage into its own integration test module to keep test files under the 400-line limit.

Also assert invalid-manifest failures via the error chain for robustness.
codescene-delta-analysis[bot]

This comment was marked as outdated.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

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)

85-88: Address the past review comment: check the error chain directly.

Lines 85-88 still check err.to_string() before inspecting the chain (lines 89-93). This pattern was flagged in a previous review. Consolidate into a single chain-based assertion to ensure resilience if the top-level message wording changes whilst the causal chain remains correct.

Apply this diff:

-    ensure!(
-        err.to_string().contains("loading manifest at"),
-        "error should mention manifest loading, got: {err}"
-    );
     let chain: Vec<String> = err.chain().map(ToString::to_string).collect();
     ensure!(
-        chain.iter().any(|s| s.contains("manifest parse error")),
-        "expected error chain to include 'manifest parse error', got: {chain:?}"
+        chain.iter().any(|s| s.contains("loading manifest at")),
+        "expected error chain to include 'loading manifest at', got: {chain:?}"
+    );
+    ensure!(
+        chain.iter().any(|s| s.contains("manifest parse error")),
+        "expected error chain to include 'manifest parse error', got: {chain:?}"
     );
In tests/runner_tests.rs at lines 85-93, replace the err.to_string().contains() check with a direct error chain assertion. Remove lines 85-88 that check err.to_string(), and modify the existing chain check at lines 89-93 to assert both "loading manifest at" and "manifest parse error" are present in the chain. Keep both assertions so the test validates the full error context.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9fc4879 and d6bfb30.

📒 Files selected for processing (2)
  • tests/runner_tests.rs (1 hunks)
  • tests/runner_tool_subcommands_tests.rs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.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_tool_subcommands_tests.rs
  • 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_tool_subcommands_tests.rs
  • tests/runner_tests.rs
🧬 Code graph analysis (1)
tests/runner_tool_subcommands_tests.rs (2)
test_support/src/env.rs (1)
  • override_ninja_env (255-266)
test_support/src/lib.rs (1)
  • fake_ninja (95-127)
⏰ 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-15-intel, 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: Sourcery review
  • GitHub Check: build-test (ubuntu-latest, stable)
🔇 Additional comments (8)
tests/runner_tests.rs (1)

9-9: LGTM: Import cleaned up after refactoring.

The removal of self and ToolName from the check_ninja import aligns with the migration of tool-specific tests to the new runner_tool_subcommands_tests.rs module.

tests/runner_tool_subcommands_tests.rs (7)

1-5: LGTM: Clear module documentation.

The module-level doc comment properly explains the file's scope and purpose.


39-59: LGTM: Proper error chain validation.

The helper correctly validates ninja failures by inspecting the error chain (lines 53-56) rather than the top-level string representation, ensuring test resilience.


61-82: LGTM: Clean helper for verifying no file persistence.

The helper appropriately validates that tool subcommands execute successfully without leaving build.ninja artefacts in the project directory.


84-107: LGTM: Exemplary error chain assertion pattern.

Lines 101-104 correctly check the error chain directly rather than err.to_string(), providing resilient assertions that remain valid even if top-level message wording changes. This is the pattern that should be followed throughout the test suite.


109-131: LGTM: Well-structured fixtures for tool-specific testing.

The type alias (line 109) and tool-specific fixtures cleanly support parameterised test cases whilst maintaining type safety and clear intent.


133-143: LGTM: Clean delegation to shared helper.

Both tests appropriately delegate to assert_ninja_failure_propagates for ninja failure scenarios.


145-170: LGTM: Excellent use of parameterised testing.

The #[case] parameterisation elegantly covers success and failure paths for both clean and graph subcommands without duplicating test logic. The dispatch pattern (lines 164-169) cleanly separates fixture-based success tests from invalid-manifest failure tests.

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

leynos commented Dec 16, 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

use std::path::{Path, PathBuf};
use test_support::{
    check_ninja::{self, ToolName},
    check_ninja,

❌ New issue: Code Duplication
The module contains 2 functions with similar structure: run_ninja_not_found,run_ninja_tool_not_found

@coderabbitai

This comment was marked as resolved.

Introduce a shared tests/common module for integration tests.

- Deduplicate create_test_manifest and ninja_with_exit_code.
- Keep rstest fixture discovery via local re-exports where needed.
codescene-delta-analysis[bot]

This comment was marked as outdated.

@coderabbitai coderabbitai Bot added the codex label Dec 16, 2025
Extract a helper function `assert_runner_not_found` to consolidate repeated
code that asserts runner functions fail with `NotFound` errors when the
ninja binary is missing. Updated existing tests `run_ninja_not_found` and
`run_ninja_tool_not_found` to use this helper, improving code reuse and
clarity.

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

This comment was marked as outdated.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 89ce60b and b7c95f0.

📒 Files selected for processing (1)
  • tests/runner_tests.rs (3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.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 (2)
  • override_ninja_env (255-266)
  • prepend_dir_to_path (196-210)
test_support/src/lib.rs (1)
  • fake_ninja (95-127)
tests/common/mod.rs (2)
  • create_test_manifest (16-22)
  • ninja_with_exit_code (28-35)
test_support/src/check_ninja.rs (1)
  • new (15-17)
🔍 Remote MCP

Summary — additional repository facts relevant to reviewing PR #249

  • Runner/ninja execution:

    • Runner generates a temporary build.ninja (or writes an --emit path), resolves Ninja program (env override supported), and invokes ninja with -f, -j and targets; temporary file kept alive by an RAII guard (NamedTempFile) so file exists while ninja runs [::Deepwiki_read_wiki_contents::].
    • Errors use anyhow::Context with descriptive messages at each stage (load manifest → build graph → generate ninja → run ninja) — useful when reviewing new error contexts in handle_ninja_tool/handle_graph [::Deepwiki_read_wiki_contents::].
  • Manifest → IR → Ninja pipeline (relevant to graph behavior):

    • Six-stage pipeline: parse YAML → template expansion (foreach/when) → deserialize → IR generation (validation, dedupe) → ninja synthesis → ninja execution. Graph generation path must run through IR + ninja synthesis to produce build.ninja before invoking ninja -t graph, matching docs/tests in this PR [::Deepwiki_read_wiki_contents::].
  • Security & command quoting (affects cmd construction passed to ninja -t graph/clean):

    • All command placeholders are shell-escaped (shell-quote, POSIX sh mode) and validated with shlex; scripts are wrapped via printf %b into a single-line command — ensures generated build.ninja lines are safe for ninja to execute [::Deepwiki_read_wiki_contents::].
  • Tests and test helpers (what PR touched and must remain consistent):

    • Test infra provides fake-ninja fixtures, NinjaEnvGuard, and helpers in tests/common/mod.rs and test_support crate; BDD steps (tests/steps/process_steps.rs) include installing fake ninja expecting -t graph or -t graph + -j — ensure new runner dispatch matches those expectations [::Deepwiki_read_wiki_contents::].
    • CliWorld holds optional NinjaEnvGuard to override PATH during scenarios; Drop takes the guard to avoid env leakage [::Deepwiki_read_wiki_contents::].
  • Determinism & output expectations:

    • Generated build.ninja and ninja rules are produced deterministically: actions and edges are sorted (hashes, path keys). Tests rely on deterministic ordering for snapshots — refactor must preserve ordering and formatting [::Deepwiki_read_wiki_contents::].
  • Documentation/docs changes:

    • Docs updated to state graph now runs via ninja -t graph on a generated build.ninja (Stage 6); roadmap and user guide updated accordingly — runner implementation should match this described flow [::Deepwiki_read_wiki_contents::].

Cited sources:

  • [::Deepwiki_read_wiki_contents::] (repository overview, pipeline, runner, manifest, IR, ninja_gen, tests, and docs pages used above)
⏰ 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-macos (x86_64-apple-darwin, macos-15-intel, 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: release / build-linux (aarch64-unknown-linux-gnu, linux-arm64, arm64) / Build linux artefacts
  • GitHub Check: Sourcery review
  • GitHub Check: build-test (ubuntu-latest, stable)
🔇 Additional comments (5)
tests/runner_tests.rs (5)

9-9: Import simplification is correct.

Removing the unused ToolName import keeps dependencies minimal.


14-15: Good refactoring to share test infrastructure.

Extracting create_test_manifest to the common module follows DRY principles and makes it available for other test files.


125-134: Excellent refactoring that eliminates test duplication.

This helper consolidates the repeated pattern from run_ninja_not_found and run_ninja_tool_not_found, directly addressing the CodeScene duplication warning.


136-147: Test correctly refactored to use the shared helper.

The closure captures the necessary setup and delegates to assert_runner_not_found, maintaining the original test behavior whilst eliminating duplication.


306-315: Test correctly refactored, completing the duplication fix.

This refactoring, together with run_ninja_not_found, fully addresses the CodeScene warning about duplicate test structure.

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

leynos commented Dec 18, 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

fn run_ninja_tool_not_found() -> Result<()> {
    assert_binary_not_found(|| {
        let cli = Cli::default();
    assert_runner_not_found(|cli| {

❌ New issue: Code Duplication
The module contains 2 functions with similar structure: run_ninja_not_found,run_ninja_tool_not_found

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Dec 19, 2025

Tip

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Read the current state of tests/runner_tests.rs to identify the duplication
cat tests/runner_tests.rs

Length of output: 10108


@leynos: The duplication between run_ninja_not_found and run_ninja_tool_not_found has already been eliminated!

Looking at the current state of tests/runner_tests.rs, both test functions now correctly delegate to the shared assert_runner_not_found helper:

Current implementation (lines 141-151):

#[rstest]
fn run_ninja_not_found() -> Result<()> {
    assert_runner_not_found(|cli| {
        let targets = BuildTargets::default();
        run_ninja(
            Path::new("does-not-exist"),
            cli,
            Path::new("build.ninja"),
            &targets,
        )
    })
}

Current implementation (lines 313-321):

#[rstest]
fn run_ninja_tool_not_found() -> Result<()> {
    assert_runner_not_found(|cli| {
        run_ninja_tool(
            Path::new("does-not-exist"),
            cli,
            Path::new("build.ninja"),
            "clean",
        )
    })
}

The helper function assert_runner_not_found (lines 131-139) successfully consolidates the common pattern of asserting NotFound errors when the ninja binary is missing. The code snippet you provided appears to be from a diff view showing a transition state, but the actual file has the refactoring properly applied.

No further action is required for this duplication issue. ✅

…ge and error messages

- Enhance test to verify error messages include the specific ninja tool invoked
  (e.g., clean, graph) when ninja exits with a failure status.
- Check presence of build file context in error outputs.
- Add doc comments to fixture re-exports for clarity.

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

@codescene-delta-analysis codescene-delta-analysis Bot left a comment

Choose a reason for hiding this comment

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

Gates Failed
Enforce advisory code health rules (1 file with Code Duplication)

Gates Passed
5 Quality Gates Passed

See analysis details in CodeScene

Reason for failure
Enforce advisory code health rules Violations Code Health Impact
runner_tests.rs 1 advisory rule 10.00 → 9.39 Suppress

Quality Gate Profile: Pay Down Tech Debt
Want more control? Customize Code Health rules or catch issues early with our IDE extension and CLI tool.

@leynos leynos merged commit 907d300 into main Dec 19, 2025
14 of 15 checks passed
@leynos leynos deleted the terragon/implement-cli-graph-subcommand-6f0ufd branch December 19, 2025 02:08
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