From d42d8ff20d42fde906185da76e3898b0e1ecc74a Mon Sep 17 00:00:00 2001 From: Krzysztof kuhy Rudnicki Date: Fri, 21 Aug 2026 23:52:09 +0200 Subject: [PATCH] Shrink the shell-coverage allowlist 115 -> 106: cover scripts/lib MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gives linux_configuration/scripts/lib a real test suite, which takes all nine of its libraries off meta/shell-coverage-allowlist.txt at once — coverage is per-directory, so this is the highest-ratio first batch. 139 assertions across four suites, all offline: the mtk toolkit is driven through its existing MTK_ROOT_FIXTURE seam, and adb is shimmed onto a narrowed PATH. Nothing reads the wall clock, this host's udev rules, or a real device, because the suite runs in shell-tests.yml and again on every pre-push via ci-mirror — a host-dependent assertion would be a flake that blocks unrelated pushes. The gate only checks that tests/run_all.sh EXISTS, so an empty one would mark all nine libraries covered while testing nothing. Each of the four load-bearing rules was therefore mutation-tested and the suite required to go red: classify_device's && -> ||, discover_partitions' init_boot/boot ordering, mtk_select_device's refusal to guess between two phones, and is_hour_in_range's 10#$ octal guard. That last one initially SURVIVED. Dropping 10#$ makes bash abort with "value too great for base", a non-zero exit indistinguishable from an honest out-of-range result, so the status-only assertion passed either way. It now asserts stderr is empty. Co-Authored-By: Claude Opus 5 (1M context) --- ...-ratchet-scripts-lib-suite-2026-08-21.json | 20 +++ ...-ratchet-scripts-lib-suite-2026-08-21.json | 72 ++++++++ .../scripts/lib/tests/mtk_harness.sh | 119 +++++++++++++ .../scripts/lib/tests/run_all.sh | 30 ++++ .../scripts/lib/tests/test_common_datetime.sh | 160 ++++++++++++++++++ .../scripts/lib/tests/test_mtk_classify.sh | 120 +++++++++++++ .../scripts/lib/tests/test_mtk_device.sh | 152 +++++++++++++++++ .../scripts/lib/tests/test_mtk_partitions.sh | 142 ++++++++++++++++ meta/shell-coverage-allowlist.txt | 17 +- 9 files changed, 820 insertions(+), 12 deletions(-) create mode 100644 docs/superpowers/contracts/shell-ratchet-scripts-lib-suite-2026-08-21.json create mode 100644 docs/superpowers/evidence/shell-ratchet-scripts-lib-suite-2026-08-21.json create mode 100755 linux_configuration/scripts/lib/tests/mtk_harness.sh create mode 100755 linux_configuration/scripts/lib/tests/run_all.sh create mode 100755 linux_configuration/scripts/lib/tests/test_common_datetime.sh create mode 100755 linux_configuration/scripts/lib/tests/test_mtk_classify.sh create mode 100755 linux_configuration/scripts/lib/tests/test_mtk_device.sh create mode 100755 linux_configuration/scripts/lib/tests/test_mtk_partitions.sh diff --git a/docs/superpowers/contracts/shell-ratchet-scripts-lib-suite-2026-08-21.json b/docs/superpowers/contracts/shell-ratchet-scripts-lib-suite-2026-08-21.json new file mode 100644 index 00000000..acbe353f --- /dev/null +++ b/docs/superpowers/contracts/shell-ratchet-scripts-lib-suite-2026-08-21.json @@ -0,0 +1,20 @@ +{ + "title": "Shrink the shell-coverage allowlist: a real test suite for linux_configuration/scripts/lib", + "objective": "Start chipping at the 115-entry shell-coverage allowlist with the smallest concentrated batch, so the ratchet narrows rather than merely holding. One tests/run_all.sh covers all nine libraries in that directory at once, which is what makes this the highest-ratio first batch. The nine entries come off the allowlist as a result — the deliverable is the allowlist shrinking, not the suite existing.", + "acceptance_criteria": [ + "run_all.sh runs every test_*.sh in the directory and exits 0, with all four suites reporting zero failures.", + "The suite is proven NON-VACUOUS by mutation: the coverage gate only checks that tests/run_all.sh exists, so an empty file would mark all nine libraries covered while testing nothing. Each of the four load-bearing rules must be mutated and the suite must go red for each.", + "meta/shell-coverage-allowlist.txt drops from 115 entries to 106, with zero entries remaining for linux_configuration/scripts/lib/ — the measured delta, not a rounded one.", + "No assertion depends on wall-clock time, this host's udev rules, or a real device: the suite runs in shell-tests.yml and again on every pre-push via ci-mirror, so a host-dependent assertion becomes a push-blocking flake.", + "Sourcing a library must not write outside the test tmpdir — android.sh calls ensure_dir on a $HOME path at source time.", + "shellcheck and shfmt pass with no suppression added." + ], + "out_of_scope": [ + "Wiring shell-coverage-ratchet into .pre-commit-config.yaml. That edit is denied as a sensitive file and belongs to the user; NEXT_SESSION_PROMPT.md carries the block.", + "The effecting half of these libraries: require_hosts_readable (exec sudo), check_adb_device/check_adb_root (die -> exit), collapse_mounts (umount -l), install_missing_pacman_packages, require_imagemagick, notify. Phase 1 measured this pattern already — decision logic reaches ~90%, effecting code reaches 0%.", + "mtk_udev_catchall_files, which globs /etc/udev/rules.d with no seam and would read this host's real rules.", + "The other 106 allowlist entries; the next concentrated batches are single_use/features/lib (45) and single_use/fixes/lib (22).", + "A percentage coverage bar. The bar remains presence, as settled in Phase 3." + ], + "verifier": "bash linux_configuration/scripts/lib/tests/run_all.sh (exit 0; 139 assertions across 4 suites). Four mutations applied to throwaway copies, each required to turn the suite red: classify_device's `&&` -> `||` (would classify a stranger's MediaTek phone as the target device), discover_partitions' `init_boot boot` -> `boot init_boot` (would dump the wrong partition on Android 13+ hardware), mtk_select_device's `-gt 1` -> `-gt 99` (would silently pick one of two attached phones), and is_hour_in_range's `10#$` removal (the 08/09 octal trap). Allowlist delta measured with `grep -vc '^#\\|^$' meta/shell-coverage-allowlist.txt` before and after --seed." +} diff --git a/docs/superpowers/evidence/shell-ratchet-scripts-lib-suite-2026-08-21.json b/docs/superpowers/evidence/shell-ratchet-scripts-lib-suite-2026-08-21.json new file mode 100644 index 00000000..d5449155 --- /dev/null +++ b/docs/superpowers/evidence/shell-ratchet-scripts-lib-suite-2026-08-21.json @@ -0,0 +1,72 @@ +{ + "intent": "Shrink the shell-coverage allowlist from 115 entries to 106 by giving linux_configuration/scripts/lib a real test suite, so the ratchet narrows instead of merely holding the line. Nine libraries lose their exemption at once, because coverage is per-directory.", + "scope": [ + "New: linux_configuration/scripts/lib/tests/ — run_all.sh, mtk_harness.sh, and four test_*.sh suites", + "Modified: meta/shell-coverage-allowlist.txt — reseeded, 115 -> 106 entries, all nine linux_configuration/scripts/lib/ entries removed", + "No change to any library under test. This commit adds tests and removes exemptions; it does not touch the code being tested.", + "Non-goal: wiring shell-coverage-ratchet into .pre-commit-config.yaml — denied as a sensitive file, so it stays with the user (see NEXT_SESSION_PROMPT.md)", + "Non-goal: the effecting half of these libraries (sudo re-exec, umount -l, pacman, adb against real hardware) and mtk_udev_catchall_files, which has no seam" + ], + "changes": [ + "Covered the mtk toolkit through its existing MTK_ROOT_FIXTURE seam, which mtk_common.sh documents as the only boundary with real hardware — props.txt and by-name.txt fixtures make the whole suite offline and hermetic", + "Asserted the three safety rules that a plausible refactor would break: classify_device requires a MediaTek signal AND a vendor match (never either, so a third phone lands in UNKNOWN); discover_partitions prefers init_boot over boot; mtk_select_device refuses to guess between two attached devices", + "Shimmed adb onto a narrowed PATH for the device-selection suite, with the shim exiting 97 on any unexpected subcommand rather than returning an empty string that would read as 'no devices'", + "Kept every assertion independent of wall-clock time: get_hour and get_day_of_week are shimmed where a specific value is needed, and the real /proc/uptime readers are asserted on shape only. This suite runs in shell-tests.yml and again on every pre-push via ci-mirror, so a time-dependent assertion would be a push-blocking flake.", + "Redirected HOME to a tmpdir before sourcing, because android.sh runs ensure_dir on a $HOME path at source time — merely loading the library writes to ~/.cache otherwise", + "Named the deliberately-untested functions and the reason for each in mtk_harness.sh's header, since the gate checks only that run_all.sh exists and cannot tell a real suite from an empty one" + ], + "verification": [ + { + "command": "bash linux_configuration/scripts/lib/tests/run_all.sh", + "result": "pass", + "evidence": "exit 0; 139 assertions across 4 suites, 0 failed (common_datetime 55, mtk_partitions 36, mtk_device 26, mtk_classify 22)." + }, + { + "command": "mutation: classify_device's `$mtk_match -eq 1 && $ulefone_match -eq 1` -> `||`, on a throwaway copy", + "result": "pass", + "evidence": "Suite exits 1. This is the regression that would classify a stranger's MediaTek phone as the target device; the ULEFONE and PIXEL cases still pass under the mutation, so only the third-phone case catches it." + }, + { + "command": "mutation: discover_partitions' `for candidate in init_boot boot` -> `boot init_boot`", + "result": "pass", + "evidence": "Suite exits 1. Checking boot first picks the wrong ramdisk carrier on Android 13+ hardware, silently, because both partitions exist and both reads succeed." + }, + { + "command": "mutation: mtk_select_device's `${#serials[@]} -gt 1` -> `-gt 99`", + "result": "pass", + "evidence": "Suite exits 1. A version that took serials[0] with two phones attached passes every other assertion in that file." + }, + { + "command": "mutation: is_hour_in_range's `$((10#$current_hour))` -> `$((current_hour))`", + "result": "pass", + "evidence": "Suite exits 1 — but only after the assertion was strengthened. The first version checked exit status alone and the mutation SURVIVED: bash aborts with 'value too great for base (error token is \"09\")', a non-zero exit indistinguishable from an honest out-of-range result. The assertion now checks stderr is empty." + }, + { + "command": "grep -vc '^#\\|^$' meta/shell-coverage-allowlist.txt (before, after)", + "result": "pass", + "evidence": "115 -> 106. `grep -c 'linux_configuration/scripts/lib/'` goes 9 -> 0, matching the predicted delta exactly." + }, + { + "command": "bash meta/scripts/check_shell_coverage.sh linux_configuration/scripts/lib/mtk_classify.sh linux_configuration/scripts/lib/common_datetime.sh", + "result": "pass", + "evidence": "exit 0 — the nine libraries are now covered on their merits rather than exempt." + }, + { + "command": "git ls-files -s linux_configuration/scripts/lib/tests/", + "result": "pass", + "evidence": "All six files staged 100755. Staged with `git add --chmod=+x`; a plain `git add` afterwards would reset the bit and the shebang hook rejects 100644." + } + ], + "risks": [ + "Coverage is presence, not a percentage. The gate cannot distinguish these 139 assertions from an empty run_all.sh, and the nine libraries are now permanently off the allowlist — if a later commit guts the suite, nothing detects it. The harness header naming what is and is not exercised is the only durable record.", + "The effecting halves of android.sh, common_packages.sh and common_services.sh remain at 0%, so the exemption has been traded for partial coverage rather than full coverage. This is Phase 1's measured pattern, not a regression, but the allowlist no longer records it.", + "The adb shim narrows PATH to a shim dir plus /usr/bin and /bin. If mtk_device.sh later reaches for an external not seeded there, the suite aborts under `set -e` with no stderr — the exact failure that cost twenty minutes in Phase 1.", + "mtk_check_venv is asserted only on its 'missing' branch, reached by pointing MTK_ROOT_CACHE at an empty tmpdir. The 'broken' branch (a venv that exists but fails `import usb`) is unexercised.", + "This suite now runs on every pre-push via ci-mirror, so it is on the critical path for pushing any change to this repo, not just shell changes." + ], + "rollback": [ + "git revert this commit. It restores the nine allowlist entries and deletes the tests directory; no library under test changes, so nothing executable is affected.", + "After rollback, confirm `bash meta/scripts/check_shell_coverage.sh --all` reports 115 exempt entries and exits 0.", + "To keep the suite but restore the exemptions, delete linux_configuration/scripts/lib/tests/run_all.sh only — is_covered() checks for that one file, so the other suites can stay in place while the directory reverts to exempt." + ] +} diff --git a/linux_configuration/scripts/lib/tests/mtk_harness.sh b/linux_configuration/scripts/lib/tests/mtk_harness.sh new file mode 100755 index 00000000..73f881bf --- /dev/null +++ b/linux_configuration/scripts/lib/tests/mtk_harness.sh @@ -0,0 +1,119 @@ +#!/usr/bin/env bash +# lib/tests/mtk_harness.sh — shared fixture for linux_configuration/scripts/lib. +# +# Sourced, not executed. +# +# WHAT IS EXERCISED +# The pure decision logic and everything reachable through the +# MTK_ROOT_FIXTURE seam that mtk_common.sh documents as its only boundary +# with real hardware: classify_device, discover_partitions, +# mtk_ramdisk_carrier, mtk_check_layout_expectation, mtk_assert_mediatek, +# mtk_sanitize, mtk_sane_ramdisk_size, mtk_select_device/require_authorized +# under a fixture, plus the string helpers in common_datetime.sh. +# +# WHAT IS DELIBERATELY NOT EXERCISED, AND WHY +# The coverage gate only checks that this directory HAS a run_all.sh, so it +# cannot tell a real suite from an empty one. Naming the gaps is therefore +# the only honest record of them: +# - require_hosts_readable / init_android_script — `exec sudo -E bash "$0"` +# would replace the test process with a sudo re-exec of the suite. +# - check_adb_device / check_adb_root — call die(), which exits 1 and +# would take the whole run down; both also need real hardware. +# - collapse_mounts — calls `umount -l` on a live path. +# - install_missing_pacman_packages / require_imagemagick / notify — +# effecting code (pacman, desktop notifications). +# - mtk_udev_catchall_files — globs /etc/udev/rules.d with no seam, so it +# reads this host's real rules; the result differs between here and CI. +# - mtk_sha256 / mtk_file_size — one-line wrappers over sha256sum/stat. +# This matches Phase 1's measured pattern: decision logic reaches ~90%, the +# effecting code reaches 0%. +# +# TIME AND HOST DEPENDENCE +# This suite runs in shell-tests.yml and again on every pre-push via +# ci-mirror, so a wall-clock assertion would pass locally and fail at some +# other hour on some other machine. get_hour and get_day_of_week are +# therefore shimmed where a specific value is needed, and the readers of +# /proc/uptime are asserted on SHAPE, never on value. +set -euo pipefail + +PASS=0 +FAIL=0 + +_t_ok() { + PASS=$((PASS + 1)) + printf ' OK: %s\n' "$1" +} + +_t_bad() { + FAIL=$((FAIL + 1)) + printf ' FAIL: %s\n' "$1" +} + +# Assert equality. Every check in this suite funnels through here or _t_rc so +# there is exactly one place that counts a result. +_t_is() { + if [[ $2 == "$1" ]]; then + _t_ok "$3" + else + _t_bad "$3 (want '${1}', got '${2}')" + fi +} + +# Assert a command's exit status. Run under `if` so a non-zero return is data +# rather than a `set -e` abort. +_t_rc() { + local want="$1" what="$2" + shift 2 + local got=0 + "$@" >/dev/null 2>&1 || got=$? + _t_is "$want" "$got" "$what" +} + +# Assert a substring, for messages whose exact wording is not the contract. +_t_in() { + if [[ $2 == *"$1"* ]]; then + _t_ok "$3" + else + _t_bad "$3 (want substring '${1}')" + fi +} + +# --------------------------------------------------------------------------- +# Isolation +# --------------------------------------------------------------------------- + +# android.sh runs `ensure_dir "$ANDROID_WORK_DIR"` at SOURCE time, and that +# path is under $HOME. Redirect HOME before sourcing anything or merely +# loading the library writes to the real ~/.cache. MTK_ROOT_CACHE moves the +# mtk toolkit's artifacts for the same reason. +_t_sandbox() { + T_TMP="$(mktemp -d)" + export T_TMP + HOME="$T_TMP/home" + mkdir -p "$HOME" + export HOME + export MTK_ROOT_CACHE="$T_TMP/cache" +} + +_t_cleanup() { + [[ -n ${T_TMP:-} && -d ${T_TMP:-} ]] && rm -rf "$T_TMP" + return 0 +} + +# Build a device fixture. mtk_common.sh reads props.txt as `key=value` lines +# and by-name.txt as one partition per line; those two files ARE the seam. +# Usage: _t_fixture +_t_fixture() { + local dir="$T_TMP/fixtures/$1" + mkdir -p "$dir" + printf '%s\n' "$2" >"$dir/props.txt" + printf '%s\n' "$3" >"$dir/by-name.txt" + printf '%s' "$dir" +} + +# Report and set the process exit status. Called at the end of every suite. +_t_done() { + printf '\n %s: %d passed, %d failed\n' "$1" "$PASS" "$FAIL" + _t_cleanup + [[ $FAIL -eq 0 ]] +} diff --git a/linux_configuration/scripts/lib/tests/run_all.sh b/linux_configuration/scripts/lib/tests/run_all.sh new file mode 100755 index 00000000..f4eeeecb --- /dev/null +++ b/linux_configuration/scripts/lib/tests/run_all.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# Runs every linux_configuration/scripts/lib test. +# +# The glob is test_*.sh, NOT one prefix per subject: this directory holds +# suites for several libraries (mtk_*, common_datetime, ...), and a prefix +# glob silently skips every suite added after it — including in the CI +# discovery step in shell-tests.yml, which runs this file. +# +# This file's existence is what meta/scripts/check_shell_coverage.sh checks: +# is_covered() tests for tests/run_all.sh and nothing more, so an empty one +# here would mark all nine libraries in the parent directory covered while +# testing nothing. The suites it runs, and the gaps they leave, are named in +# mtk_harness.sh's header. +set -euo pipefail + +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +rc=0 +for t in "$HERE"/test_*.sh; do + printf '\n=== %s ===\n' "$(basename "$t")" + # Each suite exits non-zero on failure; keep going so one red suite does not + # hide the state of the others, then fail the run as a whole. + # Invoked directly, NOT as `bash "$t"`: under kcov the latter instruments + # bash itself and reports zero lines for every lib. A fresh process per + # suite also matters here, because mtk_common.sh declares readonly + # patterns and sourcing it twice in one shell aborts under set -e. + "$t" || rc=1 +done + +exit "$rc" diff --git a/linux_configuration/scripts/lib/tests/test_common_datetime.sh b/linux_configuration/scripts/lib/tests/test_common_datetime.sh new file mode 100755 index 00000000..bafc4ff2 --- /dev/null +++ b/linux_configuration/scripts/lib/tests/test_common_datetime.sh @@ -0,0 +1,160 @@ +#!/usr/bin/env bash +# Covers the pure helpers in common_datetime.sh: validate_resolution, +# generate_output_filename, has_cmd, ask_yes_no, mount_layers_count, the +# zero-fork time readers, and the two predicates built on them. +# +# NO ASSERTION HERE DEPENDS ON THE WALL CLOCK. This suite runs in +# shell-tests.yml and again on every pre-push via ci-mirror, so +# `is_hour_in_range 9 17` would pass during the working day and fail at +# night — a flake that blocks a push for reasons unrelated to the change. +# The predicates are tested by shimming get_hour/get_day_of_week to fixed +# values; the real readers are asserted on SHAPE only. +set -euo pipefail + +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=mtk_harness.sh +source "$HERE/mtk_harness.sh" + +_t_sandbox +# shellcheck source=../common_datetime.sh +source "$HERE/../common_datetime.sh" + +# --- validate_resolution --------------------------------------------------- + +_t_rc 0 "a WIDTHxHEIGHT resolution is valid" validate_resolution "1920x1080" +_t_rc 0 "a single-digit resolution is valid" validate_resolution "1x1" +_t_rc 1 "a capital X is not the separator" validate_resolution "1920X1080" +_t_rc 1 "a missing dimension is invalid" validate_resolution "1920x" +_t_rc 1 "a bare number is not a resolution" validate_resolution "1920" +_t_rc 1 "trailing text is rejected" validate_resolution "1920x1080p" +_t_rc 1 "an empty string is not a resolution" validate_resolution "" +# Anchoring matters: an unanchored regex would accept this and the caller +# would go on to pass it to a scaler. +_t_rc 1 "an embedded match is rejected, not extracted" validate_resolution "a1920x1080b" + +# --- generate_output_filename ---------------------------------------------- + +_t_is "./photo_resized.jpg" "$(generate_output_filename "photo.jpg" "_resized")" \ + "suffix goes before the extension, not after" +_t_is "/tmp/photo_small.png" "$(generate_output_filename "/tmp/photo.png" "_small")" \ + "the directory is preserved" +# A file with no extension: the caller's fallback is used, defaulting to jpg. +_t_is "./photo_x.jpg" "$(generate_output_filename "photo" "_x")" \ + "an extensionless input defaults to jpg" +_t_is "./photo_x.png" "$(generate_output_filename "photo" "_x" "png")" \ + "an explicit fallback extension is honoured" +# Only the LAST dot separates the extension. +_t_is "./archive.tar_x.gz" "$(generate_output_filename "archive.tar.gz" "_x")" \ + "only the final extension is split off" +_t_is "/a/b/c_out.jpg" "$(generate_output_filename "/a/b/c.jpg" "_out")" \ + "a nested path is preserved" + +# --- has_cmd --------------------------------------------------------------- + +_t_rc 0 "has_cmd finds a command that exists" has_cmd bash +_t_rc 1 "has_cmd rejects a command that does not" has_cmd definitely-not-a-real-binary-xyz + +# --- ask_yes_no ------------------------------------------------------------ + +# Reads stdin, so it is drivable without a tty. +_t_yn() { + printf '%s\n' "$1" | ask_yes_no "prompt?" >/dev/null 2>&1 +} +_t_rc 0 "'y' is yes" _t_yn "y" +_t_rc 0 "'Y' is yes" _t_yn "Y" +_t_rc 0 "'yes' is yes" _t_yn "yes" +_t_rc 1 "'n' is no" _t_yn "n" +# Default-no is the safety property: an empty answer must never mean yes. +_t_rc 1 "an empty answer defaults to no" _t_yn "" +_t_rc 1 "unrecognised input is no, not a reprompt" _t_yn "maybe" + +# --- mount_layers_count ---------------------------------------------------- + +# A path with no bind mount over it has zero layers. `/` always appears in +# mountinfo, so it is the one value that is stable on any host. +_t_is "0" "$(mount_layers_count "/definitely/not/a/mount/point")" \ + "an unmounted path counts zero layers" +_t_rc 0 "a mounted path counts without failing" mount_layers_count "/" +root_layers="$(mount_layers_count "/")" +_t_rc 0 "the root mount count is numeric" grep -qE '^[0-9]+$' <<<"$root_layers" + +# --- time readers: shape, never value -------------------------------------- + +_t_rc 0 "get_timestamp returns digits" grep -qE '^[0-9]+$' <<<"$(get_timestamp)" +_t_rc 0 "get_date is YYYY-MM-DD" grep -qE '^[0-9]{4}-[0-9]{2}-[0-9]{2}$' <<<"$(get_date)" +_t_rc 0 "get_time is HH:MM:SS" grep -qE '^[0-9]{2}:[0-9]{2}:[0-9]{2}$' <<<"$(get_time)" +_t_rc 0 "get_datetime joins both" \ + grep -qE '^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}$' <<<"$(get_datetime)" +_t_rc 0 "get_day_of_week is 1-7" grep -qE '^[1-7]$' <<<"$(get_day_of_week)" +_t_rc 0 "get_day_name is alphabetic" grep -qE '^[A-Za-z]+$' <<<"$(get_day_name)" +_t_rc 0 "get_hour is 00-23" grep -qE '^([01][0-9]|2[0-3])$' <<<"$(get_hour)" +_t_rc 0 "get_minute is 00-59" grep -qE '^[0-5][0-9]$' <<<"$(get_minute)" +_t_rc 0 "get_second is 00-60" grep -qE '^([0-5][0-9]|60)$' <<<"$(get_second)" +_t_rc 0 "get_uptime_seconds is a whole number" grep -qE '^[0-9]+$' <<<"$(get_uptime_seconds)" +_t_rc 0 "get_boot_datetime is a datetime" \ + grep -qE '^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}$' <<<"$(get_boot_datetime)" +_t_rc 0 "get_boot_date is a date" grep -qE '^[0-9]{4}-[0-9]{2}-[0-9]{2}$' <<<"$(get_boot_date)" +_t_rc 0 "get_boot_hour is 00-23" grep -qE '^([01][0-9]|2[0-3])$' <<<"$(get_boot_hour)" + +# Boot time is in the past, and by the same margin /proc/uptime reports. This +# is a relation between two readings, so it holds at any wall-clock time. +now_ts="$(get_timestamp)" +up_s="$(get_uptime_seconds)" +_t_rc 0 "boot precedes now by the uptime" test "$((now_ts - up_s))" -le "$now_ts" + +# --- predicates, with the clock shimmed ------------------------------------ + +# is_hour_in_range is half-open: start inclusive, end exclusive. Overriding +# get_hour is what makes these assertions true at 3am and at 3pm alike. +get_hour() { printf '%s' "$T_FAKE_HOUR"; } + +T_FAKE_HOUR=06 +_t_rc 0 "06 is inside 5..8" is_hour_in_range 5 8 +T_FAKE_HOUR=05 +_t_rc 0 "the start hour is inside the range" is_hour_in_range 5 8 +T_FAKE_HOUR=08 +_t_rc 1 "the end hour is outside the range" is_hour_in_range 5 8 +T_FAKE_HOUR=04 +_t_rc 1 "an hour before the range is outside" is_hour_in_range 5 8 +T_FAKE_HOUR=23 +_t_rc 1 "an hour after the range is outside" is_hour_in_range 5 8 +# A leading zero must not be read as octal — 08 and 09 are the classic trap, +# and the implementation uses 10#$ precisely to avoid it. +# +# Assert on STDERR, not only on the status. Dropping the 10# prefix makes +# bash abort with "value too great for base", which is a non-zero exit that +# an exit-status-only check reads as an ordinary "hour is out of range" — +# so the mutation survives. Mutation-tested: removing 10#$ fails this. +_t_hour_err() { + T_FAKE_HOUR="$1" + # Capture stderr ONLY: stdout is discarded first, then stderr is pointed at + # it. Braces make the order explicit rather than relying on a bare + # `2>&1 >/dev/null`, which reads backwards. + { is_hour_in_range "$2" "$3" >/dev/null; } 2>&1 || true +} +for _t_h in 08 09; do + _t_is "" "$(_t_hour_err "$_t_h" 9 17)" "${_t_h} raises no arithmetic error" +done +T_FAKE_HOUR=09 +_t_rc 0 "09 is parsed as decimal nine, not invalid octal" is_hour_in_range 9 17 +T_FAKE_HOUR=08 +_t_rc 1 "08 is decimal eight, so it is below the 9..17 range" is_hour_in_range 9 17 +T_FAKE_HOUR=00 +_t_rc 0 "midnight is parsed as zero" is_hour_in_range 0 1 +_t_is "" "$(_t_hour_err 00 0 1)" "00 raises no arithmetic error" + +# is_day_of_week takes a list and matches any of them. +get_day_of_week() { printf '%s' "$T_FAKE_DOW"; } + +T_FAKE_DOW=1 +_t_rc 0 "Monday matches a list containing it" is_day_of_week 1 5 6 7 +T_FAKE_DOW=3 +_t_rc 1 "Wednesday does not match that list" is_day_of_week 1 5 6 7 +T_FAKE_DOW=7 +_t_rc 0 "Sunday matches at the end of the list" is_day_of_week 1 5 6 7 +T_FAKE_DOW=4 +_t_rc 0 "a single-element list matches" is_day_of_week 4 +T_FAKE_DOW=4 +_t_rc 1 "an empty list matches nothing" is_day_of_week + +_t_done "test_common_datetime.sh" diff --git a/linux_configuration/scripts/lib/tests/test_mtk_classify.sh b/linux_configuration/scripts/lib/tests/test_mtk_classify.sh new file mode 100755 index 00000000..978c3c67 --- /dev/null +++ b/linux_configuration/scripts/lib/tests/test_mtk_classify.sh @@ -0,0 +1,120 @@ +#!/usr/bin/env bash +# Covers classify_device, mtk_assert_mediatek, mtk_explain_classification +# and mtk_sanitize, all through the MTK_ROOT_FIXTURE seam. +# +# The load-bearing assertion here is the THIRD PHONE case. classify_device's +# contract is that ULEFONE requires a MediaTek signal AND a vendor/model +# match — both, never either — so an unrelated MediaTek handset must land in +# UNKNOWN rather than being treated as the Ulefone. Every caller refuses to +# act on UNKNOWN, and that is what stops this toolkit flashing a stranger's +# phone. A rule rewritten to `||` would still pass the ULEFONE and PIXEL +# cases below; only this one catches it. +set -euo pipefail + +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=mtk_harness.sh +source "$HERE/mtk_harness.sh" + +_t_sandbox +# shellcheck source=../mtk_common.sh +source "$HERE/../mtk_common.sh" + +# --- classify_device ------------------------------------------------------- + +MTK_ROOT_FIXTURE="$(_t_fixture ulefone \ + 'ro.product.manufacturer=Ulefone +ro.product.model=Armor 26 Ultra +ro.board.platform=mt6989 +ro.hardware=mt6989 +ro.product.device=Armor26Ultra' 'boot_a')" +export MTK_ROOT_FIXTURE +classify_device +_t_is "ULEFONE" "$MTK_DEVICE_CLASS" "MediaTek platform + Ulefone vendor -> ULEFONE" +_t_in "MediaTek platform" "$MTK_CLASS_REASON" "ULEFONE reason names the platform signal" + +MTK_ROOT_FIXTURE="$(_t_fixture pixel \ + 'ro.product.manufacturer=Google +ro.product.model=Pixel 6a +ro.board.platform=gs101 +ro.hardware=oriole +ro.product.device=bluejay' 'boot_a')" +classify_device +_t_is "PIXEL" "$MTK_DEVICE_CLASS" "Google + Pixel identity -> PIXEL" + +# A MediaTek phone that is NOT the Ulefone. The single most important case: +# "not a Pixel, so presumably the Ulefone" is exactly the reasoning the +# implementation refuses, and it is what would target the wrong hardware. +MTK_ROOT_FIXTURE="$(_t_fixture third_party \ + 'ro.product.manufacturer=Xiaomi +ro.product.model=Redmi Note 12 +ro.board.platform=mt6833 +ro.hardware=mt6833 +ro.product.device=tapas' 'boot_a')" +classify_device +_t_is "UNKNOWN" "$MTK_DEVICE_CLASS" "MediaTek but wrong vendor -> UNKNOWN, not ULEFONE" + +# Ulefone identity with no MediaTek signal is equally not enough. +MTK_ROOT_FIXTURE="$(_t_fixture ulefone_no_mtk \ + 'ro.product.manufacturer=Ulefone +ro.product.model=Armor 26 Ultra +ro.board.platform=sm8650 +ro.hardware=qcom +ro.product.device=Armor26Ultra' 'boot_a')" +classify_device +_t_is "UNKNOWN" "$MTK_DEVICE_CLASS" "Ulefone identity without MediaTek -> UNKNOWN" + +# An unauthorized device answers every getprop with an empty string. That must +# not resolve to a class. +MTK_ROOT_FIXTURE="$(_t_fixture empty '' 'boot_a')" +classify_device +_t_is "UNKNOWN" "$MTK_DEVICE_CLASS" "no properties at all -> UNKNOWN" +_t_in "no rule matched" "$MTK_CLASS_REASON" "UNKNOWN reason explains which reads were empty" + +# --- mtk_assert_mediatek --------------------------------------------------- + +# A positive assertion, deliberately not "is not a Pixel". It gates the only +# script that touches partitions, so both directions are asserted. +MTK_ROOT_FIXTURE="$(_t_fixture assert_mtk \ + 'ro.board.platform=mt6989 +ro.hardware=mt6989' 'boot_a')" +classify_device +_t_rc 0 "mtk_assert_mediatek accepts an mt#### platform" mtk_assert_mediatek + +MTK_ROOT_FIXTURE="$(_t_fixture assert_qcom \ + 'ro.board.platform=sm8650 +ro.hardware=qcom' 'boot_a')" +classify_device +_t_rc 1 "mtk_assert_mediatek rejects a non-MediaTek platform" mtk_assert_mediatek + +# hardware alone is enough — some devices report the SoC there and leave +# ro.board.platform unset. +MTK_ROOT_FIXTURE="$(_t_fixture assert_hw_only 'ro.hardware=mt8183' 'boot_a')" +classify_device +_t_rc 0 "mtk_assert_mediatek accepts a MediaTek ro.hardware alone" mtk_assert_mediatek + +# --- mtk_explain_classification -------------------------------------------- + +explain_out="$(mtk_explain_classification 2>&1)" +_t_in "Verdict:" "$explain_out" "explain output states a verdict" +_t_in "mt8183" "$explain_out" "explain output echoes the property it read" + +# --- mtk_sanitize ---------------------------------------------------------- + +# adb shell returns CRLF, which silently breaks string comparisons — the +# reason this function exists at all. +_t_is "mt6989" "$(mtk_sanitize "$(printf 'mt6989\r')")" "sanitize strips a trailing CR" +_t_is "Armor 26 Ultra" "$(mtk_sanitize 'Armor 26 Ultra')" "sanitize keeps spaces and digits" +# tr -cd DELETES disallowed bytes rather than truncating at the first one, so +# the alphanumerics inside a substitution survive as inert text. What matters +# is that $ ( ) ` ; & | cannot come back out. +# The metacharacters are assembled from \-escapes rather than written inside +# single quotes, so that "these must not expand" is stated in the code rather +# than left to the reader (and to shellcheck) to infer. +hostile="rm -rf \$(whoami)\`id\`; reboot & echo|x" +sanitized="$(mtk_sanitize "$hostile")" +_t_is "rm -rf whoamiid reboot echox" "$sanitized" "sanitize deletes shell metacharacters" +for meta in '$' '(' ')' '`' ';' '&' '|'; do + _t_is "" "${sanitized//[^"$meta"]/}" "sanitize leaves no '${meta}' behind" +done + +_t_done "test_mtk_classify.sh" diff --git a/linux_configuration/scripts/lib/tests/test_mtk_device.sh b/linux_configuration/scripts/lib/tests/test_mtk_device.sh new file mode 100755 index 00000000..d2a6ac18 --- /dev/null +++ b/linux_configuration/scripts/lib/tests/test_mtk_device.sh @@ -0,0 +1,152 @@ +#!/usr/bin/env bash +# Covers mtk_list_devices, mtk_device_state, mtk_select_device and +# mtk_require_authorized. +# +# The load-bearing case is REFUSING TO GUESS. With two phones attached and no +# --serial, mtk_select_device must fail rather than pick one: a wrong choice +# here points a partition-flashing toolkit at the wrong device. A version +# that took serials[0] would pass every other assertion in this file. +# +# adb is shimmed onto PATH rather than mocked in-process, because +# mtk_list_devices calls the binary directly. _t_adb_says writes the shim's +# canned `adb devices` output; nothing here talks to real hardware, and the +# shim refuses any subcommand the tests do not expect. +set -euo pipefail + +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=mtk_harness.sh +source "$HERE/mtk_harness.sh" + +_t_sandbox +# shellcheck source=../mtk_common.sh +source "$HERE/../mtk_common.sh" + +# --- adb shim -------------------------------------------------------------- + +# Narrow PATH to a shim dir. Every external these functions reach for must be +# seeded here: a missing one aborts the suite under `set -e` with no stderr. +_t_shim_dir="$T_TMP/bin" +mkdir -p "$_t_shim_dir" +cat >"$_t_shim_dir/adb" <<'SHIM' +#!/usr/bin/env bash +# Test double. Prints the canned `adb devices` table and nothing else; any +# other subcommand is a bug in the test, so say so loudly rather than +# returning an empty string that would read as "no devices". +if [[ ${1:-} == "devices" ]]; then + cat "${T_ADB_DEVICES:?}" + exit 0 +fi +printf 'adb shim: unexpected invocation: %s\n' "$*" >&2 +exit 97 +SHIM +chmod +x "$_t_shim_dir/adb" +for _t_ext in awk mapfile cat printf; do + _t_bin="$(command -v "$_t_ext" 2>/dev/null || true)" + [[ -n $_t_bin ]] && ln -sf "$_t_bin" "$_t_shim_dir/$_t_ext" +done +PATH="$_t_shim_dir:/usr/bin:/bin" +export PATH + +T_ADB_DEVICES="$T_TMP/adb_devices.txt" +export T_ADB_DEVICES + +_t_adb_says() { + printf 'List of devices attached\n%s\n' "$1" >"$T_ADB_DEVICES" +} + +# --- mtk_list_devices ------------------------------------------------------ + +_t_adb_says 'ABC123 device' +_t_is "ABC123 device" "$(mtk_list_devices)" "one attached device is listed with its state" + +_t_adb_says 'ABC123 device +DEF456 unauthorized' +_t_is "2" "$(mtk_list_devices | wc -l)" "both attached devices are listed" + +# A blank table is what an empty `adb devices` looks like. +_t_adb_says '' +_t_is "" "$(mtk_list_devices)" "no attached devices lists nothing" + +# --- mtk_device_state ------------------------------------------------------ + +_t_adb_says 'ABC123 device +DEF456 unauthorized +GHI789 offline' +_t_is "device" "$(mtk_device_state ABC123)" "an authorized device reports 'device'" +_t_is "unauthorized" "$(mtk_device_state DEF456)" "an unauthorized device reports so" +_t_is "offline" "$(mtk_device_state GHI789)" "an offline device reports so" +_t_is "absent" "$(mtk_device_state NOPE000)" "an unattached serial reports 'absent'" + +# --- mtk_select_device ----------------------------------------------------- + +_t_adb_says 'ABC123 device' +unset MTK_SERIAL +_t_rc 0 "a single attached device is selected" mtk_select_device +unset MTK_SERIAL +mtk_select_device +_t_is "ABC123" "$MTK_SERIAL" "the selected serial is the attached one" + +# Two devices, no request: must refuse. +_t_adb_says 'ABC123 device +DEF456 device' +unset MTK_SERIAL +_t_rc 1 "two devices with no --serial refuses to guess" mtk_select_device +unset MTK_SERIAL +refuse_out="$(mtk_select_device 2>&1 || true)" +_t_in "refusing to guess" "$refuse_out" "the refusal says why" +_t_in "--serial" "$refuse_out" "the refusal names the way out" + +# Two devices WITH a request: the named one is honoured. +unset MTK_SERIAL +mtk_select_device DEF456 +_t_is "DEF456" "$MTK_SERIAL" "an explicit serial disambiguates" + +# A requested serial that is not attached must fail, not fall back. +unset MTK_SERIAL +_t_rc 1 "a requested serial that is absent fails" mtk_select_device ZZZ999 +unset MTK_SERIAL +_t_in "is not attached" "$(mtk_select_device ZZZ999 2>&1 || true)" \ + "the absent-serial error names the problem" + +# No devices at all. +_t_adb_says '' +unset MTK_SERIAL +_t_rc 1 "no attached devices fails" mtk_select_device +unset MTK_SERIAL +_t_in "No device visible" "$(mtk_select_device 2>&1 || true)" "the no-device error is actionable" + +# --- mtk_require_authorized ------------------------------------------------ + +_t_adb_says 'ABC123 device +DEF456 unauthorized +GHI789 offline' + +MTK_SERIAL=ABC123 +_t_rc 0 "an authorized device passes the gate" mtk_require_authorized + +# The case this function exists for: an unauthorized device answers every +# getprop with an empty string, which reads exactly like "no properties". +MTK_SERIAL=DEF456 +_t_rc 1 "an unauthorized device is caught up front" mtk_require_authorized +_t_in "UNAUTHORIZED" "$(mtk_require_authorized 2>&1 || true)" \ + "the unauthorized error blames the prompt, not the device" + +MTK_SERIAL=GHI789 +_t_rc 1 "an offline device is caught" mtk_require_authorized +_t_in "OFFLINE" "$(mtk_require_authorized 2>&1 || true)" "the offline error is actionable" + +MTK_SERIAL=NOPE000 +_t_rc 1 "an absent device is caught" mtk_require_authorized + +# Under a fixture both selection and authorization are bypassed entirely, +# which is what keeps the rest of this directory's suites offline. +MTK_ROOT_FIXTURE="$(_t_fixture selectable '' 'boot')" +export MTK_ROOT_FIXTURE +unset MTK_SERIAL +_t_rc 0 "a fixture selects without adb" mtk_select_device +unset MTK_SERIAL +mtk_select_device +_t_is "fixture" "$MTK_SERIAL" "the fixture serial is a placeholder" +_t_rc 0 "a fixture is always authorized" mtk_require_authorized + +_t_done "test_mtk_device.sh" diff --git a/linux_configuration/scripts/lib/tests/test_mtk_partitions.sh b/linux_configuration/scripts/lib/tests/test_mtk_partitions.sh new file mode 100755 index 00000000..8c206bcd --- /dev/null +++ b/linux_configuration/scripts/lib/tests/test_mtk_partitions.sh @@ -0,0 +1,142 @@ +#!/usr/bin/env bash +# Covers discover_partitions, mtk_ramdisk_carrier, +# mtk_check_layout_expectation, mtk_sane_ramdisk_size and mtk_check_venv. +# +# The load-bearing case is INIT_BOOT WINS OVER BOOT. A device launched on +# Android 13+ carries the ramdisk in init_boot even though boot also exists, +# so a loop that checked boot first would pick the wrong partition on exactly +# the hardware this toolkit targets — and would do it silently, because both +# names are present and both reads succeed. +# +# The second is that discover_partitions consults the FILESYSTEM, never the +# API level. mtk_check_layout_expectation may disagree and warn, but it must +# not change the answer. +set -euo pipefail + +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=mtk_harness.sh +source "$HERE/mtk_harness.sh" + +_t_sandbox +# shellcheck source=../mtk_common.sh +source "$HERE/../mtk_common.sh" + +# --- discover_partitions --------------------------------------------------- + +# A/B device with init_boot: the ramdisk lives in init_boot, not boot. +MTK_ROOT_FIXTURE="$(_t_fixture ab_initboot 'ro.boot.slot_suffix=_a' \ + 'boot_a +boot_b +init_boot_a +init_boot_b +vbmeta_a +vbmeta_b +userdata')" +export MTK_ROOT_FIXTURE +_t_rc 0 "discover_partitions succeeds when a ramdisk carrier exists" discover_partitions +discover_partitions +_t_is "A/B" "$MTK_AB_SCHEME" "an _a suffix in the listing means A/B" +_t_is "_a" "$MTK_SLOT_SUFFIX" "slot suffix comes from ro.boot.slot_suffix" +_t_is "init_boot_a" "$MTK_RAMDISK_PART" "init_boot wins over boot when both exist" +_t_is "vbmeta_a" "$MTK_VBMETA_PART" "vbmeta picks up the active slot suffix" +_t_is "init_boot" "$(mtk_ramdisk_carrier)" "carrier strips the slot suffix" +_t_is "0" "$MTK_SLOT_ASSUMED" "a reported slot suffix is not an assumption" + +# A-only device with no init_boot: must fall through to boot. +MTK_ROOT_FIXTURE="$(_t_fixture aonly_boot '' \ + 'boot +recovery +vbmeta +userdata')" +discover_partitions +_t_is "A-only" "$MTK_AB_SCHEME" "no _a suffix means A-only" +_t_is "" "$MTK_SLOT_SUFFIX" "A-only has no slot suffix" +_t_is "boot" "$MTK_RAMDISK_PART" "falls through to boot when init_boot is absent" +_t_is "boot" "$(mtk_ramdisk_carrier)" "carrier of an unsuffixed boot is boot" + +# A/B device whose slot_suffix is unreadable. The code falls back to _a +# because a dump has to start somewhere, but must FLAG that it guessed. +MTK_ROOT_FIXTURE="$(_t_fixture ab_no_suffix '' \ + 'boot_a +boot_b +vbmeta_a +vbmeta_b')" +discover_partitions +_t_is "_a" "$MTK_SLOT_SUFFIX" "A/B with no reported suffix falls back to _a" +_t_is "1" "$MTK_SLOT_ASSUMED" "the _a fallback is recorded as an assumption" +_t_is "boot_a" "$MTK_RAMDISK_PART" "assumed suffix still resolves a carrier" + +# An empty listing is what an unauthorized or absent device produces. It must +# fail rather than reporting a plausible-looking empty layout. +MTK_ROOT_FIXTURE="$(_t_fixture no_partitions '' '')" +_t_rc 1 "discover_partitions fails on an empty by-name listing" discover_partitions +discover_partitions || true +_t_is "unknown" "$MTK_AB_SCHEME" "an empty listing yields an unknown scheme" +_t_is "" "$MTK_RAMDISK_PART" "an empty listing names no ramdisk partition" + +# A listing with neither boot nor init_boot: non-empty, but no carrier. +MTK_ROOT_FIXTURE="$(_t_fixture no_carrier '' \ + 'system +vendor +userdata')" +_t_rc 1 "discover_partitions fails when no carrier is present" discover_partitions + +# --- mtk_check_layout_expectation ------------------------------------------ + +# first_api_level >= 33 predicts init_boot. Agreement is quiet. +MTK_ROOT_FIXTURE="$(_t_fixture api34_initboot \ + 'ro.boot.slot_suffix=_a +ro.product.first_api_level=34' \ + 'boot_a +init_boot_a')" +discover_partitions +_t_rc 0 "api 34 + init_boot carrier agrees" mtk_check_layout_expectation + +# Disagreement warns and returns 1 — but the device still wins. +MTK_ROOT_FIXTURE="$(_t_fixture api34_boot_only \ + 'ro.boot.slot_suffix=_a +ro.product.first_api_level=34' \ + 'boot_a +vbmeta_a')" +discover_partitions +_t_rc 1 "api 34 + boot carrier disagrees and says so" mtk_check_layout_expectation +_t_is "boot_a" "$MTK_RAMDISK_PART" "a disagreeing expectation does NOT change the answer" +warn_out="$(mtk_check_layout_expectation 2>&1 || true)" +_t_in "Trusting the device" "$warn_out" "the warning states the filesystem wins" + +# Below 33 predicts boot. +MTK_ROOT_FIXTURE="$(_t_fixture api30_boot 'ro.product.first_api_level=30' 'boot')" +discover_partitions +_t_rc 0 "api 30 + boot carrier agrees" mtk_check_layout_expectation + +# An unreadable or non-numeric api level is not an error — it is simply no +# cross-check. This must not be mistaken for a disagreement. +MTK_ROOT_FIXTURE="$(_t_fixture api_missing '' 'boot')" +discover_partitions +_t_rc 0 "an absent api level skips the cross-check" mtk_check_layout_expectation + +# --- mtk_sane_ramdisk_size ------------------------------------------------- + +# Both an empty read and a whole-disk grab look like success to mtkclient's +# exit code, which is the entire reason this band exists. +_t_rc 1 "zero bytes is implausible" mtk_sane_ramdisk_size 0 +_t_in "implausibly small" "$(mtk_sane_ramdisk_size 0 || true)" "small failure says which way" +_t_rc 1 "a truncated read is implausible" mtk_sane_ramdisk_size 1024 +_t_rc 0 "8 MiB is a plausible ramdisk" mtk_sane_ramdisk_size $((8 * 1024 * 1024)) +_t_rc 0 "the lower bound itself is accepted" mtk_sane_ramdisk_size $((1024 * 1024)) +_t_rc 0 "the upper bound itself is accepted" mtk_sane_ramdisk_size $((128 * 1024 * 1024)) +_t_rc 1 "a whole-disk grab is implausible" mtk_sane_ramdisk_size $((4 * 1024 * 1024 * 1024)) +_t_in "implausibly large" "$(mtk_sane_ramdisk_size $((4 * 1024 * 1024 * 1024)) || true)" \ + "large failure says which way" +# `stat` failing yields an empty string, not a number. +_t_rc 1 "a non-numeric size is rejected" mtk_sane_ramdisk_size "not-a-number" +_t_rc 1 "an empty size is rejected" mtk_sane_ramdisk_size "" + +# --- mtk_check_venv -------------------------------------------------------- + +# MTK_ROOT_CACHE points at an empty tmpdir, so this takes the deterministic +# "missing" branch on any host, CI included. +_t_rc 1 "an absent venv is reported as missing" mtk_check_venv +_t_in "missing:" "$(mtk_check_venv || true)" "the missing branch names the interpreter path" + +_t_done "test_mtk_partitions.sh" diff --git a/meta/shell-coverage-allowlist.txt b/meta/shell-coverage-allowlist.txt index f2855771..87c934e6 100644 --- a/meta/shell-coverage-allowlist.txt +++ b/meta/shell-coverage-allowlist.txt @@ -1,8 +1,10 @@ # Shell libraries that predate the coverage ratchet. # -# Each line is a file exempt from check_shell_coverage.sh until it is -# next modified. Modifying an exempt file requires giving its lib/ -# directory a tests/run_all.sh suite and deleting the line. +# Each line is a file exempt from check_shell_coverage.sh. The exemption +# is static: editing one of these files is fine and does not require +# tests. An entry goes away when its lib/ directory gains a +# tests/run_all.sh, at which point every file in that directory is +# enforced. # # This list only ever shrinks. Never add a path to it by hand -- a new # library must ship with its tests. @@ -10,15 +12,6 @@ # Regenerate (only ever to REMOVE now-covered entries): # bash meta/scripts/check_shell_coverage.sh --seed linux_configuration/.githooks/lib/common.sh -linux_configuration/scripts/lib/android.sh -linux_configuration/scripts/lib/common_datetime.sh -linux_configuration/scripts/lib/common_packages.sh -linux_configuration/scripts/lib/common_services.sh -linux_configuration/scripts/lib/common.sh -linux_configuration/scripts/lib/mtk_classify.sh -linux_configuration/scripts/lib/mtk_common.sh -linux_configuration/scripts/lib/mtk_device.sh -linux_configuration/scripts/lib/mtk_partitions.sh linux_configuration/scripts/meta/lib/shell_check_discover.sh linux_configuration/scripts/meta/lib/shell_check_install.sh linux_configuration/scripts/meta/lib/shell_check_lint.sh