mirror of
https://github.com/AstronautCheetah/frame-tweaks.git
synced 2026-10-17 13:00:18 +02:00
dev/run-tests.py: - Ctrl+C killed the parts running, then every part still queued started, one after another, silently (the pool waited for them on the way out): none starts now, those running end. - Putting the headset on moved each test process's main thread only (sched_setaffinity sets one thread): a Qt test's other threads, and what they started, stayed on every core. Every thread is pinned, every second while it's worn; a sensor read that fails keeps what it said last (it was "off": every core), and --pin pins when unsure. - A part that exited 0 without its results counted as passed; output that isn't UTF-8, or a signal Python has no name for, crashed the report. - FRAME_TWEAKS_TESTS: another tests folder (for its own tests). dev/check.sh: a FRAME_TWEAKS_CHECKS it doesn't know stops the tweak check, saying so (hidden, it ran on every core with the headset on); a file moved out of packs/ counts (--no-renames). dev/test-tweaks.sh: a status.sh that broke (127, a missing command) failed only in the window; .venv and caches aren't copied (a contributor's PyQt6, 280 MB, into /tmp each run). dev/fresh-install.sh: --from checkout with VERSION X.Y.Z-rc.N made a tag get.sh didn't take; the bwrap info file, whose pid is killed on the way out, was in a folder the sandbox can write; the control groups are read-only (it could freeze or kill your services), /dev/shm and /var/tmp its own, and no X11 with Wayland there (an X program can type into the other X windows). release.yml: a test release's number comes after the highest, not the count (one deleted gave a number that exists). dev/safe-push.sh says where its replay is when the push is refused.
179 lines
9.3 KiB
Bash
Executable File
179 lines
9.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# The checks, as CI runs them (.github/workflows/ci.yml calls this, one check per step), so a
|
|
# change can be checked before it's sent (CONTRIBUTING.md, "Checks"). Any Linux PC will do; none
|
|
# of it needs a Frame.
|
|
# dev/check.sh every check
|
|
# dev/check.sh docs tests just these: shell python lint i18n commits manifests tests
|
|
# metadata docs tweaks
|
|
# A check whose tool is missing is skipped, with a note on how to get it (in CI, where every tool
|
|
# is installed, a missing one fails instead). The tweak check, a minute of installs, is left out
|
|
# when nothing it installs changed since origin/main (not in CI, and not when named). PYTHON=…
|
|
# picks the interpreter for the unit tests; otherwise the first of .venv/bin/python, python3 and
|
|
# Arrow Pad's venv that has PyQt6.
|
|
# VR first: every check runs at the lowest CPU priority, and while the Frame's headset is worn
|
|
# the tests and the tweak check keep to one core (dev/run-tests.py: FRAME_TWEAKS_CHECKS=vr or
|
|
# all decides instead). On every core they made VR stutter.
|
|
set -uo pipefail
|
|
cd "$(dirname "$0")/.." || exit 1
|
|
renice -n 19 -p $$ >/dev/null 2>&1 || true
|
|
ionice -c 2 -n 7 -p $$ 2>/dev/null || true
|
|
ALL=(shell python lint i18n commits manifests tests metadata docs tweaks)
|
|
SKIP=125 # a check's status when it was skipped (no tool uses it)
|
|
UNNEEDED=124 # ... when nothing it checks changed
|
|
NAMED=${1:+1} # (checks named on the command line run whatever changed)
|
|
|
|
# Files git would commit (tracked, and new ones not ignored: what CI's checkout holds) with a
|
|
# line matching $1; not Markdown, whose code examples can start a line with a shebang.
|
|
files_with() { git ls-files -co --exclude-standard -z -- ':!*.md' | xargs -0 -r grep -lE "$1" -- 2>/dev/null; }
|
|
missing() { # missing WHAT HINT: skip this check (fail it in CI, where everything is installed)
|
|
if [ -n "${CI:-}" ]; then echo " $1 is missing"; return 1; fi
|
|
echo " skipped: needs $1 ($2)"; return $SKIP
|
|
}
|
|
|
|
check_shell() {
|
|
command -v shellcheck >/dev/null || { missing shellcheck "apt, dnf or brew install shellcheck"; return; }
|
|
# Scripts by their shebang, and the sourced libraries (lib/common.sh) by their shellcheck
|
|
# directive (-x doesn't report what's wrong in a sourced file).
|
|
# shellcheck disable=SC2046 # (file names without spaces, one per line)
|
|
shellcheck -x -S warning $(files_with '^(#!(/bin/(ba)?sh|/usr/bin/env bash)|# shellcheck shell=)') || return 1
|
|
# Programs are replaced, not written into (lib/common.sh write_file): a running program keeps
|
|
# its old file. A redirect or a cp onto ~/.local/bin is the mistake this looks for.
|
|
if grep -nE '(>|\bcp\b[^|]*) *"\$(BIN|HOME/\.local/bin)/' packs/*/*/*.sh lib/common.sh | grep -v write_file; then
|
|
echo " use write_file (lib/common.sh) for the lines above"; return 1
|
|
fi
|
|
}
|
|
|
|
check_python() {
|
|
local f bad=0
|
|
for f in $(files_with '^#!/usr/bin/env python3') gui/*.py lib/*.py packs/*/*/*.py; do
|
|
python3 -m py_compile "$f" || bad=1
|
|
done
|
|
return $bad
|
|
}
|
|
|
|
# Mistakes Python finds only when the line runs: a name that doesn't exist, an unused import
|
|
# (ruff.toml). Every Python file, those without .py by their shebang.
|
|
check_lint() {
|
|
command -v ruff >/dev/null || { missing ruff "brew install ruff, or pipx install ruff"; return; }
|
|
# shellcheck disable=SC2046 # (file names without spaces, one per line)
|
|
ruff check --quiet $({ files_with '^#!/usr/bin/env python3'; git ls-files -co --exclude-standard -- '*.py'; } | sort -u)
|
|
}
|
|
|
|
check_i18n() { python3 dev/i18n.py check; } # the translation template is current
|
|
|
|
check_commits() {
|
|
# Outside CI, only this checkout's own commits when there's an origin/main to compare with.
|
|
local range=HEAD
|
|
[ -z "${CI:-}" ] && git rev-parse -q --verify origin/main >/dev/null && range=origin/main..HEAD
|
|
dev/check-commit-msg.sh --all "$range"
|
|
}
|
|
|
|
check_manifests() {
|
|
local pack pin
|
|
# Every tweak's manifest (README.md front matter) is valid and names its scripts, the
|
|
# example's too (dev/test-tweaks.sh would leave a broken one out, unseen).
|
|
for pack in packs/core packs/integrations packs/third-party examples/hello-pack; do
|
|
python3 lib/sft.py mods check "$pack" >/dev/null || { python3 lib/sft.py mods check "$pack"; return 1; }
|
|
done
|
|
./install.sh list >/dev/null && ./install.sh _deps all >/dev/null || return 1
|
|
./install.sh _deps signal | grep -q '^wallet signal' || { echo " signal should depend on wallet"; return 1; }
|
|
# Every pin names a version, an https URL, and a SHA256 (files) or a commit (git)...
|
|
for pin in packs/*/*/pin packs/*/*/*.pin; do
|
|
[ -f "$pin" ] || continue # (a pattern that matched nothing)
|
|
grep -qE '^version=.+' "$pin" && grep -qE '^url=https://' "$pin" &&
|
|
grep -qE '^(sha256=[0-9a-f]{64}|rev=[0-9a-f]{40})$' "$pin" || { echo " incomplete $pin"; return 1; }
|
|
done
|
|
# ... and Frame Tweaks' copy (mirror=, with its licence and licence texts; a git commit's
|
|
# archive with its own sha256), or why there's none (nomirror=).
|
|
for pin in packs/*/*/pin packs/*/*/*.pin; do
|
|
[ -f "$pin" ] || continue
|
|
if grep -q '^mirror=' "$pin"; then
|
|
grep -qE '^mirror=https://github.com/AstronautCheetah/frame-tweaks/releases/download/mirror/[^/]+$' "$pin" &&
|
|
grep -qE '^license=.+' "$pin" && grep -qE '^license_file=https://' "$pin" &&
|
|
{ grep -q '^sha256=' "$pin" || grep -qE '^mirror_sha256=[0-9a-f]{64}$' "$pin"; } ||
|
|
{ echo " bad mirror in $pin"; return 1; }
|
|
else
|
|
grep -qE '^nomirror=.+' "$pin" || { echo " $pin: no mirror= and no nomirror= reason"; return 1; }
|
|
fi
|
|
done
|
|
if grep -vE '^(#|$|[A-Za-z0-9_.-]+==[0-9][0-9A-Za-z.]*$)' lib/python-constraints.txt; then
|
|
echo " lib/python-constraints.txt: exact versions only"; return 1
|
|
fi
|
|
}
|
|
|
|
check_tests() {
|
|
local py
|
|
for py in "${PYTHON:-}" .venv/bin/python python3 "$HOME/.local/share/arrowpad/venv/bin/python"; do
|
|
[ -n "$py" ] && "$py" -c 'import PyQt6.QtWidgets' 2>/dev/null && break
|
|
py=
|
|
done
|
|
if [ -z "$py" ]; then
|
|
missing "a Python with PyQt6, for the installer window's tests" \
|
|
"once: python3 -m venv .venv && .venv/bin/pip install -c lib/python-constraints.txt PyQt6 pyyaml"
|
|
return
|
|
fi
|
|
QT_QPA_PLATFORM=offscreen "$py" dev/run-tests.py ${CI:+-v} # (several at a time)
|
|
}
|
|
|
|
check_metadata() { # the AppStream metadata and the desktop entry
|
|
command -v appstreamcli >/dev/null || { missing appstreamcli "apt install appstream"; return; }
|
|
command -v desktop-file-validate >/dev/null || { missing desktop-file-validate "apt install desktop-file-utils"; return; }
|
|
appstreamcli validate --no-net share/metainfo/*.metainfo.xml || return 1
|
|
local tmp f rc=0; tmp=$(mktemp -d)
|
|
for f in share/applications/*.desktop; do
|
|
sed 's|@REPO@|/opt/x|' "$f" > "$tmp/${f##*/}" && desktop-file-validate "$tmp/${f##*/}" || rc=1
|
|
done
|
|
rm -rf "$tmp"; return $rc
|
|
}
|
|
|
|
check_docs() { python3 dev/check-docs.py && python3 dev/gen-docs.py --check; }
|
|
|
|
# tweak_free FILE: a changed file the tweak check doesn't depend on (it installs from packs/, lib/,
|
|
# examples/, icons/, share/ and install.sh).
|
|
tweak_free() {
|
|
case $1 in
|
|
dev/test-tweaks.sh|dev/check.sh) return 1 ;;
|
|
tests/*|docs/*|locale/*|.github/*|dev/*|.gitignore|.cz.toml|ruff.toml) return 0 ;;
|
|
*/*) return 1 ;;
|
|
*.md) return 0 ;;
|
|
esac
|
|
return 1
|
|
}
|
|
|
|
check_tweaks() { # install → status → uninstall in a throwaway home (dev/test-tweaks.sh)
|
|
if [ -z "${CI:-}" ] && [ -z "$NAMED" ] && git rev-parse -q --verify origin/main >/dev/null; then
|
|
local f needed=
|
|
while IFS= read -r f; do
|
|
tweak_free "$f" || { needed=1; break; }
|
|
done < <(git diff --name-only --no-renames origin/main; git ls-files -o --exclude-standard)
|
|
# (--no-renames: a file moved out of packs/ into docs/ is listed by its old place too)
|
|
if [ -z "$needed" ]; then
|
|
echo " not needed: nothing it installs changed since origin/main (dev/check.sh tweaks runs it)"
|
|
return $UNNEEDED
|
|
fi
|
|
fi
|
|
command -v bsdtar >/dev/null || { missing bsdtar "apt install libarchive-tools"; return; }
|
|
[ -n "${CI:-}" ] || python3 -c 'import ensurepip, venv' 2>/dev/null ||
|
|
{ missing "Python's venv module" "apt install python3-venv"; return; }
|
|
# (a FRAME_TWEAKS_CHECKS it doesn't know stops here, saying so: hidden, the minute-long check
|
|
# ran on every core with the headset on)
|
|
local pin; pin=$(python3 dev/run-tests.py --pin) || return 1
|
|
if [ -n "$pin" ]; then taskset -c "$pin" dev/test-tweaks.sh; else dev/test-tweaks.sh; fi
|
|
}
|
|
|
|
checks=("${@:-${ALL[@]}}")
|
|
for c in "${checks[@]}"; do
|
|
[[ " ${ALL[*]} " == *" $c "* ]] || { echo "unknown check: $c (one of: ${ALL[*]})" >&2; exit 2; }
|
|
done
|
|
failed=() skipped=() unneeded=()
|
|
for c in "${checks[@]}"; do
|
|
echo "== $c"
|
|
"check_$c"
|
|
case $? in 0) ;; "$SKIP") skipped+=("$c") ;; "$UNNEEDED") unneeded+=("$c") ;; *) failed+=("$c") ;; esac
|
|
done
|
|
echo
|
|
[ ${#skipped[@]} -eq 0 ] || echo "skipped (a tool is missing, see above): ${skipped[*]}"
|
|
[ ${#unneeded[@]} -eq 0 ] || echo "not needed (nothing they check changed): ${unneeded[*]}"
|
|
[ ${#failed[@]} -eq 0 ] || { echo "FAILED: ${failed[*]}"; exit 1; }
|
|
echo "passed: $(( ${#checks[@]} - ${#skipped[@]} - ${#unneeded[@]} )) of ${#checks[@]} checks"
|