Files

154 lines
5.5 KiB
Bash
Executable File

#!/bin/sh
# SPDX-License-Identifier: GPL-2.0-only
#
# The test doubles must still behave like the production code they copy.
#
# tests/ucode/mocks/homeproxy-pro.uc says of isEmpty/decodeBase64Str/parseURL:
# "verbatim copy so the parser tests exercise the same string handling as
# production. Keep these in sync when homeproxy-pro.uc changes." Nothing enforced
# that sentence. tests/ucode/mocks/homeproxy_fetcher.uc carries the same note
# for redactUrl, and the *security* assertion in
# test_subscription_fetcher.uc - that a subscription token cannot reappear in
# the log - is made against that copy, not against the production function.
#
# Editing the production function alone would therefore leave every test green
# while the behaviour they claim to check had changed. This compares the two
# implementations on a shared corpus, which is what "in sync" has to mean: the
# mock is allowed to be reformatted (redactUrl's copy already reorders a couple
# of statements), only the results must agree.
#
# validation() is deliberately NOT compared. It is a pure-ucode stand-in for
# /sbin/validate_data, so it differs from the real thing by design; it is
# pinned by its own unit tests instead.
#
# Usage: sh tests/ucode/test_mock_sync.sh <repo-root> [workdir]
set -u
ROOT="$(cd "${1:-$(dirname "$0")/../..}" && pwd)"
WORK="${2:-/tmp/hp-mock-sync}"
if ! command -v ucode > "/dev/null" 2>&1; then
echo "NOT RUN: ucode is not on PATH."
exit 2
fi
rm -rf "$WORK"
mkdir -p "$WORK"
# production parseURL() validates the hostname by shelling out to the hardcoded
# /sbin/validate_data. On a device that binary exists; off-target it does not,
# and production then returns null for every URL while the mock - which carries
# its own pure-ucode validation() - parses them happily. An environment variable
# would not help: the path is a literal in the source, so the staged copy has to
# be rewritten, exactly as tests/ucode/test_generators.sh does it. Without this
# the test passed on the device and failed in CI, which is how it was found.
VALIDATE_DATA="${HP_VALIDATE_DATA:-/sbin/validate_data}"
if [ ! -x "$VALIDATE_DATA" ] && [ -x "$ROOT/tests/toolchain/validate-data.sh" ]; then
VALIDATE_DATA="$ROOT/tests/toolchain/validate-data.sh"
fi
mkdir -p "$WORK/scripts"
sed -e "s#/sbin/validate_data#$VALIDATE_DATA#" \
"$ROOT/root/etc/homeproxy-pro/scripts/homeproxy-pro.uc" > "$WORK/scripts/homeproxy-pro.uc"
cat > "$WORK/sync.uc" <<'DRIVER'
import {
isEmpty as p_isEmpty,
decodeBase64Str as p_decodeBase64Str,
redactUrl as p_redactUrl,
parseURL as p_parseURL,
shellQuote as p_shellQuote
} from '@@SCRIPTS@@/homeproxy-pro.uc';
import {
isEmpty as m_isEmpty,
decodeBase64Str as m_decodeBase64Str,
parseURL as m_parseURL,
shellQuote as m_shellQuote
} from '@@MOCK-HP@@';
import { redactUrl as f_redactUrl } from '@@MOCK-FETCHER@@';
let checks = 0, failures = 0;
function same(name, a, b) {
checks++;
const sa = sprintf('%J', a), sb = sprintf('%J', b);
if (sa === sb) return;
failures++;
printf('FAIL %s: production %s vs mock %s\n', name, sa, sb);
}
/* --- isEmpty ------------------------------------------------------------ */
const empty_corpus = ['', null, 'nil', [], {}, [1], { a: 1 }, 0, 'x', '0', false, true, [ [] ]];
for (let v in empty_corpus)
same(sprintf('isEmpty(%J)', v), p_isEmpty(v), m_isEmpty(v));
/* --- decodeBase64Str ---------------------------------------------------- */
const b64_corpus = [
'', 'aGVsbG8=', 'aGVsbG8', 'aGVsbG8h', 'a-b_c', 'a+b/c', '!!!!',
'AA==', 'AAA=', 'AAAA', ' aGVsbG8= ', 'not base64 at all', '===='
];
for (let v in b64_corpus)
same(sprintf('decodeBase64Str(%J)', v), p_decodeBase64Str(v), m_decodeBase64Str(v));
/* --- shellQuote --------------------------------------------------------- */
const quote_corpus = [
'', 'plain', "it's", "a'b'c", 'has space', '"double"', '$var', '`cmd`',
'; rm -rf /', 'a\\b', "mix'\"$`"
];
for (let v in quote_corpus)
same(sprintf('shellQuote(%J)', v), p_shellQuote(v), m_shellQuote(v));
/* --- redactUrl: the fetcher mock's copy -------------------------------- */
const url_corpus = [
'',
'https://host/path',
'https://user:pass@host/path?token=secret',
'https://user@host/path',
'https://host/path?a=1&b=2',
'https://host/path?a=1#frag',
'ss://method:password@host:8388#label',
'not a url',
'https://host/path?',
'://@?'
];
for (let v in url_corpus)
same(sprintf('redactUrl(%J)', v), p_redactUrl(v), f_redactUrl(v));
/* --- parseURL: only inputs where the real validate_data and the stand-in
* agree, since validation() is allowed to differ. ------------------------ */
const parse_corpus = [
'https://example.com/path',
'https://example.com:8443/path?x=1#h',
'http://user:pass@example.com/p',
'https://1.2.3.4/p',
'https://[2001:db8::1]:443/p',
'ftp://example.com/p',
'',
'not a url',
'https://',
'https:///path'
];
for (let v in parse_corpus)
same(sprintf('parseURL(%J)', v), p_parseURL(v), m_parseURL(v));
printf('mock sync: %d checks, %d failures\n', checks, failures);
exit(failures ? 1 : 0);
DRIVER
sed -e "s#@@SCRIPTS@@#$WORK/scripts#" \
-e "s#@@MOCK-HP@@#$ROOT/tests/ucode/mocks/homeproxy-pro.uc#" \
-e "s#@@MOCK-FETCHER@@#$ROOT/tests/ucode/mocks/homeproxy_fetcher.uc#" \
"$WORK/sync.uc" > "$WORK/sync.uc.new"
mv -f "$WORK/sync.uc.new" "$WORK/sync.uc"
if ucode -L "$ROOT/root/etc/homeproxy-pro/scripts" "$WORK/sync.uc"; then
echo "PASS: the test doubles agree with production"
rm -rf "$WORK"
exit 0
else
echo "FAIL: a test double has drifted from the production function it copies"
rm -rf "$WORK"
exit 1
fi