Files
goyomi/scripts/test-sources.sh
T
achmad 44b50937d5 feat(sourcetest): add -v flag with verbose manga list output
When -v is passed, test-sources.sh passes it through to go test -v.
sourcetest.Run uses testing.Verbose() to print the full manga list
from GetPopularManga and GetLatestUpdates, showing title + URL.
2026-05-14 13:23:29 +07:00

282 lines
9.2 KiB
Bash
Executable File

#!/usr/bin/env bash
# test-sources.sh — run source integration tests inside the compose network.
#
# Usage:
# ./scripts/test-sources.sh # run all source packages
# ./scripts/test-sources.sh ./sources/en/... # run specific packages
# ./scripts/test-sources.sh -short # metadata-only, no network calls
# ./scripts/test-sources.sh -v # verbose (print manga lists)
# ./scripts/test-sources.sh -v ./sources/en/aquamanga/... # verbose for one source
#
# Environment:
# PARALLELISM number of packages to test in parallel (default: 4)
# PKG_TIMEOUT per-package timeout (default: 120s)
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
NETWORK="goyomi_goyomi_dev"
FLARESOLVERR_SERVICE="goyomi-flaresolverr-1"
COMPOSE_FILE="$REPO_ROOT/compose.yml"
GO_IMAGE="golang:1.26"
PARALLELISM="${PARALLELISM:-4}"
PKG_TIMEOUT="${PKG_TIMEOUT:-120s}"
LOGS_DIR="$REPO_ROOT/internal/sourcetest/logs"
RUN_TIMESTAMP="$(date '+%Y-%m-%dT%H-%M-%S')"
SUMMARY_LOG="$LOGS_DIR/$RUN_TIMESTAMP.log"
# --- helpers -----------------------------------------------------------------
info() { printf '\033[1;34m[info]\033[0m %s\n' "$*"; }
ok() { printf '\033[1;32m[ok]\033[0m %s\n' "$*"; }
warn() { printf '\033[1;33m[warn]\033[0m %s\n' "$*"; }
die() { printf '\033[1;31m[error]\033[0m %s\n' "$*" >&2; exit 1; }
# --- write Python helpers to temp files --------------------------------------
PRINTER_PY="$(mktemp /tmp/goyomi-printer-XXXXXX.py)"
SUMMARY_PY="$(mktemp /tmp/goyomi-summary-XXXXXX.py)"
JSON_LOG="$(mktemp /tmp/goyomi-test-XXXXXX.json)"
trap 'rm -f "$PRINTER_PY" "$SUMMARY_PY" "$JSON_LOG"' EXIT
# Live streaming printer: reads JSON events from stdin, pretty-prints to terminal,
# and saves all events to the log file for the summary step.
cat > "$PRINTER_PY" << 'PYEOF'
import sys, json
log_path = sys.argv[1]
RESET = '\033[0m'
GREEN = '\033[1;32m'
RED = '\033[1;31m'
YELLOW = '\033[1;33m'
GRAY = '\033[0;37m'
with open(log_path, 'w') as log_file:
for raw in sys.stdin:
raw = raw.rstrip()
log_file.write(raw + '\n')
log_file.flush()
try:
ev = json.loads(raw)
except json.JSONDecodeError:
print(raw, flush=True)
continue
action = ev.get('Action', '')
pkg = ev.get('Package', '')
test = ev.get('Test', '')
output = ev.get('Output', '').rstrip()
short = pkg.split('/')[-1] if pkg else ''
if action == 'output':
skip_prefixes = ('=== RUN', '=== PAUSE', '=== CONT', '--- PASS', '--- FAIL', 'ok ')
skip_exact = {'PASS', 'FAIL', ''}
if output and output not in skip_exact and not any(output.startswith(p) for p in skip_prefixes):
print(f'{GRAY}{output}{RESET}', flush=True)
elif action == 'pass' and not test:
elapsed = ev.get('Elapsed', 0)
print(f'{GREEN}PASS{RESET} {short:<45} ({elapsed:.2f}s)', flush=True)
elif action == 'fail' and not test:
elapsed = ev.get('Elapsed', 0)
print(f'{RED}FAIL{RESET} {short:<45} ({elapsed:.2f}s)', flush=True)
elif action == 'skip' and not test:
print(f'{YELLOW}SKIP{RESET} {short}', flush=True)
PYEOF
# Summary parser: reads the saved JSON log, prints pass/fail counts to terminal,
# and writes a plain-text copy to the summary log file.
# argv: <json_log_path> <summary_log_path>
cat > "$SUMMARY_PY" << 'PYEOF'
import sys, json, re
json_path = sys.argv[1]
summary_path = sys.argv[2]
RESET = '\033[0m'
GREEN = '\033[1;32m'
RED = '\033[1;31m'
BOLD = '\033[1m'
passed = []
failed = {}
cur_sub = {}
pkg_errs = {}
with open(json_path) as f:
for raw in f:
try:
ev = json.loads(raw)
except:
continue
action = ev.get('Action', '')
pkg = ev.get('Package', '')
test = ev.get('Test', '')
output = ev.get('Output', '').rstrip()
if action == 'run' and test and '/' in test:
cur_sub[pkg] = test.split('/', 1)[1]
elif action == 'output' and test:
m = re.match(r'^\s+\S+\.go:\d+:\s+(.*)', output)
if m:
subtest = cur_sub.get(pkg, test.split('/', 1)[1] if '/' in test else test)
pkg_errs.setdefault(pkg, {}).setdefault(subtest, []).append(m.group(1).strip())
elif action == 'pass' and not test:
passed.append(pkg)
elif action == 'fail' and not test:
failed[pkg] = pkg_errs.get(pkg, {})
total = len(passed) + len(failed)
# Build plain-text lines (no ANSI codes) for the log file
plain_lines = []
plain_lines.append('')
plain_lines.append('=' * 60)
plain_lines.append(' TEST SUMMARY')
plain_lines.append('=' * 60)
plain_lines.append(f' Packages tested : {total}')
plain_lines.append(f' Passed : {len(passed)}')
plain_lines.append(f' Failed : {len(failed)}')
if failed:
plain_lines.append('')
plain_lines.append('Failed packages:')
for pkg in sorted(failed):
short = pkg.split('/')[-1]
subtests = failed[pkg]
if subtests:
for subtest, errors in subtests.items():
reason = '; '.join(errors) if errors else '(no message captured)'
plain_lines.append(f' ✗ {short} [{subtest}] {reason}')
else:
plain_lines.append(f' ✗ {short} (build error or panic)')
plain_lines.append('')
if not failed:
plain_lines.append(f'All {total} packages passed.')
else:
plain_lines.append(f'{len(failed)} package(s) failed.')
plain_lines.append('')
# Print to terminal with colours
colour = {
'Passed': GREEN, 'Failed': RED,
'Failed packages:': RED,
'All': GREEN,
}
for line in plain_lines:
if line.startswith(' Passed'):
print(f' {GREEN}Passed{RESET}' + line[9:], flush=True)
elif line.startswith(' Failed :'):
print(f' {RED}Failed{RESET} :' + line[19:], flush=True)
elif line.startswith('Failed packages:'):
print(f'{RED}Failed packages:{RESET}', flush=True)
elif line.startswith(' ✗'):
print(f' {RED}✗{RESET}' + line[3:], flush=True)
elif line.startswith('All') and 'passed' in line:
print(f'{GREEN}{line}{RESET}', flush=True)
elif line.startswith('='):
print(BOLD + line + RESET, flush=True)
elif line.strip() == 'TEST SUMMARY':
print(BOLD + line + RESET, flush=True)
else:
print(line, flush=True)
# Write plain text to summary log file
with open(summary_path, 'w') as f:
f.write('\n'.join(plain_lines) + '\n')
if failed:
f.write('\nPassed packages:\n')
for pkg in sorted(passed):
f.write(f' ✓ {pkg.split("/")[-1]}\n')
PYEOF
# --- check docker daemon -----------------------------------------------------
if ! docker info > /dev/null 2>&1; then
die "Docker daemon is not running. Start OrbStack (or Docker Desktop) first."
fi
ok "Docker daemon is running"
# --- ensure flaresolverr is up -----------------------------------------------
if docker inspect "$FLARESOLVERR_SERVICE" --format '{{.State.Running}}' 2>/dev/null | grep -q true; then
ok "FlareSolverr container is already running"
else
info "Starting FlareSolverr via docker compose..."
docker compose -f "$COMPOSE_FILE" up -d flaresolverr
for i in $(seq 1 20); do
if docker inspect "$FLARESOLVERR_SERVICE" --format '{{.State.Running}}' 2>/dev/null | grep -q true; then
ok "FlareSolverr is up"; break
fi
sleep 2
if [ "$i" -eq 20 ]; then
die "FlareSolverr did not start in time"
fi
done
fi
# --- ensure network exists ---------------------------------------------------
if ! docker network inspect "$NETWORK" > /dev/null 2>&1; then
die "Docker network '$NETWORK' not found. Run 'docker compose up -d' first."
fi
ok "Network $NETWORK exists"
# --- resolve packages to test ------------------------------------------------
SHORT_FLAG=""
VERBOSE_FLAG=""
FLAGS=""
while [ $# -gt 0 ]; do
case "$1" in
-short) SHORT_FLAG="-short"; FLAGS="$FLAGS -short"; shift ;;
-v) VERBOSE_FLAG="-v"; FLAGS="$FLAGS -v"; shift ;;
*) break ;;
esac
done
if [ $# -eq 0 ]; then
PACKAGES="./sources/en/... ./sources/all/..."
else
PACKAGES="$*"
fi
info "Packages : $PACKAGES"
info "Parallel : $PARALLELISM"
info "Timeout : $PKG_TIMEOUT"
[ -n "$SHORT_FLAG" ] && info "Mode : short (metadata only)" || info "Mode : full (live network)"
[ -n "$VERBOSE_FLAG" ] && info "Verbose : on"
# --- run tests ---------------------------------------------------------------
info "Running tests..."
echo
set +e
docker run --rm \
--network "$NETWORK" \
-e FLARESOLVERR_URL=http://flaresolverr:8191 \
-v "$REPO_ROOT":/workspace \
-v goyomi_go_mod_cache:/go/pkg/mod \
-v goyomi_go_build_cache:/root/.cache/go-build \
-w /workspace \
"$GO_IMAGE" \
go test -json -count=1 -p "$PARALLELISM" -timeout "$PKG_TIMEOUT" $FLAGS $PACKAGES \
| python3 -u "$PRINTER_PY" "$JSON_LOG"
EXIT_CODE=$?
set -e
# --- print summary -----------------------------------------------------------
mkdir -p "$LOGS_DIR"
python3 "$SUMMARY_PY" "$JSON_LOG" "$SUMMARY_LOG"
info "Summary log written to internal/sourcetest/logs/$RUN_TIMESTAMP.log"
exit $EXIT_CODE