Skip to content

feat: add a Windows PowerShell soak harness - #2046

Merged
hatayama merged 6 commits into
v3-betafrom
feat/soak-loop-windows
Jul 29, 2026
Merged

hatayama merged 6 commits into
v3-betafrom
feat/soak-loop-windows

Conversation

@hatayama

@hatayama hatayama commented Jul 28, 2026 •

Copy link
Copy Markdown
Owner

Adds scripts/soak-loop.ps1, a Windows port of the endurance harness, and the doc updates that go with it. scripts/soak-loop.sh samples metrics with ps/pgrep/perl and needs jq, so the soak could not run on Windows at all.

Same iteration plan and the same commands.csv / metrics.csv schema as the shell harness, so results from either platform are comparable.

Platform substitutions

shell PowerShell
jq ConvertFrom-Json (no dependency)
ps / pgrep Win32_Process (unity_rss_kb is Unity.exe's working set)
awk summary Import-Csv + Group-Object
perl epoch ms [DateTimeOffset]::UtcNow
sleep+kill watchdog Start-Process + WaitForExit(ms) + Kill()
trap EXIT try/finally

Start-Process takes a raw command line, so each argument is quoted against Windows argv rules locally. The script is ASCII-only: Windows PowerShell 5.1 reads a BOM-less UTF-8 script as ANSI and mangled the em dashes in the generated C# comments. All output files are written through UTF8Encoding($false) because Add-Content stamps a BOM onto an empty file. The pause-point line number is derived from the generated ticker source instead of the hand-maintained constant the shell version keeps in sync with its heredoc.

What running it against a real project changed

Most of the branch after the first commit is the harness learning to tell uloop defects from its own noise. Soaking a ~5 GB project, first alone and then three checkouts in parallel:

  • Known-benign outcomes are reported as TOLERATED, not FAIL, and counted in a separate summary column. ForceCompileUnknownResult and a completed run-tests whose overall status is Skipped are not defects. commands.csv still records the raw exit code.
  • UNITY_SERVER_BUSY is waited out rather than counted. uloop is single-flight, so one slow compile used to fail every command behind it - eight failures from one cause, in all three parallel runs.
  • The kill watchdog is configurable (-CommandTimeoutSeconds). The fixed 10 minutes killed two of three parallel runs before their first iteration; a forced recompile of this project measured 842 s / 1139 s / 1204 s under three-way load.
  • -CompileWaitTimeoutSeconds forwards --compile-wait-timeout-seconds when the pinned runner accepts it (feat: configurable compile wait timeout and working timeout recovery #2036), detected from compile --help so older runners still work. Passing it lifts the watchdog above the wait.
  • Debug code optimization is applied for the pause cycle and restored at the end, and re-applied after every restart - the mode does not survive uloop launch -r, which silently broke every post-restart pause cycle. -KeepCodeOptimization opts out.
  • run-tests no longer shares an iteration with compile --force-recompile. Both cadences default to 10, so a full rebuild was always serialised in front of the test run.

Filed from the same runs: #2032 (fixed by #2036) and #2045.

Verification

Every branch was exercised against a stub uloop executable that dumps its argv and can simulate busy, collision, dirty-scene and tolerated-failure responses: argument quoting for paths with spaces and embedded quotes, busy-wait and its exhaustion, compile-retry and its exhaustion, tests deferral, both runner-support branches of the compile-wait flag, and the code-optimization re-apply ordering. Parsed and run under both Windows PowerShell 5.1 and PowerShell 7.

End to end against a real Unity project: 10-iteration solo run, a 5-iteration verification run, and two rounds of three parallel 10-iteration runs. In the last round every command reported 0 failures apart from the pause-point issue that the final commit fixes.

Not included

scripts/soak-loop.sh is unchanged. The tolerated-outcome reporting, the busy wait, the configurable timeouts and the code-optimization handling all apply to it equally, but none of it can be verified on Windows, so mirroring is left as a follow-up rather than shipped untested. docs/soak-testing.md states which behaviours are PowerShell-only.

🤖 Generated with Claude Code

Review in cubic

hatayama and others added 6 commits July 28, 2026 23:20
scripts/soak-loop.sh samples metrics with ps/pgrep/perl and needs jq, so the
endurance harness could not run on Windows at all. scripts/soak-loop.ps1 ports
it with the same iteration plan and the same CSV schema, so results from either
platform are directly comparable.

Platform substitutions: ConvertFrom-Json instead of jq, Win32_Process instead
of ps/pgrep, Import-Csv/Group-Object instead of awk, and Start-Process with
WaitForExit(timeout) + Kill() instead of the sleep+kill watchdog. A killed
command is recorded as exit code 124 so a hung IPC call still surfaces as one
finite failure. The pause-point line number is derived from the generated
ticker source instead of a hand-maintained constant.

Windows specifics: Start-Process takes a raw command line, so each argument is
quoted against Windows argv rules locally; the script stays ASCII-only because
Windows PowerShell 5.1 reads a BOM-less UTF-8 script as ANSI and would mangle
the generated C# comments; and all output files are written through
UTF8Encoding($false) because Add-Content stamps a BOM onto an empty file.

Verified end to end under both PowerShell 7 and Windows PowerShell 5.1 against
a stub uloop executable: full loop, argument quoting for paths with spaces and
embedded quotes, pause-point arming, three-strike recovery restart, preflight
abort, and the kill watchdog.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A real run against a large project reported four failures that were not uloop
defects, which buries any genuine regression.

Two of them were preventable. enable-pause-point resolves a file and line
through information Unity only keeps under Debug code optimization, so against
a Release editor every pause cycle armed a guaranteed failure. The setup now
switches the editor to Debug when the PlayMode cycle is enabled and restores
the original mode when the run ends; -KeepCodeOptimization opts out.

The other two cannot be prevented (ForceCompileUnknownResult, and a completed
run-tests whose overall status is Skipped), so they are now reported as what
they are: a non-zero exit matching the command's documented benign outcome
logs as TOLERATED instead of FAIL and lands in a new tolerated column of the
summary rather than fails. commands.csv still records the raw exit code, so it
stays the unfiltered record and the schema stays identical to the shell
harness. The tolerance rules moved into Invoke-TimedUloop, so the log and the
iteration verdict can no longer disagree.

Switching code optimization recompiles every assembly, which the following
setup compile collided with; the retry that only the per-iteration compile had
is now shared by both through Invoke-CompileWithRetry, and exhausting its
attempts is reported as a real failure.

Verified against the same project: fails is 0 for every command, the pause
point arms and hits with tickCount captured, and the editor is left back on
Release.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…tion restore

A three-way parallel soak against one large project exposed two harness
defects.

The 600-second per-command kill watchdog was a hard-coded constant. Switching
code optimization recompiles every assembly, and with three editors compiling
at once that setup compile passed ten minutes, so two of the three runs were
killed at exit 124 before their first iteration. The bound is now
-CommandTimeoutSeconds, echoed in the run's opening log line so a result
directory says which bound produced it. The default stays 600.

The code-optimization restore claimed success without looking at the result.
Teardown runs while the editor is often still compiling, which makes the
command bounce off UNITY_SERVER_BUSY, and all three runs logged "restored
Release" while leaving their project on Debug. It now retries and only reports
success once the editor echoes the restored mode back, otherwise it warns and
names the toolbar control. Both fixes were exercised by the rerun: the restore
succeeded on all three projects after several busy retries.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A three-way parallel soak lost the same iteration in all three runs. A forced
recompile outlived the project runner's own ten-minute compile wait, Unity kept
compiling, and every command behind it - compile, get-logs, get-hierarchy,
screenshot, execute-dynamic-code, the scene rebuild and run-tests - was refused
at dispatch with UNITY_SERVER_BUSY and counted as a failure. Eight failures,
one cause, and none of them a defect.

A busy editor is back-pressure from work the harness itself started, and the
error says Retryable: true, so it is now waited out (up to 20 tries, 30s apart)
instead of counted. Only the decisive attempt reaches commands.csv, so the CSV
keeps one row per intended invocation and the recorded duration stays the
command's own.

The cadences made this collision routine rather than unlucky: --force-every and
--tests-every both default to 10, so a full rebuild was always serialised in
front of the test run. Tests due on a forced-recompile iteration now run one
iteration later, except on the last iteration where deferring would drop the
run entirely.

Also split the pause cycle's scene failure message in two. It blamed unsaved
changes for every failure, including the busy-editor ones above, when
DIRTY_SCENE is a specific answer from the guard.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The parallel soak that produced issue #2032 lost an iteration because a forced
recompile outlived the runner's fixed 10-minute compile wait. That wait is
configurable as of #2036, so -CompileWaitTimeoutSeconds now forwards
--compile-wait-timeout-seconds to every compile.

Support is detected from compile --help, the same way the domain-reload flag
already is, because the soak has to keep running against whichever runner a
project pins; an older runner logs the request as ignored instead of failing on
an unknown flag. Passing the value also lifts the kill watchdog above it, since
a watchdog at or below the compile wait would kill the command before the CLI
could report COMPILE_WAIT_TIMEOUT and turn a diagnosable timeout into a bare
exit 124.

Verified both branches against a stub runner (flag present and absent) and
confirmed the real runner accepts the flag.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three parallel soaks all failed to arm a pause point on iteration 10 while
iteration 5 had succeeded, and the failure payload matched the shape of a
Release-mode rejection even though the run had switched the editor to Debug at
setup.

Measured directly: Debug before `uloop launch -r`, Release after it. The
setting does not survive the restart, so every pause cycle following the
iteration-5 restart was arming against a Release editor. The forced recompile
that immediately preceded the failure was not involved - an earlier run had
already forced a recompile and armed successfully in the same iteration, with
no restart in between.

Debug is now re-applied after the scheduled and the recovery restart, beside
the scene rebuild those paths already do, without disturbing the mode recorded
for the final restore.

Also let a command declare more than one tolerated outcome. A plain compile
issued after a forced one that outlived the runner's wait now attaches to it
and returns the forced compile's own unknown-result message; the harness only
knew the collision message, so it counted that recovery as a failure. The
matched pattern is reported back so the retry still fires for a collision and
not for an attached result.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 28, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Updated soak-testing documentation to cover a PowerShell loop variant, platform prerequisites, invocation parameters, runtime timeout and back-pressure behavior, Debug code-optimization pause points, tolerated failures, and PowerShell-specific results.

Changes

Soak Testing Documentation

Layer / File(s) Summary
Usage and platform prerequisites
docs/soak-testing.md
Introduces the shell and PowerShell variants, documents per-platform requirements, and adds PowerShell quick-start and parameter guidance.
Runtime execution guidance
docs/soak-testing.md
Documents binary selection, watchdog and compile-wait timeouts, dispatcher back-pressure, retries, and sequencing of heavy operations.
Pause points and result interpretation
docs/soak-testing.md
Explains Debug code-optimization handling, TOLERATED outcomes, summary counters, raw command exit codes, and PowerShell log output.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding a Windows PowerShell soak harness.
Description check ✅ Passed The description is directly related to the changeset and accurately describes the new PowerShell harness and docs.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/soak-loop-windows

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
docs/soak-testing.md (1)

43-48: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Show the PowerShell 7 invocation as well.

The example uses powershell, which demonstrates Windows PowerShell 5.1 but not the documented PowerShell 7 path. Add an equivalent pwsh example or explicitly label this command as 5.1-specific.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/soak-testing.md` around lines 43 - 48, Update the soak-testing command
example to also show the equivalent PowerShell 7 invocation using pwsh, or
explicitly label the existing powershell command as Windows PowerShell
5.1-specific while documenting the pwsh path.
🤖 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.

Nitpick comments:
In `@docs/soak-testing.md`:
- Around line 43-48: Update the soak-testing command example to also show the
equivalent PowerShell 7 invocation using pwsh, or explicitly label the existing
powershell command as Windows PowerShell 5.1-specific while documenting the pwsh
path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 7bf3ac53-45ab-4cc1-965b-b14351d8bcd1

📥 Commits

Reviewing files that changed from the base of the PR and between c0e392c and 0d964fd.

⛔ Files ignored due to path filters (1)
  • scripts/soak-loop.ps1 is excluded by none and included by none
📒 Files selected for processing (1)
  • docs/soak-testing.md

@hatayama
hatayama merged commit a49e7ad into v3-beta Jul 29, 2026
12 checks passed
@hatayama
hatayama deleted the feat/soak-loop-windows branch July 29, 2026 16:18
RyanXie123 pushed a commit to RyanXie123/unity-cli-loop that referenced this pull request Sep 22, 2026
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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