Skip to content

Collapse the looping end-to-end scenarios to one Run per behavior - #217

Merged
JacobStephens2 merged 4 commits into
issue-174from
issue-206
Sep 30, 2026
Merged

JacobStephens2 merged 4 commits into
issue-174from
issue-206

Conversation

@JacobStephens2

Copy link
Copy Markdown
Owner

Summary

Each looping end-to-end test in the four slowest binaries now keeps one case end to end. The other cases moved to unit tests at the level where they actually differ.

 tests/spec_run.rs
-  sigterm_… / sighup_… / sigint_…  (3 Spec runs)
+  a_signal_to_the_spec_run_alone_…            (1, SIGTERM)   + interrupt::tests
-  each_unready_label_…            (3 Spec runs)
+  an_unready_label_…              (1, wontfix)               + spec_run::tests
-  parallel_1_… for [parallel, --parallel]    (2)
+  parallel_1_… --parallel         (1)                         + args::tests
 tests/run_notification.rs
-  interrupted: INT/TERM/HUP (3) · failed send: 2 replies × 2 outcomes (4)
-  credentials key: 3 · broken credentials: 3 · no-email spellings: 2
-  RESEND_API_KEY unset/empty: 2 · email.always absent/false: 2
+  one Run each                    + resend_key::tests, args::tests, config::tests
 tests/release.rs
-  agent fails / prints nothing (2 releases) · 7 bad versions · 3 low versions
-  3 kinds of "no bump PR summary"
+  one script run each             + tests that source the script and call
+                                    release_summary / is_plain_version /
+                                    is_higher / bump_summary directly
 tests/merge_run.rs
   budget and cap tests keep their real rounds, now with a comment saying so,
                                    + run::tests (Budgets accounting)

Other code changes:

  • Scripts: release.sh and release-notes.sh call main only when run directly, so tests can source them. The version pattern moved into is_plain_version.
  • resend_key.rs: the empty-RESEND_API_KEY rule moved into environment_key(value), so it can be tested without touching the process environment.
  • Repetition kept: tests where the repetition is the behavior say so in a comment. These are the Repair cap and upstream-move budget tests, the Spec-run rerun, and the Run vs Merge run prompt comparison. So are the release-notes rerun and help/version/update.

Evidence

Full suite: cargo test passes, 24 of 24 binaries, including 76 unit tests (was 65).

How timings were taken: each test executable ran directly with libtest --report-time, base first, then this branch. The base was origin/issue-174 at dbbe8de, which already includes #205 and #204. The machine is shared, 4 cores. Load averages are noted; single-test times vary by about ±1s between runs.

Totals per binary. CPU is user+sys. "Σ tests" is the sum of the per-test times.

Binary Load Wall before → after CPU before → after Σ tests before → after
spec_run 4.3 / 4.8 103.8s → 99.8s 291.2s → 280.8s 400.6s → 386.2s
merge_run 2.4 / 3.0 31.1s → 30.4s 77.1s → 77.5s 110.4s → 110.3s
release 3.9 / 5.0 30.3s → 29.1s 80.2s → 79.6s 117.8s → 112.5s
run_notification 5.2 / 4.9 15.0s → 8.7s 34.0s → 22.5s 48.4s → 30.7s

What moved and what didn't:

  • merge_run had no multi-Run loops; its changes are comments and unit tests only.
  • spec_run's wall time is set by its long single-Run scenarios, so fewer Runs barely move its total.

Each test changed

spec_run

Test Before After
each_unready_label_keeps_a_ticket_from_running → an_unready_label_keeps_a_ticket_from_running 10.39s 4.72s
sigterm_while_a_ticket_runs_… → a_signal_to_the_spec_run_alone_fails_the_ticket_run_then_ends_the_spec_run_as_interrupted 1.71s 1.70s
sighup_while_a_ticket_runs_… (removed) 2.37s —
sigint_to_the_spec_run_alone_is_passed_on_to_the_ticket_run (removed) 3.13s —
parallel_1_runs_the_tickets_one_at_a_time 22.71s 10.80s
repairs_exhausted_on_the_spec_pr_send_it_back_to_draft_and_exit_1 (comment only) 13.99s 12.49s
rerunning_a_failed_spec_run_… (comment only) 11.84s 10.45s

merge_run (comments only)

Test Before After
foreign_commits_that_keep_coming_spend_the_upstream_move_budget 5.26s 5.75s
a_review_repair_counts_against_the_repair_cap 6.69s 7.07s
merge_failures_that_keep_moving_the_base_branch_spend_the_base_move_budget 8.89s 7.84s
a_repair_needed_after_a_failed_merge_counts_against_the_repair_cap 7.37s 7.58s
a_merge_run_gives_the_agent_the_same_implement_prompt_as_a_run 4.67s 4.73s

release

Test Before After
when_the_agent_fails_or_prints_nothing_… → when_the_agent_fails_the_summary_is_the_generated_notes 7.77s 4.41s
the_summary_is_the_agents_unless_it_fails_or_prints_only_blank_space (new) — 0.17s
without_a_bump_pr_summary_the_release_body_is_… 3.76s 2.51s
the_summary_is_only_that_of_a_pr_merged_as_the_tagged_commit (new) — 0.10s
a_version_that_is_not_plain_semver_is_refused 3.04s 2.12s
only_plain_x_y_z_versions_with_no_leading_zeros_pass (new) — 0.11s
a_version_not_higher_than_the_one_on_main_is_refused 2.33s 2.07s
a_version_is_higher_only_by_its_parts_as_numbers (new) — 0.09s
the_release_body_is_the_bump_prs_summary_… (comment only) 5.01s 5.08s
when_the_bump_pr_cannot_be_read_… (comment only) 4.50s 4.20s

run_notification

Test Before After
an_interrupted_run_sends_one_notification_that_it_was_interrupted 3.48s 1.07s
without_resend_api_key_the_run_stops_before_any_work_and_sends_nothing 0.31s 0.11s
a_failed_send_is_a_warning_that_changes_neither_the_exit_code_nor_stdout 6.45s 1.89s
no_email_skips_the_notification_email_always_asks_for 4.08s 1.88s
without_resend_api_key_a_run_sends_with_the_key_in_the_credentials 5.40s 1.52s
broken_credentials_stop_a_run_that_asks_for_a_notification_before_any_work 0.48s 0.11s
help_version_and_update_never_read_the_credentials (comment only) 0.27s 0.23s
email_always_without_an_address_or_a_key_… → email_always_without_an_address_stops_the_run_before_any_work 0.23s 0.15s
without_email_always_a_run_without_the_flag_sends_nothing → email_always_false_makes_a_run_without_the_flag_send_nothing 3.49s 1.57s

New unit tests:

  • interrupt::tests raises SIGINT, SIGTERM and SIGHUP in turn and checks each is recorded.
  • run::tests covers the shared Repair cap and upstream-move budget.
  • spec_run::tests checks each Unready label.
  • resend_key::tests covers an empty RESEND_API_KEY and each way the Credentials can be broken.
  • args::tests covers both spellings of no-email and parallel.
  • config::tests covers email.to without email.always.

Merge Danger

Door: two-way

Tests only, apart from two small refactors made for testability. The scripts' main guard changes nothing when they are run as before (scripts/release.sh, and release-notes.sh from the workflow).

Blast Radius: tests

If a moved case was mis-covered, a regression in that case would only show in unit tests rather than end to end.

Unaddressed findings

Standards

  • Testing past the seam in tests/release.rs (sourcing the scripts, overriding summary_input): skipped. The codebase-design skill allows internal seams used by a module's own tests, and bash offers no other level at which the cases differ.
  • Middle Man, from_environment delegating to environment_key: skipped. It is the seam that keeps unit tests from touching the process environment.
  • Assert-message wording differs between files ("stderr: {}" vs "{}"): skipped. Each file keeps its existing style.

Spec

  • No unit test for a failed Run plus a failed send, only a comment: skipped. main settles the exit code and prints stdout before RunNotification::send, which returns (), so no seam exists without restructuring main. The reply shapes are covered by email::error_text's unit test.
  • No unit test for the email.always plus Credentials key case: skipped. email.always only picks NotificationAsk::Send(None), which config::tests covers. The key is then found by the same Resend::new call as with the flag, which stays end to end.
  • The no-email and parallel loops were argument-parsing work left to Unit tests for argument parsing; one Run per --email end-to-end test #205: kept. Unit tests for argument parsing; one Run per --email end-to-end test #205 merged without them and added the args seam, so two small unit tests were enough to meet this issue's one-Run-per-behavior criterion.
  • The interrupt unit test checks the flag, not the outcome: skipped. The outcome is FailedRun.interrupted = interrupt::requested(), which doesn't depend on the signal. The remaining end-to-end tests show the flag reaching the outcome.
  • Extra cases in the new release unit tests (blank space, agent success): kept. They are cheap and pin the boundary of the fallback.

Closes #206

🤖 Generated with Claude Code

JacobStephens2 and others added 4 commits September 30, 2026 21:37
Each looping test in spec_run, run_notification and release keeps one case
end to end and moves the rest to unit tests where the cases differ: the
interrupt's signals, the Spec run's Unready labels, the Resend key lookup,
and the release scripts' version, summary and bump-PR steps, which the tests
now call by sourcing the scripts. The Repair and upstream-move budget tests
keep their real rounds, say so, and gain unit tests of the counting.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@JacobStephens2
JacobStephens2 merged commit 9b1b1e0 into issue-174 Sep 30, 2026
13 checks passed
@JacobStephens2
JacobStephens2 deleted the issue-206 branch September 30, 2026 22:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant