Files
insta360-link-tools/tests/test_resolver.sh
T
brad f95853dc25 Add GPL-3.0 licensing and prepare for a public repository
metadata.json claimed MIT while no LICENSE file existed, so the repository
had no effective licence at all. It is now GPL-3.0-or-later: the full FSF
text in LICENSE, the identifier in metadata.json, and REUSE-style
SPDX-FileCopyrightText and SPDX-License-Identifier headers on all 33 source
files, which is the convention KDE itself uses.

Worth recording that GPL-3.0 is a preference here rather than an obligation.
Nothing in the dependency graph forces it: linkctl is MIT and is invoked as a
subprocess rather than linked, and the KDE QML modules the applet imports are
LGPL.

Removed two things that should not ship in a public repository. tests's QML
linter had an absolute sandbox path, /tmp/pyside/PySide6/qmllint, baked into
its search list; it now looks for Qt's qmllint and PySide6's pyside6-qmllint
on PATH and honours a QMLLINT override, and says how to install one when it
finds nothing. The systemd unit carried a Documentation= URL pointing at a
host that does not exist, and its ExecStart silently assumed a particular
checkout directory; the URL is gone and the path is now an obvious
PATH/TO/CHECKOUT placeholder next to the comment telling you to edit it.

Added a Credits section naming the work this is built on, since the project
is mostly glue: linkctl does the camera control, schlarpc's firmware teardown
documents the extension-unit protocol, and vrwallace's controller established
that the Link 2 needs both pan and tilt axes in a single VIDIOC_S_EXT_CTRLS
call, which is the detail the whole thing depends on.

Also made the two Python tests executable, matching the other scripts.

Audited for operator-specific detail: no credentials, tokens, private
hostnames, IP addresses or absolute home paths in any tracked file or
anywhere in the history's diffs. Three merge commits do carry Reviewed-on and
Reviewed-by trailers naming a private forge, added by the forge at merge
time; those are commit metadata rather than file content and are handled
separately.

Tested: 169 checks across seven suites plus the QML linter, all passing after
the header insertion, which confirms it did not disturb the order-sensitive
`.pragma library` line in logic.js or the qmldir module definitions.
shellcheck clean.
2026-10-13 20:54:23 -07:00

128 lines
4.7 KiB
Bash
Executable File

#!/usr/bin/env bash
# SPDX-FileCopyrightText: 2026 branderson
# SPDX-License-Identifier: GPL-3.0-or-later
#
# Checks the binary-resolution command the Plasma widget generates, by running
# it in a real shell against real directories.
#
# This matters because Plasma executes applet commands through
# KProcess::setShellCommand, which is `/bin/sh -c` inheriting plasmashell's
# environment. That environment never sources a login profile, so anything
# that puts linkctl on PATH from a shell rc file (mise or asdf shims,
# ~/.local/bin on some distributions) is invisible to the widget even though
# it works in a terminal. tests/test_joystick_logic.js checks the string's
# shape; this checks that the string actually finds a binary.
#
# Run: tests/test_resolver.sh
set -uo pipefail
HERE=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
readonly HERE
readonly LOGIC="$HERE/../plasmoid/package/contents/ui/logic.js"
command -v node >/dev/null 2>&1 || { echo "SKIP: node not available"; exit 0; }
# Ask logic.js for the command, so the test cannot drift from the shipped code.
CMD=$(node -e '
const fs = require("fs"), vm = require("vm");
const src = fs.readFileSync(process.argv[1], "utf8");
const ctx = { Math, JSON, String };
vm.createContext(ctx);
vm.runInContext(src.replace(/^\s*\.pragma\s+library\s*$/m, ""), ctx);
process.stdout.write(ctx.resolveCommand("linkctl"));
' "$LOGIC")
[[ -n $CMD ]] || { echo "FAIL: could not generate the resolver command"; exit 1; }
TMP=$(mktemp -d)
cleanup() { rm -rf "$TMP"; }
trap cleanup EXIT
checks=0
failures=0
# Run the generated command with a deliberately bare environment, the way
# plasmashell would, and compare what it prints.
expect() {
local label=$1 home=$2 path=$3 want=$4 got
checks=$((checks + 1))
got=$(env -i HOME="$home" PATH="$path" sh -c "$CMD" 2>/dev/null)
if [[ $got == "$want" ]]; then
printf 'ok %s\n' "$label"
else
failures=$((failures + 1))
printf 'FAIL %s\n got: %s\n want: %s\n' \
"$label" "${got:-<empty>}" "${want:-<empty>}"
fi
}
make_exe() { mkdir -p "$(dirname "$1")" && printf '#!/bin/sh\n' > "$1" && chmod +x "$1"; }
mkdir -p "$TMP/empty"
# On PATH: the ordinary case, and the only one that worked before the fix.
make_exe "$TMP/onpath/linkctl"
expect "finds it on PATH" "$TMP/empty" "$TMP/onpath:/usr/bin:/bin" \
"$TMP/onpath/linkctl"
# Not on PATH at all. These are the cases that broke the widget while the
# terminal kept working.
make_exe "$TMP/mise/.local/share/mise/shims/linkctl"
expect "finds a mise shim off PATH" "$TMP/mise" "/usr/bin:/bin" \
"$TMP/mise/.local/share/mise/shims/linkctl"
make_exe "$TMP/localbin/.local/bin/linkctl"
expect "finds ~/.local/bin off PATH" "$TMP/localbin" "/usr/bin:/bin" \
"$TMP/localbin/.local/bin/linkctl"
make_exe "$TMP/cargo/.cargo/bin/linkctl"
expect "finds ~/.cargo/bin off PATH" "$TMP/cargo" "/usr/bin:/bin" \
"$TMP/cargo/.cargo/bin/linkctl"
# Absent entirely: must print nothing, so the widget can say so plainly
# rather than running something unexpected.
expect "prints nothing when absent" "$TMP/empty" "/usr/bin:/bin" ""
# Present but not executable must not count as found.
mkdir -p "$TMP/noexec/.local/bin"
printf 'not executable' > "$TMP/noexec/.local/bin/linkctl"
expect "ignores a non-executable file" "$TMP/noexec" "/usr/bin:/bin" ""
# PATH wins over the fallback list, so an explicit install is preferred.
make_exe "$TMP/both/.local/bin/linkctl"
make_exe "$TMP/preferred/linkctl"
expect "prefers PATH over the search list" "$TMP/both" \
"$TMP/preferred:/usr/bin:/bin" "$TMP/preferred/linkctl"
# The uniqueness suffix exists only to change the string Plasma keys its
# command containers by. It must not change what the shell actually runs.
ask_logic() {
node -e '
const fs = require("fs"), vm = require("vm");
const src = fs.readFileSync(process.argv[1], "utf8");
const ctx = { Math, JSON, String };
vm.createContext(ctx);
vm.runInContext(src.replace(/^\s*\.pragma\s+library\s*$/m, ""), ctx);
const tick = ctx.tickCommand(ctx.baseCmd("echo", ""), 1, 1, 6, false, false);
process.stdout.write(process.argv[2] === "unique"
? ctx.uniqueify(tick, 42) : tick);
' "$LOGIC" "$1"
}
checks=$((checks + 1))
plain_out=$(sh -c "$(ask_logic plain)" 2>&1)
unique_out=$(sh -c "$(ask_logic unique)" 2>&1)
if [[ $plain_out == "$unique_out" && -n $plain_out ]]; then
printf 'ok %s\n' "uniqueness suffix does not change what the shell runs"
else
failures=$((failures + 1))
printf 'FAIL %s\n plain: %s\n unique: %s\n' \
"uniqueness suffix does not change what the shell runs" \
"$plain_out" "$unique_out"
fi
printf '\n%d checks, %d failures\n' "$checks" "$failures"
[[ $failures -eq 0 ]]