Files
shturman/tests/e2e/run.sh
T
2026-06-25 15:43:10 +03:00

359 lines
24 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# Сквозной E2E Штурмана в Lima-VM (приёмка v0.1/v0.6 + шагающий скелет, спека §9.3/§9.4).
# Запуск: just e2e (двухфазно через reboot) или just run (однофазно, без reboot).
#
# Фазы (env E2E_PHASE):
# pre — сборка → install бинарей/юнитов → старт shturman.target → проверки §9.3 (1–8)
# → выставить персист-пробу (Settings.Set + запомнить machine-id); [по умолчанию]
# post — после reboot: персист настройки сохранился + machine-id стабилен (every-boot bind, §9.3.4).
#
# Системная шина устройства (§5.1). dev-mocks включён дефолт-фичей сборки (fake-ACC).
set -uo pipefail
REPO=/shturman
cd "$REPO" || { echo "нет $REPO"; exit 1; }
PHASE="${E2E_PHASE:-pre}"
IDLE_SECS="${E2E_IDLE_SECS:-20}" # окно простоя для eMMC-прокси (§7.5)
EMMC_MAX_SECTORS="${E2E_EMMC_MAX_SECTORS:-4096}" # порог T (🟡 калибруется; 4096 сект ≈ 2 МБ/окно)
FRAME=/run/shturman/frame.png
PROBE_KEY=ui.theme
PROBE_VAL=night
MID_BEFORE=/data/state/e2e-mid-before # снимок machine-id до reboot (персист в /data)
export PATH="$HOME/.cargo/bin:$PATH"
export CARGO_TARGET_DIR="${CARGO_TARGET_DIR:-$HOME/.cache/shturman/target}"
pass() { echo " ✓ $*"; }
fail() { echo "E2E FAIL: $*" >&2; exit 1; }
info() { echo; echo "== $* =="; }
# Имена на шине (зеркало crates/shturman-ipc/src/names.rs).
P_NAME=ru.shturman.Power; P_PATH=/ru/shturman/Power; P_IFACE=ru.shturman.Power1
P_MOCK=ru.shturman.dev.PowerMock1
S_NAME=ru.shturman.Settings; S_PATH=/ru/shturman/Settings; S_IFACE=ru.shturman.Settings1
settings_get() { busctl --system call "$S_NAME" "$S_PATH" "$S_IFACE" Get s "$1" 2>/dev/null; }
# =========================== POST-reboot фаза ===========================
if [ "$PHASE" = post ]; then
info "POST-reboot: персист настроек + machine-id стабилен (§9.3.4)"
# дождаться, пока сервисы поднимутся после автозагрузки target
for _ in $(seq 1 30); do systemctl is-active --quiet shturman-settings && break; sleep 1; done
findmnt /data >/dev/null || fail "/data не смонтирован после reboot"
pass "/data смонтирован после reboot"
# настройка пережила reboot
got=$(settings_get "$PROBE_KEY")
echo "$got" | grep -q "\"$PROBE_VAL\"" || fail "Settings.$PROBE_KEY != $PROBE_VAL после reboot (got: $got)"
pass "Settings.$PROBE_KEY = $PROBE_VAL пережил reboot"
# machine-id стабилен (every-boot bind из /data/state/machine-id)
sudo test -f "$MID_BEFORE" || fail "нет снимка $MID_BEFORE (фаза pre не отработала?)"
before=$(sudo cat "$MID_BEFORE"); now=$(cat /etc/machine-id)
[ -n "$now" ] || fail "/etc/machine-id пуст"
[ "$before" = "$now" ] || fail "machine-id изменился: было $before, стало $now"
src=$(sudo cat /data/state/machine-id)
[ "$now" = "$src" ] || fail "/etc/machine-id($now) != /data/state/machine-id($src) — bind не сработал"
pass "machine-id стабилен после reboot ($now), привязан из /data"
# journald volatile теперь естественно (drop-in присутствовал на boot)
test -d /run/log/journal && ! test -d /var/log/journal \
&& pass "journald volatile (/run/log/journal)" || echo " WARN: journald не строго volatile"
echo; echo "E2E POST OK ✅"
exit 0
fi
# ============================ PRE фаза ============================
info "сборка (release, VM-локальный target=$CARGO_TARGET_DIR)"
cargo build --release --workspace || fail "сборка"
for b in firstboot settings power shell splash; do
sudo install -m755 "$CARGO_TARGET_DIR/release/shturman-$b" /usr/local/bin/ || fail "install shturman-$b"
done
pass "бинари установлены в /usr/local/bin"
info "раскладка systemd-юнитов + dbus policy (из репо — подхватить правки)"
sudo install -m644 systemd/shturman.target systemd/data.mount \
systemd/shturman-stage0.target systemd/shturman-stage1.target systemd/shturman-stage2.target \
/etc/systemd/system/
sudo install -m644 systemd/shturman-firstboot.service systemd/shturman-machineid.service \
systemd/shturman-power.service systemd/shturman-settings.service \
systemd/shturman-shell.service systemd/shturman-splash.service \
systemd/shturman-stage2-warmup.service systemd/shturman-savetime.service \
/etc/systemd/system/
sudo install -d /etc/dbus-1/system.d
sudo install -m644 systemd/dbus/ru.shturman.conf /etc/dbus-1/system.d/
sudo install -d /etc/systemd/journald.conf.d /etc/systemd/oomd.conf.d
sudo install -m644 systemd/journald-shturman.conf /etc/systemd/journald.conf.d/shturman.conf
sudo install -m644 systemd/oomd-shturman.conf /etc/systemd/oomd.conf.d/shturman.conf
sudo install -m644 systemd/zram-generator.conf /etc/systemd/zram-generator.conf
sudo install -d /etc/tmpfiles.d
sudo install -m644 systemd/tmpfiles-shturman.conf /etc/tmpfiles.d/shturman.conf
sudo systemd-tmpfiles --create /etc/tmpfiles.d/shturman.conf || true
# watchdog (B05/A14) + save-time .timer (B07)
sudo install -d /etc/systemd/system.conf.d
sudo install -m644 systemd/watchdog-shturman.conf /etc/systemd/system.conf.d/shturman-watchdog.conf
sudo install -m644 systemd/shturman-savetime.timer /etc/systemd/system/
sudo systemctl daemon-reload
# применить конфиги детерминированно (на свежем boot drop-in’ы появились после старта демонов)
sudo systemctl reload dbus 2>/dev/null || true
sudo systemctl restart systemd-journald 2>/dev/null || true
sudo rm -rf /var/log/journal 2>/dev/null || true # устаревший persistent-журнал (до drop-in); volatile его не пересоздаст
sudo modprobe zram 2>/dev/null || true # zram-модуль (linux-modules-extra); может отсутствовать в vz-ядре
sudo systemctl start "systemd-zram-setup@zram0.service" 2>/dev/null || true
sudo systemctl restart systemd-oomd 2>/dev/null || sudo systemctl start systemd-oomd 2>/dev/null || true # подхватить oomd.conf.d
pass "юниты/политики разложены"
info "старт shturman.target (зонтик → Stage 0/1/2)"
sudo systemctl start shturman.target || fail "shturman.target не стартовал"
# Перезапустить демоны, чтобы подхватить свежесобранные бинари (на повторном just run — иначе крутится
# старый бинарь, start=no-op; на чистом vm-reset сервисы и так стартуют с новым). shell НЕ трогаем:
# иначе frame.png стал бы новее stage2.ready и сломал бы ассерт «warmup после кадра» (порядок фаз).
sudo systemctl restart shturman-power.service shturman-settings.service
for _ in $(seq 1 15); do systemctl is-active --quiet shturman-shell && break; sleep 1; done
# ---- 1. /data до сервисов + реальные power-safe опции (§9.3.1) ----
info "1. /data смонтирован, реальные non-default опции + volatile-слой"
findmnt /data >/dev/null || fail "/data не смонтирован"
opts=$(findmnt -no OPTIONS /data)
echo "$opts" | grep -q errors=remount-ro || fail "нет errors=remount-ro (opts: $opts)"
pass "/data: $opts"
# volatile-слой (tmpfs) присутствует — кадр/журнал/транзиент пишутся сюда, не на flash (A11).
# Полный RO-rootfs + overlay(upper на tmpfs) — на HW/v4 (A/B boot-select нет в VM, §7.1); тут — дисциплина + tmpfs.
findmnt -t tmpfs /run >/dev/null || fail "/run не tmpfs (нет volatile-слоя)"
pass "volatile-слой: /run = tmpfs"
# ---- 2. first-boot маркер + идемпотентность (§9.3.2) ----
info "2. first-boot маркер + machine-id"
sudo test -f /data/.shturman-provisioned || fail "нет маркера .shturman-provisioned"
sudo test -f /data/state/machine-id || fail "нет /data/state/machine-id"
sudo systemctl start shturman-firstboot.service # повторно — Condition гейтит → no-op
pass "first-boot маркер на месте, повторный запуск no-op"
# ---- 3. per-unit critical set active (degraded не маскирует, §9.3.1) ----
info "3. per-unit critical set"
for u in shturman-power shturman-settings shturman-shell; do
systemctl is-active --quiet "$u" || fail "$u не active ($(systemctl is-active "$u" 2>&1))"
pass "$u: active"
done
for u in shturman-firstboot shturman-machineid; do
state=$(systemctl is-active "$u" 2>&1)
# oneshot валиден в active (отработал, RemainAfterExit) ИЛИ inactive (корректно пропущен Condition'ом
# на повторном boot: firstboot — marker есть; reflects clean+re-run). Реальный сбой = failed/activating.
case "$state" in
active | inactive) pass "$u: $state (oneshot)" ;;
*) fail "$u не active/inactive (сбой): $state" ;;
esac
done
# ---- 4. имена на системной шине (own) + сервис отвечает (§9.3.3) ----
info "4. имена на шине + отклик"
busctl --system list | grep -q "$P_NAME" || fail "нет $P_NAME на шине"
busctl --system list | grep -q "$S_NAME" || fail "нет $S_NAME на шине"
busctl --system call "$P_NAME" "$P_PATH" "$P_IFACE" GetPowerState | grep -q running \
|| fail "Power.GetPowerState != running"
pass "$P_NAME / $S_NAME владеют именами; GetPowerState=running"
# ---- 5. fake-ACC: SetAcc -> AccChanged (§9.3.5) ----
info "5. fake-ACC SetAcc -> AccChanged"
mon=$(mktemp)
# sudo нужен busctl для eavesdrop системной шины; редирект в $mon — намеренно user-owned mktemp (SC2024 ок).
# shellcheck disable=SC2024
sudo busctl --system monitor "$P_NAME" >"$mon" 2>&1 &
MON=$!
sleep 1
busctl --system call "$P_NAME" "$P_PATH" "$P_MOCK" SetAcc b false || { sudo kill "$MON" 2>/dev/null; fail "вызов SetAcc"; }
sleep 1
sudo kill "$MON" 2>/dev/null; wait "$MON" 2>/dev/null
grep -q AccChanged "$mon" || { echo "--- monitor ---"; cat "$mon"; fail "AccChanged не наблюдаем"; }
rm -f "$mon"
busctl --system call "$P_NAME" "$P_PATH" "$P_MOCK" SetAcc b true >/dev/null 2>&1 || true # вернуть acc=on
pass "AccChanged наблюдаем после SetAcc"
# ---- 7. первый Slint-кадр: PNG не пустой (§9.3.6) ----
info "7. первый Slint-кадр (software-render PNG)"
sudo test -f "$FRAME" || fail "нет кадра $FRAME (shell.service не отрендерил?)"
sz=$(sudo stat -c%s "$FRAME"); [ "$sz" -gt 10000 ] || fail "кадр подозрительно мал ($sz Б)"
sudo head -c8 "$FRAME" | od -An -tx1 | tr -d ' \n' | grep -qi "89504e47" || fail "$FRAME не PNG"
pass "кадр $FRAME: $sz Б, валидный PNG"
# ---- Stage 0/1/2 разделены (v0.2 boot-конвейер) ----
info "Stage 0/1/2: фазы разделены + splash до кадра + warmup после"
for t in shturman-stage0 shturman-stage1 shturman-stage2; do
systemctl is-active --quiet "$t.target" || fail "$t.target не достигнут ($(systemctl is-active "$t.target" 2>&1))"
pass "$t.target reached"
done
sudo test -f /run/shturman/splash.png || fail "нет splash.png (Stage 0)"
sudo head -c8 /run/shturman/splash.png | od -An -tx1 | tr -d ' \n' | grep -qi 89504e47 || fail "splash.png не PNG"
fr=$(sudo stat -c %Y "$FRAME"); sp=$(sudo stat -c %Y /run/shturman/splash.png)
[ "$sp" -le "$fr" ] || fail "splash.png ($sp) позже frame.png ($fr) — Stage 0 не раньше Stage 1"
sudo test -f /run/shturman/stage2.ready || fail "нет stage2.ready (Stage 2 warmup не отработал)"
w2=$(sudo stat -c %Y /run/shturman/stage2.ready)
[ "$w2" -ge "$fr" ] || fail "stage2.ready ($w2) раньше кадра ($fr) — Stage 2 не деферред"
pass "порядок фаз: splash($sp) ≤ frame($fr) ≤ stage2($w2)"
# boot-тайминг (функц., НЕ гейт; вердикт — RK3588, performance §2)
echo " $(systemd-analyze time 2>/dev/null | head -1 || echo 'systemd-analyze н/д')"
# ---- power-safe (v0.3): FSM ShutdownImminent + N циклов зажигания + abort + power-cut ----
info "power-safe: ShutdownImminent + N=3 цикла зажигания + abort + power-cut"
# Чистый FSM Running для циклов (свежий бинарь + сброс любого «залипшего» состояния от §5 fake-ACC).
# reset-failed: блок ниже намеренно рестартит power N+ раз — сбрасываем счётчик StartLimitBurst (дефолт 5/10s),
# иначе systemd ловит start-limit-hit и power падает в failed (имя ru.shturman.Power на шине теряется).
sudo systemctl reset-failed shturman-power.service shturman-settings.service 2>/dev/null || true
sudo systemctl restart shturman-power.service
for _ in $(seq 1 10); do systemctl is-active --quiet shturman-power && break; sleep 1; done
sleep 1 # дать power re-acquire ru.shturman.Power на шине
P_CALL() { busctl --system call "$P_NAME" "$P_PATH" "$P_MOCK" "$@"; }
busctl --system call "$S_NAME" "$S_PATH" "$S_IFACE" Set sv ui.theme s night >/dev/null
echo 0 | sudo tee /data/state/power-cycles >/dev/null
observe_imminent() { # SetAcc(false) → ждём ShutdownImminent на шине
local mon; mon=$(mktemp)
# shellcheck disable=SC2024
sudo busctl --system monitor "$P_NAME" >"$mon" 2>&1 & local M=$!
sleep 0.7; P_CALL SetAcc b false >/dev/null; sleep 0.7
sudo kill "$M" 2>/dev/null; wait "$M" 2>/dev/null
grep -q ShutdownImminent "$mon" || { echo "--- mon ---"; cat "$mon"; rm -f "$mon"; return 1; }
rm -f "$mon"
}
for i in 1 2 3; do
observe_imminent || fail "цикл $i: ShutdownImminent не наблюдаем"
n=$(($(sudo cat /data/state/power-cycles) + 1))
sudo systemctl stop shturman-stage1.target # стоп сервисов (освобождает /data)
sudo umount /etc/machine-id 2>/dev/null || true # снять machineid-bind, иначе /data busy
sync; sudo umount /data || fail "цикл $i: umount /data (PONR)"
findmnt /data >/dev/null && fail "цикл $i: /data не размонтирован (PONR не достигнут)"
sudo systemctl reset-failed shturman-power.service shturman-settings.service 2>/dev/null || true
sudo systemctl start shturman.target # re-mount data.mount + сервисы
# machineid — oneshot RemainAfterExit (уже active): plain start его НЕ перезапускает, bind не вернётся.
# restart форсит ExecStart → пере-bind /data/state/machine-id поверх снятого выше. Без этого /etc/machine-id
# залипает на нижнем rootfs-значении, и POST-чек стабильности machine-id (§9.3.4) падает после reboot.
sudo systemctl restart shturman-machineid.service
for _ in $(seq 1 15); do systemctl is-active --quiet shturman-settings && systemctl is-active --quiet shturman-power && break; sleep 1; done
findmnt /data >/dev/null || fail "цикл $i: /data не вернулся после remount"
echo "$n" | sudo tee /data/state/power-cycles >/dev/null
pass "цикл зажигания $i: stop→umount(PONR)→remount→restart, /data вернулся"
done
got=$(busctl --system call "$S_NAME" "$S_PATH" "$S_IFACE" Get s ui.theme 2>/dev/null)
echo "$got" | grep -q '"night"' || fail "ui.theme потерян после циклов"
[ "$(sudo cat /data/state/power-cycles)" = 3 ] || fail "счётчик циклов != 3"
pass "N=3 цикла: /data + счётчик целы (нет потери)"
# abort до PONR
mon=$(mktemp)
# shellcheck disable=SC2024
sudo busctl --system monitor "$P_NAME" >"$mon" 2>&1 & M=$!
sleep 0.7; P_CALL SetAcc b false >/dev/null; sleep 0.3; P_CALL SetAcc b true >/dev/null; sleep 0.7
sudo kill "$M" 2>/dev/null; wait "$M" 2>/dev/null
grep -q ShutdownAborted "$mon" || { cat "$mon"; rm -f "$mon"; fail "ShutdownAborted не наблюдаем"; }
rm -f "$mon"
findmnt /data >/dev/null || fail "/data не смонтирован после abort"
busctl --system call "$P_NAME" "$P_PATH" "$P_IFACE" GetPowerState | grep -q running || fail "не running после abort"
pass "abort до PONR: ShutdownAborted + /data RW + running"
# power-cut-сим: SIGKILL во время shutdown → /data консистентен
P_CALL SetAcc b false >/dev/null; sleep 0.3
sudo systemctl kill -s KILL shturman-power.service shturman-settings.service 2>/dev/null || true
sudo systemctl stop shturman-stage1.target 2>/dev/null || true
sudo umount /etc/machine-id 2>/dev/null || true
sudo umount /data 2>/dev/null || sudo umount -l /data 2>/dev/null || true
findmnt /data >/dev/null && fail "power-cut: /data не размонтирован (fsck был бы на смонтированном)"
sudo fsck.ext4 -n /var/lib/shturman/data.img >/dev/null 2>&1 || fail "fsck /data не clean после power-cut"
sudo systemctl reset-failed shturman-power.service shturman-settings.service 2>/dev/null || true
sudo systemctl start shturman.target # re-mount + restart
sudo systemctl restart shturman-machineid.service # пере-bind machine-id (plain start не перезапускает oneshot — см. цикл)
for _ in $(seq 1 15); do systemctl is-active --quiet shturman-settings && systemctl is-active --quiet shturman-power && break; sleep 1; done
sudo grep -q night /data/settings/settings.json || fail "last durable value потерян после power-cut"
pass "power-cut-сим: /data консистентен (fsck clean, night present)"
# watchdog/save-time конфиг
test -f /etc/systemd/system.conf.d/shturman-watchdog.conf || fail "нет watchdog-конфига"
systemctl is-active --quiet shturman-savetime.timer && pass "savetime.timer активен" || echo " WARN: savetime.timer не активен"
pass "watchdog-конфиг на месте"
# ---- v0.4: thermal-trip + throttling + MCU fail-safe (мок-sensor/MCU) ----
# Каждый под-тест стартует с чистого running-power (рестарт сбрасывает MockTempSource→20, FSM→running,
# и счётчик start-limit). thermal-abort покрыт integration-тестом (P8.7) — в E2E не гоняем с grace-окном.
info "v0.4: thermal-trip → ShutdownImminent(thermal); throttling-банд; MCU fail-safe (hang → cut)"
P_RESTART() { # чистый рестарт power → running
sudo systemctl reset-failed shturman-power.service 2>/dev/null || true
sudo systemctl restart shturman-power.service
for _ in $(seq 1 10); do systemctl is-active --quiet shturman-power && break; sleep 1; done
sleep 1.5 # дать циклам (poll/heartbeat ~1с) стартовать
}
# thermal-trip: SetTemp ≥ critical → ShutdownImminent(thermal) (монитор poll ~1с; ловим до grace-commit)
P_RESTART
mon=$(mktemp)
# shellcheck disable=SC2024
sudo busctl --system monitor "$P_NAME" >"$mon" 2>&1 & M=$!
sleep 0.4; P_CALL SetTemp i 99; sleep 1.6
sudo kill "$M" 2>/dev/null; wait "$M" 2>/dev/null
grep -q ShutdownImminent "$mon" || { cat "$mon"; rm -f "$mon"; fail "thermal: ShutdownImminent не наблюдаем"; }
grep -q thermal "$mon" || { cat "$mon"; rm -f "$mon"; fail "thermal: reason != thermal"; }
rm -f "$mon"
pass "thermal-trip: SetTemp≥critical → ShutdownImminent(thermal)"
# throttling-банд (85..95) → ThermalState=throttle, БЕЗ shutdown
P_RESTART
P_CALL SetTemp i 88; sleep 2
ts=$(busctl --system get-property "$P_NAME" "$P_PATH" "$P_IFACE" ThermalState 2>/dev/null)
echo "$ts" | grep -q throttle || { echo "ThermalState=$ts"; fail "thermal: ThermalState != throttle на 88°C"; }
busctl --system call "$P_NAME" "$P_PATH" "$P_IFACE" GetPowerState | grep -q running || fail "thermal: throttle не должен ронять"
pass "throttling-банд: ThermalState=throttle на 88°C, без shutdown"
# MCU fail-safe: HangSoc → heartbeat пропал → MCU режет (FSM → off) детерминированно (B09)
P_RESTART
P_CALL HangSoc
ok=0
for _ in $(seq 1 10); do
sleep 1
busctl --system call "$P_NAME" "$P_PATH" "$P_IFACE" GetPowerState 2>/dev/null | grep -q off && { ok=1; break; }
done
[ "$ok" = 1 ] || fail "MCU fail-safe: power не off после HangSoc (B09 не сработал)"
pass "MCU fail-safe: HangSoc → MCU cut (FSM off), детерминированно"
P_RESTART # чистый running для последующих блоков
# ---- 8. base-бюджеты: journald / zram / fake-hwclock / eMMC-прокси (§9.3.7) ----
info "8. base-бюджеты (функц.)"
# journald volatile: активный журнал в /run/log/journal, persistent /var/log/journal отсутствует (A10)
test -d /run/log/journal || fail "journald не volatile (нет /run/log/journal)"
test -d /var/log/journal && fail "journald пишет в persistent /var/log/journal (нарушение A10)"
pass "journald volatile (/run/log/journal, без /var/log/journal)"
# ровно одно zram-устройство (A09); модуль zram может отсутствовать в vz-ядре — честная VM↔HW-граница
zn=$(zramctl --noheadings 2>/dev/null | wc -l | tr -d ' ')
if [ "$zn" = 1 ]; then pass "zram: ровно одно устройство ($(zramctl --noheadings --output NAME 2>/dev/null))"
elif [ "$zn" = 0 ]; then echo " WARN: zram-устройств нет (модуль zram отсутствует в vz-ядре — HW-only, §13)"
else fail "zram: ожидалось одно устройство, найдено $zn"; fi
# fake-hwclock пишет в /data, не в /etc (A11). Override — FILE из /etc/default/fake-hwclock
# (сервис в Lima masked: Lima сам синхронит время — на HW юнит размаскирован, EnvironmentFile тот же).
sudo sh -c '. /etc/default/fake-hwclock 2>/dev/null; FILE="${FILE:-/data/state/fake-hwclock.data}" fake-hwclock save' || true
sudo test -f /data/state/fake-hwclock.data || fail "fake-hwclock не записал в /data/state/fake-hwclock.data"
sudo test -f /etc/fake-hwclock.data && fail "fake-hwclock пишет в /etc (нарушение A11)"
pass "fake-hwclock → /data/state/fake-hwclock.data (не в /etc)"
# systemd-oomd: запущен + наша политика загружена (A09). PSI/cgroup2 нужны — в vz есть; иначе honest WARN.
if [ "$(systemctl is-active systemd-oomd 2>/dev/null)" = active ] && test -f /etc/systemd/oomd.conf.d/shturman.conf; then
pass "systemd-oomd active, политика oomd.conf.d/shturman.conf загружена"
else echo " WARN: systemd-oomd не active (нет PSI/пакета — проверь провижининг)"; fi
# eMMC-прокси: дельта записанных секторов loop-/data за окно простоя
src=$(findmnt -no SOURCE /data); dev=$(basename "$src")
if [ ! -e "/sys/block/$dev" ]; then dev=$(losetup -j /var/lib/shturman/data.img -O NAME --noheadings 2>/dev/null | tr -d ' ' | xargs -r basename); fi
read_w() { awk -v d="$dev" '$3==d {print $10}' /proc/diskstats; }
s0=$(read_w); echo " окно простоя ${IDLE_SECS}s (loop-dev=$dev)…"; sleep "$IDLE_SECS"; s1=$(read_w)
delta=$(( ${s1:-0} - ${s0:-0} ))
echo " eMMC-прокси: записано $delta секторов за ${IDLE_SECS}s (порог $EMMC_MAX_SECTORS, ~калибровка)"
[ "$delta" -le "$EMMC_MAX_SECTORS" ] || fail "eMMC: дельта $delta > порога $EMMC_MAX_SECTORS секторов"
pass "eMMC-прокси в пороге"
# ---- персист-проба для POST-фазы ----
info "персист-проба (для проверки после reboot)"
busctl --system call "$S_NAME" "$S_PATH" "$S_IFACE" Set sv "$PROBE_KEY" s "$PROBE_VAL" || fail "Settings.Set"
got=$(settings_get "$PROBE_KEY"); echo "$got" | grep -q "\"$PROBE_VAL\"" || fail "Set не применился (got: $got)"
sudo cp /etc/machine-id "$MID_BEFORE" # снимок до reboot
pass "Settings.$PROBE_KEY=$PROBE_VAL выставлен; machine-id снят в $MID_BEFORE"
echo; echo "E2E PRE OK ✅ (для полной приёмки — reboot + фаза post: just e2e)"