mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 20:27:02 +01:00
monitor: backfill to 100% coverage, fixing two defects it exposed
The split shipped verbatim-proven and green, but measuring it afterwards
showed the tests barely touched it: monitor.sh 44.44%, the policy probes
7.69%, and the health probes never loaded at all. Every _check_* decides
whether phone enforcement is reported as healthy, so an untested probe is
an enforcement failure that reports itself as fine.
The mock stubs adb_root_shell at the function boundary rather than putting
a fake adb on PATH: the probes never invoke adb, they call a function from
adb_common.sh, so a PATH stub would never be consulted. Dispatch is by
pattern, not exact string, because the real commands embed awk programs
with escaped $2, nested quotes and 2>/dev/null redirections.
Two defects the new tests found:
- _mon_escape_json never escaped double quotes. Written as
${escaped//"/\"}, bash reads the bare quote as opening a quoted string,
so the pattern silently degraded to `/\` with an empty replacement. Any
check message containing a quote produced invalid JSON.
- monitor_print_format_warning's multi-line array literal and
collect_snapshot's `{ ... } >file` group held 12 lines that no test
could ever cover: kcov instruments the continuation lines and the
closing brace, but bash only reports a statement at its first line.
Reproduced in an isolated 9-line case before changing anything.
Assertions are mutation-proofed: 18 mutations of the probes' status
decisions, 18 killed. The first run had one survivor -- the repairable
flag was asserted nowhere -- now pinned in both directions.
monitor.sh 104/104 = 100.00% (was 44.44%)
monitor_checks_policy.sh 65/65 = 100.00% (was 7.69%)
monitor_checks_health.sh 79/79 = 100.00% (was 0%)
test_monitor.sh 82 passed, 0 failed (was 9)
Cases live in two sourced files purely to stay under the 250-line cap;
test_monitor.sh remains the single entry point, so one coverage command
still measures all three subjects. Over-cap count unchanged at 22.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
This commit is contained in:
parent
584985d06e
commit
0f210e5532
@ -0,0 +1,20 @@
|
||||
{
|
||||
"title": "Backfill monitor.sh and both probe libraries to 100% line coverage",
|
||||
"objective": "Close the pre-existing test debt the monitor.sh split made visible: monitor.sh at 44.44%, monitor_checks_policy.sh at 7.69% and monitor_checks_health.sh never loaded at all. Every _check_* probe decides whether phone enforcement is reported as healthy, so an untested probe is an enforcement failure that reports itself as fine.",
|
||||
"acceptance_criteria": [
|
||||
"bash meta/scripts/shell_coverage.sh phone_focus_mode/lib/tests/test_monitor.sh <subject> exits 0 at its default 100% minimum for each of monitor.sh, monitor_checks_policy.sh and monitor_checks_health.sh.",
|
||||
"bash phone_focus_mode/lib/tests/test_monitor.sh reports a test count strictly greater than the 9 it reported before this change, with 0 failures.",
|
||||
"Every assertion is mutation-proofed: a scripted run breaks each _check_* status decision in turn and requires the suite to fail, with zero survivors.",
|
||||
"bash meta/scripts/check_file_length.sh --all reports no more over-cap files than before this change (22), so no new file crosses the 250-line cap.",
|
||||
"shellcheck passes on each touched file individually, with no -x and no disable directive, since the pre-commit hook reads each file standalone.",
|
||||
"No suppression of any kind is added: no shellcheck disable, no lowered coverage threshold, no skipped assertion.",
|
||||
"The mock reaches the probes through the adb_root_shell function, not a PATH stub, so adb_common.sh's device-detection and locking surface stays out of the test."
|
||||
],
|
||||
"out_of_scope": [
|
||||
"Deploying to the phone or otherwise verifying against a real device; there is no device in this session and the probes are proven with stubs, hashes and real runs instead.",
|
||||
"Splitting any of the other brief-scoped files (phone_backup.sh, curfew_enforcer.sh, hosts_enforcer.sh, focus_daemon.sh, config.sh, deploy.sh, focus_ctl.sh), which are separate commits.",
|
||||
"Fixing test_magisk_service.sh, which hangs identically at HEAD and is unrelated to the monitor seam.",
|
||||
"Changing what any probe reports to the user beyond the two defects fixed here."
|
||||
],
|
||||
"verifier": "bash phone_focus_mode/lib/tests/test_monitor.sh (expects 82 passed, 0 failed), plus bash meta/scripts/shell_coverage.sh phone_focus_mode/lib/tests/test_monitor.sh {monitor.sh,monitor_checks_policy.sh,monitor_checks_health.sh} each at 100.00%, plus the mutation run at 18 killed / 0 survived, plus bash meta/scripts/check_file_length.sh --all still showing 22 files, plus pre-commit run --files on the touched files."
|
||||
}
|
||||
@ -0,0 +1,82 @@
|
||||
{
|
||||
"intent": "Take monitor.sh and its two probe libraries from 44.44% / 7.69% / 0% line coverage to 100%, so that the twelve enforcement probes behind the phone monitor are actually tested rather than merely present.",
|
||||
"scope": [
|
||||
"phone_focus_mode/lib/monitor.sh - two defects fixed, two constructs restructured for measurability",
|
||||
"phone_focus_mode/lib/tests/monitor_harness.sh - new: the fake device",
|
||||
"phone_focus_mode/lib/tests/monitor_cases_health.sh - new: helper and health-probe assertions",
|
||||
"phone_focus_mode/lib/tests/monitor_cases_policy.sh - new: policy-probe and snapshot assertions",
|
||||
"phone_focus_mode/lib/tests/test_monitor.sh - reduced to the entry point that sources the three",
|
||||
"Non-goal: any device deploy. There is no phone in this session; the probes are proven with stubs and real runs.",
|
||||
"Non-goal: monitor_checks_health.sh and monitor_checks_policy.sh are untouched - they reached 100% on tests alone."
|
||||
],
|
||||
"changes": [
|
||||
"Added a fake device that mocks adb_root_shell at the function boundary rather than stubbing an adb binary on PATH. The probes never invoke adb; they call adb_root_shell, a function from adb_common.sh, so a PATH stub would never be consulted. Dispatch is by pattern rather than exact string because the real commands embed awk programs with escaped $2, nested quotes and 2>/dev/null redirections.",
|
||||
"Fixed a real JSON-escaping defect in _mon_escape_json: the substitution was written ${escaped//\"/\\\"}, where bash reads the bare quote as opening a quoted string, so the pattern silently degraded to `/\\` with an empty replacement and no quote was ever escaped. Any check message containing a double quote produced invalid JSON, which monitor_report.py then failed to parse. Found by the new test, not by inspection.",
|
||||
"Rewrote two constructs that no test could ever cover: a multi-line array literal in monitor_print_format_warning and a `{ ... } >file` redirection group in monitor_collect_snapshot. kcov instruments the continuation lines and the closing brace, but bash only ever reports a statement at its first line, so those 12 lines were permanently unreachable. Reproduced in an isolated 9-line case before changing anything; both are now per-statement.",
|
||||
"Removed an 11-line block of blank lines left in monitor.sh by the earlier split.",
|
||||
"Split the assertions across two sourced case files so every test file stays under the 250-line cap while test_monitor.sh remains the single entry point, keeping one coverage command able to measure all three subjects.",
|
||||
"Resolved SC2329 and SC2034 structurally rather than with disables: the non-numeric-count guard is now driven through a child bash process fed by a quoted heredoc (the device dns_iptables_harness.sh already uses), and the FORMAT_INDICATORS reset is read back immediately."
|
||||
],
|
||||
"verification": [
|
||||
{
|
||||
"command": "bash phone_focus_mode/lib/tests/test_monitor.sh",
|
||||
"result": "pass",
|
||||
"evidence": "Results: 82 passed, 0 failed. Was 9 passed before this change."
|
||||
},
|
||||
{
|
||||
"command": "bash meta/scripts/shell_coverage.sh phone_focus_mode/lib/tests/test_monitor.sh monitor.sh",
|
||||
"result": "pass",
|
||||
"evidence": "monitor.sh: 104/104 lines = 100.00% (exit 0 at the default 100% minimum). Baseline was 48/108 = 44.44%."
|
||||
},
|
||||
{
|
||||
"command": "bash meta/scripts/shell_coverage.sh phone_focus_mode/lib/tests/test_monitor.sh monitor_checks_policy.sh",
|
||||
"result": "pass",
|
||||
"evidence": "monitor_checks_policy.sh: 65/65 lines = 100.00%. Baseline was 5/65 = 7.69%."
|
||||
},
|
||||
{
|
||||
"command": "bash meta/scripts/shell_coverage.sh phone_focus_mode/lib/tests/test_monitor.sh monitor_checks_health.sh",
|
||||
"result": "pass",
|
||||
"evidence": "monitor_checks_health.sh: 79/79 lines = 100.00%. Baseline was 0% - kcov reported 'instrumented no lines' because the file was never loaded."
|
||||
},
|
||||
{
|
||||
"command": "mutation run: break each _check_* status decision in turn, require the suite to fail, restore",
|
||||
"result": "pass",
|
||||
"evidence": "18 mutations, 18 killed, 0 survived. The first run had 1 survivor - the repairable flag was asserted nowhere - which was closed by pinning it in both directions on a dead daemon and a healthy check."
|
||||
},
|
||||
{
|
||||
"command": "bash meta/scripts/check_file_length.sh --all",
|
||||
"result": "pass",
|
||||
"evidence": "22 over-cap files, unchanged from before this commit. An intermediate 411-line test_monitor.sh briefly made it 23; that is why the cases are split across two files."
|
||||
},
|
||||
{
|
||||
"command": "shellcheck on each touched file individually (no -x)",
|
||||
"result": "pass",
|
||||
"evidence": "All seven of monitor.sh, monitor_checks_health.sh, monitor_checks_policy.sh, monitor_harness.sh, monitor_cases_health.sh, monitor_cases_policy.sh and test_monitor.sh report clean standalone."
|
||||
},
|
||||
{
|
||||
"command": "timeout 60 bash phone_focus_mode/run_phone.sh --help",
|
||||
"result": "pass",
|
||||
"evidence": "exit 0, proving the source chain through monitor.sh into both probe libraries still resolves after the edits."
|
||||
},
|
||||
{
|
||||
"command": "jscpd, the pre-commit hook's exact invocation, measured in a clean HEAD worktree with and without these files",
|
||||
"result": "pass",
|
||||
"evidence": "1.43% at HEAD, 1.45% with these four files added - one new 19-line clone of the _t_pass/_t_fail/_t_eq block, well under the 2% gate. Working-tree runs report 2.47% because of vendored .venv site-packages, which is pre-existing and out of scope."
|
||||
},
|
||||
{
|
||||
"command": "bash phone_focus_mode/lib/tests/{test_dns_iptables,test_tether_enforcer,test_adb_common,test_adb_trusted}.sh",
|
||||
"result": "pass",
|
||||
"evidence": "24, 17, 10 and 6 passed respectively, all 0 failed - the sibling suites are unaffected."
|
||||
}
|
||||
],
|
||||
"risks": [
|
||||
"The JSON-escaping fix changes what monitor_collect_snapshot writes for any check message containing a double quote. That output was invalid JSON before, so nothing could have depended on it, but a consumer parsing the old malformed shape leniently would now see a different string.",
|
||||
"The probes are verified against a stub device, not a real phone. The mock encodes what the commands are believed to return; a ROM whose dumpsys or df output differs in shape would still be mis-parsed, and no test here would catch it.",
|
||||
"monitor_collect_snapshot now writes report.json with three redirections instead of one grouped redirect. If the process were killed between them the file would be truncated rather than absent; previously the group could leave a partial file too, so this is a change in shape rather than in kind."
|
||||
],
|
||||
"rollback": [
|
||||
"git revert the commit. The three subjects return to their previous behaviour, including the JSON-escaping defect.",
|
||||
"After rollback, re-run bash phone_focus_mode/lib/tests/test_monitor.sh - it should report 9 passed - and timeout 60 bash phone_focus_mode/run_phone.sh --help expecting exit 0.",
|
||||
"The coverage regression is the point of the change, so a rollback should be paired with re-opening the backfill rather than treated as resolved."
|
||||
]
|
||||
}
|
||||
@ -29,7 +29,11 @@ _mon_escape_json() {
|
||||
local escaped="$1"
|
||||
|
||||
escaped=${escaped//\\/\\\\}
|
||||
escaped=${escaped//"/\\"}
|
||||
# The quote must be backslash-escaped in the pattern. Written bare as
|
||||
# ${escaped//"/\\"} bash reads the quote as opening a quoted string, so the
|
||||
# pattern silently becomes `/\` with an empty replacement and no quote is
|
||||
# ever escaped — which emitted invalid JSON for any message containing one.
|
||||
escaped=${escaped//\"/\\\"}
|
||||
escaped=${escaped//$'\n'/\\n}
|
||||
escaped=${escaped//$'\r'/\\r}
|
||||
printf '%s' "${escaped}"
|
||||
@ -126,27 +130,29 @@ monitor_is_formatted() {
|
||||
(( missing_count >= FORMAT_DETECTION_MIN_MISSING ))
|
||||
}
|
||||
|
||||
# Each array element is appended on its own statement rather than listed
|
||||
# across a multi-line literal: kcov instruments the continuation lines but
|
||||
# bash only ever reports the statement's first line, so a literal spanning N
|
||||
# lines is permanently stuck at 1/N covered however thoroughly it is tested.
|
||||
monitor_print_format_warning() {
|
||||
local -a missing_indicators=("$@")
|
||||
local -a box_lines=(
|
||||
""
|
||||
"The following expected components were NOT found:"
|
||||
)
|
||||
local -a box_lines=()
|
||||
local indicator=""
|
||||
|
||||
box_lines+=("")
|
||||
box_lines+=("The following expected components were NOT found:")
|
||||
|
||||
for indicator in "${missing_indicators[@]}"; do
|
||||
box_lines+=(" ✗ ${indicator}")
|
||||
done
|
||||
|
||||
box_lines+=(
|
||||
""
|
||||
"This strongly suggests the phone was factory-reset or formatted."
|
||||
""
|
||||
"Next step: run the full recovery workflow:"
|
||||
" ./scripts/run_all/run_phone.sh fresh-phone"
|
||||
""
|
||||
"Do NOT run 'auto' mode — it will not restore anything."
|
||||
)
|
||||
box_lines+=("")
|
||||
box_lines+=("This strongly suggests the phone was factory-reset or formatted.")
|
||||
box_lines+=("")
|
||||
box_lines+=("Next step: run the full recovery workflow:")
|
||||
box_lines+=(" ./scripts/run_all/run_phone.sh fresh-phone")
|
||||
box_lines+=("")
|
||||
box_lines+=("Do NOT run 'auto' mode — it will not restore anything.")
|
||||
|
||||
_box "PHONE APPEARS TO HAVE BEEN WIPED" "${box_lines[@]}"
|
||||
}
|
||||
@ -155,13 +161,6 @@ monitor_print_format_warning() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
monitor_collect_snapshot() {
|
||||
local snapshot_dir="$1"
|
||||
local tmp_checks=""
|
||||
@ -183,11 +182,13 @@ monitor_collect_snapshot() {
|
||||
_check_companion_app "${tmp_checks}"
|
||||
_check_boot_persistence "${tmp_checks}"
|
||||
|
||||
{
|
||||
printf '{"timestamp":"%s","device":"%s","checks":[\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$( _mon_escape_json "${ADB_SERIAL}" )"
|
||||
paste -sd ',' "${tmp_checks}"
|
||||
printf '\n]}\n'
|
||||
} >"${report_path}"
|
||||
# Redirected per statement rather than as a `{ ... } >file` group: kcov
|
||||
# counts the group's closing brace as an instrumented line that bash never
|
||||
# reports, which alone held this file below 100%.
|
||||
printf '{"timestamp":"%s","device":"%s","checks":[\n' \
|
||||
"$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$(_mon_escape_json "${ADB_SERIAL}")" >"${report_path}"
|
||||
paste -sd ',' "${tmp_checks}" >>"${report_path}"
|
||||
printf '\n]}\n' >>"${report_path}"
|
||||
|
||||
cp "${report_path}" "$(dirname "${snapshot_dir}")/latest.json" 2>/dev/null || true
|
||||
rm -f "${tmp_checks}"
|
||||
|
||||
249
phone_focus_mode/lib/tests/monitor_cases_health.sh
Executable file
249
phone_focus_mode/lib/tests/monitor_cases_health.sh
Executable file
@ -0,0 +1,249 @@
|
||||
#!/usr/bin/env bash
|
||||
# lib/tests/monitor_cases_health.sh — assertions for monitor.sh's own helpers
|
||||
# (JSON escaping, hosts-target resolution, pidfile/liveness, format
|
||||
# indicators) and the device-health probes in monitor_checks_health.sh.
|
||||
#
|
||||
# Sourced by test_monitor.sh after monitor_harness.sh, which owns the fake
|
||||
# device, the subject, and the PASS/FAIL counters this file adds to.
|
||||
set -euo pipefail
|
||||
|
||||
# --- helpers under monitor.sh ----------------------------------------------
|
||||
|
||||
line="$(_mon_check "test_check" "ok" "some_cmd" "all good" "false")"
|
||||
_t_eq "test_check" "$(_field "${line}" check)" "_mon_check outputs check name"
|
||||
_t_eq "ok" "$(_field "${line}" status)" "_mon_check outputs status"
|
||||
|
||||
escaped="$(_mon_check 'a"b\c' "ok" "s" $'l1\nl2\r' "false")"
|
||||
case "${escaped}" in
|
||||
*'a\"b\\c'*) _t_pass "_mon_check escapes quotes and backslashes" ;;
|
||||
*) _t_fail "_mon_check did not escape quotes/backslashes: ${escaped}" ;;
|
||||
esac
|
||||
case "${escaped}" in
|
||||
*'l1\nl2\r'*) _t_pass "_mon_check escapes newlines and carriage returns" ;;
|
||||
*) _t_fail "_mon_check did not escape newlines: ${escaped}" ;;
|
||||
esac
|
||||
|
||||
_t_eq "a b" "$(_trim_output $' a b \r\n')" "_trim_output strips CR and outer space"
|
||||
_t_eq "" "$(_safe_adb_root_output "no-such-command")" "_safe_adb_root_output swallows failure"
|
||||
|
||||
# --- hosts target resolution ------------------------------------------------
|
||||
|
||||
_dev_reset
|
||||
_t_eq "" "$(_monitor_resolve_hosts_target)" "resolve_hosts_target empty when no candidate exists"
|
||||
|
||||
_dev_reset
|
||||
_dev_present /system/etc/hosts
|
||||
_t_eq "/system/etc/hosts" "$(_monitor_resolve_hosts_target)" \
|
||||
"resolve_hosts_target short-circuits on the primary target"
|
||||
|
||||
_dev_reset
|
||||
_dev_present /vendor/etc/hosts
|
||||
_t_eq "/vendor/etc/hosts" "$(_monitor_resolve_hosts_target)" \
|
||||
"resolve_hosts_target falls through to the first existing candidate"
|
||||
|
||||
# --- pidfile and liveness ---------------------------------------------------
|
||||
|
||||
_dev_reset
|
||||
_t_eq "" "$(_monitor_read_pidfile /data/local/tmp/focus_mode/daemon.pid)" \
|
||||
"read_pidfile empty when the pidfile is absent"
|
||||
|
||||
_dev_set pidfile_daemon 1234
|
||||
_t_eq "1234" "$(_monitor_read_pidfile /data/local/tmp/focus_mode/daemon.pid)" \
|
||||
"read_pidfile returns the stored pid"
|
||||
|
||||
_dev_reset
|
||||
if ! _monitor_pid_matches_script 999 "focus_daemon.sh"; then
|
||||
_t_pass "pid_matches_script fails when the pid is not alive"
|
||||
else
|
||||
_t_fail "pid_matches_script should fail for a dead pid"
|
||||
fi
|
||||
|
||||
_dev_pid 999
|
||||
_dev_cmdline 999 "/system/bin/sh focus_daemon.sh"
|
||||
if _monitor_pid_matches_script 999 "focus_daemon.sh"; then
|
||||
_t_pass "pid_matches_script succeeds when cmdline matches"
|
||||
else
|
||||
_t_fail "pid_matches_script should succeed on a cmdline match"
|
||||
fi
|
||||
|
||||
# A live pid whose cmdline is hidden is trusted anyway — the deliberate
|
||||
# false-negative guard at monitor.sh's "Some Android shells hide cmdline".
|
||||
_dev_reset
|
||||
_dev_pid 777
|
||||
if _monitor_pid_matches_script 777 "focus_daemon.sh"; then
|
||||
_t_pass "pid_matches_script trusts a live pid with unreadable cmdline"
|
||||
else
|
||||
_t_fail "pid_matches_script should trust a live pid when cmdline is hidden"
|
||||
fi
|
||||
|
||||
# --- format indicators ------------------------------------------------------
|
||||
|
||||
_dev_reset
|
||||
FORMAT_INDICATORS=(
|
||||
"present|test -f /present"
|
||||
"missing-one|test -f /missing-one"
|
||||
"missing-two|test -f /missing-two"
|
||||
)
|
||||
_dev_present /present
|
||||
|
||||
mapfile -t missing < <(monitor_check_format_indicators)
|
||||
_t_eq "2" "${#missing[@]}" "check_format_indicators reports only the missing ones"
|
||||
_t_eq "missing-one missing-two" "${missing[*]}" "check_format_indicators names them in order"
|
||||
_t_eq "2" "$(monitor_count_missing_format_indicators)" "count_missing_format_indicators counts them"
|
||||
|
||||
if monitor_is_formatted; then
|
||||
_t_pass "monitor_is_formatted true at the missing-indicator threshold"
|
||||
else
|
||||
_t_fail "monitor_is_formatted should be true with 2 missing and threshold 2"
|
||||
fi
|
||||
|
||||
_t_eq "fatal" "$(_field "$(_probe _check_format_indicators)" status)" \
|
||||
"_check_format_indicators is fatal at the threshold"
|
||||
|
||||
_dev_present /missing-one
|
||||
_t_eq "warn" "$(_field "$(_probe _check_format_indicators)" status)" \
|
||||
"_check_format_indicators warns below the threshold"
|
||||
|
||||
_dev_present /missing-two
|
||||
_t_eq "ok" "$(_field "$(_probe _check_format_indicators)" status)" \
|
||||
"_check_format_indicators is ok with none missing"
|
||||
|
||||
if ! monitor_is_formatted; then
|
||||
_t_pass "monitor_is_formatted false when nothing is missing"
|
||||
else
|
||||
_t_fail "monitor_is_formatted should be false with no missing indicators"
|
||||
fi
|
||||
|
||||
# The guard calls _fatal, which exits, so it runs in a child rather than by
|
||||
# shadowing the function here: an inline redefinition is invoked only
|
||||
# indirectly, so standalone the linter calls it dead (SC2329). The child's
|
||||
# source comes from a quoted heredoc, as in dns_iptables_harness.sh — it is
|
||||
# code for the *child* to expand, so $VAR must survive this file untouched.
|
||||
_mh_bad_count() {
|
||||
MH_HARNESS="${SCRIPT_DIR}/monitor_harness.sh" bash <<'CHILD'
|
||||
. "${MH_HARNESS}"
|
||||
monitor_count_missing_format_indicators() { printf 'not-a-number\n'; }
|
||||
monitor_is_formatted
|
||||
CHILD
|
||||
}
|
||||
|
||||
if ! _mh_bad_count 2>/dev/null; then
|
||||
_t_pass "monitor_is_formatted rejects a non-numeric count"
|
||||
else
|
||||
_t_fail "monitor_is_formatted should abort on a non-numeric count"
|
||||
fi
|
||||
|
||||
monitor_print_format_warning "missing-one" "missing-two"
|
||||
_t_pass "monitor_print_format_warning renders without error"
|
||||
|
||||
# Reset for the probes below, which must see no format indicators. Read back
|
||||
# immediately so the assignment has a reader in this file (SC2034).
|
||||
FORMAT_INDICATORS=()
|
||||
_t_eq "0" "${#FORMAT_INDICATORS[@]}" "format indicators reset before the health probes"
|
||||
|
||||
# --- battery ----------------------------------------------------------------
|
||||
|
||||
_dev_reset
|
||||
_t_eq "warn" "$(_field "$(_probe _check_battery)" status)" "battery warns when the level is unreadable"
|
||||
|
||||
_dev_set battery_level 5
|
||||
_t_eq "warn" "$(_field "$(_probe _check_battery)" status)" "battery warns below the threshold"
|
||||
|
||||
_dev_set battery_level 80
|
||||
_dev_set battery_health Good
|
||||
_dev_set battery_temp 305
|
||||
json="$(_probe _check_battery)"
|
||||
_t_eq "ok" "$(_field "${json}" status)" "battery ok above the threshold"
|
||||
case "$(_field "${json}" message)" in
|
||||
*"health Good"*) _t_pass "battery message carries health and temperature" ;;
|
||||
*) _t_fail "battery message lost health/temp: $(_field "${json}" message)" ;;
|
||||
esac
|
||||
|
||||
# Health and temp missing must not demote the status; they render as unknown.
|
||||
_dev_reset
|
||||
_dev_set battery_level 80
|
||||
case "$(_field "$(_probe _check_battery)" message)" in
|
||||
*"health unknown"*) _t_pass "battery reports unknown health without warning" ;;
|
||||
*) _t_fail "battery should render missing health as unknown" ;;
|
||||
esac
|
||||
|
||||
# --- storage ----------------------------------------------------------------
|
||||
|
||||
_dev_reset
|
||||
_t_eq "warn" "$(_field "$(_probe _check_storage)" status)" "storage warns when both df paths fail"
|
||||
|
||||
_dev_set df_emulated 1000000
|
||||
_t_eq "ok" "$(_field "$(_probe _check_storage)" status)" "storage falls back to /storage/emulated/0"
|
||||
|
||||
_dev_reset
|
||||
_dev_set df_sdcard 1024
|
||||
_t_eq "warn" "$(_field "$(_probe _check_storage)" status)" "storage warns below the MB threshold"
|
||||
|
||||
_dev_set df_sdcard 2000000
|
||||
json="$(_probe _check_storage)"
|
||||
_t_eq "ok" "$(_field "${json}" status)" "storage ok above the threshold"
|
||||
_t_eq "Free storage: 1953 MB" "$(_field "${json}" message)" "storage converts KB to MB"
|
||||
|
||||
# --- daemons ----------------------------------------------------------------
|
||||
|
||||
readonly PIDFILE="/data/local/tmp/focus_mode/daemon.pid"
|
||||
|
||||
_dev_reset
|
||||
json="$(_probe _check_daemon focus_daemon focus_daemon.sh "${PIDFILE}")"
|
||||
_t_eq "error" "$(_field "${json}" status)" \
|
||||
"daemon errors when neither pidfile nor pgrep finds it"
|
||||
# repairable drives whether the monitor offers to restart it, so it is pinned
|
||||
# separately from the status: a dead daemon that reports itself unrepairable
|
||||
# is reported but never fixed.
|
||||
case "${json}" in
|
||||
*'"repairable":true'*) _t_pass "a dead daemon is marked repairable" ;;
|
||||
*) _t_fail "a dead daemon must be repairable: ${json}" ;;
|
||||
esac
|
||||
|
||||
_dev_set pidfile_daemon 4321
|
||||
_dev_pid 4321
|
||||
_dev_cmdline 4321 "focus_daemon.sh"
|
||||
json="$(_probe _check_daemon focus_daemon focus_daemon.sh "${PIDFILE}")"
|
||||
_t_eq "ok" "$(_field "${json}" status)" "daemon ok via the pidfile"
|
||||
_t_eq "focus_daemon running (PID 4321)" "$(_field "${json}" message)" "daemon names the pidfile pid"
|
||||
|
||||
# Stale pidfile, live process: the pgrep fallback is what must find it.
|
||||
_dev_reset
|
||||
_dev_set pidfile_daemon 4321
|
||||
_dev_set pgrep_focus_daemon.sh 8888
|
||||
_dev_pid 8888
|
||||
json="$(_probe _check_daemon focus_daemon focus_daemon.sh "${PIDFILE}")"
|
||||
_t_eq "ok" "$(_field "${json}" status)" "daemon ok via the pgrep fallback"
|
||||
_t_eq "focus_daemon running (PID 8888)" "$(_field "${json}" message)" "daemon names the pgrep pid"
|
||||
|
||||
# pgrep reports a pid that has since exited — neither path may claim ok.
|
||||
_dev_reset
|
||||
_dev_set pgrep_focus_daemon.sh 8888
|
||||
_t_eq "error" "$(_field "$(_probe _check_daemon focus_daemon focus_daemon.sh "${PIDFILE}")" status)" \
|
||||
"daemon errors when the pgrep pid is already dead"
|
||||
|
||||
# --- hosts daemon -----------------------------------------------------------
|
||||
|
||||
_dev_reset
|
||||
_t_eq "warn" "$(_field "$(_probe _check_hosts_daemon)" status)" \
|
||||
"hosts daemon skipped when the ROM has no hosts target"
|
||||
|
||||
_dev_present /system/etc/hosts
|
||||
_t_eq "error" "$(_field "$(_probe _check_hosts_daemon)" status)" \
|
||||
"hosts daemon checked once a hosts target exists"
|
||||
|
||||
# --- launcher daemon --------------------------------------------------------
|
||||
|
||||
_dev_reset
|
||||
_t_eq "warn" "$(_field "$(_probe _check_launcher_daemon)" status)" \
|
||||
"launcher daemon skipped when launcher is unconfigured"
|
||||
|
||||
_dev_reset
|
||||
_dev_present /data/local/tmp/focus_mode/minimalist_launcher.activity
|
||||
_t_eq "error" "$(_field "$(_probe _check_launcher_daemon)" status)" \
|
||||
"launcher daemon checked once a snapshot exists"
|
||||
|
||||
_dev_reset
|
||||
_dev_present pkg_launcher
|
||||
_t_eq "error" "$(_field "$(_probe _check_launcher_daemon)" status)" \
|
||||
"launcher daemon checked once the launcher is installed"
|
||||
178
phone_focus_mode/lib/tests/monitor_cases_policy.sh
Executable file
178
phone_focus_mode/lib/tests/monitor_cases_policy.sh
Executable file
@ -0,0 +1,178 @@
|
||||
#!/usr/bin/env bash
|
||||
# lib/tests/monitor_cases_policy.sh — enforcement-integrity assertions:
|
||||
# hosts integrity, DNS, launcher state, companion app, boot persistence, and
|
||||
# the snapshot/summary/severity surface of monitor.sh.
|
||||
#
|
||||
# Sourced by test_monitor.sh after monitor_harness.sh, which owns the fake
|
||||
# device, the subject, and the PASS/FAIL counters this file adds to. Split out
|
||||
# to keep every test file under the repo's 250-line cap; one entry point still
|
||||
# runs the lot, so a single coverage command measures all three subjects.
|
||||
set -euo pipefail
|
||||
|
||||
# --- hosts integrity --------------------------------------------------------
|
||||
|
||||
_dev_reset
|
||||
_t_eq "warn" "$(_field "$(_probe _check_hosts_integrity)" status)" \
|
||||
"hosts integrity skipped when no hosts target exists"
|
||||
|
||||
_dev_present /system/etc/hosts
|
||||
_t_eq "fatal" "$(_field "$(_probe _check_hosts_integrity)" status)" \
|
||||
"hosts integrity fatal when the canonical copy is missing"
|
||||
|
||||
_dev_present /data/local/tmp/focus_mode/hosts.canonical
|
||||
_t_eq "error" "$(_field "$(_probe _check_hosts_integrity)" status)" \
|
||||
"hosts integrity errors when the hashes cannot be read"
|
||||
|
||||
_dev_set hosts_expected_sha "abc123def456789"
|
||||
_t_eq "error" "$(_field "$(_probe _check_hosts_integrity)" status)" \
|
||||
"hosts integrity errors when only one hash is readable"
|
||||
|
||||
_dev_set hosts_actual_sha "999888777666555"
|
||||
json="$(_probe _check_hosts_integrity)"
|
||||
_t_eq "error" "$(_field "${json}" status)" "hosts integrity errors on a hash mismatch"
|
||||
case "$(_field "${json}" message)" in
|
||||
*"Hosts mismatch"*) _t_pass "hosts integrity names the mismatch" ;;
|
||||
*) _t_fail "hosts integrity message should report a mismatch" ;;
|
||||
esac
|
||||
|
||||
_dev_set hosts_actual_sha "abc123def456789"
|
||||
_t_eq "ok" "$(_field "$(_probe _check_hosts_integrity)" status)" \
|
||||
"hosts integrity ok when the hashes agree"
|
||||
|
||||
# --- DNS --------------------------------------------------------------------
|
||||
|
||||
_dev_reset
|
||||
_t_eq "error" "$(_field "$(_probe _check_dns)" status)" \
|
||||
"dns errors when private DNS is off but the chains are missing"
|
||||
|
||||
_dev_present dns_chain
|
||||
_t_eq "ok" "$(_field "$(_probe _check_dns)" status)" \
|
||||
"dns ok when private DNS is off and the chains exist"
|
||||
|
||||
_dev_set private_dns_mode "off"
|
||||
_t_eq "ok" "$(_field "$(_probe _check_dns)" status)" "dns ok for an explicit off mode"
|
||||
|
||||
_dev_set private_dns_mode "null"
|
||||
_t_eq "ok" "$(_field "$(_probe _check_dns)" status)" "dns ok for a null mode"
|
||||
|
||||
_dev_set private_dns_mode "hostname"
|
||||
json="$(_probe _check_dns)"
|
||||
_t_eq "error" "$(_field "${json}" status)" "dns errors when private DNS is enabled"
|
||||
case "$(_field "${json}" message)" in
|
||||
*"mode=hostname"*) _t_pass "dns names the enabled mode" ;;
|
||||
*) _t_fail "dns should report the enabled mode" ;;
|
||||
esac
|
||||
|
||||
# --- launcher state ---------------------------------------------------------
|
||||
|
||||
_dev_reset
|
||||
_t_eq "warn" "$(_field "$(_probe _check_launcher)" status)" \
|
||||
"launcher warns when it is absent and unconfigured"
|
||||
|
||||
_dev_present /data/local/tmp/focus_mode/minimalist_launcher.activity
|
||||
_t_eq "fatal" "$(_field "$(_probe _check_launcher)" status)" \
|
||||
"launcher fatal when a snapshot exists but the package is gone"
|
||||
|
||||
_dev_reset
|
||||
_dev_present pkg_launcher
|
||||
_t_eq "warn" "$(_field "$(_probe _check_launcher)" status)" \
|
||||
"launcher warns when the snapshot metadata is empty"
|
||||
|
||||
_dev_set launcher_desired "com.qqlabs.minimalistlauncher/.MainActivity"
|
||||
_dev_set launcher_actual "com.android.launcher3/.Launcher"
|
||||
json="$(_probe _check_launcher)"
|
||||
_t_eq "error" "$(_field "${json}" status)" "launcher errors on a HOME activity mismatch"
|
||||
case "$(_field "${json}" message)" in
|
||||
*"Launcher default mismatch"*) _t_pass "launcher names the mismatch" ;;
|
||||
*) _t_fail "launcher should report the default mismatch" ;;
|
||||
esac
|
||||
|
||||
_dev_set launcher_actual "com.qqlabs.minimalistlauncher/.MainActivity"
|
||||
_t_eq "ok" "$(_field "$(_probe _check_launcher)" status)" "launcher ok when the activities agree"
|
||||
|
||||
# An unreadable current activity must not be reported as a mismatch.
|
||||
_dev_reset
|
||||
_dev_present pkg_launcher
|
||||
_dev_set launcher_desired "com.qqlabs.minimalistlauncher/.MainActivity"
|
||||
_t_eq "ok" "$(_field "$(_probe _check_launcher)" status)" \
|
||||
"launcher ok when the active activity cannot be read"
|
||||
|
||||
# --- companion app and boot persistence -------------------------------------
|
||||
|
||||
_dev_reset
|
||||
_t_eq "warn" "$(_field "$(_probe _check_companion_app)" status)" "companion app warns when missing"
|
||||
|
||||
_dev_present pkg_companion
|
||||
_t_eq "ok" "$(_field "$(_probe _check_companion_app)" status)" "companion app ok when installed"
|
||||
|
||||
_dev_reset
|
||||
_t_eq "fatal" "$(_field "$(_probe _check_boot_persistence)" status)" \
|
||||
"boot persistence fatal when the Magisk script is missing"
|
||||
|
||||
_dev_present /data/adb/service.d/99-focus-mode.sh
|
||||
json="$(_probe _check_boot_persistence)"
|
||||
_t_eq "ok" "$(_field "${json}" status)" \
|
||||
"boot persistence ok when the Magisk script is executable"
|
||||
case "${json}" in
|
||||
*'"repairable":false'*) _t_pass "a healthy check is not marked repairable" ;;
|
||||
*) _t_fail "a healthy check must not be repairable: ${json}" ;;
|
||||
esac
|
||||
|
||||
# --- snapshot, summary and severity ----------------------------------------
|
||||
|
||||
_dev_reset
|
||||
SNAP="${TEST_TMPDIR}/snap/run"
|
||||
monitor_collect_snapshot "${SNAP}"
|
||||
|
||||
if [[ -f "${SNAP}/report.json" ]]; then
|
||||
_t_pass "collect_snapshot writes report.json"
|
||||
else
|
||||
_t_fail "collect_snapshot did not write report.json"
|
||||
fi
|
||||
|
||||
if [[ -f "${TEST_TMPDIR}/snap/latest.json" ]]; then
|
||||
_t_pass "collect_snapshot copies the report to latest.json"
|
||||
else
|
||||
_t_fail "collect_snapshot did not write latest.json"
|
||||
fi
|
||||
|
||||
checks_seen="$(grep -o '"check":"[a-z_]*"' "${SNAP}/report.json" | sort -u | wc -l)"
|
||||
_t_eq "12" "${checks_seen}" "collect_snapshot emits one entry per probe"
|
||||
|
||||
if python3 -c 'import json,sys; json.load(open(sys.argv[1]))' "${SNAP}/report.json"; then
|
||||
_t_pass "collect_snapshot writes parseable JSON"
|
||||
else
|
||||
_t_fail "collect_snapshot produced invalid JSON"
|
||||
fi
|
||||
|
||||
case "$(cat "${SNAP}/report.json")" in
|
||||
*'"device":"test-serial"'*) _t_pass "collect_snapshot records the device serial" ;;
|
||||
*) _t_fail "collect_snapshot did not record the serial" ;;
|
||||
esac
|
||||
|
||||
monitor_print_summary "${SNAP}" >/dev/null
|
||||
_t_pass "print_summary renders an existing report"
|
||||
|
||||
monitor_print_summary "${TEST_TMPDIR}/no-such-dir" >/dev/null
|
||||
_t_pass "print_summary warns and returns 0 for a missing report"
|
||||
|
||||
if monitor_severity_exit "${TEST_TMPDIR}/no-such-dir"; then
|
||||
_t_pass "severity_exit returns 0 when no report exists"
|
||||
else
|
||||
_t_fail "severity_exit should return 0 for a missing report"
|
||||
fi
|
||||
|
||||
# The all-fatal snapshot above must exit nonzero.
|
||||
if ! monitor_severity_exit "${SNAP}"; then
|
||||
_t_pass "severity_exit returns 1 when a check is fatal"
|
||||
else
|
||||
_t_fail "severity_exit should return 1 on a fatal check"
|
||||
fi
|
||||
|
||||
printf '%s\n' '{"checks":[{"check":"x","status":"ok","source":"s","message":"mentions fatal and error","repairable":false}]}' \
|
||||
>"${SNAP}/report.json"
|
||||
if monitor_severity_exit "${SNAP}"; then
|
||||
_t_pass "severity_exit ignores fatal/error words in free text"
|
||||
else
|
||||
_t_fail "severity_exit should only inspect the JSON status field"
|
||||
fi
|
||||
210
phone_focus_mode/lib/tests/monitor_harness.sh
Executable file
210
phone_focus_mode/lib/tests/monitor_harness.sh
Executable file
@ -0,0 +1,210 @@
|
||||
#!/usr/bin/env bash
|
||||
# lib/tests/monitor_harness.sh — the fake device behind the monitor.sh tests.
|
||||
#
|
||||
# Sourced, not executed. Unlike dns_iptables_harness.sh there are no PATH
|
||||
# stubs here: every probe in monitor_checks_{health,policy}.sh reaches the
|
||||
# device through adb_root_shell, which is a *function* from adb_common.sh, so
|
||||
# a stub binary on PATH would never be consulted. Mocking at the function
|
||||
# boundary is both sufficient and far cheaper — it keeps adb_common.sh's
|
||||
# device-detection and locking surface out of the test entirely.
|
||||
#
|
||||
# monitor.sh and monitor_checks_policy.sh declare readonly constants, so the
|
||||
# subject can be sourced exactly once per process. State is therefore reset
|
||||
# through the mock's own store (_dev_reset), never by re-sourcing.
|
||||
set -euo pipefail
|
||||
|
||||
_MH_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
readonly _MH_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"
|
||||
}
|
||||
|
||||
# Assert two strings match, naming the expectation either way.
|
||||
_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
|
||||
}
|
||||
|
||||
TEST_TMPDIR="$(mktemp -d)"
|
||||
trap 'rm -rf "${TEST_TMPDIR}"' EXIT
|
||||
|
||||
readonly DEV="${TEST_TMPDIR}/device"
|
||||
|
||||
# --- device state -----------------------------------------------------------
|
||||
#
|
||||
# One file per fact the probes can observe. Seeding a fact is writing a file;
|
||||
# the mock never grows a new case arm when a test wants a new scenario. That
|
||||
# is the _seed_jumps/_fail_op idea from dns_iptables_harness.sh lifted to the
|
||||
# function boundary.
|
||||
|
||||
_dev_reset() {
|
||||
rm -rf "${DEV}"
|
||||
mkdir -p "${DEV}"
|
||||
}
|
||||
|
||||
# _dev_set <key> <value> — record an observable value (battery level, a hash).
|
||||
_dev_set() {
|
||||
printf '%s' "$2" >"${DEV}/$1"
|
||||
}
|
||||
|
||||
# _dev_get <key> [default] — read one back, or the default when never seeded.
|
||||
_dev_get() {
|
||||
if [[ -f "${DEV}/$1" ]]; then
|
||||
cat "${DEV}/$1"
|
||||
else
|
||||
printf '%s' "${2:-}"
|
||||
fi
|
||||
}
|
||||
|
||||
# _dev_present <path...> — mark files/paths as existing for `test -f`/`-x`/-s.
|
||||
_dev_present() {
|
||||
local path=""
|
||||
for path in "$@"; do
|
||||
printf '%s\n' "${path}" >>"${DEV}/present"
|
||||
done
|
||||
}
|
||||
|
||||
_dev_has() {
|
||||
[[ -f "${DEV}/present" ]] && grep -qxF "$1" "${DEV}/present"
|
||||
}
|
||||
|
||||
# _dev_pid <pid> — mark a PID as alive for `kill -0`.
|
||||
_dev_pid() {
|
||||
printf '%s\n' "$1" >>"${DEV}/pids"
|
||||
}
|
||||
|
||||
_dev_pid_alive() {
|
||||
[[ -f "${DEV}/pids" ]] && grep -qxF "$1" "${DEV}/pids"
|
||||
}
|
||||
|
||||
# _dev_cmdline <pid> <text> — what /proc/<pid>/cmdline greps against. Absent
|
||||
# means the grep fails, which is the "Android hides cmdline" path.
|
||||
_dev_cmdline() {
|
||||
printf '%s' "$2" >"${DEV}/cmdline_$1"
|
||||
}
|
||||
|
||||
# --- the mock ---------------------------------------------------------------
|
||||
#
|
||||
# Dispatch is by *pattern*, not exact string: the real commands embed awk
|
||||
# programs with escaped $2, nested quotes and 2>/dev/null redirections, which
|
||||
# an exact-match case arm cannot survive being edited around.
|
||||
#
|
||||
# Contract matches the real adb_root_shell: stdout is the command output,
|
||||
# exit status is the command's status.
|
||||
adb_root_shell() {
|
||||
local cmd="$*"
|
||||
|
||||
case "${cmd}" in
|
||||
'test -f '*|'test -x '*|"test -s '"*)
|
||||
local target="${cmd#test -? }"
|
||||
target="${target#\'}"
|
||||
target="${target%\'}"
|
||||
_dev_has "${target}"
|
||||
;;
|
||||
'kill -0 '*)
|
||||
local pid="${cmd#kill -0 }"
|
||||
pid="${pid%% *}"
|
||||
_dev_pid_alive "${pid}"
|
||||
;;
|
||||
*'/cmdline'*)
|
||||
# tr '\0' ' ' </proc/<pid>/cmdline | grep -q <script_name>
|
||||
local pid="${cmd#*/proc/}"
|
||||
pid="${pid%%/*}"
|
||||
local want="${cmd##* }"
|
||||
[[ -f "${DEV}/cmdline_${pid}" ]] && grep -qF "${want}" "${DEV}/cmdline_${pid}"
|
||||
;;
|
||||
*'dumpsys battery'*'level:'*) _dev_emit battery_level ;;
|
||||
*'dumpsys battery'*'health:'*) _dev_emit battery_health ;;
|
||||
*'dumpsys battery'*'temperature:'*) _dev_emit battery_temp ;;
|
||||
*'df /sdcard'*) _dev_emit df_sdcard ;;
|
||||
*'df /storage/emulated/0'*) _dev_emit df_emulated ;;
|
||||
'pgrep -f '*) _dev_emit "pgrep_$(_mh_script_of "${cmd}")" ;;
|
||||
*'cat /data/local/tmp/focus_mode/'*'.pid'*) _dev_emit "pidfile_$(_mh_pidfile_of "${cmd}")" ;;
|
||||
*'sha256sum'*) _dev_emit hosts_actual_sha ;;
|
||||
*'cat /data/local/tmp/focus_mode/hosts.sha256'*) _dev_emit hosts_expected_sha ;;
|
||||
*'cat /data/local/tmp/focus_mode/minimalist_launcher.activity'*) _dev_emit launcher_desired ;;
|
||||
*'resolve-activity'*) _dev_emit launcher_actual ;;
|
||||
*'settings get global private_dns_mode'*) _dev_emit private_dns_mode ;;
|
||||
*'iptables -L '*) _dev_has "dns_chain" ;;
|
||||
'pm path '*) _dev_has "pkg_launcher" ;;
|
||||
'pm list packages -e '*) _dev_has "pkg_companion" ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Emit a seeded value on stdout, failing like a real command when unseeded so
|
||||
# that _safe_adb_root_output's `|| true` path stays reachable.
|
||||
_dev_emit() {
|
||||
[[ -f "${DEV}/$1" ]] || return 1
|
||||
cat "${DEV}/$1"
|
||||
}
|
||||
|
||||
# `pgrep -f 'foo.sh' 2>/dev/null | head -1` -> foo.sh
|
||||
_mh_script_of() {
|
||||
local rest="${1#pgrep -f \'}"
|
||||
printf '%s' "${rest%%\'*}"
|
||||
}
|
||||
|
||||
# `if [ -f /.../daemon.pid ]; then cat /.../daemon.pid; fi` -> daemon
|
||||
_mh_pidfile_of() {
|
||||
local rest="${1##*focus_mode/}"
|
||||
printf '%s' "${rest%%.pid*}"
|
||||
}
|
||||
|
||||
# --- ambient definitions the subject expects from its callers ---------------
|
||||
|
||||
_info() { :; }
|
||||
_warn() { :; }
|
||||
_error() { :; }
|
||||
_box() { :; }
|
||||
|
||||
_fatal() {
|
||||
printf 'FATAL: %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
FORMAT_INDICATORS=()
|
||||
FORMAT_DETECTION_MIN_MISSING=2
|
||||
BATTERY_WARN_BELOW=20
|
||||
STORAGE_WARN_BELOW_MB=500
|
||||
ADB_SERIAL="test-serial"
|
||||
|
||||
: "${FORMAT_DETECTION_MIN_MISSING}" "${BATTERY_WARN_BELOW}"
|
||||
: "${STORAGE_WARN_BELOW_MB}" "${ADB_SERIAL}" "${FORMAT_INDICATORS[*]}"
|
||||
|
||||
_dev_reset
|
||||
|
||||
# shellcheck source=../monitor.sh
|
||||
. "${_MH_DIR}/../monitor.sh"
|
||||
|
||||
# --- probe drivers ----------------------------------------------------------
|
||||
|
||||
# _probe <fn> [args...] — run one _check_* into a fresh file and echo the JSON.
|
||||
# Guarded: a probe that exits nonzero under `set -e` would otherwise abort the
|
||||
# whole suite mid-run and still leave a plausible-looking coverage figure.
|
||||
_probe() {
|
||||
local out="${TEST_TMPDIR}/probe.json"
|
||||
: >"${out}"
|
||||
"$@" "${out}" || printf '{"status":"PROBE-FAILED"}' >"${out}"
|
||||
cat "${out}"
|
||||
}
|
||||
|
||||
# _field <json> <key> — pull one string field out of a probe's JSON line.
|
||||
_field() {
|
||||
local json="$1" key="$2"
|
||||
[[ "${json}" =~ \"${key}\":\"([^\"]*)\" ]] && printf '%s' "${BASH_REMATCH[1]}"
|
||||
}
|
||||
@ -1,154 +1,26 @@
|
||||
#!/usr/bin/env bash
|
||||
# Unit tests for monitor.sh helpers that do not require a real device.
|
||||
# Unit tests for monitor.sh and its two probe libraries, against the fake
|
||||
# device in monitor_harness.sh. No real device is needed.
|
||||
#
|
||||
# Every _check_* status branch gets its own case: the probes are what decides
|
||||
# whether enforcement is reported as healthy, so a probe that silently always
|
||||
# says "ok" is the failure this file exists to catch.
|
||||
#
|
||||
# This is the only entry point — the cases live in two sourced files purely to
|
||||
# stay under the 250-line cap, so one run still measures all three subjects:
|
||||
#
|
||||
# bash meta/scripts/shell_coverage.sh \
|
||||
# phone_focus_mode/lib/tests/test_monitor.sh monitor.sh
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
adb_root_shell() {
|
||||
return 1
|
||||
}
|
||||
|
||||
_info() {
|
||||
:
|
||||
}
|
||||
|
||||
_warn() {
|
||||
:
|
||||
}
|
||||
|
||||
_error() {
|
||||
:
|
||||
}
|
||||
|
||||
_fatal() {
|
||||
printf 'FATAL: %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
_box() {
|
||||
:
|
||||
}
|
||||
|
||||
FORMAT_INDICATORS=()
|
||||
FORMAT_DETECTION_MIN_MISSING=2
|
||||
BATTERY_WARN_BELOW=20
|
||||
STORAGE_WARN_BELOW_MB=500
|
||||
ADB_SERIAL="test-serial"
|
||||
|
||||
: "${FORMAT_DETECTION_MIN_MISSING}"
|
||||
: "${BATTERY_WARN_BELOW}"
|
||||
: "${STORAGE_WARN_BELOW_MB}"
|
||||
: "${ADB_SERIAL}"
|
||||
: "${FORMAT_INDICATORS[*]}"
|
||||
|
||||
source "${SCRIPT_DIR}/../monitor.sh"
|
||||
|
||||
PASS=0
|
||||
FAIL=0
|
||||
|
||||
_t_pass() {
|
||||
PASS=$((PASS + 1))
|
||||
printf ' OK: %s\n' "$1"
|
||||
}
|
||||
|
||||
_t_fail() {
|
||||
FAIL=$((FAIL + 1))
|
||||
printf ' FAIL: %s\n' "$1"
|
||||
}
|
||||
|
||||
TMPDIR_TEST="$(mktemp -d)"
|
||||
trap 'rm -rf "${TMPDIR_TEST}"' EXIT
|
||||
|
||||
line="$(_mon_check "test_check" "ok" "some_cmd" "all good" "false")"
|
||||
if [[ "${line}" == *'"check":"test_check"'* ]]; then
|
||||
_t_pass "_mon_check outputs check name"
|
||||
else
|
||||
_t_fail "_mon_check missing check name"
|
||||
fi
|
||||
|
||||
if [[ "${line}" == *'"status":"ok"'* ]]; then
|
||||
_t_pass "_mon_check outputs status"
|
||||
else
|
||||
_t_fail "_mon_check missing status"
|
||||
fi
|
||||
|
||||
if [[ -z "$(_monitor_resolve_hosts_target)" ]]; then
|
||||
_t_pass "_monitor_resolve_hosts_target returns empty when no hosts file exists"
|
||||
else
|
||||
_t_fail "_monitor_resolve_hosts_target should be empty when all candidates are missing"
|
||||
fi
|
||||
|
||||
adb_root_shell() {
|
||||
case "$*" in
|
||||
'test -f /system/etc/hosts') return 1 ;;
|
||||
'test -f /etc/hosts') return 1 ;;
|
||||
'test -f /vendor/etc/hosts') return 0 ;;
|
||||
'test -f /system/system/etc/hosts') return 1 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
if [[ "$(_monitor_resolve_hosts_target)" == "/vendor/etc/hosts" ]]; then
|
||||
_t_pass "_monitor_resolve_hosts_target picks first existing candidate"
|
||||
else
|
||||
_t_fail "_monitor_resolve_hosts_target did not return expected candidate"
|
||||
fi
|
||||
|
||||
adb_root_shell() {
|
||||
return 1
|
||||
}
|
||||
|
||||
if ! monitor_is_formatted 2>/dev/null; then
|
||||
_t_pass "monitor_is_formatted returns 1 when no indicators are missing"
|
||||
else
|
||||
_t_fail "monitor_is_formatted should return 1 with empty FORMAT_INDICATORS"
|
||||
fi
|
||||
|
||||
if monitor_severity_exit "${TMPDIR_TEST}/nonexistent"; then
|
||||
_t_pass "monitor_severity_exit returns 0 when no report exists"
|
||||
else
|
||||
_t_fail "monitor_severity_exit should return 0 for missing report"
|
||||
fi
|
||||
|
||||
printf '%s\n' '{"checks":[{"check":"x","status":"fatal","source":"s","message":"m","repairable":false}]}' > "${TMPDIR_TEST}/report.json"
|
||||
if ! monitor_severity_exit "${TMPDIR_TEST}"; then
|
||||
_t_pass "monitor_severity_exit returns 1 on fatal status"
|
||||
else
|
||||
_t_fail "monitor_severity_exit should return 1 on fatal status"
|
||||
fi
|
||||
|
||||
printf '%s\n' '{"checks":[{"check":"x","status":"ok","source":"s","message":"mentions fatal and error in text","repairable":false}]}' > "${TMPDIR_TEST}/report.json"
|
||||
if monitor_severity_exit "${TMPDIR_TEST}"; then
|
||||
_t_pass "monitor_severity_exit ignores free-text fatal/error words in message"
|
||||
else
|
||||
_t_fail "monitor_severity_exit should only inspect the JSON status field"
|
||||
fi
|
||||
|
||||
FORMAT_INDICATORS=(
|
||||
"present|test -f /present"
|
||||
"missing-one|test -f /missing-one"
|
||||
"missing-two|test -f /missing-two"
|
||||
)
|
||||
|
||||
: "${FORMAT_INDICATORS[*]}"
|
||||
|
||||
adb_root_shell() {
|
||||
case "$*" in
|
||||
'test -f /present')
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
mapfile -t missing_indicators < <(monitor_check_format_indicators)
|
||||
if [[ ${#missing_indicators[@]} -eq 2 ]] && [[ "${missing_indicators[0]}" == "missing-one" ]] && [[ "${missing_indicators[1]}" == "missing-two" ]]; then
|
||||
_t_pass "monitor_check_format_indicators prints only missing indicator names"
|
||||
else
|
||||
_t_fail "monitor_check_format_indicators did not return the expected missing indicators"
|
||||
fi
|
||||
# shellcheck source=monitor_harness.sh
|
||||
. "${SCRIPT_DIR}/monitor_harness.sh"
|
||||
# shellcheck source=monitor_cases_health.sh
|
||||
. "${SCRIPT_DIR}/monitor_cases_health.sh"
|
||||
# shellcheck source=monitor_cases_policy.sh
|
||||
. "${SCRIPT_DIR}/monitor_cases_policy.sh"
|
||||
|
||||
printf '\nResults: %d passed, %d failed\n' "${PASS}" "${FAIL}"
|
||||
[[ "${FAIL}" -eq 0 ]]
|
||||
|
||||
Loading…
Reference in New Issue
Block a user