update 2026-10-08 13:17:49

This commit is contained in:
action committed 2026-10-08 13:17:49 +08:00
1 parent 4c2363b0c6
commit a406d6528c
88 files changed
+423 -21796

No files matched your search

+1
View File
@@ -72,4 +72,5 @@ sh -n root/usr/libexec/rpcd/luci.oxidns
sh -n root/etc/init.d/oxidns
sh -n scripts/build-luci-package.sh
sh -n scripts/integration-check.sh
sh -n scripts/service-check.sh
sh -n scripts/release-check.sh
@@ -62,7 +62,38 @@ assert_unsafe_upload_rejected() {
json_ok "v.ok === false && v.code === 'uploaded_archive_unsafe'"
}
INIT_STUB_DIR="$(mktemp -d "${TMPDIR:-/tmp}/oxidns-init-stub.XXXXXX")"
printf '#!/bin/sh\nsleep 30\n' > "$INIT_STUB_DIR/oxidns"
: > "$INIT_STUB_DIR/config.yaml"
chmod 755 "$INIT_STUB_DIR/oxidns"
# Drive start_service from the real init script and record the procd params it sets.
printf '%s\n' \
'set -eu' \
'PROCD_ARGS="'"$INIT_STUB_DIR"'/procd-args"' \
'config_load() { :; }' \
'config_get() { case "$1" in CONFIG_PATH) CONFIG_PATH="'"$INIT_STUB_DIR"'/config.yaml" ;; WORKING_DIR) WORKING_DIR="'"$INIT_STUB_DIR"'" ;; esac; }' \
'config_get_bool() { eval "$1=\$FAKE_UCI_VALUE"; }' \
'procd_open_instance() { :; }' \
'procd_close_instance() { :; }' \
'procd_set_param() { printf "%s\n" "$*" >> "$PROCD_ARGS"; }' \
'. "'"$PWD"'/root/etc/init.d/oxidns"' \
'PROG="'"$INIT_STUB_DIR"'/oxidns"' \
'start_service' > "$INIT_STUB_DIR/drive.sh"
INIT_STUB_ARGS="$INIT_STUB_DIR/procd-args"
init_log_destination_args() {
: > "$INIT_STUB_ARGS"
FAKE_UCI_VALUE="$1" /bin/sh "$INIT_STUB_DIR/drive.sh"
}
init_log_destination_args 1
grep -qx 'stdout 1' "$INIT_STUB_ARGS"
grep -qx 'stderr 1' "$INIT_STUB_ARGS"
init_log_destination_args 0
grep -qx 'stdout 0' "$INIT_STUB_ARGS"
grep -qx 'stderr 0' "$INIT_STUB_ARGS"
rm -rf "$INIT_STUB_DIR"
scripts/check.sh
scripts/service-check.sh
root/usr/libexec/rpcd/luci.oxidns list | json_ok "'status' in v && 'core_install' in v && 'core_reinstall' in v && 'core_upload_install' in v && 'core_progress' in v && 'core_remove' in v && 'logs_recent' in v && 'settings_read' in v && !('config_basic_read' in v) && !('config_basic_save' in v)"
root/usr/libexec/rpcd/luci.oxidns call status | json_ok "v.ok === true && v.core && v.core.installed === false && v.webui && v.webui.installed === false && typeof v.webui.url === 'string' && typeof v.webui.local_only === 'boolean' && typeof v.webui.wildcard === 'boolean' && !('api' in v) && !('api_base_url' in v) && !('package' in v) && !('package_manager' in v)"
@@ -216,6 +247,25 @@ EOF
rm -rf "$REMOVE_PREFLIGHT_DIR"
rm -rf "$FAKE_UCI_DIR"
printf '%s' '{"limit":"20"}' | root/usr/libexec/rpcd/luci.oxidns call logs_recent | json_ok "v.ok === true && v.source === 'logread' && Array.isArray(v.lines) && !('entries' in v)"
SERVICE_LOG_DIR="$(mktemp -d "${TMPDIR:-/tmp}/oxidns-service-log-test.XXXXXX")"
# Records only the last argument, which is the message passed after `-t <tag>`.
cat > "$SERVICE_LOG_DIR/logger" <<'EOF'
#!/bin/sh
for last; do :; done
printf '%s\n' "$last" >> "$FAKE_LOGGER_OUT"
EOF
chmod 755 "$SERVICE_LOG_DIR/logger"
assert_service_event() {
: > "$SERVICE_LOG_DIR/events.log"
printf '%s' '{}' |
PATH="$SERVICE_LOG_DIR:$PATH" FAKE_LOGGER_OUT="$SERVICE_LOG_DIR/events.log" \
root/usr/libexec/rpcd/luci.oxidns call "service_$1" |
json_ok "v.ok === false && v.code === '$2'"
test "$(cat "$SERVICE_LOG_DIR/events.log")" = "$3"
}
assert_service_event start core_not_installed "service start ignored: OxiDNS core is not installed"
assert_service_event stop service_unavailable "service stop failed: init script not found: /etc/init.d/oxidns"
rm -rf "$SERVICE_LOG_DIR"
DIST_DIR="$(mktemp -d "${TMPDIR:-/tmp}/luci-app-oxidns-dist.XXXXXX")"
scripts/build-luci-package.sh 0.1.0 "$DIST_DIR" >/dev/null
+281
View File
@@ -0,0 +1,281 @@
#!/bin/sh
set -eu
REPO_DIR="$PWD"
SERVICE_TEST_DIR="$(mktemp -d "${TMPDIR:-/tmp}/oxidns-service-check.XXXXXX")"
cleanup_service_test() {
test_rc="$?"
if [ "$test_rc" -ne 0 ]; then
for diagnostic in result error events; do
[ ! -f "$SERVICE_TEST_DIR/$diagnostic" ] || cat "$SERVICE_TEST_DIR/$diagnostic" >&2
done
fi
rm -rf "$SERVICE_TEST_DIR"
}
trap cleanup_service_test EXIT
trap 'exit 1' HUP INT TERM
export REPO_DIR SERVICE_TEST_DIR
mkdir "$SERVICE_TEST_DIR/bin"
# Model procd retaining the old instance until several state queries have passed.
# sleep advances a fake clock so timeout tests do not wait ten real seconds.
cat > "$SERVICE_TEST_DIR/bin/ubus" <<'EOF'
#!/bin/sh
set -eu
[ "$*" = '-t 2 call service list {"name":"oxidns"}' ]
printf 'query\n' >> "$SERVICE_TEST_DIR/events"
case "$SERVICE_TEST_CASE" in
query_failed) exit 1 ;;
invalid_json) printf 'not JSON\n'; exit 0 ;;
invalid_root) printf '[]\n'; exit 0 ;;
invalid_shape) printf '{"oxidns":{"instances":null}}\n'; exit 0 ;;
empty_service) printf '{"oxidns":{}}\n'; rm -f "$SERVICE_TEST_DIR/instance"; exit 0 ;;
empty_instances) printf '{"oxidns":{"instances":{}}}\n'; rm -f "$SERVICE_TEST_DIR/instance"; exit 0 ;;
esac
query_count="$(cat "$SERVICE_TEST_DIR/query-count")"
query_count=$((query_count + 1))
printf '%s\n' "$query_count" > "$SERVICE_TEST_DIR/query-count"
if [ ! -f "$SERVICE_TEST_DIR/instance" ]; then
printf '{}\n'
elif [ "$SERVICE_TEST_CASE" = timeout ] || [ "$query_count" -eq 1 ]; then
printf '{"oxidns":{"instances":{"instance1":{"running":false},"instance2":{"running":true}}}}\n'
elif [ "$query_count" -eq 2 ]; then
# running:false must not be mistaken for an instance that has been removed.
printf '{"oxidns":{"instances":{"instance1":{"running":false}}}}\n'
else
rm -f "$SERVICE_TEST_DIR/instance"
printf '{}\n'
fi
EOF
# Minimal jsonfilter double for these service-list queries, including no-match
# exit status. Use a real jsonfilter when one is available on the test host.
if command -v jsonfilter >/dev/null 2>&1; then
ln -s "$(command -v jsonfilter)" "$SERVICE_TEST_DIR/bin/jsonfilter"
else
cat > "$SERVICE_TEST_DIR/bin/jsonfilter" <<'EOF'
#!/usr/bin/env node
const fs = require('fs');
try {
const value = JSON.parse(fs.readFileSync(0, 'utf8'));
const expression = process.argv[3];
let matches;
switch (expression) {
case '@': matches = [value]; break;
case '@.oxidns': matches = [value?.oxidns]; break;
case '@.oxidns.instances': matches = [value?.oxidns?.instances]; break;
case '@.oxidns.instances[*]':
matches = Object.values(value?.oxidns?.instances || {}); break;
default:
if (!/^@\.[a-z_]+$/.test(expression))
throw new Error('Unexpected query: ' + expression);
matches = [value?.[expression.slice(2)]];
}
matches = matches.filter(v => v !== undefined);
if (!matches.length) process.exit(1);
for (const match of matches) {
if (process.argv[2] === '-t')
console.log(match === null ? 'null' : Array.isArray(match) ? 'array' : typeof match);
else if (match !== null)
console.log(typeof match === 'string' ? match : JSON.stringify(match));
}
} catch (error) {
console.error(error.message);
process.exit(1);
}
EOF
fi
cat > "$SERVICE_TEST_DIR/bin/sleep" <<'EOF'
#!/bin/sh
set -eu
[ "$1" = 1 ]
printf 'wait\n' >> "$SERVICE_TEST_DIR/events"
clock_value="$(cat "$SERVICE_TEST_DIR/clock")"
printf '%s\n' "$((clock_value + 1))" > "$SERVICE_TEST_DIR/clock"
EOF
cat > "$SERVICE_TEST_DIR/bin/date" <<'EOF'
#!/bin/sh
set -eu
[ "$1" = '+%s' ]
cat "$SERVICE_TEST_DIR/clock"
EOF
cat > "$SERVICE_TEST_DIR/bin/logger" <<'EOF'
#!/bin/sh
printf '%s\n' "$*" >> "$SERVICE_TEST_DIR/audit"
EOF
cat > "$SERVICE_TEST_DIR/init" <<'EOF'
#!/bin/sh
set -eu
printf '%s\n' "$1" >> "$SERVICE_TEST_DIR/events"
case "$1" in
stop)
[ "$SERVICE_TEST_CASE" != stop_failed ] || exit 1
;;
start)
# An early start would reuse the old instance and its log destination.
[ ! -f "$SERVICE_TEST_DIR/instance" ] || exit 1
[ "$SERVICE_TEST_CASE" != start_failed ] || exit 1
config_load() { :; }
config_get() {
case "$1" in
CONFIG_PATH) CONFIG_PATH="$SERVICE_TEST_DIR/config.yaml" ;;
WORKING_DIR) WORKING_DIR="$SERVICE_TEST_DIR/work" ;;
esac
}
config_get_bool() { LOG_SYSLOG="$SERVICE_TEST_LOG_SYSLOG"; }
procd_open_instance() { :; }
procd_close_instance() { :; }
procd_set_param() { printf '%s\n' "$*" >> "$SERVICE_TEST_DIR/procd-args"; }
. "$REPO_DIR/root/etc/init.d/oxidns"
PROG="$SERVICE_TEST_DIR/oxidns"
start_service
;;
*) exit 1 ;;
esac
EOF
printf '#!/bin/sh\nexit 0\n' > "$SERVICE_TEST_DIR/oxidns"
chmod 755 "$SERVICE_TEST_DIR/init" "$SERVICE_TEST_DIR/oxidns" "$SERVICE_TEST_DIR/bin/"*
# Source the real rpcd backend without dispatching a mutation, then only
# redirect its system paths/validation to fixtures. Exercise real callers.
cat > "$SERVICE_TEST_DIR/drive.sh" <<'EOF'
#!/bin/sh
set -eu
test_mode="$1"
set -- list
. "$REPO_DIR/root/usr/libexec/rpcd/luci.oxidns" >/dev/null
OXIDNS_BIN="$SERVICE_TEST_DIR/oxidns"
TMP_ROOT="$SERVICE_TEST_DIR"
init_script() { printf '%s\n' "$SERVICE_TEST_DIR/init"; }
load_settings() {
CONFIG_PATH="$SERVICE_TEST_DIR/config.yaml"
WORKING_DIR="$SERVICE_TEST_DIR/work"
}
validate_config_file() { printf 'configuration is valid\n'; }
core_progress_command() { :; }
core_progress_log() { :; }
service_running() { return 0; }
WEBUI_DIR="$SERVICE_TEST_DIR/webui"
case "$test_mode" in
restart|stop) run_service "$test_mode" ;;
config)
# Avoid the config backup's unrelated timestamp call in this test.
rm -f "$SERVICE_TEST_DIR/config.yaml"
emit_config_save
;;
upload)
load_settings
CORE_TMP_DIR="$(mktemp -d "$TMP_ROOT/upload.XXXXXX")"
UPLOAD_PATH="$CORE_TMP_DIR/input"
mkdir "$CORE_TMP_DIR/unpack"
cp "$OXIDNS_BIN" "$CORE_TMP_DIR/unpack/oxidns"
printf '# replacement\n' >> "$CORE_TMP_DIR/unpack/oxidns"
validate_uploaded_binary() { return 0; }
install_uploaded_core_from_unpack binary "$CORE_TMP_DIR/unpack"
;;
release)
load_settings
mkdir -p "$SERVICE_TEST_DIR/release"
cp "$OXIDNS_BIN" "$SERVICE_TEST_DIR/release/oxidns"
printf '# replacement\n' >> "$SERVICE_TEST_DIR/release/oxidns"
tar -czf "$SERVICE_TEST_DIR/release.tar.gz" -C "$SERVICE_TEST_DIR/release" oxidns
require_download_transport() { return 0; }
download_file() {
if [ "$1" = 'fixture://archive' ]; then
cp "$SERVICE_TEST_DIR/release.tar.gz" "$2"
else
printf '{}\n' > "$2"
fi
}
select_release_asset() {
SELECT_TAG=v0.1.0
SELECT_ASSET_NAME=fixture.tar.gz
SELECT_URL=fixture://archive
SELECT_SHA256="$(sha256_file "$SERVICE_TEST_DIR/release.tar.gz")"
}
install_core_from_release reinstall v0.1.0 full
;;
esac
EOF
export PATH="$SERVICE_TEST_DIR/bin:$PATH"
reset_service_case() {
SERVICE_TEST_CASE="$1"
SERVICE_TEST_LOG_SYSLOG="$2"
export SERVICE_TEST_CASE SERVICE_TEST_LOG_SYSLOG
: > "$SERVICE_TEST_DIR/events"
: > "$SERVICE_TEST_DIR/audit"
: > "$SERVICE_TEST_DIR/procd-args"
: > "$SERVICE_TEST_DIR/instance"
: > "$SERVICE_TEST_DIR/config.yaml"
printf '0\n' > "$SERVICE_TEST_DIR/query-count"
printf '100\n' > "$SERVICE_TEST_DIR/clock"
}
run_service_case() {
printf '{"content":"test: true","restart":true}' |
/bin/sh "$SERVICE_TEST_DIR/drive.sh" "$1" > "$SERVICE_TEST_DIR/result" 2> "$SERVICE_TEST_DIR/error"
}
assert_result() {
node -e "const v=JSON.parse(require('fs').readFileSync(process.argv[1],'utf8')); if (!($1)) process.exit(1);" "$SERVICE_TEST_DIR/result"
}
for log_setting in 0 1; do
for caller in restart config upload release; do
reset_service_case delayed "$log_setting"
run_service_case "$caller"
assert_result 'v.ok === true'
test "$(cat "$SERVICE_TEST_DIR/events")" = "$(printf 'stop\nquery\nwait\nquery\nwait\nquery\nstart')"
grep -qx "stdout $log_setting" "$SERVICE_TEST_DIR/procd-args"
grep -qx "stderr $log_setting" "$SERVICE_TEST_DIR/procd-args"
done
done
for state in stopped empty_service empty_instances; do
reset_service_case "$state" 0
[ "$state" != stopped ] || rm "$SERVICE_TEST_DIR/instance"
run_service_case restart
assert_result 'v.ok === true'
test "$(cat "$SERVICE_TEST_DIR/events")" = "$(printf 'stop\nquery\nstart')"
done
for failure in timeout query_failed invalid_json invalid_root invalid_shape stop_failed start_failed; do
for caller in restart config upload release; do
reset_service_case "$failure" 0
core_before="$(cat "$SERVICE_TEST_DIR/oxidns")"
if run_service_case "$caller"; then
printf 'unexpected success: %s %s\n' "$failure" "$caller" >&2
exit 1
fi
assert_result 'v.ok === false'
if [ "$failure" = timeout ]; then
assert_result "JSON.stringify(v).includes('timed out')"
test "$(cat "$SERVICE_TEST_DIR/clock")" -eq 110
fi
if [ "$failure" != start_failed ]; then
! grep -qx start "$SERVICE_TEST_DIR/events"
test "$(cat "$SERVICE_TEST_DIR/oxidns")" = "$core_before"
fi
case "$caller" in
restart)
assert_result "v.code === 'service_failed'"
grep -q 'service restart failed:' "$SERVICE_TEST_DIR/audit"
;;
config) assert_result "v.code === 'service_restart_failed'" ;;
upload|release)
if [ "$failure" = start_failed ]; then
assert_result "v.code === 'service_restart_failed'"
else
assert_result "v.code === 'service_stop_failed'"
fi
;;
esac
done
done
reset_service_case delayed 0
run_service_case stop
assert_result 'v.ok === true'
test "$(cat "$SERVICE_TEST_DIR/events")" = "$(printf 'stop\nquery\nwait\nquery\nwait\nquery')"
printf 'Service restart checks passed\n'