Skip to content

Kill mutation-testing survivors (#55, #56, #57, #58, #59) - #63

Open
leynos wants to merge 8 commits into
mainfrom
kill-mutation-survivors
Open

Kill mutation-testing survivors (#55, #56, #57, #58, #59)#63
leynos wants to merge 8 commits into
mainfrom
kill-mutation-survivors

Conversation

@leynos

@leynos leynos commented Jul 13, 2026

Copy link
Copy Markdown
Owner

Addresses #55, #56, #57, #58, #59.

Summary

Kills all 32 mutation-testing survivors from the full dispatch run
(29074663742,
dataset SHA 1004e607): 26 real test gaps closed with new unit tests, one
subsumed predicate clause deleted (two mutants), one equivalent mutant and
three test-backdoor scaffolding functions excluded via a new
.cargo/mutants.toml with inline justifications. Scoped confirmation
re-runs surfaced four further missed mutants beyond the worklist; these are
also killed. Two new network-boundary mutants remain and are documented
below.

Review walkthrough

Survivor counts (before → after)

Scoped re-runs: cargo mutants --file <file> -- --all-features at this
branch's head.

File Worklist survivors After Scoped re-run result
src/config_store/mod.rs 4 0 18 caught, 6 unviable
src/init/mod.rs 6 0 18 caught, 6 unviable
src/init/helpers.rs 4 0 14 caught
src/scaleway/mod.rs 6 0 15 caught, 7 unviable
src/scaleway/lifecycle/wait.rs 5 0 (2 new, see Notes) 9 caught, 6 unviable, 2 missed
src/scaleway/lifecycle/create.rs 1 0 3 caught, 2 unviable
src/sync/mod.rs 2 0 14 caught, 7 unviable
src/main.rs 3 excluded (scaffolding) patterns verified via --list
src/backend.rs 1 excluded (equivalent) pattern verified via --list
Total 32 0 of 32

Four additional missed mutants beyond the worklist were surfaced by the
scoped re-runs and killed in the same PR: the path_exists NotFound
match guard (config store), FormatFailure::fmt, and both
append_teardown_note replacements (init).

Red–green evidence

Each module's new tests were verified by hand-applying a representative
surviving mutant, observing the failure, reverting, and observing the
pass. One transcript per module:

config_store (mod.rs:139 delete !):

=== RED config_store: mod.rs:139 delete ! ===
test config_store::tests::current_volume_id_returns_none_when_config_missing ... FAILED
test config_store::tests::current_volume_id_round_trips_written_id ... FAILED
=== GREEN config_store restored ===
test result: ok. 2 passed; 0 failed

init (helpers.rs:3 replace * with + in BYTES_PER_GB):

=== RED init: helpers.rs:3 replace * with + in BYTES_PER_GB ===
test init::helpers::tests::volume_size_bytes_converts_gb ... FAILED
assertion `left == right` failed
  left: 2099200
 right: 2147483648
=== GREEN init restored ===
test result: ok. 13 passed; 0 failed

scaleway classification (mod.rs:42 replace || with &&):

=== RED scaleway/mod.rs:42 replace || with && ===
test scaleway::tests::is_instance_type_error_classifies::case_2 ... FAILED
test scaleway::tests::is_instance_type_error_classifies::case_1 ... FAILED
=== GREEN scaleway/mod.rs restored ===
test result: ok. 4 passed; 0 failed

lifecycle wait (wait.rs replace != with == in the state check):

=== RED wait.rs: replace != with == (state check) ===
test ...wait::wait_for_public_ip_returns_missing_ip ... FAILED
test ...wait::wait_for_public_ip_returns_networking_once_running ... FAILED
expected networking, got instance id missing public IPv4 address
=== GREEN wait.rs restored ===
test result: ok. 6 passed; 0 failed

lifecycle create (create.rs:50 replace == with !=):

=== RED create.rs:50 replace == with != (poweron action) ===
test ...power_on_if_needed_ignores_non_poweron_actions ... FAILED
expected PowerOnNotAllowed error, got Err(Provider { message: "Request: error decoding response body: ..." })
=== GREEN create.rs restored ===
test result: ok. 3 passed; 0 failed

sync (mod.rs:241 delete !):

=== RED sync/mod.rs:241 delete ! (strict host key checking) ===
test sync::tests::ssh::common_ssh_options_toggles_strict_host_key_checking::case_1 ... FAILED
test sync::tests::ssh::common_ssh_options_toggles_strict_host_key_checking::case_2 ... FAILED
=== GREEN sync/mod.rs restored ===
test result: ok. 6 passed; 0 failed

Validation

  • make check-fmt, make lint (doc, clippy, Whitaker dylint), and
    make test (--all-targets --all-features, warnings denied) all pass.
  • make markdownlint and make test-workflow-contracts pass.
  • Scoped cargo mutants --file re-runs per file as tabulated above.

Notes

  • Two new missed mutants remain in wait.rs, both at the network
    boundary: fetch_instance -> Ok(None) (the function is a thin mapping
    over the live Scaleway list-instances call) and the delegation mutant
    wait_until_gone -> Ok(()) on the thin wrapper introduced by the seam.
    Killing either would require stubbing the Scaleway HTTP API in unit
    tests; they are documented here rather than excluded so future runs
    keep reporting them honestly. In the harvested run the former appears
    to account for the single timeout (the 300-second production
    wait-timeout spun until the harness gave up).
  • The power_on_if_needed kill is observable because the mutant path
    attempts a real API call that fails; the un-mutated test path performs
    no network I/O.

Summary by Sourcery

Strengthen Scaleway lifecycle, init, config store, and sync SSH behaviour with new unit tests and polling helpers, and configure mutation-testing exclusions for non-production scaffolding and an equivalent builder mutant.

New Features:

  • Introduce generic polling helpers for Scaleway instance readiness and teardown that can be driven by scripted fetch closures.
  • Add dedicated init module tests for configuration validation, mkfs failure message formatting, and teardown-note construction.
  • Add tests for config store volume ID persistence and error reporting when probing through non-directory parents.
  • Add tests for Scaleway backend instance-type error classification and cache volume ID validation.
  • Add tests for SSH option construction, covering strict host key checking and known-hosts file behaviour.
  • Add a new lifecycle test ensuring non-poweron actions are reported as not powerable.

Enhancements:

  • Refactor Scaleway wait loops to delegate to shared polling helpers, reducing duplication between production code and tests.
  • Simplify lifecycle wait tests to exercise the real polling loops via scripted snapshot sequences instead of a fake backend.
  • Tighten the BYTES_PER_GB test to assert the literal byte value rather than recomputing it from the constant, improving mutation sensitivity.

Build:

  • Add a cargo-mutants configuration file that excludes test-backdoor scaffolding in main and an equivalent InstanceRequest builder mutant from mutation testing runs.

Documentation:

  • Document the rationale for removed Scaleway error-classification clause and the behaviour of polling helpers and test-driven fetch closures.

Tests:

  • Expand Scaleway lifecycle wait and teardown tests to cover successful IP acquisition, missing IP error, residual resource timeout, and SSH readiness behaviour.
  • Add rstest-based coverage for Scaleway instance-type error classification and cache volume validation edge cases.
  • Add rstest-based init tests for validation, mkfs failure message arms, and teardown note behaviour.
  • Add config store tests covering volume ID round-trip, missing-config handling, and non-directory parent path errors.
  • Add sync SSH option tests for strict host key checking toggles and known-hosts file inclusion/omission.
  • Add a lifecycle power-on test that verifies non-poweron actions do not trigger API calls and are reported as not allowed.

References

leynos added 4 commits July 13, 2026 20:54
Address the survivors from the full mutation-testing dispatch run
(dataset SHA 1004e60) harvested into issues #55-#59:

- Config store (#55): exercise the real on-disk
  ConfigStore::current_volume_id path with a round-trip test and a
  missing-config test; previously only a test double was observed.
- Init (#56): add unit tests for InitConfig::validate and a four-case
  table test for format_failure_message, and de-tautologize the
  BYTES_PER_GB conversion test by asserting the literal byte count.
- Scaleway classification (#57): add unit tests for
  is_instance_type_error and validate_cache_volume_id, and delete the
  dead third clause of is_instance_type_error, which was subsumed by
  the first clause and could never affect the result.
- Scaleway lifecycle (#58): introduce a fetch seam by extracting the
  polling loop bodies into generic poll_for_public_ip and
  poll_until_gone helpers parameterized over an instance-fetch
  closure. The wait tests now drive the production loops with
  scripted snapshots instead of a FakeBackend duplicating the
  algorithm. Add a happy-path readiness test, an already-gone
  teardown test, and a power_on_if_needed case where "poweron" is
  not among the allowed actions.
- Sync (#59): assert the StrictHostKeyChecking and
  UserKnownHostsFile options are emitted and omitted as the
  configuration toggles.

Add .cargo/mutants.toml excluding the cfg-gated test-backdoor
scaffolding in src/main.rs (mirroring the existing
src/test_support.rs exclude-glob) and the equivalent
InstanceRequest::builder Default::default() mutant, with
justifications inline.
The "replace <fn>" patterns missed operator mutants inside the
test-backdoor functions (e.g. "replace == with != in
enable_fake_modes"). Bare function-name substrings cover both the
whole-function and in-function mutant name forms.
Scoped cargo-mutants confirmation runs surfaced three further missed
mutants beyond the harvested worklist:

- config_store path_exists: the NotFound match guard replaced with
  true silently treated any directory-open failure as "config does
  not exist". A new test probes a path whose parent is a regular
  file and asserts the NotADirectory failure surfaces as an Io error.
- init FormatFailure::fmt replaced with Ok(Default::default()):
  assert the Display output carries the failure message.
- init append_teardown_note replaced with a fixed string: assert the
  message passes through unchanged without a teardown error and that
  a teardown error is appended.
Clippy's too-many-arguments ceiling (four) rejected the five-argument
poll_for_public_ip helper. Carry the SSH port and timing values in a
small copyable settings struct instead.
@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Summary

  • Kill all 32 mutation-testing survivors from issues #55Mutation testing: sync SSH option survivors #59.
  • Add regression tests for configuration, initialization, Scaleway lifecycle, and SSH synchronization.
  • Refactor lifecycle polling into reusable asynchronous helpers with PollSettings.
  • Remove a redundant instance-type predicate.
  • Add justified mutation-testing exclusions in .cargo/mutants.toml.
  • Address additional survivors found by scoped reruns.
  • Document two remaining network-boundary mutants in wait.rs.

Validation

  • Pass formatting, linting, unit tests, Markdown validation, workflow-contract tests, and scoped mutation runs.

Walkthrough

The PR extracts reusable Scaleway lifecycle polling helpers and adds regression coverage for lifecycle, configuration, initialisation, Scaleway classification, synchronisation, and mutation-testing behaviour.

Changes

Lifecycle and regression coverage

Layer / File(s) Summary
Closure-driven lifecycle polling
src/scaleway/lifecycle/wait.rs, src/scaleway/lifecycle/tests/wait.rs
Add generic asynchronous polling helpers. Delegate public-IP and teardown waits to them. Test readiness, missing addresses, disappearance, timeouts, and SSH fixtures.
Scaleway lifecycle behaviour
src/scaleway/lifecycle/tests/mod.rs, src/scaleway/mod.rs
Test exact poweron action matching. Expand parameterised coverage for error classification, cache-volume validation, and tags.
Configuration and initialisation validation
src/config_store/tests.rs, src/init/mod.rs, src/init/tests.rs, src/init/helpers.rs
Test configuration round-tripping and I/O errors. Add initialisation validation, formatting, teardown, and literal byte-count assertions.
Synchronisation test support and mutation exclusions
src/sync/tests/*, .cargo/mutants.toml
Propagate configuration errors, test SSH options, replace the streaming helper with a macro, and exclude equivalent or test-only mutants.

Possibly related issues

  • leynos/mriya issue 58 — The lifecycle polling and power_on_if_needed regression tests address the issue objectives.

Possibly related PRs

Suggested labels: Issue

Poem

Polling waits, then states align,
Tests trace each changing sign.
SSH options speak with care,
Config errors travel there.
Mutants find no place to hide.


Caution

Pre-merge checks failed

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

  • Ignore

❌ Failed checks (1 error, 2 warnings, 2 inconclusive)

Check name Status Explanation Resolution
Unit Architecture ❌ Error The new polling helpers inject instance fetching but directly call Instant::now() and tokio::time::sleep; tests use real delays, so wall-clock dependence remains hidden and non-substitutable. Inject a narrow clock/sleeper at the polling boundary, or pass an explicit deadline/tick abstraction, and test timeout behaviour without real wall-clock waits.
Testing (Unit And Behavioural) ⚠️ Warning Lifecycle tests call private poll_* helpers with scripted closures; no new test exercises fetch_instance or wrapper behaviour, and the PR records two network-boundary mutants. Add a deterministic network-boundary integration test that drives Backend::wait_for_ready and destroy through a stubbed Scaleway API, including fetch and teardown delegation assertions.
Testing (Property / Proof) ⚠️ Warning The new polling helpers define outcomes over arbitrary snapshot sequences, but tests cover only fixed scripts and the project has no proptest or model-checking coverage. Add bounded proptest cases for snapshot states, IP validity, absence, errors, and orderings; compare each result with a small reference model.
Developer Documentation ❓ Inconclusive Evidence collection is still in progress. Inspect the full PR diff and the repository's developer and design documentation before deciding.
Performance And Resource Use ❓ Inconclusive Investigation is still in progress. Inspect the polling loops and all changed code before deciding.
✅ Passed checks (15 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the mutation-testing work and links issues #55#59 as required.
Description check ✅ Passed The description directly explains the mutation-testing fixes, added tests, refactors, exclusions, and validation results.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Testing (Overall) ✅ Passed Pass the check: tests exercise real config I/O, all init message arms, production polling helpers, lifecycle errors, classification, tagging, and SSH options with concrete assertions.
User-Facing Documentation ✅ Passed The PR adds tests and an internal polling refactor; it does not change user-facing behaviour. The existing guide already documents polling, SSH options, cache volumes, and init usage.
Module-Level Documentation ✅ Passed Accept the check: every Rust module file and inline module has //! documentation that states its purpose and role; detailed docs also describe relationships where needed.
Testing (Compile-Time / Ui) ✅ Passed Pass this check: the PR adds no compile-fail or type-level contract; unit tests exercise the generic helpers, and focused assertions cover small stable outputs without needing snapshots.
Domain Architecture ✅ Passed Keep domain ports in backend and volume traits; the PR confines polling, Tokio, Scaleway API, and provider errors to the Scaleway adapter.
Observability ✅ Passed Accept: the Scaleway change relocates existing polling loops without changing behaviour or error context; all other changes are tests or mutation configuration, so no new operational failure mode l...
Security And Privacy ✅ Passed The full PR diff adds tests and a crate-internal polling seam only; scans found no secrets, credential-bearing URLs, new privileged access, or unsafe input sinks.
Concurrency And State ✅ Passed Keep the check passing: polling owns a local FnMut closure and awaits each fetch serially; no new shared mutable state, locks, or background tasks were added, and scripted tests cover sequencing.
Architectural Complexity And Maintainability ✅ Passed Accept the change: the internal polling closures and PollSettings remove duplicated FakeBackend logic, have immediate test use, preserve explicit wrappers, and add no dependencies or new architectu...
Rust Compiler Lint Integrity ✅ Passed Keep the check passing: the PR adds no broad lint suppressions or artificial anchors; new test modules are cfg-gated, helpers have real call sites, and the sole new clone repeats a test snapshot in...
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch kill-mutation-survivors

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

@sourcery-ai

sourcery-ai Bot commented Jul 13, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds targeted unit tests and small refactors to make previously surviving mutation-testing changes observable, deletes one subsumed predicate clause, and configures cargo-mutants exclusions for known scaffolding and an equivalent mutant, thereby closing real test gaps without altering production behaviour.

Sequence diagram for ScalewayBackend wait_for_public_ip polling via generic helper

sequenceDiagram
    actor Caller
    participant ScalewayBackend
    participant poll_for_public_ip
    participant fetch_closure
    participant ScalewayAPI

    Caller->>ScalewayBackend: wait_for_public_ip(handle)
    ScalewayBackend->>poll_for_public_ip: poll_for_public_ip(handle, PollSettings, || fetch_instance(handle))

    loop until deadline
        poll_for_public_ip->>fetch_closure: fetch()
        fetch_closure->>ScalewayBackend: fetch_instance(handle)
        ScalewayBackend->>ScalewayAPI: list_instances(handle)
        ScalewayAPI-->>ScalewayBackend: Result<Option<InstanceSnapshot>, ScalewayBackendError>
        ScalewayBackend-->>fetch_closure: Result<Option<InstanceSnapshot>, ScalewayBackendError>
        fetch_closure-->>poll_for_public_ip: Result<Option<InstanceSnapshot>, ScalewayBackendError>

        alt instance running with public_ip
            poll_for_public_ip-->>Caller: Ok(InstanceNetworking)
        else instance running without public_ip
            poll_for_public_ip-->>Caller: Err(ScalewayBackendError::MissingPublicIp)
        else never reaches running state before timeout
            poll_for_public_ip-->>Caller: Err(ScalewayBackendError::Timeout)
        end
    end
Loading

File-Level Changes

Change Details Files
ConfigStore volume ID and path existence behaviour are now exercised via real filesystem-based tests, covering round-trip writes, missing configs, and non-directory parents.
  • Add tests for current_volume_id to assert exact round-trip of a written ID.
  • Add test confirming current_volume_id returns Ok(None) when no config file exists.
  • Add test for path_exists to ensure probing through a regular file surfaces a NotADirectory condition as an Io error citing the parent path.
src/config_store/tests.rs
Init configuration and mkfs failure formatting logic gain unit tests, and a tautological constant-based assertion is replaced with a literal value to make mutants observable.
  • Add tests covering InitConfig::validate for zero and non-zero volume sizes.
  • Add table-driven tests for format_failure_message covering all exit-code/stderr combinations.
  • Add tests for FormatFailure Display and append_teardown_note behaviour with and without teardown errors.
  • Change volume_size_bytes test to assert literal bytes value instead of using BYTES_PER_GB in the assertion.
  • Wire a dedicated init tests module via a cfg(test) submodule.
src/init/helpers.rs
src/init/mod.rs
src/init/tests.rs
Scaleway backend error classification and cache volume validation gain unit tests, and a dead clause in the instance-type error predicate is removed.
  • Add helper constructors and rstest-based cases to exercise ScalewayBackend::is_instance_type_error classifications.
  • Add tests ensuring validate_cache_volume_id rejects trimmed identical IDs and accepts distinct IDs.
  • Delete the subsumed third clause in is_instance_type_error and document why it was redundant.
src/scaleway/mod.rs
Scaleway lifecycle wait loops are refactored into generic polling helpers over a fetch closure, and tests are updated to drive these helpers directly with scripted sequences instead of a fake backend.
  • Introduce PollSettings struct and poll_for_public_ip/poll_until_gone helpers parameterized over a fetch closure returning InstanceSnapshot results.
  • Refactor ScalewayBackend::wait_for_public_ip and wait_until_gone to delegate to the new helpers using self.fetch_instance.
  • Update wait-loop tests to remove FakeBackend, add scripted_fetch and handle helpers, and cover running-with-IP, running-without-IP, already-gone, and residual-resource scenarios.
  • Simplify SSH readiness tests to reuse a shared handle constructor.
src/scaleway/lifecycle/wait.rs
src/scaleway/lifecycle/tests/wait.rs
Scaleway lifecycle power-on logic gains a test that exposes incorrect action filtering by ensuring non-poweron actions are reported as not allowed.
  • Add async test verifying power_on_if_needed returns PowerOnNotAllowed when only a reboot action is offered for a stopped instance.
src/scaleway/lifecycle/tests/mod.rs
Sync SSH option construction gains tests for strict host key checking and known hosts file handling, ensuring flags are present or absent according to configuration.
  • Add helper to capture common_ssh_options as owned strings.
  • Add parameterized tests confirming StrictHostKeyChecking=no is toggled based on ssh_strict_host_key_checking.
  • Add tests ensuring UserKnownHostsFile is emitted when ssh_known_hosts_file is non-blank and omitted for blank or whitespace-only values.
src/sync/tests/ssh.rs
cargo-mutants configuration is introduced to exclude test-only scaffolding in main and a documented equivalent mutant in backend from mutation runs.
  • Add .cargo/mutants.toml with regex-based exclusions for test-backdoor functions in src/main.rs.
  • Add exclusion pattern for the known equivalent InstanceRequest::builder->InstanceRequestBuilder mutant, with inline justification comments.
  • Document interaction with existing src/test_support.rs exclude-glob from the GitHub Actions workflow.
.cargo/mutants.toml

Possibly linked issues


Tips and commands

Interacting with Sourcery

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

Customizing Your Experience

Access your dashboard to:

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

Getting Help

codescene-access[bot]

This comment was marked as outdated.

CodeScene's delta gate flagged the two structurally identical
validate_cache_volume_id tests as duplication. A single parameterized
case table keeps both assertions (identical trimmed IDs rejected,
distinct IDs accepted) without the repeated scaffolding.
codescene-access[bot]

This comment was marked as outdated.

CodeScene's delta gate flagged instance_tags_adds_test_run_tag and
volume_tags_adds_test_run_tag as structurally identical (the omits
pair had the same shape). Parameterize each pair over the tag-builder
function and its base tag, removing the repeated scaffolding without
losing any assertion.
codescene-access[bot]

This comment was marked as outdated.

codescene-access[bot]

This comment was marked as outdated.

Add module and test-level comments naming the mutation-survivor issue
each new test or assertion kills (#55, #56, #57, #58, #59), so the
kill sites are traceable back to the triage issues without relying
solely on the PR body.
codescene-access[bot]

This comment was marked as outdated.

codescene-access[bot]

This comment was marked as outdated.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Sorry @leynos, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6053f54ffb

ℹ️ About Codex in GitHub

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

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

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

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

Comment on lines +100 to +101
/// Polls `fetch` until the instance reports a running state with a parseable
/// public IP, or the timeout elapses.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Add usage examples to the polling helper documentation

The new documentation for poll_for_public_ip, and likewise poll_until_gone below, describes possible results but provides no example demonstrating invocation and outcome. Add concise examples using a scripted fetch sequence so the functions comply with the repository's mandatory documentation convention.

AGENTS.md reference: AGENTS.md:L27-L30

Useful? React with 👍 / 👎.

move || ready(Ok(queue.pop_front().unwrap_or(None)))
}

fn handle() -> InstanceHandle {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Use an rstest fixture for the shared instance handle

In the inspected wait-test module, handle() provides identical setup to four tests but is implemented as an ordinary helper. Convert it to an #[fixture] and inject the handle into those tests, as the repository explicitly requires rstest fixtures for shared setup.

AGENTS.md reference: AGENTS.md:L203-L206

Useful? React with 👍 / 👎.

Review feedback on the mutation-survivor work:

- Add `# Examples` sections to `poll_for_public_ip` and `poll_until_gone`
  showing a scripted fetch sequence and the resulting outcome. Both helpers
  are crate-internal, so the blocks are `ignore`d per the repository's
  doctest guide; the executed equivalents live in `super::tests::wait`.

- Convert the shared `handle()` helper in the wait tests to an rstest
  `#[fixture]` injected into the six tests that need it.

Also clears the Whitaker `no_expect_outside_tests` findings this surfaced in
the sync tests, where helper functions are not recognized as tests:
`run_remote_with_fake_output` and `ssh_option_strings` now propagate the
config-validation error, and `assert_streaming_runner_output` becomes a macro
so it expands inside the calling test and reports that test's line number.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
codescene-access[bot]

This comment was marked as outdated.

@codescene-access codescene-access Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No quality gates enabled for this code.

@pandalump

Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

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.

@coderabbitai coderabbitai Bot added the Issue label Aug 6, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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)
src/scaleway/mod.rs (1)

318-345: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover the blank and padded test-run identifiers.

build_tags at lines 110-121 has three input-dependent behaviours: absent id, blank id after trimming, and trimming of a padded id. These cases drive only the absent path and one already-clean id. A mutant that deletes the trimmed.is_empty() guard survives. A mutant that removes id.trim() also survives.

Add a whitespace-only case to the omission test and a padded case to the addition test.

♻️ Proposed extra cases
 #[rstest]
-#[case(ScalewayBackend::instance_tags, "ephemeral")]
-#[case(ScalewayBackend::volume_tags, "cache")]
-fn tags_omit_test_tag_when_unset(
+// A blank identifier must be treated as absent, not appended as an empty tag.
+#[case(ScalewayBackend::instance_tags, "ephemeral", None)]
+#[case(ScalewayBackend::volume_tags, "cache", None)]
+#[case(ScalewayBackend::instance_tags, "ephemeral", Some("   "))]
+fn tags_omit_test_tag_when_absent_or_blank(
     #[case] tags_fn: fn(Option<&str>) -> Vec<String>,
     #[case] base_tag: &str,
+    #[case] test_run_id: Option<&str>,
 ) {
-    let tags = tags_fn(None);
+    let tags = tags_fn(test_run_id);
     assert_eq!(tags, vec![String::from("mriya"), String::from(base_tag)]);
 }
 
 #[rstest]
-#[case(ScalewayBackend::instance_tags, "ephemeral")]
-#[case(ScalewayBackend::volume_tags, "cache")]
+#[case(ScalewayBackend::instance_tags, "ephemeral", "run-123")]
+#[case(ScalewayBackend::volume_tags, "cache", "run-123")]
+// Surrounding whitespace must be trimmed before the tag is built.
+#[case(ScalewayBackend::instance_tags, "ephemeral", "  run-123  ")]
 fn tags_add_test_run_tag(
     #[case] tags_fn: fn(Option<&str>) -> Vec<String>,
     #[case] base_tag: &str,
+    #[case] test_run_id: &str,
 ) {
-    let tags = tags_fn(Some("run-123"));
+    let tags = tags_fn(Some(test_run_id));
     assert_eq!(
         tags,
         vec![
             String::from("mriya"),
             String::from(base_tag),
             String::from("mriya-test-run-run-123"),
         ]
     );
 }
🤖 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/scaleway/mod.rs` around lines 318 - 345, Add a whitespace-only test case
to tags_omit_test_tag_when_unset and assert it returns only the base tags,
covering the trimmed-empty identifier path. Update tags_add_test_run_tag to pass
a padded identifier and assert the generated test-run tag uses the trimmed value
without surrounding whitespace.
🤖 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 `@src/scaleway/lifecycle/tests/mod.rs`:
- Around line 132-147: Merge the duplicate
power_on_if_needed_ignores_non_poweron_actions test into
power_on_if_needed_errors_when_not_allowed by parameterizing the latter with
#[case] values for an empty action list and a single reboot action. Pass the
#[case] allowed Vec<Action> into snapshot, retain the PowerOnNotAllowed
assertion, and remove the standalone duplicate test while preserving the `#58`
exact-match coverage.

In `@src/scaleway/lifecycle/tests/wait.rs`:
- Around line 79-91: Add a test alongside wait_for_public_ip_returns_missing_ip
that scripts absent snapshots and snapshots whose status is not running, then
calls poll_for_public_ip and asserts ScalewayBackendError::Timeout with action
"wait_for_ready". Ensure the sequence exercises both the absent-snapshot branch
and the saw_running == false terminal path.

---

Outside diff comments:
In `@src/scaleway/mod.rs`:
- Around line 318-345: Add a whitespace-only test case to
tags_omit_test_tag_when_unset and assert it returns only the base tags, covering
the trimmed-empty identifier path. Update tags_add_test_run_tag to pass a padded
identifier and assert the generated test-run tag uses the trimmed value without
surrounding whitespace.
🪄 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: 35701ffc-46c7-4d29-9b42-5da8d8676ce0

📥 Commits

Reviewing files that changed from the base of the PR and between a9867d0 and 80ca665.

📒 Files selected for processing (12)
  • .cargo/mutants.toml
  • src/config_store/tests.rs
  • src/init/helpers.rs
  • src/init/mod.rs
  • src/init/tests.rs
  • src/scaleway/lifecycle/tests/mod.rs
  • src/scaleway/lifecycle/tests/wait.rs
  • src/scaleway/lifecycle/wait.rs
  • src/scaleway/mod.rs
  • src/sync/tests/remote.rs
  • src/sync/tests/ssh.rs
  • src/sync/tests/streaming.rs
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • leynos/mapsplice (auto-detected)

Comment on lines +132 to +147
// Kills the `power_on_if_needed` equality-mutant survivor tracked in #58.
#[rstest]
#[tokio::test]
async fn power_on_if_needed_ignores_non_poweron_actions(backend_fixture: ScalewayBackend) {
// A stopped instance offering only unrelated actions must be reported
// as not powerable; only an exact "poweron" action may trigger the
// power-on request.
let snap = snapshot("id", "stopped", [Action::from("reboot")], None);
let zone = Zone::from("zone");
let result = backend_fixture.power_on_if_needed(&zone, &snap).await;
assert!(
matches!(result, Err(ScalewayBackendError::PowerOnNotAllowed { .. })),
"expected PowerOnNotAllowed error, got {result:?}"
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Merge this test with power_on_if_needed_errors_when_not_allowed.

Lines 132-146 duplicate lines 122-130. The only difference is the allowed-actions argument. Express both inputs as #[case] arguments of one test. The equality-mutant coverage for #58 is preserved, because the "reboot" case still requires an exact poweron match.

As per path instructions: "Replace duplicated tests with #[rstest(...)] parameterised cases."

♻️ Proposed parameterised test
-// Kills the `power_on_if_needed` equality-mutant survivor tracked in `#58`.
-#[rstest]
-#[tokio::test]
-async fn power_on_if_needed_ignores_non_poweron_actions(backend_fixture: ScalewayBackend) {
-    // A stopped instance offering only unrelated actions must be reported
-    // as not powerable; only an exact "poweron" action may trigger the
-    // power-on request.
-    let snap = snapshot("id", "stopped", [Action::from("reboot")], None);
-    let zone = Zone::from("zone");
-    let result = backend_fixture.power_on_if_needed(&zone, &snap).await;
-    assert!(
-        matches!(result, Err(ScalewayBackendError::PowerOnNotAllowed { .. })),
-        "expected PowerOnNotAllowed error, got {result:?}"
-    );
-}

Replace the earlier test at lines 121-130 with the merged version:

// A stopped instance must be reported as not powerable unless it offers an
// exact "poweron" action. Kills the equality-mutant survivor tracked in `#58`.
#[rstest]
#[case(Vec::<Action>::new())]
#[case(vec![Action::from("reboot")])]
#[tokio::test]
async fn power_on_if_needed_errors_when_not_allowed(
    backend_fixture: ScalewayBackend,
    #[case] allowed: Vec<Action>,
) {
    let snap = snapshot("id", "stopped", allowed, None);
    let zone = Zone::from("zone");
    let result = backend_fixture.power_on_if_needed(&zone, &snap).await;
    assert!(
        matches!(result, Err(ScalewayBackendError::PowerOnNotAllowed { .. })),
        "expected PowerOnNotAllowed error, got {result:?}"
    );
}
📝 Committable suggestion

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

Suggested change
// Kills the `power_on_if_needed` equality-mutant survivor tracked in #58.
#[rstest]
#[tokio::test]
async fn power_on_if_needed_ignores_non_poweron_actions(backend_fixture: ScalewayBackend) {
// A stopped instance offering only unrelated actions must be reported
// as not powerable; only an exact "poweron" action may trigger the
// power-on request.
let snap = snapshot("id", "stopped", [Action::from("reboot")], None);
let zone = Zone::from("zone");
let result = backend_fixture.power_on_if_needed(&zone, &snap).await;
assert!(
matches!(result, Err(ScalewayBackendError::PowerOnNotAllowed { .. })),
"expected PowerOnNotAllowed error, got {result:?}"
);
}
// A stopped instance must be reported as not powerable unless it offers an
// exact "poweron" action. Kills the equality-mutant survivor tracked in `#58`.
#[rstest]
#[case(Vec::<Action>::new())]
#[case(vec![Action::from("reboot")])]
#[tokio::test]
async fn power_on_if_needed_errors_when_not_allowed(
backend_fixture: ScalewayBackend,
#[case] allowed: Vec<Action>,
) {
let snap = snapshot("id", "stopped", allowed, None);
let zone = Zone::from("zone");
let result = backend_fixture.power_on_if_needed(&zone, &snap).await;
assert!(
matches!(result, Err(ScalewayBackendError::PowerOnNotAllowed { .. })),
"expected PowerOnNotAllowed error, got {result:?}"
);
}
🤖 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/scaleway/lifecycle/tests/mod.rs` around lines 132 - 147, Merge the
duplicate power_on_if_needed_ignores_non_poweron_actions test into
power_on_if_needed_errors_when_not_allowed by parameterizing the latter with
#[case] values for an empty action list and a single reboot action. Pass the
#[case] allowed Vec<Action> into snapshot, retain the PowerOnNotAllowed
assertion, and remove the standalone duplicate test while preserving the `#58`
exact-match coverage.

Source: Path instructions

Comment on lines +79 to 91
#[rstest]
#[tokio::test]
async fn wait_for_public_ip_returns_missing_ip(handle: InstanceHandle) {
let fetch = scripted_fetch(vec![
Some(super::snapshot("id", "running", Vec::<Action>::new(), None)),
Some(super::snapshot("id", "running", Vec::<Action>::new(), None)),
]);
let result = poll_for_public_ip(&handle, poll_settings(Duration::from_millis(5)), fetch).await;
assert!(
matches!(result, Err(ScalewayBackendError::MissingPublicIp { .. })),
"unexpected wait_for_public_ip outcome: {result:?}"
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a case for the never-running timeout path.

poll_for_public_ip has two terminal error arms. The tests exercise only MissingPublicIp. Nothing drives saw_running == false, so the ScalewayBackendError::Timeout { action: "wait_for_ready" } arm is unverified. A mutant that sets saw_running = true unconditionally, or that swaps the two terminal errors, survives this suite. The same gap leaves the absent-snapshot branch (let Some(server) = ... else) unexercised.

Add a test that scripts only absent and non-running snapshots.

♻️ Proposed additional test
+#[rstest]
+#[tokio::test]
+async fn wait_for_public_ip_times_out_when_never_running(handle: InstanceHandle) {
+    // An absent instance followed by a non-running one must exhaust the
+    // deadline and report a timeout rather than a missing address.
+    let fetch = scripted_fetch(vec![
+        None,
+        Some(super::snapshot(
+            "id",
+            "starting",
+            Vec::<Action>::new(),
+            None,
+        )),
+    ]);
+    let result = poll_for_public_ip(&handle, poll_settings(Duration::from_millis(5)), fetch).await;
+    assert!(
+        matches!(
+            result,
+            Err(ScalewayBackendError::Timeout { ref action, .. }) if action == "wait_for_ready"
+        ),
+        "unexpected wait_for_public_ip outcome: {result:?}"
+    );
+}
📝 Committable suggestion

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

Suggested change
#[rstest]
#[tokio::test]
async fn wait_for_public_ip_returns_missing_ip(handle: InstanceHandle) {
let fetch = scripted_fetch(vec![
Some(super::snapshot("id", "running", Vec::<Action>::new(), None)),
Some(super::snapshot("id", "running", Vec::<Action>::new(), None)),
]);
let result = poll_for_public_ip(&handle, poll_settings(Duration::from_millis(5)), fetch).await;
assert!(
matches!(result, Err(ScalewayBackendError::MissingPublicIp { .. })),
"unexpected wait_for_public_ip outcome: {result:?}"
);
}
#[rstest]
#[tokio::test]
async fn wait_for_public_ip_returns_missing_ip(handle: InstanceHandle) {
let fetch = scripted_fetch(vec![
Some(super::snapshot("id", "running", Vec::<Action>::new(), None)),
Some(super::snapshot("id", "running", Vec::<Action>::new(), None)),
]);
let result = poll_for_public_ip(&handle, poll_settings(Duration::from_millis(5)), fetch).await;
assert!(
matches!(result, Err(ScalewayBackendError::MissingPublicIp { .. })),
"unexpected wait_for_public_ip outcome: {result:?}"
);
}
#[rstest]
#[tokio::test]
async fn wait_for_public_ip_times_out_when_never_running(handle: InstanceHandle) {
// An absent instance followed by a non-running one must exhaust the
// deadline and report a timeout rather than a missing address.
let fetch = scripted_fetch(vec![
None,
Some(super::snapshot(
"id",
"starting",
Vec::<Action>::new(),
None,
)),
]);
let result = poll_for_public_ip(&handle, poll_settings(Duration::from_millis(5)), fetch).await;
assert!(
matches!(
result,
Err(ScalewayBackendError::Timeout { ref action, .. }) if action == "wait_for_ready"
),
"unexpected wait_for_public_ip outcome: {result:?}"
);
}
🤖 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/scaleway/lifecycle/tests/wait.rs` around lines 79 - 91, Add a test
alongside wait_for_public_ip_returns_missing_ip that scripts absent snapshots
and snapshots whose status is not running, then calls poll_for_public_ip and
asserts ScalewayBackendError::Timeout with action "wait_for_ready". Ensure the
sequence exercises both the absent-snapshot branch and the saw_running == false
terminal path.

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.

2 participants