mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-14 17:30:47 +01:00
Shrink the shell-coverage allowlist 115 -> 106: cover scripts/lib
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) <noreply@anthropic.com>
This commit is contained in:
parent
50854e7837
commit
d42d8ff20d
@ -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."
|
||||
}
|
||||
@ -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."
|
||||
]
|
||||
}
|
||||
119
linux_configuration/scripts/lib/tests/mtk_harness.sh
Executable file
119
linux_configuration/scripts/lib/tests/mtk_harness.sh
Executable file
@ -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 <name> <props-text> <by-name-text>
|
||||
_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 ]]
|
||||
}
|
||||
30
linux_configuration/scripts/lib/tests/run_all.sh
Executable file
30
linux_configuration/scripts/lib/tests/run_all.sh
Executable file
@ -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"
|
||||
160
linux_configuration/scripts/lib/tests/test_common_datetime.sh
Executable file
160
linux_configuration/scripts/lib/tests/test_common_datetime.sh
Executable file
@ -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"
|
||||
120
linux_configuration/scripts/lib/tests/test_mtk_classify.sh
Executable file
120
linux_configuration/scripts/lib/tests/test_mtk_classify.sh
Executable file
@ -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"
|
||||
152
linux_configuration/scripts/lib/tests/test_mtk_device.sh
Executable file
152
linux_configuration/scripts/lib/tests/test_mtk_device.sh
Executable file
@ -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"
|
||||
142
linux_configuration/scripts/lib/tests/test_mtk_partitions.sh
Executable file
142
linux_configuration/scripts/lib/tests/test_mtk_partitions.sh
Executable file
@ -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"
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user