[Nexthop] run_test.py: fail a test when a unit it left running crashes - #1603
Open
hillol-nexthop wants to merge 1 commit into
Open
hillol-nexthop wants to merge 1 commit into
hillol-nexthop wants to merge 1 commit into
Conversation
Every runner leaves some production systemd units running while its test binary executes (sai_test keeps fsdb and the platform services, sai_agent and link keep qsfp_service, the fboss2 CLI suite runs against the agents themselves), and those units run with Restart=always. A crash the test caused is therefore invisible to gtest: systemd brings the unit back, the binary's own readiness wait rides through it, and the test reports OK while a core sits on disk and the hardware may never have received the config. TestRunner._run_test_guarded now wraps every _run_test call (cold- and warm-boot phases, and PlatformServicesTestRunner's own loop) and, once the binary exits, looks for a crash inside the window. When it finds one, _apply_agent_crash downgrades that test's OK/SKIPPED results to FAILED and appends a banner naming the reason to the binary's transcript, so the summary CI parses counts it. Two signals, in the new fboss_test_runner/crash_detection.py: * A unit's main process died uncleanly inside the window, taken from PID 1's own journal records (find_unclean_unit_exits): core dump, kill by anything but SIGTERM/SIGINT/SIGHUP, non-zero exit, OOM kill. Clean stops and restarts (the CLI's commit restart, a warm-boot phase bouncing a test-owned unit, a console logout bouncing serial-getty) exit 0 or die by SIGTERM and do not register. The record's own microsecond timestamp decides membership in the window, so a cold boot the runner performed right before launching the binary is not charged to the test. The journal is used rather than `systemctl show`: NRestarts only moves for Restart=-triggered restarts (a manual restart followed by a crash reads "unchanged"), and Result is reset to "success" as soon as the unit is started again, so a check that runs after the test binary exits has already lost the crash. * A core file that was not on disk before the binary started (list_core_dumps before and after, set difference), from any process except the test binary itself, which already failed on its own. Comparing sets rather than mtimes charges one core to exactly one test.
hillol-nexthop
force-pushed
the
run-test-unit-crash-detection
branch
from
September 4, 2026 08:51
cc62882 to
31a89f8
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Every
run_test.pyrunner leaves some production systemd units running while its test binary executes (sai_testkeeps fsdb and the platform services up,sai_agent/linkkeep qsfp_service up,bsp_testskeeps both agents up while unloading kmods, the fboss2 CLI suite runs against the agents themselves), and all of those units run withRestart=always(RestartSec=30). A crash the test caused is therefore invisible to gtest: systemd brings the unit back, the binary's own readiness wait rides through it, and the test reports OK while a core sits on disk and the hardware may never have received the config. The motivating case was a CLI test whose commit crashed the hw_agent on every run and still passed 4/4 times, one hw_agent core each. gtest cannot see that, so the harness has to.TestRunner._run_test_guarded()now wraps every_run_testcall (cold- and warm-boot phases, andPlatformServicesTestRunner's own loop) and, once the binary exits, looks for a crash inside the window. When it finds one,_apply_agent_crash()downgrades every OK/SKIPPED result of that test to FAILED (so the console/CSV/JSON summary counts it) and appends a banner naming the reason to the binary's transcript.Two signals, both read after the binary exits, in the new
fboss_test_runner/crash_detection.py:journalctl -o json --since=@<start> _PID=1,find_unclean_unit_exits):code=dumped(any signal),code=killedby anything but SIGTERM/SIGINT/SIGHUP, a non-zero exit status (other than the 128+N a wrapper relays for those signals), or an OOM kill. Clean stops and restarts — thesystemctl restartthe fboss2 CLI performs on commit, a warm-boot phase bouncing the test-owned OSS unit, a console logout bouncingserial-getty— exit 0 or die by SIGTERM and do not register. Units the runner masked are inactive and produce nothing, so no per-runner watch list is needed./var/lib/systemd/coredumpor/var/core(list_core_dumpsbefore and after, set difference), from any process except the test binary itself (that test already failed on its own). Comparing sets rather than mtimes charges one core to exactly one test, even when systemd-coredump finishes writing it after the check or the next test starts a second later.Why the journal rather than
systemctl show:NRestartsonly moves forRestart=-triggered restarts (a manual restart followed by a crash reads "unchanged"), andResultis reset tosuccessthe moment the unit is started again — by the time the check runs after the binary exits, a crash 30 s earlier has already been papered over. The journal line keeps the exit code and signal, and its microsecond timestamp decides membership in the window, so a cold boot the runner performed right before launching the binary is not charged to the test. Whenjournalctlis unavailable (workstation, container) that signal is simply empty; the core check still runs.Known limitations: a crash that lands between tests (after one binary exits, before the next starts) is attributed to no test. Any unit on the box is watched, so a unit crash-looping for reasons unrelated to the test (a mis-configured platform daemon, say) fails every test in the run — that is a true statement about the device, but it is not attributed to a cause. An agent that aborts on every graceful stop would flag every committing CLI test. The production agents failing to come back after the post-run
enable_servicesrestart is outside every window and is a follow-up.Test Plan
python3 -m pytest fboss/oss/scripts/run_scripts/fboss_test_runner/unittests→ 338 passed on top of currentmain(323 existing + 15 new:crash_detection×7 — crash/clean-stop/OOM classification incl.serial-gettyexit 0 and wrapper status 143, record-timestamp window, unparseable lines, journalctl missing, core listing, comm truncation;_run_test_guarded×6 — unit crash, new core, pre-existing core ignored, own-binary core ignored, both, healthy window, downgrade rules keep the transcript; end-to-end through_run_tests×2).fboss_sw_agentabort is logged by PID 1 asfboss_sw_agent.service: Main process exited, code=dumped, status=6/ABRTwithEXIT_CODE=dumped, and--since=@<epoch>is accepted._classify_unit_exitreports it.