mirror of
https://github.com/openwrt/packages.git
synced 2026-04-15 10:51:55 +00:00
* add: ucode-mod-uloop dependency * add: parallel downloads using uloop * fix: explicit allow for domains from allow-lists * fix: get environment information for getInitStatus RPCD call * add: update tests Signed-off-by: Stan Grishin <stangri@melmac.ca>
383 lines
12 KiB
Bash
383 lines
12 KiB
Bash
#!/usr/bin/env bash
|
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
# Functional test runner for adblock-fast.
|
|
#
|
|
# Adapts the mwan4 mock-and-expect pattern for adblock-fast:
|
|
# - Patches ES module imports to require() calls
|
|
# - Redirects hardcoded paths to a temp directory
|
|
# - Exports internal functions for test access
|
|
# - Uses real shell commands (sed/sort/grep/awk) with mock UCI/UBus
|
|
#
|
|
# Usage: cd source.openwrt.melmac.ca/adblock-fast && bash tests/run_tests.sh [test_file...]
|
|
|
|
set -o pipefail
|
|
|
|
line='........................................'
|
|
|
|
# ── Temp directories ─────────────────────────────────────────────────
|
|
|
|
TESTDIR="/tmp/adb_test.$$"
|
|
patch_dir="/tmp/adb_test_modules.$$"
|
|
stub_dir="$TESTDIR/stubs"
|
|
|
|
mkdir -p "$TESTDIR"/{var_run/adblock-fast,var,shm,var_lib_unbound,etc,cache,tmp}
|
|
mkdir -p "$patch_dir"
|
|
mkdir -p "$stub_dir"
|
|
|
|
trap "rm -rf '$TESTDIR' '$patch_dir'" EXIT
|
|
|
|
# ── Copy test data ───────────────────────────────────────────────────
|
|
|
|
cp -r ./tests/data "$TESTDIR/data"
|
|
|
|
# ── Prepare resolved mock fixtures (replace TESTDIR placeholder) ─────
|
|
|
|
mkdir -p "$TESTDIR/mocks_resolved/uci" "$TESTDIR/mocks_resolved/ubus"
|
|
for f in ./tests/mocks/uci/*.json; do
|
|
sed "s|TESTDIR|$TESTDIR|g" "$f" > "$TESTDIR/mocks_resolved/uci/$(basename "$f")"
|
|
done
|
|
for f in ./tests/mocks/ubus/*.json; do
|
|
cp "$f" "$TESTDIR/mocks_resolved/ubus/$(basename "$f")"
|
|
done
|
|
|
|
# ── Create resolver stubs ───────────────────────────────────────────
|
|
|
|
cat > "$stub_dir/dnsmasq" << 'STUB'
|
|
#!/bin/sh
|
|
case "$1" in
|
|
--version)
|
|
echo "Dnsmasq version 2.89"
|
|
echo "Compile time options: IPv6 GNU-getopt no-DBus no-UBus no-i18n no-IDN DHCP DHCPv6 no-Lua TFTP no-conntrack ipset nftset auth no-cryptohash no-DNSSEC loop-detect inotify dumpfile"
|
|
;;
|
|
--test)
|
|
echo "dnsmasq: syntax check OK."
|
|
exit 0
|
|
;;
|
|
esac
|
|
STUB
|
|
chmod +x "$stub_dir/dnsmasq"
|
|
|
|
for cmd in smartdns unbound; do
|
|
printf '#!/bin/sh\nexit 0\n' > "$stub_dir/$cmd"
|
|
chmod +x "$stub_dir/$cmd"
|
|
done
|
|
|
|
# Create ipset/nft stubs
|
|
for cmd in ipset nft; do
|
|
printf '#!/bin/sh\nexit 0\n' > "$stub_dir/$cmd"
|
|
chmod +x "$stub_dir/$cmd"
|
|
done
|
|
|
|
# Create resolveip stub
|
|
cat > "$stub_dir/resolveip" << 'STUB'
|
|
#!/bin/sh
|
|
echo "127.0.0.1"
|
|
exit 0
|
|
STUB
|
|
chmod +x "$stub_dir/resolveip"
|
|
|
|
# ── Patch adblock-fast.uc ───────────────────────────────────────────
|
|
|
|
# The sed pipeline:
|
|
# 1. Convert ES module imports to require() calls
|
|
# 2. Redirect hardcoded paths to TESTDIR
|
|
# 3. Extend is_present() search paths with stub_dir
|
|
# 4. Export internal test helpers
|
|
|
|
sed \
|
|
-e "s|import { readfile, writefile, popen, stat, unlink, rename, open, glob, mkdir, mkstemp, symlink, chmod, chown, realpath, lsdir, access, dirname } from 'fs';|let _fs = require('fs'), readfile = _fs.readfile, writefile = _fs.writefile, popen = _fs.popen, stat = _fs.stat, unlink = _fs.unlink, rename = _fs.rename, open = _fs.open, glob = _fs.glob, mkdir = _fs.mkdir, mkstemp = _fs.mkstemp, symlink = _fs.symlink, chmod = _fs.chmod, chown = _fs.chown, realpath = _fs.realpath, lsdir = _fs.lsdir, access = _fs.access, dirname = _fs.dirname;|" \
|
|
-e "s|import { cursor } from 'uci';|let _uci = require('uci'), cursor = _uci.cursor;|" \
|
|
-e "s|import { connect } from 'ubus';|let _ubus = require('ubus'), connect = _ubus.connect;|" \
|
|
-e "s|import \* as uloop from 'uloop';|let uloop = null;|" \
|
|
-e "s|dnsmasq_file: '/var/run/adblock-fast/adblock-fast.dnsmasq'|dnsmasq_file: '${TESTDIR}/var_run/adblock-fast/adblock-fast.dnsmasq'|" \
|
|
-e "s|config_file: '/etc/config/adblock-fast'|config_file: '${TESTDIR}/etc/adblock-fast'|" \
|
|
-e "s|run_file: '/dev/shm/adblock-fast'|run_file: '${TESTDIR}/shm/adblock-fast'|" \
|
|
-e "s|status_file: '/dev/shm/adblock-fast.status.json'|status_file: '${TESTDIR}/shm/adblock-fast.status.json'|" \
|
|
-e "s|'/var/run/' + pkg.name|'${TESTDIR}/var_run/' + pkg.name|g" \
|
|
-e "s|'/var/lib/unbound/adb_list.' + pkg.name|'${TESTDIR}/var_run/' + pkg.name + '/adb_list.' + pkg.name|g" \
|
|
-e "s|'/var/' + pkg.name|'${TESTDIR}/var/' + pkg.name|g" \
|
|
-e "s|for (let dir in \['/usr/sbin', '/usr/bin', '/sbin', '/bin'\])|for (let dir in ['${stub_dir}', '/usr/sbin', '/usr/bin', '/sbin', '/bin'])|" \
|
|
-e "s|stat('/etc/config/dhcp')|stat('${TESTDIR}/etc/dhcp')|g" \
|
|
-e "s|stat('/etc/config/smartdns')|stat('${TESTDIR}/etc/smartdns')|g" \
|
|
./files/lib/adblock-fast/adblock-fast.uc > "$patch_dir/adblock-fast.uc"
|
|
|
|
# Append test-helper exports to the patched module.
|
|
# We add a _test_internals object that gives tests access to module-private state.
|
|
# NOTE: cfg is accessed via get_cfg()/set_cfg() because env.load_config()
|
|
# reassigns cfg, which would make a direct reference stale.
|
|
sed -i '/^export default {/,/^};/{
|
|
/process_file_url,/a\
|
|
\t// Test helpers (injected by test runner)\
|
|
\t_test_internals: {\
|
|
\t\tdownload_lists: download_lists,\
|
|
\t\tdetect_file_type: detect_file_type,\
|
|
\t\tdns_modes: dns_modes,\
|
|
\t\tget_cfg: function() { return cfg; },\
|
|
\t\tset_cfg: function(k, v) { cfg[k] = v; },\
|
|
\t\tstate: state,\
|
|
\t\tenv: env,\
|
|
\t\tdns_output: dns_output,\
|
|
\t\tstatus_data: status_data,\
|
|
\t\tlist_formats: list_formats,\
|
|
\t\ttmp: tmp,\
|
|
\t\tappend_urls: append_urls,\
|
|
\t\tcount_lines: count_lines,\
|
|
\t\tcount_blocked_domains: count_blocked_domains,\
|
|
\t},
|
|
}' "$patch_dir/adblock-fast.uc"
|
|
|
|
# Patch cli.uc too (for tests that exercise the CLI path)
|
|
sed \
|
|
-e "s|import adb from 'adblock-fast';|let adb = require('adblock-fast');|" \
|
|
./files/lib/adblock-fast/cli.uc > "$patch_dir/cli.uc"
|
|
|
|
# ── Set up environment ───────────────────────────────────────────────
|
|
|
|
export TMPDIR="$TESTDIR/tmp"
|
|
export PATH="$stub_dir:$PATH"
|
|
|
|
# ucode invocation: patched module first, then mocklib, then original source
|
|
ucode="ucode -S -L$patch_dir -L./tests/lib -L./files/lib/adblock-fast"
|
|
|
|
# ── Test framework (adapted from mwan4) ──────────────────────────────
|
|
|
|
extract_sections() {
|
|
local file=$1
|
|
local dir=$2
|
|
local count=0
|
|
local tag line outfile
|
|
|
|
while IFS= read -r line; do
|
|
case "$line" in
|
|
"-- Testcase --")
|
|
tag="test"
|
|
count=$((count + 1))
|
|
outfile=$(printf "%s/%03d.in" "$dir" $count)
|
|
printf "" > "$outfile"
|
|
;;
|
|
"-- Environment --")
|
|
tag="env"
|
|
count=$((count + 1))
|
|
outfile=$(printf "%s/%03d.env" "$dir" $count)
|
|
printf "" > "$outfile"
|
|
;;
|
|
"-- Expect stdout --"|"-- Expect stderr --"|"-- Expect exitcode --")
|
|
tag="${line#-- Expect }"
|
|
tag="${tag% --}"
|
|
count=$((count + 1))
|
|
outfile=$(printf "%s/%03d.%s" "$dir" $count "$tag")
|
|
printf "" > "$outfile"
|
|
;;
|
|
"-- File "*" --")
|
|
tag="file"
|
|
outfile="${line#-- File }"
|
|
outfile="$(echo "${outfile% --}" | xargs)"
|
|
outfile="$dir/files$(readlink -m "/${outfile:-file}")"
|
|
mkdir -p "$(dirname "$outfile")"
|
|
printf "" > "$outfile"
|
|
;;
|
|
"-- End --")
|
|
tag=""
|
|
outfile=""
|
|
;;
|
|
*)
|
|
if [ -n "$tag" ]; then
|
|
printf "%s\\n" "$line" >> "$outfile"
|
|
fi
|
|
;;
|
|
esac
|
|
done < "$file"
|
|
|
|
# Post-process: replace TESTDIR placeholder in extracted files
|
|
# - files/ directory (mock data)
|
|
# - expect sections (.stdout, .stderr) so tests can reference TESTDIR paths
|
|
# NOTE: Do NOT substitute in .in files — those use TESTDIR as a ucode global variable
|
|
find "$dir/files" -type f 2>/dev/null | while read -r f; do
|
|
sed -i "s|TESTDIR|$TESTDIR|g" "$f"
|
|
done
|
|
for f in "$dir"/*.stdout "$dir"/*.stderr; do
|
|
[ -f "$f" ] && sed -i "s|TESTDIR|$TESTDIR|g" "$f"
|
|
done
|
|
|
|
return $(ls -l "$dir/"*.in 2>/dev/null | wc -l)
|
|
}
|
|
|
|
run_testcase() {
|
|
local num=$1
|
|
local dir=$2
|
|
local in=$3
|
|
local env=$4
|
|
local out=$5
|
|
local err=$6
|
|
local code=$7
|
|
local fail=0
|
|
|
|
# Clean test state between runs
|
|
rm -rf "$TESTDIR"/var_run/adblock-fast/*
|
|
rm -f "$TESTDIR"/var/adblock-fast.*
|
|
rm -f "$TESTDIR"/shm/adblock-fast*
|
|
rm -f "$TESTDIR"/var_lib_unbound/*
|
|
rm -f "$TESTDIR"/tmp/adblock-fast*
|
|
mkdir -p "$TESTDIR"/var_run/adblock-fast
|
|
|
|
$ucode \
|
|
-D MOCK_SEARCH_PATH='["'"$dir"'/files", "'"$TESTDIR"'/mocks_resolved", "./tests/mocks"]' \
|
|
-D TESTDIR='"'"$TESTDIR"'"' \
|
|
${env:+-F "$env"} \
|
|
-l mocklib \
|
|
- <"$in" >"$dir/res.out" 2>"$dir/res.err"
|
|
|
|
printf "%d\n" $? > "$dir/res.code"
|
|
|
|
touch "$dir/empty"
|
|
|
|
if ! cmp -s "$dir/res.err" "${err:-$dir/empty}"; then
|
|
[ $fail = 0 ] && printf "!\n"
|
|
printf "Testcase #%d: Expected stderr did not match:\n" $num
|
|
diff -u --color=always --label="Expected stderr" --label="Resulting stderr" "${err:-$dir/empty}" "$dir/res.err"
|
|
printf -- "---\n"
|
|
fail=1
|
|
fi
|
|
|
|
if ! cmp -s "$dir/res.out" "${out:-$dir/empty}"; then
|
|
[ $fail = 0 ] && printf "!\n"
|
|
printf "Testcase #%d: Expected stdout did not match:\n" $num
|
|
diff -u --color=always --label="Expected stdout" --label="Resulting stdout" "${out:-$dir/empty}" "$dir/res.out"
|
|
printf -- "---\n"
|
|
fail=1
|
|
fi
|
|
|
|
if [ -n "$code" ] && ! cmp -s "$dir/res.code" "$code"; then
|
|
[ $fail = 0 ] && printf "!\n"
|
|
printf "Testcase #%d: Expected exit code did not match:\n" $num
|
|
diff -u --color=always --label="Expected code" --label="Resulting code" "$code" "$dir/res.code"
|
|
printf -- "---\n"
|
|
fail=1
|
|
fi
|
|
|
|
return $fail
|
|
}
|
|
|
|
run_test() {
|
|
local file=$1
|
|
local name=${file##*/}
|
|
local res ecode eout eerr ein eenv tests
|
|
local testcase_first=0 failed=0 count=0
|
|
|
|
printf "%s %s " "$name" "${line:${#name}}"
|
|
|
|
mkdir "/tmp/test.$$"
|
|
|
|
extract_sections "$file" "/tmp/test.$$"
|
|
tests=$?
|
|
|
|
[ -f "/tmp/test.$$/001.in" ] && testcase_first=1
|
|
|
|
for res in "/tmp/test.$$/"[0-9]*; do
|
|
case "$res" in
|
|
*.in)
|
|
count=$((count + 1))
|
|
|
|
if [ $testcase_first = 1 ]; then
|
|
# Flush previous test
|
|
if [ -n "$ein" ]; then
|
|
run_testcase $count "/tmp/test.$$" "$ein" "$eenv" "$eout" "$eerr" "$ecode" || failed=$((failed + 1))
|
|
|
|
eout=""
|
|
eerr=""
|
|
ecode=""
|
|
eenv=""
|
|
fi
|
|
|
|
ein=$res
|
|
else
|
|
run_testcase $count "/tmp/test.$$" "$res" "$eenv" "$eout" "$eerr" "$ecode" || failed=$((failed + 1))
|
|
|
|
eout=""
|
|
eerr=""
|
|
ecode=""
|
|
eenv=""
|
|
fi
|
|
|
|
;;
|
|
*.env) eenv=$res ;;
|
|
*.stdout) eout=$res ;;
|
|
*.stderr) eerr=$res ;;
|
|
*.exitcode) ecode=$res ;;
|
|
esac
|
|
done
|
|
|
|
# Flush last test
|
|
if [ $testcase_first = 1 ] && [ -n "$ein" ]; then
|
|
run_testcase $count "/tmp/test.$$" "$ein" "$eenv" "$eout" "$eerr" "$ecode" || failed=$((failed + 1))
|
|
fi
|
|
|
|
rm -r "/tmp/test.$$"
|
|
|
|
if [ $failed = 0 ]; then
|
|
printf "OK\n"
|
|
else
|
|
printf "%s %s FAILED (%d/%d)\n" "$name" "${line:${#name}}" $failed $tests
|
|
fi
|
|
|
|
return $failed
|
|
}
|
|
|
|
|
|
n_tests=0
|
|
n_fails=0
|
|
|
|
select_tests="$@"
|
|
|
|
use_test() {
|
|
local input="$(readlink -f "$1")"
|
|
local test
|
|
|
|
[ -f "$input" ] || return 1
|
|
[ -n "$select_tests" ] || return 0
|
|
|
|
for test in $select_tests; do
|
|
test="$(readlink -f "$test")"
|
|
|
|
[ "$test" != "$input" ] || return 0
|
|
done
|
|
|
|
return 1
|
|
}
|
|
|
|
for catdir in tests/[0-9][0-9]_*; do
|
|
[ -d "$catdir" ] || continue
|
|
|
|
printf "\n##\n## Running %s tests\n##\n\n" "${catdir##*/[0-9][0-9]_}"
|
|
|
|
for testfile in "$catdir/"[0-9][0-9]_*; do
|
|
use_test "$testfile" || continue
|
|
|
|
n_tests=$((n_tests + 1))
|
|
run_test "$testfile" || n_fails=$((n_fails + 1))
|
|
done
|
|
done
|
|
|
|
# ── Shell script syntax checks ──────────────────────────────────────
|
|
|
|
printf "\n##\n## Checking shell script syntax\n##\n\n"
|
|
for shellscript in \
|
|
files/etc/init.d/* \
|
|
files/etc/uci-defaults/*; do
|
|
[ -f "$shellscript" ] || continue
|
|
head -1 "$shellscript" | grep -q '^#!/bin/sh' || continue
|
|
name="${shellscript#files/}"
|
|
n_tests=$((n_tests + 1))
|
|
printf "%s %s " "$name" "${line:${#name}}"
|
|
if sh -n "$shellscript" 2>/dev/null; then
|
|
printf "OK\n"
|
|
else
|
|
printf "FAIL\n"
|
|
sh -n "$shellscript"
|
|
n_fails=$((n_fails + 1))
|
|
fi
|
|
done
|
|
|
|
printf "\nRan %d tests, %d okay, %d failures\n" $n_tests $((n_tests - n_fails)) $n_fails
|
|
exit $n_fails
|