[ci]: run the test suite on FreeBSD - #2129
Conversation
The FreeBSD entries in lint.yml only cross-compile, so no gopsutil test has ever executed on a FreeBSD kernel. Refs shirou#1898
shirou
left a comment
There was a problem hiding this comment.
Thank you for taking this on — this is exactly what I hoped for in #1898, and I'd like to merge it. Running the suite on a real FreeBSD kernel with no source changes is a great addition.
Could I ask for one fix and a few smaller adjustments first?
1. The sleep 900 holds the step open for 15 minutes after the tests finish.
script -q /dev/null sleep 900 & is backgrounded but still holds the ssh session's stdout/stderr, and ssh doesn't return until that channel closes. So the step ends when the sleep expires, not when go test does:
| first run (cold) | second run (cache hit) | |
|---|---|---|
| step starts | 15:04:45 | 09:03:47 |
prepare |
ran (17s) | skipped, cache hit |
run starts, sleep 900 & |
15:06:24 — setup took 99s | 09:04:35 — setup took 48s |
go test ./... done |
15:06:55 — 31s | 09:04:57 — 23s |
| step ends | 15:21:24 — 900s later | 09:19:35 — 900s later |
| total | 16m39s | 15m48s |
The second run is the clearest evidence: the cache halved setup time, yet the step as a whole got only 51 seconds shorter. Everything past setup is the fixed wait.
Detaching the holder's fds resolves it, and a trap makes its lifetime explicit:
script -q /dev/null sleep 3600 </dev/null >/dev/null 2>&1 &
pty_pid=$!
trap 'kill "$pty_pid" 2>/dev/null' EXIT
sleep 1
go test ./...The EXIT trap still runs under set -e, so go test's exit status is preserved. This also removes a hidden flake: as written, 900s is an upper bound too, since the pty has to be open at the moment process.TestTerminal runs. If the suite grows, the holder would die first and TestTerminal would fail in a way that looks like a FreeBSD bug rather than a CI timing issue.
2. Would you mind dropping cache-after-prepare: true? Comparing the two runs, it saves roughly 20 seconds, while the cold run pays an extra 27s restart and a 1.25 GB upload. Two side effects concern me more than the time: that 1.25 GB competes for the repo's 10 GB cache budget with test.yml's setup-go caches (8 OS images × every Go version, evicted LRU), and the cache key hashes the prepare script — so FreeBSD would quietly stay on go1.25.12 indefinitely while test.yml tests every release. If you'd rather keep it, pinning the package explicitly (pkg install -y go125) would at least make the version visible and bust the cache when bumped, in the same spirit as the pinned psutil==7.2.2 there.
3. Could you pin release? It currently resolves to the action's default (15.1 in these runs). Since we SHA-pin the action itself, it seems better not to let the OS underneath move on its own.
4. copyback: false — nothing needs to come back from a test-only job.
With these, a run should land around 1.5–2 minutes.
One follow-up, not for this PR: the pty trick is working around a bug on our side. In getTerminalMap (process/process_posix.go:47-55), when /dev/pts can't be opened and no /dev/ttyp* exists, we discard the /dev/tty* entries already collected and return an error — so Terminal() fails instead of returning "" on any FreeBSD system with no pty open, a jail for instance. Linux always has /dev/pts, which is why it never surfaced. Fixing that would let the script line here go away entirely — would you be interested in opening an issue? (cpu.Info() requiring /var/run/dmesg.boot is similar, though writing the file is a fair way to emulate a booted system.)
A short note that everything runs as root in the VM would also help, since permission-denied paths aren't covered by this job.
Thank you also for #2130 and #2131 — both are well researched, and I'll follow up there. #2131 in particular is timely: #2128 has just added TestTimesTotalMatchesPerCPUSum on Windows, asserting that cpu-total is the field-wise sum of the per-CPU stats, so the averaged KERN_CPTIME breaks the same invariant. As you say, that reads as more than a test tolerance. Once those are resolved I'd be glad to take an OpenBSD job as well.
Detach the pty holder's descriptors so the step ends when go test does rather than when the sleep expires, and bound its lifetime with an EXIT trap. Drop cache-after-prepare, pin the release, and set copyback: false.
|
All four are in, and thank you for the timing table -- it made the first
Inside the VM the tests themselves take 20 seconds, and the step now ends The file as it now stands on the branch, run unmodified: 1m52s, 11 One thing I wanted to settle before pushing: an EXIT trap can replace the
I also added a line noting that everything in the step runs as root, so On Thank you also for reading #2130 and #2131 so closely. I had not connected |
gopsutil has FreeBSD-specific source files in every package --
cpu,disk,host,load,mem,net,process,sensors-- butlint.ymlonlycross-compiles them from a Linux runner. No gopsutil test has ever executed on
a FreeBSD kernel.
Makefilesays as much for the other BSDs: "tested only forsuccessful builds. Value testing is not performed."
That gap shipped a release. In #1898, v4.25.7 panicked in
process.ProcessesWithContexton FreeBSD, and you reproduced it onFreeBSD-13.4 with
Test_SendSignal. A job runninggo test ./...on a realFreeBSD would have caught it before the tag.
You also asked for exactly this action in that issue:
What the job needed
Three things a booted FreeBSD system has and the minimal VM image does not.
All three are handled in the workflow; no source change was required.
lsof--process.TestConnectionsshells out to it (feat: use lsof for net_connections on FreeBSD #1551)./var/run/dmesg.boot--cpu.TestInforeads it, and the image does notrun
/etc/rc.d/dmesg./dev/pts--process.TestTerminalreads that directory, and devfsmaterialises it only while a pty is open. The job runs over a
non-interactive ssh session, so it keeps one pty allocated.
Tests run with cgo enabled, which is what the
# FIXMEline forCGO_ENABLED: "1", GOOS: freebsdinlint.ymlcannot reach bycross-compilation.
Verification
https://github.com/neilpang/gopsutil/actions/runs/30753529510/job/91511662686
11 packages, zero failures, about 16 minutes.
cache-after-prepare: truecaches the prepared image, so later runs skip
pkg installentirely.Not included: OpenBSD and NetBSD
I ran both as well. Neither is green, and neither can be fixed from the
workflow, so I left them out rather than propose a red job:
process.TestConcurrentpanics reproducibly (twice; twogoroutines the second time) at
process_openbsd.go:406, wherecallKernProcSyscalltakes&buf[0]after the sizing sysctl returned azero length. The guard added in Fix panic on OpenBSD and FreeBSD systems if KinfoProc size has an unexpected size #1694 lives in
getKProc, which runs afterthat point. Separately,
cpu.TestTimesfails on any host with 4 or moreCPUs, since
cpu-totalcomes fromkern.cp_timewhile the per-CPU pathsums
kern.cpustats, and the test'smarginof 2.0 only tolerates up to 3.I will open an issue with the traces.
process.NewProcessreturnsnot implemented yet, andhost.TestInfo/mem.TestVirtualMemoryfail on fields the port does notpopulate. Those look like real gaps in the port rather than CI problems.
Happy to add either once those are addressed.