testsAndMisc/phone_focus_mode/lib/tests/daemon_libs_harness.sh
Krzysztof kuhy Rudnicki 094c4765f3
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
focus_daemon: split 543 -> 206 + 165 + 140 + 94, with 110 tests
Location/curfew goes to daemon_location.sh, the derived state files to
daemon_state.sh, the app layer to daemon_apps.sh. All 23 functions moved
verbatim -- nothing restructured, which matters more here than anywhere
else in this session.

focus_daemon.sh has five `while IFS= read -r pkg; do ... done <file` loops
and EVERY body calls pm. Piping any of them in lets pm inherit and drain
the loop's stdin, so it would process one package instead of all of them --
measured elsewhere in this repo as 1 of 54. That audit ran BEFORE the
extraction, not after coverage came back short, and all five moved
untouched with a note above them saying why.

CURRENT_MODE and _CURFEW_TICK_CACHED are each written in both the entry
script and a library, so both stay declared with main, same call as
NET_BUILT in the curfew split.

110 assertions and 33 mutations, all killed. Two survived the first run and
both were defects in my own tests: the whitelist and sysprotect exactness
checks used a SUPERSTRING of a listed entry, which fails under grep -qF and
grep -qxF alike and so proved nothing about which was in use.

Two subjects are NOT at 100%, and the numbers are reported as measured:

  daemon_state.sh     67/67 = 100.00%
  daemon_location.sh  26/40 =  65.00%
  daemon_apps.sh      71/76 =  93.42%

Both shortfalls are kcov attribution failures, shown to be artifacts rather
than gaps. daemon_location.sh's missing lines are all inside the embedded
awk Haversine; mutating the Earth radius and dropping the longitude term
both fail the suite, and an isolated probe shows a one-line awk measuring
100% where the identical multi-line one measures 0%. Collapsing the formula
to satisfy the tool would cost the readability of the only real mathematics
in the repo, so it stays.

daemon_apps.sh's five are the trailing `done <file` of the very loops
described above. That is the same artifact piping fixed in curfew_net.sh,
and it is exactly what must NOT be done here. Redirecting two of them from
/dev/null fails the suite, so the lines run; all three are pinned in the
mutation spec instead.

Over-cap count 19 -> 18.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-17 16:54:04 +02:00

161 lines
4.3 KiB
Bash
Executable File

#!/usr/bin/env bash
# lib/tests/daemon_libs_harness.sh — the fake device behind the three
# libraries split out of focus_daemon.sh.
#
# Sourced, not executed. All three drive the phone through real binaries — pm,
# dumpsys, cmd, date — so the stubs go on PATH.
#
# `date` is stubbed rather than mocked as a function because is_curfew_now
# calls it in a command substitution; a shell function would work here but
# would not survive the subject being run any other way, and the point of the
# midnight-wrap cases is to pin the clock precisely.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly SCRIPT_DIR
readonly PHONE_DIR="${SCRIPT_DIR}/../.."
PASS=0
FAIL=0
_t_pass() {
PASS=$((PASS + 1))
printf ' OK: %s\n' "$1"
}
_t_fail() {
FAIL=$((FAIL + 1))
printf ' FAIL: %s\n' "$1"
}
_t_eq() {
local want="$1" got="$2" what="$3"
if [[ "$got" == "$want" ]]; then
_t_pass "$what"
else
_t_fail "$what (want '${want}', got '${got}')"
fi
}
RUN="$(mktemp -d)"
trap 'rm -rf "${RUN}"' EXIT
mkdir -p "${RUN}/bin" "${RUN}/state" "${RUN}/dev"
readonly DEV="${RUN}/dev"
# --- stubs ------------------------------------------------------------------
# pm records every call and honours a per-package failure flag, so the
# "disable refused" branches are reachable.
cat >"${RUN}/bin/pm" <<'STUB'
#!/usr/bin/env bash
printf 'pm %s\n' "$*" >>"${DEV}/calls.log"
case "$1" in
list)
[[ -f "${DEV}/pm_list" ]] && cat "${DEV}/pm_list"
exit 0
;;
disable-user)
pkg="${*: -1}"
grep -qxF "${pkg}" "${DEV}/pm_undisableable" 2>/dev/null && exit 1
printf '%s\n' "${pkg}" >>"${DEV}/disabled"
exit 0
;;
enable)
pkg="${*: -1}"
grep -qxF "${pkg}" "${DEV}/pm_unenableable" 2>/dev/null && exit 1
printf '%s\n' "${pkg}" >>"${DEV}/enabled"
exit 0
;;
esac
exit 0
STUB
chmod +x "${RUN}/bin/pm"
cat >"${RUN}/bin/dumpsys" <<'STUB'
#!/usr/bin/env bash
printf 'dumpsys %s\n' "$*" >>"${DEV}/calls.log"
[[ -f "${DEV}/dumpsys_out" ]] && cat "${DEV}/dumpsys_out"
exit 0
STUB
chmod +x "${RUN}/bin/dumpsys"
cat >"${RUN}/bin/cmd" <<'STUB'
#!/usr/bin/env bash
printf 'cmd %s\n' "$*" >>"${DEV}/calls.log"
[[ -f "${DEV}/cmd_out" ]] && cat "${DEV}/cmd_out"
exit 0
STUB
chmod +x "${RUN}/bin/cmd"
# `date` answers from a pinned clock when one is set, so the curfew-window
# cases are deterministic rather than dependent on when the suite runs.
cat >"${RUN}/bin/date" <<'STUB'
#!/usr/bin/env bash
if [[ -f "${DEV}/now" && "$1" == "+%H%M" ]]; then
cat "${DEV}/now"
exit 0
fi
exec /usr/bin/date "$@"
STUB
chmod +x "${RUN}/bin/date"
export DEV
export PATH="${RUN}/bin:${PATH}"
# --- ambient definitions the libraries expect from the daemon --------------
log() { printf '%s\n' "$*" >>"${DEV}/log"; }
STATE_DIR="${RUN}/state"
STATUS_FILE="${RUN}/state/status.json"
MODE_FILE="${RUN}/state/mode"
DISABLED_APPS_FILE="${RUN}/state/disabled_apps.txt"
CURFEW_FORCE_FILE="${RUN}/state/curfew_force"
CURFEW_OVERRIDE_FILE="${RUN}/state/curfew_override"
NIGHT_CURFEW_ENABLED=1
NIGHT_CURFEW_START="2300"
NIGHT_CURFEW_END="0500"
WHITELIST="com.allowed.one com.allowed.two"
NIGHT_WHITELIST="com.night.only"
SYSTEM_NEVER_DISABLE="com.android."
BLOCKED_SYSTEM_APPS="com.android.browser"
RADIUS=100
# Written by init/enable/disable in the entry script and read by main, so it
# lives there; the harness stands in for that ownership.
CURRENT_MODE="normal"
_CURFEW_TICK_CACHED=""
_CURFEW_TICK_RESULT=0
# shellcheck source=../../daemon_location.sh
. "${PHONE_DIR}/daemon_location.sh"
# shellcheck source=../../daemon_state.sh
. "${PHONE_DIR}/daemon_state.sh"
# shellcheck source=../../daemon_apps.sh
. "${PHONE_DIR}/daemon_apps.sh"
# --- helpers ----------------------------------------------------------------
_reset_dev() {
rm -rf "${DEV}"
mkdir -p "${DEV}"
_CURFEW_TICK_CACHED=""
_CURFEW_TICK_RESULT=0
}
_set_now() { printf '%s\n' "$1" >"${DEV}/now"; }
_calls() { cat "${DEV}/calls.log" 2>/dev/null || printf ''; }
_log() { cat "${DEV}/log" 2>/dev/null || printf ''; }
_disabled() { cat "${DEV}/disabled" 2>/dev/null || printf ''; }
_enabled() { cat "${DEV}/enabled" 2>/dev/null || printf ''; }
# Seed the installed third-party package list `pm list packages -3 -e` returns.
_seed_packages() {
local pkg=""
: >"${DEV}/pm_list"
for pkg in "$@"; do
printf 'package:%s\n' "${pkg}" >>"${DEV}/pm_list"
done
}