From 12a1476859ab1f82ee7a5901b0333ac470006907 Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Sun, 30 Aug 2026 07:41:27 +0000 Subject: [PATCH] root-ubuntu: cover configure_sensors with tests The sensor detection step had no test at all, so the two things that make it more than `sensors-detect --auto` were unguarded: the systemd-detect-virt check that keeps it off VMs, and the i2c-dev modprobe that has to happen first because --auto answers no to loading it. Both are now asserted, along with the re-check of /sys/class/hwmon after detection (sensors-detect exits 0 on a machine with no supported chips, so its status alone proves nothing) and the warn-but-return-0 path that keeps a failed probe from aborting the provisioning run. A last check ties the two halves together: lm-sensors in BASE_PACKAGES, and the step wired into apt. The six commands the function reaches the machine through are shadowed, so the real body runs and nothing touches the box the tests run on. Verified by mutation: neutering the VM guard fails the VM test, dropping the modprobe fails the i2c-dev test. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01GFdGuLQcpyGhsKzbU4voLK --- test/root-ubuntu.test.ts | 89 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/test/root-ubuntu.test.ts b/test/root-ubuntu.test.ts index 86cfed7..1b6b8d8 100644 --- a/test/root-ubuntu.test.ts +++ b/test/root-ubuntu.test.ts @@ -793,3 +793,92 @@ describe('configure_swap', () => { expect(help).toContain('SWAP_SIZE=2G'); }); }); + +describe('configure_sensors', () => { + /** + * Everything this function does to the machine goes through six commands: + * `command -v`, systemd-detect-virt, compgen -G over /sys, modprobe, + * sensors-detect and systemctl. Shadowing those six runs the real body and + * leaves the assertions about which branch it took. HWMON stands in for + * "/sys/class/hwmon has a temp*_input in it", and the sensors-detect stub + * flips it -- which is what makes the re-check after detection meaningful + * rather than a restatement of the stub's own return code. + */ + const stubs = ` + info() { echo "info: $*"; } + note() { echo "note: $*"; } + warn() { echo "warn: $*"; } + command() { + case "\${2:-}" in + sensors-detect) return "\${NO_SENSORS_DETECT:-0}" ;; + systemd-detect-virt) return 0 ;; + esac + return 1 + } + systemd-detect-virt() { printf '%s' "\${FAKE_VIRT:-none}"; } + compgen() { [[ "\${HWMON:-0}" == 1 ]]; } + modprobe() { MODPROBED="\$*"; } + systemctl() { SYSTEMCTLED="\$*"; } + sensors-detect() { DETECT_RAN="\$*"; HWMON="\${DETECT_FINDS:-0}"; return "\${DETECT_RC:-0}"; } + `; + + const sensors = (env = '') => + shell( + ['configure_sensors'], + `${stubs}\n${env} configure_sensors\n` + + 'echo "ran=[${DETECT_RAN-}] modprobe=[${MODPROBED-}] systemctl=[${SYSTEMCTLED-}]"', + ); + + it('does nothing at all when lm-sensors is not installed', () => { + const out = sensors('NO_SENSORS_DETECT=1'); + expect(out).toContain('lm-sensors not installed'); + expect(out).toContain('ran=[]'); + }); + + it('skips the probe in a VM, which is never shown the host thermal hardware', () => { + // Without this guard sensors-detect writes an empty config on every + // Droplet, which reads like a failed detection rather than like a machine + // with nothing to detect. + const out = sensors('FAKE_VIRT=kvm'); + expect(out).toContain('running under kvm'); + expect(out).toContain('ran=[]'); + }); + + it('leaves a box alone when hwmon already has readings', () => { + const out = sensors('HWMON=1'); + expect(out).toContain('already available'); + expect(out).toContain('ran=[]'); + }); + + it('loads i2c-dev before probing, because --auto answers no to that prompt', () => { + // sensors-detect finds SMBus chips through /dev/i2c-*, which do not exist + // until i2c-dev is loaded, and --auto takes the default answer -- no. + const out = sensors('DETECT_FINDS=1'); + expect(out).toContain('modprobe=[i2c-dev]'); + expect(out).toContain('ran=[--auto]'); + expect(out).toContain('note: hardware sensors detected'); + expect(out).toContain('systemctl=[restart kmod]'); + }); + + it('re-checks hwmon afterwards rather than trusting a zero exit', () => { + // sensors-detect exits 0 on a machine with no supported chips, so the + // only honest evidence is a temp*_input appearing under /sys. + const out = sensors('DETECT_FINDS=0'); + expect(out).toContain('ran=[--auto]'); + expect(out).toContain('found no supported chips'); + expect(out).not.toContain('hardware sensors detected'); + }); + + it('warns when the probe errors, and still returns 0 so the run continues', () => { + const out = sensors('DETECT_RC=1'); + expect(out).toContain('warn: sensors-detect failed'); + expect(status(['configure_sensors'], `${stubs}\nDETECT_RC=1 configure_sensors`)).toBe(0); + }); + + it('installs the package it needs, and is wired into the apt step', () => { + // A detection step is dead code if the package never lands, and the + // package is dead weight if nothing ever probes for the chips. + expect(SOURCE).toMatch(/^\tlm-sensors i2c-tools/m); + expect(SOURCE).toContain('try "sensors" configure_sensors'); + }); +});