Native lm-sensors output plus optional TEMPer USB probe, unified in
lm-sensors format behind one snmpd extend (lmsensors). Idempotent
deploy binds snmpd to explicit LAN+Tailscale addresses only and
source-scopes the community to the pollers (LibreNMS, Home Assistant
app-connector LAN IP, admin workstation, Cloudron) per the founder's
security ruling. TDD unit suite included; fleet verified live (6 hosts)
and negative-tested (refused source).
💘 Generated with Crush
Assisted-by: Crush:glm-5.2
This commit is contained in:
+8
-8
@@ -5,12 +5,12 @@ A commit is blocked while any task below remains unchecked.
|
|||||||
|
|
||||||
## Current Tasks
|
## Current Tasks
|
||||||
|
|
||||||
(all done — session complete)
|
(all done — session state persisted to Redmine #439/#341/#458/#459,
|
||||||
|
Discourse audit #298 replies #37/#38, and git)
|
||||||
|
|
||||||
- [x] Fleet audit + naming convergence (#446)
|
- [x] PDU AP7830 verdict: metered, LibreNMS graphs Output Current (#439)
|
||||||
- [x] Kuma ICMP 84/84 + fixed devices (#435)
|
- [x] tsys1 nut-server restart + HA reachability both UPS (#439, #456)
|
||||||
- [x] DHCP/PTR cleanup, DNS incident + recovery (#420/#449)
|
- [x] Sensor stack TDD + deploy: 6 hosts, scoped ACLs, verified (#341, #458)
|
||||||
- [x] TSYSGroupAIOS adoption completed (test.sh gate live)
|
- [x] Security hardening rulings a+b executed + verified (#439, #458)
|
||||||
- [x] Sept FCP plan of record (#313, #452, #453)
|
- [x] HA founder handoff steps written to #439
|
||||||
- [x] Tripp Lite → tsys9 NUT server live (#439, #372)
|
- [x] Tickets spawned: #455 upsmon fleet, #456/#458 AWX shadows, #457 tuning, #459 tsys4 egress (fixed)
|
||||||
- [x] AWX IaC mandate ticketed (#454)
|
|
||||||
|
|||||||
@@ -0,0 +1,145 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Deploy lm-sensors extend (native sensors + TEMPer) + snmpd on a host [#341/#439]
|
||||||
|
#
|
||||||
|
# Run ON the target host via the remote.sh chokepoint (bash -s). Copy the
|
||||||
|
# wrapper over first (vm-copy / prox-copy). Idempotent: safe to re-run.
|
||||||
|
#
|
||||||
|
# Native lm-sensors (CPU/board temps, fans) is installed on every target.
|
||||||
|
# The TEMPer USB layer (venv + temperusb + udev rule) is installed ONLY when
|
||||||
|
# a 0c45:7401 probe is attached.
|
||||||
|
#
|
||||||
|
# Usage (from repo root):
|
||||||
|
# VM_IP=stlpc-artroom VM_USER=root bash tests/remote.sh vm-copy dcinfra/sensors/temper/lmsensors-extend.sh /tmp/lmsensors-extend.sh
|
||||||
|
# VM_IP=stlpc-artroom VM_USER=root bash tests/remote.sh vm-file dcinfra/sensors/temper/deploy.sh
|
||||||
|
# PROX_HOST=pfv-tsys1 bash tests/remote.sh prox-copy dcinfra/sensors/temper/lmsensors-extend.sh /tmp/lmsensors-extend.sh
|
||||||
|
# PROX_HOST=pfv-tsys1 bash tests/remote.sh prox-file dcinfra/sensors/temper/deploy.sh
|
||||||
|
#
|
||||||
|
# snmpd is a targeted exception: sensor hosts + tsys-* Proxmox hosts
|
||||||
|
# (founder-directed 2026-08-27), not fleet-wide.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
COMMUNITY="${SNMP_COMMUNITY:-kn3lmgmt}"
|
||||||
|
VENV_DIR="${VENV_DIR:-/opt/temper-venv}"
|
||||||
|
WRAP_SRC="${WRAP_SRC:-/tmp/lmsensors-extend.sh}"
|
||||||
|
WRAP_DST="/usr/local/bin/lmsensors-extend"
|
||||||
|
WRAP_DST_OLD="/usr/local/bin/temper-lmsensors"
|
||||||
|
SNMPD_CONF="/etc/snmp/snmpd.conf"
|
||||||
|
UDEV_RULE="/etc/udev/rules.d/99-temper.rules"
|
||||||
|
MARK_BEGIN="# lmsensors-extend managed block begin"
|
||||||
|
MARK_END="# lmsensors-extend managed block end"
|
||||||
|
OLD_MARK_BEGIN="# temper-lmsensors managed block begin"
|
||||||
|
OLD_MARK_END="# temper-lmsensors managed block end"
|
||||||
|
# Source allowlist (founder ruling 2026-08-27: bind Tailscale-only where
|
||||||
|
# possible; HA is a Tailscale APP connector so its polls source from its LAN
|
||||||
|
# IP and must be ACL'd explicitly — no broad subnet grants).
|
||||||
|
# 192.168.3.176 tsys-librenms LAN (poller)
|
||||||
|
# 100.86.204.77 tsys-librenms Tailscale
|
||||||
|
# 192.168.3.12 pfv-bms / Home Assistant LAN (app connector source)
|
||||||
|
# 100.67.108.125 pfv-bms Tailscale
|
||||||
|
# 100.101.187.119 admin workstation Tailscale
|
||||||
|
# 152.53.37.179 + 2a0a:4cc0:2000:686::a496:3bff:fe35:610a Cloudron VPS
|
||||||
|
# (future Uptime Kuma SNMP polling)
|
||||||
|
SNMP_SOURCES="${SNMP_SOURCES:-192.168.3.176 100.86.204.77 192.168.3.12 100.67.108.125 100.101.187.119 152.53.37.179 2a0a:4cc0:2000:686:a496:3bff:fe35:610a}"
|
||||||
|
|
||||||
|
log() { echo "[deploy] $*"; }
|
||||||
|
|
||||||
|
has_probe() {
|
||||||
|
command -v lsusb >/dev/null 2>&1 && lsusb | grep -qi '0c45:7401'
|
||||||
|
}
|
||||||
|
|
||||||
|
host_lan_ip() {
|
||||||
|
# first 192.168.* address (mgmt/user LAN; excludes 127/10.100 storage/dockers)
|
||||||
|
ip -4 addr show 2>/dev/null | grep -oP 'inet \K192\.168\.[0-9.]+' | head -1
|
||||||
|
}
|
||||||
|
|
||||||
|
host_ts_ip() {
|
||||||
|
command -v tailscale >/dev/null 2>&1 && tailscale ip -4 2>/dev/null | head -1
|
||||||
|
}
|
||||||
|
|
||||||
|
[[ $EUID -eq 0 ]] || { echo "must run as root" >&2; exit 1; }
|
||||||
|
[[ -f "$WRAP_SRC" ]] || { echo "wrapper not found at $WRAP_SRC (copy it first)" >&2; exit 1; }
|
||||||
|
|
||||||
|
# --- native lm-sensors layer (all hosts) ---
|
||||||
|
if ! dpkg -s lm-sensors >/dev/null 2>&1; then
|
||||||
|
log "installing lm-sensors"
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get update -qq
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq lm-sensors
|
||||||
|
fi
|
||||||
|
if [[ -z "$(sensors 2>/dev/null)" ]]; then
|
||||||
|
log "no sensor readings yet; trying coretemp/k10temp modules"
|
||||||
|
modprobe coretemp 2>/dev/null || true
|
||||||
|
modprobe k10temp 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
log "native sensors: $(sensors 2>/dev/null | grep -c '°C') temp readings"
|
||||||
|
|
||||||
|
# --- TEMPer USB layer (probe-conditional) ---
|
||||||
|
if has_probe; then
|
||||||
|
log "TEMPer probe present: installing venv layer"
|
||||||
|
if ! dpkg -s python3-venv >/dev/null 2>&1; then
|
||||||
|
log "installing python3-venv"
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get update -qq
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq python3-venv
|
||||||
|
fi
|
||||||
|
if [[ ! -x "$VENV_DIR/bin/pip" ]]; then
|
||||||
|
[[ -d "$VENV_DIR" ]] && { log "removing broken venv $VENV_DIR"; rm -rf "$VENV_DIR"; }
|
||||||
|
log "creating venv $VENV_DIR"
|
||||||
|
python3 -m venv "$VENV_DIR"
|
||||||
|
fi
|
||||||
|
log "ensuring temperusb in venv"
|
||||||
|
"$VENV_DIR/bin/pip" install --quiet --disable-pip-version-check temperusb
|
||||||
|
|
||||||
|
# TEMPer USB dongle must be readable by the unprivileged snmpd user
|
||||||
|
# (extend runs as Debian-snmp; pyusb needs the raw usb device node).
|
||||||
|
log "installing udev rule for 0c45:7401"
|
||||||
|
cat >"$UDEV_RULE" <<'EOF'
|
||||||
|
SUBSYSTEM=="usb", ATTRS{idVendor}=="0c45", ATTRS{idProduct}=="7401", MODE="0666"
|
||||||
|
EOF
|
||||||
|
udevadm control --reload-rules
|
||||||
|
udevadm trigger
|
||||||
|
else
|
||||||
|
log "no TEMPer probe: skipping venv/udev layer"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "installing wrapper to $WRAP_DST"
|
||||||
|
install -m 0755 "$WRAP_SRC" "$WRAP_DST"
|
||||||
|
rm -f "$WRAP_DST_OLD"
|
||||||
|
|
||||||
|
if ! dpkg -s snmpd >/dev/null 2>&1 || ! command -v snmpget >/dev/null 2>&1; then
|
||||||
|
log "installing snmpd + snmp client"
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get update -qq
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq snmpd snmp
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "configuring snmpd (community=$COMMUNITY, scoped sources)"
|
||||||
|
LAN_IP="$(host_lan_ip)"
|
||||||
|
TS_IP="$(host_ts_ip)"
|
||||||
|
AGENT_ADDRS=""
|
||||||
|
[[ -n "$LAN_IP" ]] && AGENT_ADDRS="udp:${LAN_IP}:161"
|
||||||
|
[[ -n "$TS_IP" ]] && AGENT_ADDRS="${AGENT_ADDRS:+$AGENT_ADDRS,}udp:${TS_IP}:161"
|
||||||
|
[[ -n "$AGENT_ADDRS" ]] || { echo "no LAN/Tailscale address found; refusing to bind 0.0.0.0" >&2; exit 1; }
|
||||||
|
sed -i "/^$OLD_MARK_BEGIN\$/,/^$OLD_MARK_END\$/d" "$SNMPD_CONF"
|
||||||
|
sed -i "/^$MARK_BEGIN\$/,/^$MARK_END\$/d" "$SNMPD_CONF"
|
||||||
|
sed -i 's/^agentaddress/#agentaddress/' "$SNMPD_CONF"
|
||||||
|
{
|
||||||
|
echo "$MARK_BEGIN"
|
||||||
|
echo "agentaddress $AGENT_ADDRS"
|
||||||
|
for src in $SNMP_SOURCES; do
|
||||||
|
echo "rocommunity $COMMUNITY $src"
|
||||||
|
done
|
||||||
|
echo "extend lmsensors $WRAP_DST"
|
||||||
|
echo "$MARK_END"
|
||||||
|
} >>"$SNMPD_CONF"
|
||||||
|
log "agentaddress: $AGENT_ADDRS"
|
||||||
|
|
||||||
|
log "restarting snmpd"
|
||||||
|
systemctl enable --now snmpd >/dev/null
|
||||||
|
systemctl restart snmpd
|
||||||
|
|
||||||
|
log "wrapper output:"
|
||||||
|
"$WRAP_DST"
|
||||||
|
log "snmp extend self-test (expect lm-sensors block below):"
|
||||||
|
sleep 1
|
||||||
|
snmpget -v2c -c "$COMMUNITY" -Ovq localhost \
|
||||||
|
.1.3.6.1.4.1.8072.1.3.2.3.1.2.9.108.109.115.101.110.115.111.114.115 2>/dev/null || \
|
||||||
|
log "WARN: snmpget self-test failed (check snmpd logs)"
|
||||||
|
log "done"
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# lm-sensors extend for snmpd: native sensors + TEMPer USB block [#341/#439]
|
||||||
|
#
|
||||||
|
# Deployed to /usr/local/bin/lmsensors-extend on sensor hosts by deploy.sh.
|
||||||
|
# Output = lm-sensors format, consumed by Home Assistant's SNMP integration
|
||||||
|
# (nsExtendOutputFull."lmsensors") and, if enabled later, LibreNMS's
|
||||||
|
# lm-sensors app.
|
||||||
|
#
|
||||||
|
# Layers (each optional, at least one required):
|
||||||
|
# 1. native /usr/bin/sensors output (CPU/board temps, fans) — lm-sensors pkg
|
||||||
|
# 2. TEMPer USB probe (temperusb in venv /opt/temper-venv) — probe-optional
|
||||||
|
# (venv-on-host is founder-approved for sensor hosts, incl. Proxmox)
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
VENV_PY="${VENV_PY:-/opt/temper-venv/bin/python}"
|
||||||
|
NATIVE_SENSORS_BIN="${NATIVE_SENSORS_BIN:-/usr/bin/sensors}"
|
||||||
|
CHIP_NAME="${CHIP_NAME:-temper-usb-1}"
|
||||||
|
|
||||||
|
# Render lm-sensors format block from internal/external Celsius values.
|
||||||
|
# Empty/absent external renders internal only. Non-numeric input is rejected.
|
||||||
|
format_block() {
|
||||||
|
local internal="${1:-}" external="${2:-}"
|
||||||
|
local num_re='^-?[0-9]+(\.[0-9]+)?$'
|
||||||
|
[[ "$internal" =~ $num_re ]] || {
|
||||||
|
echo "error: internal temp not numeric: '$internal'" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
local signed
|
||||||
|
printf '%s\nAdapter: USB adapter\n' "$CHIP_NAME"
|
||||||
|
signed="${internal#+}"; [[ "$signed" == -* ]] || signed="+$signed"
|
||||||
|
printf 'Internal: %s°C\n' "$signed"
|
||||||
|
if [[ -n "$external" && "$external" =~ $num_re ]]; then
|
||||||
|
signed="${external#+}"; [[ "$signed" == -* ]] || signed="+$signed"
|
||||||
|
printf 'External: %s°C\n' "$signed"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# True when a TEMPer USB dongle (0c45:7401) is attached and the venv exists.
|
||||||
|
has_temper_probe() {
|
||||||
|
command -v lsusb >/dev/null 2>&1 || return 1
|
||||||
|
[[ -x "$VENV_PY" ]] || return 1
|
||||||
|
lsusb | grep -qi '0c45:7401'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Read the TEMPer probe via temperusb; prints "<internal> <external>" in C.
|
||||||
|
# Sensor 0 = internal, sensor 1 = external probe (if attached).
|
||||||
|
read_temper() {
|
||||||
|
"$VENV_PY" - <<'PY'
|
||||||
|
from temperusb import TemperHandler
|
||||||
|
|
||||||
|
internal = external = ""
|
||||||
|
for device in TemperHandler().get_devices():
|
||||||
|
try:
|
||||||
|
temps = device.get_temperatures(sensors=[0, 1])
|
||||||
|
except (ValueError, KeyError):
|
||||||
|
temps = device.get_temperatures()
|
||||||
|
for index, slot in ((0, "internal"), (1, "external")):
|
||||||
|
reading = temps.get(index)
|
||||||
|
if reading and reading.get("temperature_c") is not None:
|
||||||
|
value = f"{reading['temperature_c']:.1f}"
|
||||||
|
if slot == "internal":
|
||||||
|
internal = value
|
||||||
|
else:
|
||||||
|
external = value
|
||||||
|
print(f"{internal} {external}".strip())
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
run_native_sensors() {
|
||||||
|
[[ -x "$NATIVE_SENSORS_BIN" ]] || return 1
|
||||||
|
"$NATIVE_SENSORS_BIN" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
local out="" block internal external native
|
||||||
|
if native="$(run_native_sensors)" && [[ -n "$native" ]]; then
|
||||||
|
out+="$native"$'\n'
|
||||||
|
fi
|
||||||
|
if has_temper_probe; then
|
||||||
|
if block="$(read_temper 2>/dev/null)" \
|
||||||
|
&& read -r internal external <<<"$block" \
|
||||||
|
&& block="$(format_block "${internal:-}" "${external:-}")"; then
|
||||||
|
out+="$block"$'\n'
|
||||||
|
else
|
||||||
|
echo "warn: TEMPer probe present but read failed" >&2
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
[[ -n "$out" ]] || {
|
||||||
|
echo "error: no sensor sources available (lm-sensors pkg? TEMPer venv?)" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
printf '%s' "$out"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
||||||
|
main
|
||||||
|
fi
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Unit tests for the lm-sensors extend wrapper (dcinfra/sensors/temper) [#341/#439]
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
SCRIPT="$PROJECT_ROOT/dcinfra/sensors/temper/lmsensors-extend.sh"
|
||||||
|
|
||||||
|
failed=0
|
||||||
|
|
||||||
|
# Script must exist and source cleanly (main guarded for sourcing)
|
||||||
|
if [[ -f "$SCRIPT" ]] && (source "$SCRIPT"); then
|
||||||
|
echo "✅ script exists + sources cleanly"
|
||||||
|
else
|
||||||
|
echo "❌ script missing or fails to source: $SCRIPT"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
source "$SCRIPT"
|
||||||
|
|
||||||
|
# lm-sensors format block: chip, adapter, one line per sensor, +X.X°C
|
||||||
|
test_format_two_sensors() {
|
||||||
|
local got expected
|
||||||
|
expected=$(printf 'temper-usb-1\nAdapter: USB adapter\nInternal: +23.5°C\nExternal: +18.2°C')
|
||||||
|
got="$(format_block "23.5" "18.2")"
|
||||||
|
if [[ "$got" == "$expected" ]]; then
|
||||||
|
echo "✅ format_block renders two-sensor lm-sensors output"
|
||||||
|
else
|
||||||
|
echo "❌ format_block two-sensor output mismatch"
|
||||||
|
printf 'got:\n%s\nexpected:\n%s\n' "$got" "$expected"
|
||||||
|
((++failed))
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
test_format_single_sensor() {
|
||||||
|
local got expected
|
||||||
|
expected=$(printf 'temper-usb-1\nAdapter: USB adapter\nInternal: +23.5°C')
|
||||||
|
got="$(format_block "23.5" "")"
|
||||||
|
if [[ "$got" == "$expected" ]]; then
|
||||||
|
echo "✅ format_block omits absent external sensor"
|
||||||
|
else
|
||||||
|
echo "❌ format_block single-sensor output mismatch"
|
||||||
|
printf 'got:\n%s\nexpected:\n%s\n' "$got" "$expected"
|
||||||
|
((++failed))
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
test_format_negative() {
|
||||||
|
if format_block "-5.0" | grep -q 'Internal: -5.0°C'; then
|
||||||
|
echo "✅ format_block handles negative temps"
|
||||||
|
else
|
||||||
|
echo "❌ format_block mangles negative temps"
|
||||||
|
((++failed))
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
test_format_garbage_rejected() {
|
||||||
|
if format_block "not-a-number" >/dev/null 2>&1; then
|
||||||
|
echo "❌ format_block accepted non-numeric input"
|
||||||
|
((++failed))
|
||||||
|
else
|
||||||
|
echo "✅ format_block rejects non-numeric input"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# main() composition with stubbed binaries via PATH + env overrides
|
||||||
|
STUB_DIR="$(mktemp -d "/tmp/lmsensors-test-$$-XXXXXX")"
|
||||||
|
trap 'rm -rf "$STUB_DIR"' EXIT
|
||||||
|
|
||||||
|
make_stub_lsusb() {
|
||||||
|
if [[ "${1:-}" == "probe" ]]; then
|
||||||
|
printf '#!/bin/bash\necho "Bus 001 Device 007: ID 0c45:7401 Microdia TEMPer Temperature Sensor"\n' >"$STUB_DIR/lsusb"
|
||||||
|
else
|
||||||
|
printf '#!/bin/bash\nexit 0\n' >"$STUB_DIR/lsusb"
|
||||||
|
fi
|
||||||
|
chmod +x "$STUB_DIR/lsusb"
|
||||||
|
}
|
||||||
|
|
||||||
|
make_stub_sensors() {
|
||||||
|
printf '#!/bin/bash\nprintf "coretemp-isa-0000\\nAdapter: ISA adapter\\nPackage id 0: +45.0°C\\nCore 0: +41.0°C\\n"\n' >"$STUB_DIR/sensors"
|
||||||
|
chmod +x "$STUB_DIR/sensors"
|
||||||
|
}
|
||||||
|
|
||||||
|
make_stub_python() {
|
||||||
|
printf '#!/bin/bash\necho "23.5 18.2"\n' >"$STUB_DIR/fakepython"
|
||||||
|
chmod +x "$STUB_DIR/fakepython"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_main_native_only() {
|
||||||
|
make_stub_lsusb noprobe
|
||||||
|
make_stub_sensors
|
||||||
|
local got
|
||||||
|
got="$(PATH="$STUB_DIR:$PATH" NATIVE_SENSORS_BIN="$STUB_DIR/sensors" \
|
||||||
|
VENV_PY=/nonexistent main)"
|
||||||
|
if grep -q 'Package id 0' <<<"$got" && ! grep -q 'temper-usb-1' <<<"$got"; then
|
||||||
|
echo "✅ main: native sensors only (no probe)"
|
||||||
|
else
|
||||||
|
echo "❌ main: native-only composition wrong"
|
||||||
|
((++failed))
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
test_main_temper_only() {
|
||||||
|
make_stub_lsusb probe
|
||||||
|
make_stub_python
|
||||||
|
local got
|
||||||
|
got="$(PATH="$STUB_DIR:$PATH" NATIVE_SENSORS_BIN=/nonexistent \
|
||||||
|
VENV_PY="$STUB_DIR/fakepython" main)"
|
||||||
|
if grep -q 'Internal: +23.5°C' <<<"$got" && ! grep -q 'Package id 0' <<<"$got"; then
|
||||||
|
echo "✅ main: TEMPer only (no lm-sensors bin)"
|
||||||
|
else
|
||||||
|
echo "❌ main: temper-only composition wrong"
|
||||||
|
((++failed))
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
test_main_merge() {
|
||||||
|
make_stub_lsusb probe
|
||||||
|
make_stub_sensors
|
||||||
|
make_stub_python
|
||||||
|
local got
|
||||||
|
got="$(PATH="$STUB_DIR:$PATH" NATIVE_SENSORS_BIN="$STUB_DIR/sensors" \
|
||||||
|
VENV_PY="$STUB_DIR/fakepython" main)"
|
||||||
|
if grep -q 'Package id 0' <<<"$got" && grep -q 'Internal: +23.5°C' <<<"$got"; then
|
||||||
|
echo "✅ main: native + TEMPer merged"
|
||||||
|
else
|
||||||
|
echo "❌ main: merge composition wrong"
|
||||||
|
((++failed))
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
test_main_neither_fails() {
|
||||||
|
make_stub_lsusb noprobe
|
||||||
|
if PATH="$STUB_DIR:$PATH" NATIVE_SENSORS_BIN=/nonexistent \
|
||||||
|
VENV_PY=/nonexistent main >/dev/null 2>&1; then
|
||||||
|
echo "❌ main: succeeded with no sensor sources"
|
||||||
|
((++failed))
|
||||||
|
else
|
||||||
|
echo "✅ main: fails cleanly with no sensor sources"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
test_format_two_sensors
|
||||||
|
test_format_single_sensor
|
||||||
|
test_format_negative
|
||||||
|
test_format_garbage_rejected
|
||||||
|
test_main_native_only
|
||||||
|
test_main_temper_only
|
||||||
|
test_main_merge
|
||||||
|
test_main_neither_fails
|
||||||
|
|
||||||
|
if ((failed > 0)); then
|
||||||
|
echo "❌ lm-sensors extend: $failed test(s) failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "PASS: lm-sensors extend unit tests"
|
||||||
Reference in New Issue
Block a user