diff --git a/docs/superpowers/evidence/mtk-root-run-tests-split-2026-08-16.json b/docs/superpowers/evidence/mtk-root-run-tests-split-2026-08-16.json new file mode 100644 index 00000000..9364b514 --- /dev/null +++ b/docs/superpowers/evidence/mtk-root-run-tests-split-2026-08-16.json @@ -0,0 +1,49 @@ +{ + "intent": "Bring linux_configuration/scripts/mtk_root/tests/run_tests.sh (421 lines) under the 250-line cap by extracting the assertion helpers and the 20-dump-stock.sh test group into sourced libs.", + "scope": [ + "linux_configuration/scripts/mtk_root/tests/run_tests.sh", + "linux_configuration/scripts/mtk_root/tests/lib/assertions.sh (new)", + "linux_configuration/scripts/mtk_root/tests/lib/dump_tests.sh (new)", + "Non-goal: no assertion changed, no test removed, renamed or skipped" + ], + "changes": [ + "Move pass/fail/assert_contains/assert_not_contains/assert_eq/strip_noncode/run_recon into lib/assertions.sh.", + "Move the six 20-dump-stock.sh tests (refusals, happy path, truncation, ordering, facts file) into lib/dump_tests.sh.", + "run_tests.sh keeps SCRIPT_DIR, the counters, cleanup/trap, the eight recon tests and main(), and sources both libs." + ], + "verification": [ + { + "command": "bash run_tests.sh (before the split, from the tests directory)", + "result": "pass", + "evidence": "=== 58 passed, 0 failed ===, exit 0. Suite is hermetic: every test drives a fixture directory via MTK_ROOT_FIXTURE, so no phone is involved." + }, + { + "command": "bash run_tests.sh (after the split), full stdout diffed against the baseline capture", + "result": "pass", + "evidence": "=== 58 passed, 0 failed ===, exit 0, and `diff` of the complete 89-line output reported no differences -- OUTPUT IDENTICAL. This is a real-execution check with 58 assertions, not a stubbed trace." + }, + { + "command": "bash -n on all three files; shellcheck -x on the entry; shellcheck on both libs", + "result": "pass", + "evidence": "All exit 0 with no output." + }, + { + "command": "pre-commit run --files ", + "result": "pass", + "evidence": "No hook reported a failure." + }, + { + "command": "bash ~/utils/scripts/check_file_length.sh on all three", + "result": "pass", + "evidence": "421 -> 218 (entry) + 157 (dump_tests) + 75 (assertions), all under 250." + } + ], + "risks": [ + "The libs depend on caller state: pass/fail increment PASS and FAIL, run_recon reads WORKROOT, FIXTURES and RECON. All are defined in run_tests.sh before the source lines, and the header comments say so.", + "All three files are shfmt-dirty, but run_tests.sh was already shfmt-dirty at HEAD (2-space indent vs shfmt's tabs) and the new libs inherit that existing style. No clean file was made dirty; shfmt is not a pre-commit hook in this repo." + ], + "rollback": [ + "git revert the commit; both libs disappear and run_tests.sh returns to 421 lines.", + "Re-run `bash run_tests.sh` from the tests directory and confirm '58 passed, 0 failed'." + ] +} diff --git a/linux_configuration/scripts/mtk_root/tests/lib/assertions.sh b/linux_configuration/scripts/mtk_root/tests/lib/assertions.sh new file mode 100755 index 00000000..7582200d --- /dev/null +++ b/linux_configuration/scripts/mtk_root/tests/lib/assertions.sh @@ -0,0 +1,75 @@ +#!/bin/bash + +# ============================================================================ +# Assertion helpers and fixture drivers for the mtk_root fixture tests. +# +# Split out of run_tests.sh to keep it under the 250-line cap. The seam passes +# state deliberately: pass/fail increment the caller's PASS and FAIL counters, +# and run_recon reads WORKROOT, FIXTURES and RECON. The caller defines all of +# them before sourcing this file. +# ============================================================================ + +# shellcheck shell=bash + +pass() { + PASS=$((PASS + 1)) + printf ' [PASS] %s\n' "$1" +} + +fail() { + FAIL=$((FAIL + 1)) + printf ' [FAIL] %s\n' "$1" + [[ -n ${2:-} ]] && printf ' %s\n' "$2" + return 0 +} + +assert_contains() { + if [[ $2 == *"$3"* ]]; then + pass "$1" + else + fail "$1" "expected to find: $3" + fi +} + +assert_not_contains() { + if [[ $2 != *"$3"* ]]; then + pass "$1" + else + fail "$1" "did not expect: $3" + fi +} + +assert_eq() { + if [[ $2 == "$3" ]]; then + pass "$1" + else + fail "$1" "expected '$3', got '$2'" + fi +} + +# strip_noncode +# Reduce a script to the lines that could actually execute something, so the +# read-only scan below can look for dangerous verbs in command position. +# +# Drops comments, and drops the ARGUMENTS of output builtins only - 10-recon.sh +# exists to talk about flashing and unlocking, so those words appear +# legitimately inside printf/echo text. Everything else keeps its quotes: an +# earlier version stripped ALL quoted strings, which let +# `mtk_adb shell "reboot bootloader"` slip through the scan entirely. +strip_noncode() { + sed -E \ + -e 's/(^|[[:space:]])#.*$/\1/' \ + -e 's/^[[:space:]]*(printf|echo|cat)[[:space:]].*$//' \ + -e 's/\|[[:space:]]*(printf|echo|cat)[[:space:]].*$//' \ + "$1" +} + +# run_recon [extra args...] - prints combined output. +run_recon() { + local fixture="$1" + shift + MTK_ROOT_FIXTURE="$FIXTURES/$fixture" \ + MTK_ROOT_CACHE="$WORKROOT/cache" \ + MTK_SERIAL="TEST$RANDOM" \ + "$RECON" "$@" 2>&1 +} diff --git a/linux_configuration/scripts/mtk_root/tests/lib/dump_tests.sh b/linux_configuration/scripts/mtk_root/tests/lib/dump_tests.sh new file mode 100755 index 00000000..ec322960 --- /dev/null +++ b/linux_configuration/scripts/mtk_root/tests/lib/dump_tests.sh @@ -0,0 +1,157 @@ +#!/bin/bash + +# ============================================================================ +# Fixture tests for 20-dump-stock.sh: refusals, the happy path, truncation +# detection, ordering against 10-recon.sh, and the facts file it writes. +# +# Split out of run_tests.sh to keep both files under the 250-line cap. Sourced +# by run_tests.sh, which owns the counters and the fixture paths these use. +# ============================================================================ + +# shellcheck shell=bash + +test_dump_refuses_non_mediatek() { + printf '\n20-dump-stock.sh refuses a non-MediaTek device\n' + [[ -x $DUMP ]] || { + fail "20-dump-stock.sh present" "not found or not executable" + return 0 + } + + local out="" rc=0 + out="$(MTK_ROOT_FIXTURE="$FIXTURES/pixel-boot-ab" \ + MTK_ROOT_CACHE="$WORKROOT/cache" \ + MTK_SERIAL="TESTPIXEL" \ + "$DUMP" --yes 2>&1)" || rc=$? + + assert_eq "exits non-zero on Pixel" "$rc" "1" + assert_contains "refuses explicitly" "$out" "refus" + assert_not_contains "no dump attempted" "$out" "Dumping" +} + +test_dump_happy_path() { + printf '\n20-dump-stock.sh produces a manifest from a stubbed read\n' + [[ -x $DUMP ]] || { + fail "20-dump-stock.sh present" "not found or not executable" + return 0 + } + + # Stub stands in for mtkclient: writes a plausibly-sized file. This exercises + # find_latest_facts, read_facts_value, verify_dump, the size band, the sha256 + # column and the manifest - none of which the refusal test reaches. + local stub="$WORKROOT/stub-dump.sh" + cat >"$stub" <<'STUB' +#!/bin/bash +set -euo pipefail +truncate -s 8M "$2" +printf 'ANDROID!' | dd of="$2" conv=notrunc status=none +STUB + chmod +x "$stub" + + local cache="$WORKROOT/dumpcache" + mkdir -p "$cache" + local serial="DUMPTEST1" + + # Chain the real recon output in, rather than hand-writing a facts file: + # this also proves the two scripts agree on the format. + MTK_ROOT_FIXTURE="$FIXTURES/ulefone-initboot-ab" \ + MTK_ROOT_CACHE="$cache" MTK_SERIAL="$serial" "$RECON" >/dev/null 2>&1 + + local out="" rc=0 + out="$(MTK_ROOT_FIXTURE="$FIXTURES/ulefone-initboot-ab" \ + MTK_ROOT_CACHE="$cache" MTK_SERIAL="$serial" \ + MTK_DUMP_CMD="$stub" "$DUMP" --yes 2>&1)" || rc=$? + + assert_eq "exits zero on success" "$rc" "0" + assert_contains "dumped the init_boot partition" "$out" "init_boot_a" + + local manifest="$cache/stock/$serial/manifest.txt" + if [[ ! -f $manifest ]]; then + fail "manifest written" "not found at $manifest" + return 0 + fi + pass "manifest written" + + local content="" + content="$(cat "$manifest")" + assert_contains "manifest names the image" "$content" "init_boot_a.img" + assert_contains "manifest records vbmeta" "$content" "vbmeta_a.img" + assert_contains "manifest records carrier" "$content" "ramdisk_carrier=init_boot" + assert_contains "manifest records size" "$content" "8388608 bytes" + # A sha256 is 64 hex chars; assert one is actually present. + if grep -qE '^[0-9a-f]{64} ' <<<"$content"; then + pass "manifest records sha256" + else + fail "manifest records sha256" "no 64-char hex digest found" + fi + + # Idempotence: a second run must not silently re-dump over a good backup. + out="$(MTK_ROOT_FIXTURE="$FIXTURES/ulefone-initboot-ab" \ + MTK_ROOT_CACHE="$cache" MTK_SERIAL="$serial" \ + MTK_DUMP_CMD="$stub" "$DUMP" --yes 2>&1)" || true + assert_contains "second run is a no-op" "$out" "already exists" +} + +test_dump_rejects_truncated_image() { + printf '\n20-dump-stock.sh flags an implausibly small dump\n' + [[ -x $DUMP ]] || return 0 + + # mtkclient exits 0 on a truncated read, so size is the only signal that the + # "backup" is worthless. A 512-byte init_boot must not pass silently. + local stub="$WORKROOT/stub-truncated.sh" + cat >"$stub" <<'STUB' +#!/bin/bash +set -euo pipefail +truncate -s 512 "$2" +STUB + chmod +x "$stub" + + local cache="$WORKROOT/truncache" + mkdir -p "$cache" + local serial="DUMPTEST2" + + MTK_ROOT_FIXTURE="$FIXTURES/ulefone-initboot-ab" \ + MTK_ROOT_CACHE="$cache" MTK_SERIAL="$serial" "$RECON" >/dev/null 2>&1 + + local out="" rc=0 + out="$(MTK_ROOT_FIXTURE="$FIXTURES/ulefone-initboot-ab" \ + MTK_ROOT_CACHE="$cache" MTK_SERIAL="$serial" \ + MTK_DUMP_CMD="$stub" "$DUMP" --yes 2>&1)" || rc=$? + + assert_eq "exits non-zero" "$rc" "1" + assert_contains "calls it implausibly small" "$out" "implausibly small" + assert_contains "warns against trusting it" "$out" "Do NOT treat this as a usable backup" +} + +test_dump_requires_recon_first() { + printf '\n20-dump-stock.sh refuses without a recon facts file\n' + [[ -x $DUMP ]] || return 0 + + local out="" rc=0 + out="$(MTK_ROOT_FIXTURE="$FIXTURES/ulefone-initboot-ab" \ + MTK_ROOT_CACHE="$WORKROOT/emptycache" MTK_SERIAL="NOFACTS1" \ + MTK_DUMP_CMD="/bin/true" "$DUMP" --yes 2>&1)" || rc=$? + + assert_eq "exits non-zero" "$rc" "1" + assert_contains "points at 10-recon.sh" "$out" "10-recon.sh" + assert_contains "explains it will not guess" "$out" "rather than guessing" +} + +test_facts_file_written() { + printf '\nfacts file is written and machine-readable\n' + local out="" facts="" + out="$(run_recon ulefone-initboot-ab)" + + facts="$(find "$WORKROOT/cache" -name 'device-facts-ULEFONE-*.txt' -print -quit 2>/dev/null)" + if [[ -z $facts ]]; then + fail "facts file created" "none found under $WORKROOT/cache" + return 0 + fi + pass "facts file created" + + local content="" + content="$(cat "$facts")" + assert_contains "records ramdisk partition" "$content" "ramdisk_partition=init_boot_a" + assert_contains "records carrier" "$content" "ramdisk_carrier=init_boot" + assert_contains "records scheme" "$content" "ab_scheme=A/B" + assert_contains "includes by-name listing" "$content" "[by-name]" +} diff --git a/linux_configuration/scripts/mtk_root/tests/run_tests.sh b/linux_configuration/scripts/mtk_root/tests/run_tests.sh index 98259284..29100cc6 100755 --- a/linux_configuration/scripts/mtk_root/tests/run_tests.sh +++ b/linux_configuration/scripts/mtk_root/tests/run_tests.sh @@ -30,68 +30,8 @@ cleanup() { } trap cleanup EXIT -pass() { - PASS=$((PASS + 1)) - printf ' [PASS] %s\n' "$1" -} - -fail() { - FAIL=$((FAIL + 1)) - printf ' [FAIL] %s\n' "$1" - [[ -n ${2:-} ]] && printf ' %s\n' "$2" - return 0 -} - -assert_contains() { - if [[ $2 == *"$3"* ]]; then - pass "$1" - else - fail "$1" "expected to find: $3" - fi -} - -assert_not_contains() { - if [[ $2 != *"$3"* ]]; then - pass "$1" - else - fail "$1" "did not expect: $3" - fi -} - -assert_eq() { - if [[ $2 == "$3" ]]; then - pass "$1" - else - fail "$1" "expected '$3', got '$2'" - fi -} - -# strip_noncode -# Reduce a script to the lines that could actually execute something, so the -# read-only scan below can look for dangerous verbs in command position. -# -# Drops comments, and drops the ARGUMENTS of output builtins only - 10-recon.sh -# exists to talk about flashing and unlocking, so those words appear -# legitimately inside printf/echo text. Everything else keeps its quotes: an -# earlier version stripped ALL quoted strings, which let -# `mtk_adb shell "reboot bootloader"` slip through the scan entirely. -strip_noncode() { - sed -E \ - -e 's/(^|[[:space:]])#.*$/\1/' \ - -e 's/^[[:space:]]*(printf|echo|cat)[[:space:]].*$//' \ - -e 's/\|[[:space:]]*(printf|echo|cat)[[:space:]].*$//' \ - "$1" -} - -# run_recon [extra args...] - prints combined output. -run_recon() { - local fixture="$1" - shift - MTK_ROOT_FIXTURE="$FIXTURES/$fixture" \ - MTK_ROOT_CACHE="$WORKROOT/cache" \ - MTK_SERIAL="TEST$RANDOM" \ - "$RECON" "$@" 2>&1 -} +# shellcheck source=./lib/assertions.sh +source "$SCRIPT_DIR/lib/assertions.sh" # --------------------------------------------------------------------------- @@ -246,151 +186,8 @@ test_read_only_scan_catches_injection() { assert_eq "no false positive on the real script" "${hits:-none}" "none" } -test_dump_refuses_non_mediatek() { - printf '\n20-dump-stock.sh refuses a non-MediaTek device\n' - [[ -x $DUMP ]] || { - fail "20-dump-stock.sh present" "not found or not executable" - return 0 - } - - local out="" rc=0 - out="$(MTK_ROOT_FIXTURE="$FIXTURES/pixel-boot-ab" \ - MTK_ROOT_CACHE="$WORKROOT/cache" \ - MTK_SERIAL="TESTPIXEL" \ - "$DUMP" --yes 2>&1)" || rc=$? - - assert_eq "exits non-zero on Pixel" "$rc" "1" - assert_contains "refuses explicitly" "$out" "refus" - assert_not_contains "no dump attempted" "$out" "Dumping" -} - -test_dump_happy_path() { - printf '\n20-dump-stock.sh produces a manifest from a stubbed read\n' - [[ -x $DUMP ]] || { - fail "20-dump-stock.sh present" "not found or not executable" - return 0 - } - - # Stub stands in for mtkclient: writes a plausibly-sized file. This exercises - # find_latest_facts, read_facts_value, verify_dump, the size band, the sha256 - # column and the manifest - none of which the refusal test reaches. - local stub="$WORKROOT/stub-dump.sh" - cat >"$stub" <<'STUB' -#!/bin/bash -set -euo pipefail -truncate -s 8M "$2" -printf 'ANDROID!' | dd of="$2" conv=notrunc status=none -STUB - chmod +x "$stub" - - local cache="$WORKROOT/dumpcache" - mkdir -p "$cache" - local serial="DUMPTEST1" - - # Chain the real recon output in, rather than hand-writing a facts file: - # this also proves the two scripts agree on the format. - MTK_ROOT_FIXTURE="$FIXTURES/ulefone-initboot-ab" \ - MTK_ROOT_CACHE="$cache" MTK_SERIAL="$serial" "$RECON" >/dev/null 2>&1 - - local out="" rc=0 - out="$(MTK_ROOT_FIXTURE="$FIXTURES/ulefone-initboot-ab" \ - MTK_ROOT_CACHE="$cache" MTK_SERIAL="$serial" \ - MTK_DUMP_CMD="$stub" "$DUMP" --yes 2>&1)" || rc=$? - - assert_eq "exits zero on success" "$rc" "0" - assert_contains "dumped the init_boot partition" "$out" "init_boot_a" - - local manifest="$cache/stock/$serial/manifest.txt" - if [[ ! -f $manifest ]]; then - fail "manifest written" "not found at $manifest" - return 0 - fi - pass "manifest written" - - local content="" - content="$(cat "$manifest")" - assert_contains "manifest names the image" "$content" "init_boot_a.img" - assert_contains "manifest records vbmeta" "$content" "vbmeta_a.img" - assert_contains "manifest records carrier" "$content" "ramdisk_carrier=init_boot" - assert_contains "manifest records size" "$content" "8388608 bytes" - # A sha256 is 64 hex chars; assert one is actually present. - if grep -qE '^[0-9a-f]{64} ' <<<"$content"; then - pass "manifest records sha256" - else - fail "manifest records sha256" "no 64-char hex digest found" - fi - - # Idempotence: a second run must not silently re-dump over a good backup. - out="$(MTK_ROOT_FIXTURE="$FIXTURES/ulefone-initboot-ab" \ - MTK_ROOT_CACHE="$cache" MTK_SERIAL="$serial" \ - MTK_DUMP_CMD="$stub" "$DUMP" --yes 2>&1)" || true - assert_contains "second run is a no-op" "$out" "already exists" -} - -test_dump_rejects_truncated_image() { - printf '\n20-dump-stock.sh flags an implausibly small dump\n' - [[ -x $DUMP ]] || return 0 - - # mtkclient exits 0 on a truncated read, so size is the only signal that the - # "backup" is worthless. A 512-byte init_boot must not pass silently. - local stub="$WORKROOT/stub-truncated.sh" - cat >"$stub" <<'STUB' -#!/bin/bash -set -euo pipefail -truncate -s 512 "$2" -STUB - chmod +x "$stub" - - local cache="$WORKROOT/truncache" - mkdir -p "$cache" - local serial="DUMPTEST2" - - MTK_ROOT_FIXTURE="$FIXTURES/ulefone-initboot-ab" \ - MTK_ROOT_CACHE="$cache" MTK_SERIAL="$serial" "$RECON" >/dev/null 2>&1 - - local out="" rc=0 - out="$(MTK_ROOT_FIXTURE="$FIXTURES/ulefone-initboot-ab" \ - MTK_ROOT_CACHE="$cache" MTK_SERIAL="$serial" \ - MTK_DUMP_CMD="$stub" "$DUMP" --yes 2>&1)" || rc=$? - - assert_eq "exits non-zero" "$rc" "1" - assert_contains "calls it implausibly small" "$out" "implausibly small" - assert_contains "warns against trusting it" "$out" "Do NOT treat this as a usable backup" -} - -test_dump_requires_recon_first() { - printf '\n20-dump-stock.sh refuses without a recon facts file\n' - [[ -x $DUMP ]] || return 0 - - local out="" rc=0 - out="$(MTK_ROOT_FIXTURE="$FIXTURES/ulefone-initboot-ab" \ - MTK_ROOT_CACHE="$WORKROOT/emptycache" MTK_SERIAL="NOFACTS1" \ - MTK_DUMP_CMD="/bin/true" "$DUMP" --yes 2>&1)" || rc=$? - - assert_eq "exits non-zero" "$rc" "1" - assert_contains "points at 10-recon.sh" "$out" "10-recon.sh" - assert_contains "explains it will not guess" "$out" "rather than guessing" -} - -test_facts_file_written() { - printf '\nfacts file is written and machine-readable\n' - local out="" facts="" - out="$(run_recon ulefone-initboot-ab)" - - facts="$(find "$WORKROOT/cache" -name 'device-facts-ULEFONE-*.txt' -print -quit 2>/dev/null)" - if [[ -z $facts ]]; then - fail "facts file created" "none found under $WORKROOT/cache" - return 0 - fi - pass "facts file created" - - local content="" - content="$(cat "$facts")" - assert_contains "records ramdisk partition" "$content" "ramdisk_partition=init_boot_a" - assert_contains "records carrier" "$content" "ramdisk_carrier=init_boot" - assert_contains "records scheme" "$content" "ab_scheme=A/B" - assert_contains "includes by-name listing" "$content" "[by-name]" -} +# shellcheck source=./lib/dump_tests.sh +source "$SCRIPT_DIR/lib/dump_tests.sh" main() { WORKROOT="$(mktemp -d)"