Repository navigation
autotest: reboot the vehicle before every test - #34417
peterbarker merged 7 commits into
Conversation
17e35e5 to
18b89f1
Compare
Previous review (superseded 2026-09-17)Automated review note — AI-generated (Claude), validated against the live diff. Please sanity-check before acting. Reviewed at head Full report, with everything that was checked: https://uav.tridgell.net/DevCallReviews/2026_09_16_AIReview/devcall_pr_reviews.html#pr34417 Verdict: COMMENT — 2 issues below. A 23-line diff with a very large blast radius, and that asymmetry is the review. Measured A/B across six full suite runs (Tracker/Sub/Rover × two pymavlink versions, 208 distinct tests, 362 pre-test reboots): identical pass/fail sets in every pair, no reboot failures. The cost is right in CI and ~5× higher for a developer. Issues
6 further minor notes (style, coverage, pre-existing items) are in the report rather than here, so they don't bury the above. Independent cold pass: no actionable defect, APPROVE. Reviewed by: Claude (full read of the diff, thread and surrounding source) + an independent Codex cold pass that was not shown these findings. Findings marked both reviewers were reproduced independently twice. |
Previous review (2026-09-16)Automated review note — AI-generated (Claude), validated against the live diff. Please sanity-check before acting. Re-reviewed at head Full report, including everything that was checked and found clean: https://uav.tridgell.net/DevCallReviews/2026_09_17_AIReview/devcall_pr_reviews.html#pr34417 Verdict: REQUEST CHANGES Draft, so incompleteness is not the issue — but the red CI job is a real regression caused by this change, and it was reproduced locally by mutation. The failing test is BUG — The per-test reboot pushes CopterTests2b to the 500-log cap, the log number wraps, and the suite then reads a stale log. Each link verified: ISSUE — The latent defect this change exposes: ISSUE — Fixing only CopterTests2b leaves the trap armed — plane-tests1b is ~12 tests behind it. plane-tests1b now reaches 488/500 logs (master 364). It passes today, but it is close enough that the next few tests added anywhere in that suite will trip the same silent-stale-log failure. ISSUE — STILL OPEN from the previous round: the advertised cost holds only with an unreleased pymavlink. Lower-priority notes (2)
What was checked and found cleanThe previous round's second finding is RESOLVED. "'Before every test' is not what the flag delivers; clear it in the teardown" — commit |
Previous review (head `90fd59741d`)Automated review note — AI-generated (Claude), validated against the live diff. Please sanity-check before acting. Full report: https://uav.tridgell.net/DevCallReviews/2026_09_17_AIReview/devcall_pr_reviews.html#pr34417 Re-reviewed at head The BUG I raised last round is fixed. Verdict moves REQUEST CHANGES → COMMENT. Previous round
Still open — the root cause
Two corrections to my own previous numbers, both from the second pass and both accepted: the call-site count is 20 direct + 40 through the reader wrapper, not 23 + 40; and "every one fails silently" was too strong — some sites only print paths, and downstream assertions can fail visibly. Headroom, from archived high-water ratios (Sub 3.74, Rover 2.25, Tracker 2.2 logs/test — not 2.0 as I first said) against Keying off Smaller points
WithdrawnI previously raised, as unconfirmed, that CI at this head: 73 green, 29 queued or running, zero failures. Every |
Previous review (2026-09-17)Automated review note — AI-generated (Claude), cross-checked by an independent Codex pass against the live diff. Please sanity-check before acting. Full report, including everything that was checked and found clean: https://uav.tridgell.net/DevCallReviews/2026_09_17_AIReview/devcall_pr_reviews.html#pr34417 COMMENTRe-reviewed at head The new three-line guard is necessary, not defensive tidying, and I verified the whole causal chain rather than assuming it. Raising Verified end to end Parsing your own cancelled RED/GREEN under the netns wrapper on Still open
Smaller points
Withdrawn, and staying withdrawn: the |
Previous review (2026-09-18)Automated review note — AI-generated (Claude), validated against the live diff. Please sanity-check before acting. Full report, with what was checked and found clean: https://uav.tridgell.net/DevCallReviews/2026_09_18/devcall_pr_reviews.html#pr34417 Re-reviewed at head ISSUE —
|
480af16 to
948a14d
Compare
Previous review (2026-09-18)Automated review note — AI-generated (Claude), validated against the live diff. Please sanity-check before acting. Full report: https://uav.tridgell.net/DevCallReviews/2026_09_19_AIReview/devcall_pr_reviews.html#pr34417 Re-reviewed at head Verdict: COMMENTStill a draft, and the delta since the last round is exactly one line — the explanatory comment at CI at this head: ✗ 1 failing, 101 passing. Previous round — every finding accounted for
Findings
Both a primary read and an independent second pass were run; where the two disagreed the disagreement is stated in the report rather than averaged away. Anything marked unconfirmed was not reproduced — please push back on it. |
948a14d to
369e9a1
Compare
Previous review (2026-09-20)Automated review note — AI-generated (Claude), validated against the live diff (Claude + Codex cross-checked). Please sanity-check before acting. Re-reviewed at head Full report: https://uav.tridgell.net/DevCallReviews/2026_09_20/devcall_pr_reviews.html#pr34417 Verdict: COMMENT. Still a draft. This round is a pure rebase — zero content change. Using the merge-base method (old base CI is now fully green: 102 checks, 102 pass. The red Previous round — every finding accounted for
New this roundNOTE — Measured from the job logs, not estimated. Direct: CopterTests2b at this head runs 159 pre-test reboots over 188 tests (29 skipped by Indirect: Total attributable: 42.21 + 51.35 = +93.6 s on a master span of 3393.6 s = +2.76 %, 0.498 s per test — about twice the body's 0.245 s aggregate. Note CopterTests2b alone (188 tests) exceeds the body's +103 s projection for the whole 422-test Copter suite. plane-tests1b shows the same shape (100 reboots / 18.7 s direct, +395.6 s simulated arming over 165 calls). What I deliberately don't claim: the raw step-duration delta. The observed span is +164.1 s over the master mean, but GitHub's No code change needed — but if you quote figures in the body, the measured Copter number is the honest one, and the accounting is clearer split into "reboot" and "EKF reconvergence forced by the reboot", since on Copter the second is larger than the first (51.4 s vs 42.2 s) — the opposite of the body's "the reboots themselves are 73 s of the 89 s". NOTE — NOTE — What was checked and found cleanOrdering is correct, which is the main correctness risk here:
The commit message's claim that One correction to my own previous wording: the leak exemptions ( Not checked: whether any test that manages its own reboots needs an escape hatch. The change applies to every vehicle's suite unconditionally and none is provided — worth a thought for firmware-update, storage or watchdog tests. Both a primary read and an independent second pass were run; disagreements are recorded in the report rather than averaged away. The cost figures above come from the primary pass only — the second pass had no network access this run and could not re-derive them, so treat them as one measurement. |
369e9a1 to
f2deac4
Compare
Global Size Summary (Total Flash delta in bytes)Built from f62253f rebased onto 4a2081a
|
Previous review (2026-09-24)Automated review note — AI-generated (Claude), validated against the live diff. Please sanity-check before acting. Re-reviewed at head Both new commits are direct answers to last round's two new findings, and the rewritten PR body now carries measured cost figures. One of the three teardown reboot paths was missed, and it happens to be the most common one — on the measurement below it accounts for about two thirds of the feature's direct cost. Re-raised, now measured
From this head's Cheapest fix, and it subsumes all three assignments: set Notes
Checked and cleanThe No SITL or autotest was run locally for this PR — all measurements come from GitHub job logs (this head's tests1e and the master run at this PR's merge-base), one run per side, so per-test timings carry the usual single-run risk. The sim-second figures are host-normalised; the wall figures are not. The Codex cross-check could not reproduce the log-derived counts because its sandbox has no network to fetch the logs; that is a limit of the check, not a refutation. |
f2deac4 to
dc68c36
Compare
Previous review (2026-09-24)Automated review note — AI-generated (Claude), cross-checked by an independent Codex pass against the live diff. Please sanity-check before acting. Re-reviewed at head Full report: https://uav.tridgell.net/DevCallReviews/followups/2026_09_24_1756/devcall_pr_reviews.html#pr34417 Verdict: COMMENT (unchanged). No blockers. Both actionable items from last round are fixed — and on the headline one you took the cheaper fix I suggested rather than a narrower one, which subsumes all three teardown paths instead of patching them individually. The PR is also out of draft now, and the red Resolved
Still open — re-raised, not new (unconfirmed, not observed)
New
Dropped as a false positiveI had a nit about Checked clean: |
dc68c36 to
3f5d98f
Compare
Previous review (2026-09-28)Automated review note — AI-generated (Claude), validated against the live diff (Claude + Codex cross-checked). Please sanity-check before acting. Re-reviewed at head All 3 actionable findings from my previous comment are resolved, and I found nothing new. CI is 104/104 green.
How the guard was checked: I ran a 3-test Copter SITL step at this head (GetCapabilities, SetModesViaModeSwitch, ParameterChecks), using scratch fault injection that I reverted afterwards. SetModesViaModeSwitch was made to fail, and the post-test reboot to raise
The fresh-SITL flag behaves as intended. The first test in the step skips the pre-test reboot. After a teardown reboot or a fallback reset, the next test also skips it. After a passing test, the next test reboots. The fallback's own Commit split is 1 × |
Each test now starts from a fresh boot, so nothing a test leaves in the vehicle's RAM - its position, EKF state, home, mission progress, learned values - reaches the next one. SITL's reboot is an execv of the same command line, so the simulator restarts at the startup location too; storage (parameters, missions, fence) lives in files and survives, which is what the teardown's clears and the parameter-leak check are for. The suite has been paying for this isolation in ones and twos for years: tests which reboot in their first few lines because they cannot trust what came before, tests which fail only when a particular neighbour runs first, and the teardown's growing list of things to put back. A whole class of them - "test A passes alone and fails after test B" - cannot occur once every test boots its own vehicle. A SITL which has only just been started is not rebooted: nothing has run against it yet, so a single-test run costs exactly what it did before. The fresh mark is cleared as soon as the test function returns, so a test which restarts SITL part way through (as every mid-test reboot does under --valgrind) does not let the next test skip its reboot; a restart by the teardown still marks it fresh.
An autotest suite step creates a log for most tests and now reboots before each one, so a single step can create more than 500 logs. Past 500 the log number wraps to 1, and the harness, which takes the highest-numbered log as the current one, then reads a stale log: CopterTests2b's CompassLearnCopyFromEKFAffinity failed that way. Raise the cap, and with it the LOG_MAX_FILES default, to 5000 for SITL only. Real vehicles keep the 500 limit, so LOG_MAX_FILES' documented range stays at 500, with a comment saying why SITL differs and that values above 500 are still in testing; the description no longer states the cap. Note that get_num_logs() assumes a wrapped directory wrapped at the cap in force now - it adds (LOG_MAX_FILES - smallest_above_last) + 1 - so a logs/ which wrapped under the old 500 is counted against the new one: a directory holding 00000001..00000500 with LASTLOG.TXT at 300 reports 5000 logs, 4500 of them phantom. That needs a logs/ which has already wrapped, so CI's fresh directories are unaffected, and it comes right once the log number passes 500. Left alone here rather than widening this commit.
progress() echoes every message to the vehicle as STATUSTEXT, in 50-byte chunks numbered by the uint8 chunk_seq. A message longer than 256 chunks (12800 bytes) made pymavlink raise struct.error, and because progress() is also called from the harness's exception handling, the error escaped the test and failed the whole step. log_list() prints every log name, so a suite step with more than about 600 logs hit this: CopterTests2b's EKF3SRCPerCore took the rest of the step down once SITL was allowed more than 500 logs. Send at most 256 chunks; the full text has already been printed locally.
log_list() prints the name of every log in logs/ on one line, and progress() also sends each line to the vehicle as STATUSTEXT. With several hundred logs that is well over two hundred STATUSTEXT chunks per call, each written into the dataflash log the caller is about to read; with enough logs it overflowed chunk_seq, and because print_exception_caught() lists the logs too, any test failure was then reported as that struct.error instead of its real cause. Print the listing locally only. send_statustext()'s 256-chunk limit stays as the backstop.
The teardown reboots SITL after a test which failed or ended armed. That leaves exactly what the pre-test reboot is for - a vehicle which has run nothing since it booted - but only the reset path said so, via start_SITL(), so the next test rebooted a second time for nothing.
SITL reboots with execv, which replaces the vehicle process and nothing else, so simulated peripherals started alongside it survive with their RAM intact - CANGPSCopterMission reboots that way and restarts its peripheral itself.
run_one_test_attempt() reboots the vehicle after a test which failed, or which finished still armed. Both calls were unguarded, and neither is inside the method's try: an exception there leaves run_one_test_attempt(), and run_tests() catches only pexpect.TIMEOUT, so it reaches autotest.py and fails the step - abandoning every test the vehicle had left to run. The reboot is tidying up after a test which has already failed, so it is exactly the place least able to afford taking the rest of the suite with it. Fall back to reset_SITL_commandline(), which the neighbouring branches already use when the vehicle is too sick to reboot. Not observed in a run; found by reading the failure paths.
3f5d98f to
f62253f
Compare
|
Automated review note — AI-generated (Claude+Codex), validated against the live diff. Please sanity-check before acting. Reviewed at head This head (f62253f) is a pure rebase of 3f5d98f, which I reviewed last time: git range-diff shows all seven patches identical. Nothing new turned up. The earlier fixes are still in place; only their line numbers moved. reboot_sitl_or_reset() is now at Tools/autotest/vehicle_test_suite.py:2635, called from :10215 and :10224. A Copter SITL run at this head with an injected reboot timeout on the failed-but-alive branch still falls back to a reset, reports the failing test, and goes on to pass the next test. The armed-at-end branch was not re-run in SITL this round. Its patch is unchanged, and an extracted-method probe covers it. Unlike last time, CI is not finished at this head: 103 pass, 7 pending (including five copter SITL jobs), 0 failing. Those pending jobs are worth a glance before merge. |
Summary
Reboot the vehicle before every autotest test, so no test inherits another test's in-RAM state. Measured cost on the two Copter CI buckets: +3.6 % and +0.3 %.
Classification & Testing (check all that apply and add your own)
Testing, all of it an A/B of master against master plus these commits, run back
to back on the same idle machine:
BalanceBot, Sailboat, Tracker, Blimp), 364 tests: the
test.*steps go2036 s -> 2125 s, +4.4 %, with the same 364 tests passing on both sides
and no failures either side. Measured before the teardown already-rebooted
case was handled, so an upper bound.
same three standing environment failures on both sides, and one fewer in the
reboot half.
--parallel=32, all twelve suites, 1091 tests (on top of Add --parallel autotest option #34279, whichis where
--parallellives): the pool's span goes 430.2 s -> 445.9 s,+3.6 %.
AutoModeAccelChangesleaves therover ~850 m from home; forcing
DriveRTLto run next makes it fail onmaster with
Didn't see wp 3, and pass with this change.Description
run_one_test_attempt()reboots SITL before handing control to the test. ASITL which has only just been started, or which the teardown has just rebooted,
is not rebooted again, so a single-test run costs what it always did.
A test that passes alone and fails after some particular neighbour is a
recurring and expensive class of bug, and it cannot happen to a test which
boots its own vehicle. The suite has been paying for that isolation piecemeal:
18 tests open with a bare
reboot_sitl()orreset_SITL_commandline(), 187 of951 test methods reboot or reset within their first three statements, and the
per-test teardown has grown a list of things to put back, each added after some
test was found inheriting something.
--reset-after-every-testexists for thesame reason but restarts SITL with
wipe=Trueand re-applies the wholeparameter set, which is far more expensive.
The worked example above is latent in master's own test list today, hidden only
because
DriveRTLhappens to sit earlier in the Rover list than the testsmerged alongside it.
What a reboot resets, and what it does not
SITL reboots with
execv()of its own command line, so the vehicle and thesimulator restart together: position and attitude, EKF state and origin, home,
mission progress, armed state, and anything held only in RAM. Storage -
parameters, missions, rally points, fence - is a file and survives, which is
what the teardown's clears and
--check-parameter-leaksare for. The execreplaces the vehicle process alone, so a simulated peripheral started alongside
it keeps its state; a test which cares restarts its peripherals itself, as
CANGPSCopterMissiondoes.What it costs
What the reboot costs. Measured at this head, on one idle 16C/32T
machine, master against the branch, back to back, no failures on either side:
CopterTests1eCopterTests2bOne pair per suite, so +/-1 % is noise and the tests2b figure means only "too
small to resolve this way".
reboot_sitl()returns once it has seen threeRC_CHANNELSand does not wait for the EKF, so the reconvergence a rebootforces is paid later inside each test's
wait_ready_to_arm(); both are insidethe step times above.
Most tests do not pay for a reboot at all: the teardown already reboots after a
failed test, after a test which ended armed, and whenever a context pop
requires it, and the next test then skips its own - 20 reboots across 69 tests
in tests1e, 58 across 202 in tests2b.
The overhead lands hardest on suites of short tests: in an earlier serial
eight-suite A/B it was +1.0 % on QuadPlane and +21 % on Tracker, whose mean
test is 1.2 s.
The reconnect after a reboot needs a pymavlink which accepts
reconnect_delay,or each refused connect costs a fixed 1 s. That is in the submodule CI
installs from, and in the 2.4.50 release; on 2.4.49 the cost is 1.10 s per
reboot instead of 0.23 s.
The other commits
More boots means more logs, and that broke two things which the old log count
had been hiding:
AP_Logger: allow 5000 log files in SITL.CopterTests2breaches 611logs with this change, against master's 454 and a 500 cap. Past the cap the
log number wraps to 1 while the harness takes the highest-numbered file as
the current log, so
CompassLearnCopyFromEKFAffinityread a stale one andfailed. The cap, and with it the
LOG_MAX_FILESdefault, becomes 5000 forSITL only; real vehicles keep 500.
autotest: do not overflow STATUSTEXT's chunk_seqandkeep log_list()'s listing out of STATUSTEXT.log_list()prints every logname on one line, and
progress()also sent each line to the vehicle in50-byte chunks numbered by a uint8. Past ~600 logs that overflowed, and
because
progress()is also called while handling a test's exception, anyfailure was then reported as
struct.errorinstead of its real cause. Thelisting no longer goes to the vehicle, and the chunk count is capped as a
backstop.
What it does not fix
State which survives a reboot: anything in storage, anything the harness itself
remembers, and the log directory. It does nothing for the sampling races and
wall-clock-budget flakes, which are a separate family. The harness's
"highest-numbered log is the current one" assumption is still wrong after a
wrap; the larger cap only keeps the suites well away from it.
Follow-ups, not in this PR
The 18 tests which open with a reboot can drop it, and
--reset-after-every-testnarrows to the cases which genuinely needwipe=True.This PR was AI-assisted: the change, the measurements and this description were
produced with Claude Code, and reviewed and tested by me.
🤖 Generated with Claude Code