Repository navigation
feat: add a Windows PowerShell soak harness - #2046
Conversation
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>
📝 WalkthroughWalkthroughUpdated 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. ChangesSoak Testing Documentation
Estimated code review effort: 2 (Simple) | ~10 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
docs/soak-testing.md (1)
43-48: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winShow 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 equivalentpwshexample 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
⛔ Files ignored due to path filters (1)
scripts/soak-loop.ps1is excluded by none and included by none
📒 Files selected for processing (1)
docs/soak-testing.md
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds
scripts/soak-loop.ps1, a Windows port of the endurance harness, and the doc updates that go with it.scripts/soak-loop.shsamples metrics withps/pgrep/perland needsjq, so the soak could not run on Windows at all.Same iteration plan and the same
commands.csv/metrics.csvschema as the shell harness, so results from either platform are comparable.Platform substitutions
jqConvertFrom-Json(no dependency)ps/pgrepWin32_Process(unity_rss_kbisUnity.exe's working set)awksummaryImport-Csv+Group-Objectperlepoch ms[DateTimeOffset]::UtcNowsleep+killwatchdogStart-Process+WaitForExit(ms)+Kill()trap EXITtry/finallyStart-Processtakes 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 throughUTF8Encoding($false)becauseAdd-Contentstamps 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:
TOLERATED, notFAIL, and counted in a separate summary column.ForceCompileUnknownResultand a completedrun-testswhose overall status isSkippedare not defects.commands.csvstill records the raw exit code.UNITY_SERVER_BUSYis 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.-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.-CompileWaitTimeoutSecondsforwards--compile-wait-timeout-secondswhen the pinned runner accepts it (feat: configurable compile wait timeout and working timeout recovery #2036), detected fromcompile --helpso older runners still work. Passing it lifts the watchdog above the wait.uloop launch -r, which silently broke every post-restart pause cycle.-KeepCodeOptimizationopts out.run-testsno longer shares an iteration withcompile --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
uloopexecutable 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.shis 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.mdstates which behaviours are PowerShell-only.🤖 Generated with Claude Code