phone_focus_mode: split test_adb_common.sh 287 -> 134 + 105
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

Mirrors the source split in the previous commit: the six tests exercising
adb_verify_trusted_identity and adb_forget_trusted_device move to
test_adb_trusted.sh, beside the adb_trusted.sh they cover.

Shared fixtures go to adb_test_harness.sh (76) -- counters, run_test, the adb()
mock, the injection payloads, and the XDG_STATE_HOME redirect into a mktemp
dir. That redirect had to travel with the mock: a test file sourcing the
library without it writes to live phone state.

10 + 6 = 16, the pre-split count. That sum is the check that matters -- a
dropped test or a file that failed to source its fixtures would lower the
total while both files still printed success. Also verified both exit 0
explicitly (the summary line prints either way), and that a copy with one
assertion replaced by `return 1` still exits 1, so neither file is vacuously
green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
This commit is contained in:
Krzysztof Rudnicki 2026-08-17 10:34:45 +02:00
parent 466b074dd7
commit 6a019633e5
4 changed files with 238 additions and 159 deletions

View File

@ -0,0 +1,51 @@
{
"intent": "Bring phone_focus_mode/lib/tests/test_adb_common.sh (287 lines) under the repo-wide 250-line cap without losing a single test, completing the adb_common split started in the previous commit.",
"scope": [
"phone_focus_mode/lib/tests/test_adb_common.sh — reduced to the ten logging, cooldown, lock, sanitiser, root and device-selection tests",
"phone_focus_mode/lib/tests/test_adb_trusted.sh — new: the six enrolled-device tests, mirroring the adb_trusted.sh split",
"phone_focus_mode/lib/tests/adb_test_harness.sh — new: the shared counters, run_test, TEST_TMPDIR, XDG_STATE_HOME redirect and adb() mock",
"Non-goal: adding, deleting or weakening any assertion; all sixteen are carried across verbatim"
],
"changes": [
"Split the sixteen tests along the same seam as the source split in the previous commit: the six that exercise adb_verify_trusted_identity and adb_forget_trusted_device moved to test_adb_trusted.sh, matching adb_trusted.sh, and the other ten stayed.",
"Moved the shared fixtures into adb_test_harness.sh, sourced by both files from their own directory. It keeps the XDG_STATE_HOME redirect into a mktemp dir, so neither test file can touch the real ~/.local/state/phone_focus_mode — that redirect had to move as a unit with the mock, since a test file sourcing the library without it would write to live state.",
"Both test files keep the original exit convention ([[ FAIL -eq 0 ]] as the last statement), so each fails the shell with a non-zero status rather than merely printing a count.",
"The harness keeps the single-quoted injection payloads verbatim, including the comment explaining that expanding $evil or `cmd` there would execute them in the harness and test nothing."
],
"verification": [
{
"command": "bash lib/tests/test_adb_common.sh && bash lib/tests/test_adb_trusted.sh",
"result": "pass",
"evidence": "10 passed / 0 failed and 6 passed / 0 failed. 10 + 6 = 16, exactly the pre-split count, which is the discriminating check for a test split: had a test been dropped or a file failed to source its fixtures, the total would have fallen while both files still reported success."
},
{
"command": "Exit-status check on both files",
"result": "pass",
"evidence": "Both exit 0. Checked explicitly rather than read off the printed summary, because the summary line prints regardless of outcome."
},
{
"command": "Mutation check: a copy of test_adb_common.sh with one assertion body replaced by `return 1`",
"result": "pass",
"evidence": "The mutated copy exits 1. This proves the split files are not vacuously green — that the run_test dispatch still reaches the test bodies and a genuine failure still fails the script."
},
{
"command": "pre-commit run --files on all three files",
"result": "pass",
"evidence": "Every hook passes, including shellcheck with no --severity filter, and the shebang/executable check that rejected the previous commit's first attempt. Zero suppressions added."
},
{
"command": "bash ~/utils/scripts/check_file_length.sh --all",
"result": "pass",
"evidence": "test_adb_common.sh 134, test_adb_trusted.sh 105, adb_test_harness.sh 76 — no adb file of any kind appears in the over-cap listing now, source or test."
}
],
"risks": [
"adb_test_harness.sh carries a shebang and the executable bit to match every sibling in lib/tests/, but it is not a test file — running it directly defines fixtures and exits 0 without asserting anything. Its header comment says so. A future test-runner that globs lib/tests/*.sh would count it as a passing suite.",
"The two test files now each source the library independently, so a fixture change has to satisfy both. That is the same tradeoff as the source split and is the reason the harness exists rather than the fixtures being duplicated.",
"This package has no CI workflow, so these tests only run when invoked by hand or by the pre-push ci-mirror; nothing runs them on every commit."
],
"rollback": [
"git revert the commit — one edited file and two added files, with no data, config or schema migration.",
"After rollback, re-run bash phone_focus_mode/lib/tests/test_adb_common.sh; expect 16 passed, 0 failed from the single file."
]
}

View File

@ -0,0 +1,76 @@
#!/usr/bin/env bash
# lib/tests/adb_test_harness.sh — shared fixtures for the adb_common test files.
# Sourced by test_adb_common.sh and test_adb_trusted.sh; not a test file itself.
#
# Provides: the PASS/FAIL counters, run_test, a TEST_TMPDIR with an EXIT trap,
# an XDG_STATE_HOME redirect so no test touches the real state dir, the adb()
# mock, and the two injection payloads the sanitiser is tested against.
# Unit tests for adb_common.sh helper functions (no real device needed).
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PASS=0
FAIL=0
_t_pass() {
PASS=$((PASS + 1))
printf ' OK: %s\n' "$1"
}
_t_fail() {
FAIL=$((FAIL + 1))
printf ' FAIL: %s\n' "$1"
}
TEST_TMPDIR="$(mktemp -d)"
trap 'rm -rf "${TEST_TMPDIR}"' EXIT
export XDG_STATE_HOME="${TEST_TMPDIR}/state"
mkdir -p "${XDG_STATE_HOME}"
source "${SCRIPT_DIR}/../adb_common.sh"
ADB_MOCK_MODEL=$'Pixel "7";$(rm -rf /)`danger`\nline2'
# Single quotes are the whole point: this is an injection payload that must
# reach the code under test LITERALLY. Expanding $evil / `cmd` here would
# execute them in the test harness and test nothing.
# shellcheck disable=SC2016
ADB_MOCK_FINGERPRINT='google/pixel:14/UP1A.231005.007/$evil;`cmd`'
adb() {
if [[ "$#" -eq 1 && "$1" == "devices" ]]; then
printf 'List of devices attached\nSERIAL123\tdevice\n'
return 0
fi
if [[ "$1" == "-s" && "$3" == "shell" && "$4" == "getprop" && "$5" == "ro.product.model" ]]; then
printf '%s\r\n' "${ADB_MOCK_MODEL}"
return 0
fi
if [[ "$1" == "-s" && "$3" == "shell" && "$4" == "getprop" && "$5" == "ro.build.fingerprint" ]]; then
printf '%s\r\n' "${ADB_MOCK_FINGERPRINT}"
return 0
fi
if [[ "$1" == "-s" && "$3" == "shell" && "$4" == "su" && "$5" == "--mount-master" && "$6" == "-c" && "$7" == "echo ok" ]]; then
printf 'ok\n'
return 0
fi
printf 'Unexpected adb invocation:' >&2
printf ' %q' "$@" >&2
printf '\n' >&2
return 1
}
run_test() {
local name="$1"
shift
if "$@"; then
_t_pass "${name}"
else
_t_fail "${name}"
fi
}

View File

@ -1,73 +1,12 @@
#!/usr/bin/env bash
# Unit tests for adb_common.sh helper functions (no real device needed).
# Unit tests for adb_common.sh and adb_locking.sh (no real device needed).
# The enrolled-device tests live in test_adb_trusted.sh; both share the
# fixtures in adb_test_harness.sh.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PASS=0
FAIL=0
_t_pass() {
PASS=$((PASS + 1))
printf ' OK: %s\n' "$1"
}
_t_fail() {
FAIL=$((FAIL + 1))
printf ' FAIL: %s\n' "$1"
}
TEST_TMPDIR="$(mktemp -d)"
trap 'rm -rf "${TEST_TMPDIR}"' EXIT
export XDG_STATE_HOME="${TEST_TMPDIR}/state"
mkdir -p "${XDG_STATE_HOME}"
source "${SCRIPT_DIR}/../adb_common.sh"
ADB_MOCK_MODEL=$'Pixel "7";$(rm -rf /)`danger`\nline2'
# Single quotes are the whole point: this is an injection payload that must
# reach the code under test LITERALLY. Expanding $evil / `cmd` here would
# execute them in the test harness and test nothing.
# shellcheck disable=SC2016
ADB_MOCK_FINGERPRINT='google/pixel:14/UP1A.231005.007/$evil;`cmd`'
adb() {
if [[ "$#" -eq 1 && "$1" == "devices" ]]; then
printf 'List of devices attached\nSERIAL123\tdevice\n'
return 0
fi
if [[ "$1" == "-s" && "$3" == "shell" && "$4" == "getprop" && "$5" == "ro.product.model" ]]; then
printf '%s\r\n' "${ADB_MOCK_MODEL}"
return 0
fi
if [[ "$1" == "-s" && "$3" == "shell" && "$4" == "getprop" && "$5" == "ro.build.fingerprint" ]]; then
printf '%s\r\n' "${ADB_MOCK_FINGERPRINT}"
return 0
fi
if [[ "$1" == "-s" && "$3" == "shell" && "$4" == "su" && "$5" == "--mount-master" && "$6" == "-c" && "$7" == "echo ok" ]]; then
printf 'ok\n'
return 0
fi
printf 'Unexpected adb invocation:' >&2
printf ' %q' "$@" >&2
printf '\n' >&2
return 1
}
run_test() {
local name="$1"
shift
if "$@"; then
_t_pass "${name}"
else
_t_fail "${name}"
fi
}
_HARNESS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=adb_test_harness.sh
source "${_HARNESS_DIR}/adb_test_harness.sh"
test_box_does_not_crash() {
_box "Test title" "line 1" "line 2" >/dev/null 2>&1
@ -180,92 +119,6 @@ EOF
return 0
}
test_verify_trusted_identity_accepts_exact_match() {
export ADB_SERIAL='SERIAL123'
adb_save_trusted_device
adb_verify_trusted_identity >/dev/null 2>&1
}
test_verify_trusted_identity_rejects_model_mismatch() {
export ADB_SERIAL='SERIAL123'
adb_save_trusted_device
cat >"${ADB_ENROLLED_DEVICE_FILE}" <<'EOF'
TRUSTED_SERIAL='SERIAL123'
TRUSTED_MODEL='Different Model'
TRUSTED_FINGERPRINT='google/pixel:14/UP1A.231005.007/evilcmd'
EOF
if (adb_verify_trusted_identity >/dev/null 2>&1); then
return 1
fi
return 0
}
test_verify_trusted_identity_rejects_fingerprint_mismatch() {
export ADB_SERIAL='SERIAL123'
adb_save_trusted_device
cat >"${ADB_ENROLLED_DEVICE_FILE}" <<'EOF'
TRUSTED_SERIAL='SERIAL123'
TRUSTED_MODEL='Pixel 7rm -rf /dangerline2'
TRUSTED_FINGERPRINT='different/fingerprint'
EOF
if (adb_verify_trusted_identity >/dev/null 2>&1); then
return 1
fi
return 0
}
test_verify_trusted_identity_accepts_any_enrolled_device() {
# Two phones enrolled from one PC: the second must not evict the first,
# which is the whole reason the store is a directory.
export ADB_SERIAL='SERIAL_ONE'
adb_save_trusted_device
export ADB_SERIAL='SERIAL_TWO'
adb_save_trusted_device
adb_verify_trusted_identity >/dev/null 2>&1 || return 1
export ADB_SERIAL='SERIAL_ONE'
adb_verify_trusted_identity >/dev/null 2>&1 || return 1
return 0
}
test_verify_trusted_identity_rejects_unenrolled_device() {
export ADB_SERIAL='SERIAL_ONE'
adb_save_trusted_device
# An unknown serial must still abort, or multi-device support would have
# quietly turned the guard off.
export ADB_SERIAL='SERIAL_STRANGER'
if (adb_verify_trusted_identity >/dev/null 2>&1); then
return 1
fi
return 0
}
test_forget_trusted_device_removes_only_that_record() {
export ADB_SERIAL='SERIAL_ONE'
adb_save_trusted_device
export ADB_SERIAL='SERIAL_TWO'
adb_save_trusted_device
adb_forget_trusted_device 'SERIAL_ONE' >/dev/null 2>&1
adb_list_trusted_serials | grep -qx 'SERIAL_TWO' || return 1
if adb_list_trusted_serials | grep -qx 'SERIAL_ONE'; then
return 1
fi
return 0
}
run_test "_box output without crash" test_box_does_not_crash
run_test "adb_check_cooldown 0 returns 0 (proceed)" test_check_cooldown_zero_allows
run_test "adb_check_cooldown blocks when marker is fresh" test_check_cooldown_blocks_when_marker_fresh
@ -276,12 +129,6 @@ run_test "_sanitize_device_string strips dangerous characters" test_sanitize_dev
run_test "adb_save_trusted_device sanitizes and safely quotes values" test_save_trusted_device_sanitizes_and_quotes
run_test "adb_verify_root succeeds when root shell returns ok" test_verify_root_uses_root_shell
run_test "adb_select_device rejects multiple devices even with trusted record" test_select_device_rejects_multiple_devices_even_with_trusted_record
run_test "adb_verify_trusted_identity accepts exact saved identity" test_verify_trusted_identity_accepts_exact_match
run_test "adb_verify_trusted_identity rejects model mismatch" test_verify_trusted_identity_rejects_model_mismatch
run_test "adb_verify_trusted_identity rejects fingerprint mismatch" test_verify_trusted_identity_rejects_fingerprint_mismatch
run_test "adb_verify_trusted_identity accepts any enrolled device" test_verify_trusted_identity_accepts_any_enrolled_device
run_test "adb_verify_trusted_identity rejects unenrolled device" test_verify_trusted_identity_rejects_unenrolled_device
run_test "adb_forget_trusted_device removes only that record" test_forget_trusted_device_removes_only_that_record
printf '\nResults: %d passed, %d failed\n' "${PASS}" "${FAIL}"
[[ "${FAIL}" -eq 0 ]]

View File

@ -0,0 +1,105 @@
#!/usr/bin/env bash
# Unit tests for adb_trusted.sh: verifying a connected phone against the
# enrolled-device records, and forgetting one. Shares the fixtures in
# adb_test_harness.sh with test_adb_common.sh.
set -euo pipefail
_HARNESS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=adb_test_harness.sh
source "${_HARNESS_DIR}/adb_test_harness.sh"
test_verify_trusted_identity_accepts_exact_match() {
export ADB_SERIAL='SERIAL123'
adb_save_trusted_device
adb_verify_trusted_identity >/dev/null 2>&1
}
test_verify_trusted_identity_rejects_model_mismatch() {
export ADB_SERIAL='SERIAL123'
adb_save_trusted_device
cat >"${ADB_ENROLLED_DEVICE_FILE}" <<'EOF'
TRUSTED_SERIAL='SERIAL123'
TRUSTED_MODEL='Different Model'
TRUSTED_FINGERPRINT='google/pixel:14/UP1A.231005.007/evilcmd'
EOF
if (adb_verify_trusted_identity >/dev/null 2>&1); then
return 1
fi
return 0
}
test_verify_trusted_identity_rejects_fingerprint_mismatch() {
export ADB_SERIAL='SERIAL123'
adb_save_trusted_device
cat >"${ADB_ENROLLED_DEVICE_FILE}" <<'EOF'
TRUSTED_SERIAL='SERIAL123'
TRUSTED_MODEL='Pixel 7rm -rf /dangerline2'
TRUSTED_FINGERPRINT='different/fingerprint'
EOF
if (adb_verify_trusted_identity >/dev/null 2>&1); then
return 1
fi
return 0
}
test_verify_trusted_identity_accepts_any_enrolled_device() {
# Two phones enrolled from one PC: the second must not evict the first,
# which is the whole reason the store is a directory.
export ADB_SERIAL='SERIAL_ONE'
adb_save_trusted_device
export ADB_SERIAL='SERIAL_TWO'
adb_save_trusted_device
adb_verify_trusted_identity >/dev/null 2>&1 || return 1
export ADB_SERIAL='SERIAL_ONE'
adb_verify_trusted_identity >/dev/null 2>&1 || return 1
return 0
}
test_verify_trusted_identity_rejects_unenrolled_device() {
export ADB_SERIAL='SERIAL_ONE'
adb_save_trusted_device
# An unknown serial must still abort, or multi-device support would have
# quietly turned the guard off.
export ADB_SERIAL='SERIAL_STRANGER'
if (adb_verify_trusted_identity >/dev/null 2>&1); then
return 1
fi
return 0
}
test_forget_trusted_device_removes_only_that_record() {
export ADB_SERIAL='SERIAL_ONE'
adb_save_trusted_device
export ADB_SERIAL='SERIAL_TWO'
adb_save_trusted_device
adb_forget_trusted_device 'SERIAL_ONE' >/dev/null 2>&1
adb_list_trusted_serials | grep -qx 'SERIAL_TWO' || return 1
if adb_list_trusted_serials | grep -qx 'SERIAL_ONE'; then
return 1
fi
return 0
}
run_test "adb_verify_trusted_identity accepts exact saved identity" test_verify_trusted_identity_accepts_exact_match
run_test "adb_verify_trusted_identity rejects model mismatch" test_verify_trusted_identity_rejects_model_mismatch
run_test "adb_verify_trusted_identity rejects fingerprint mismatch" test_verify_trusted_identity_rejects_fingerprint_mismatch
run_test "adb_verify_trusted_identity accepts any enrolled device" test_verify_trusted_identity_accepts_any_enrolled_device
run_test "adb_verify_trusted_identity rejects unenrolled device" test_verify_trusted_identity_rejects_unenrolled_device
run_test "adb_forget_trusted_device removes only that record" test_forget_trusted_device_removes_only_that_record
printf '\nResults: %d passed, %d failed\n' "${PASS}" "${FAIL}"
[[ "${FAIL}" -eq 0 ]]