mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-02 21:02:14 +03:00
Compare commits
1 Commits
fix/v3851-
...
feat/test-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
852384f683 |
@@ -177,6 +177,13 @@ npm run test:all
|
||||
# Single test file (Node.js native test runner — most tests use this)
|
||||
node --import tsx/esm --test tests/unit/your-file.test.ts
|
||||
|
||||
# Only the unit tests impacted by your change (same TIA selector as the CI gate, #8084)
|
||||
npm run test:scoped # changes in the last commit (or the working tree)
|
||||
npm run test:scoped:staged # staged changes only — pairs well with a pre-commit run
|
||||
npm run test:scoped:full # rebuild the import-graph map first (after adding/moving files)
|
||||
# Exit 1 + "run the full suite" means a hub file (tsconfig, package.json, …) or an
|
||||
# unmapped source changed — the selector fails safe, it never silently skips.
|
||||
|
||||
# Vitest (MCP server, autoCombo, cache)
|
||||
npm run test:vitest
|
||||
|
||||
|
||||
@@ -126,6 +126,7 @@
|
||||
"test:unit:fast": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-isolation=none tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,translator,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-isolation=none \"tests/unit/dashboard/**/*.test.ts\" && npm run test:unit:serial",
|
||||
"test:scoped": "bash scripts/quality/test-scoped.sh",
|
||||
"test:scoped:staged": "bash scripts/quality/test-scoped.sh --staged",
|
||||
"test:scoped:full": "bash scripts/quality/test-scoped.sh --full",
|
||||
"test:unit:shard": "concurrently --kill-others-on-fail -n s1,s2 \"npm:test:unit:shard:1\" \"npm:test:unit:shard:2\"",
|
||||
"test:unit:shard:1": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=10 --test-shard=1/2 tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,translator,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=10 --test-shard=1/2 \"tests/unit/dashboard/**/*.test.ts\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=1 --test-shard=1/2 \"tests/unit/serial/**/*.test.ts\"",
|
||||
"test:unit:shard:2": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=10 --test-shard=2/2 tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,translator,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=10 --test-shard=2/2 \"tests/unit/dashboard/**/*.test.ts\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=1 --test-shard=2/2 \"tests/unit/serial/**/*.test.ts\"",
|
||||
|
||||
@@ -39,7 +39,18 @@ export function selectImpacted({ changed, map }) {
|
||||
return [...out].sort();
|
||||
}
|
||||
|
||||
// `--stdin`: read the changed-file list from stdin (one path per line) instead of
|
||||
// diffing git. Used by scripts/quality/test-scoped.sh so `--staged` selects from the
|
||||
// index — the git-diff path here only knows about commits, never the working tree.
|
||||
export function changedFilesFromStdin(text) {
|
||||
return String(text || "")
|
||||
.split(/\r?\n/)
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function changedFiles() {
|
||||
if (process.argv.includes("--stdin")) return changedFilesFromStdin(fs.readFileSync(0, "utf8"));
|
||||
const baseRef = process.env.GITHUB_BASE_REF;
|
||||
const baseTarget = process.env.GITHUB_BASE_SHA || (baseRef ? `origin/${baseRef}` : "HEAD~1");
|
||||
const stdout = execFileSync(
|
||||
|
||||
@@ -2,25 +2,46 @@
|
||||
# test-scoped — run only unit tests impacted by your changes.
|
||||
#
|
||||
# Usage:
|
||||
# npm run test:scoped # tests for changes vs HEAD~1
|
||||
# npm run test:scoped -- --staged # tests for staged changes only
|
||||
# npm run test:scoped # tests for changes vs HEAD~1 (working tree if no commit)
|
||||
# npm run test:scoped:staged # tests for staged changes only
|
||||
# npm run test:scoped:full # rebuild the import-graph impact map first, then select
|
||||
#
|
||||
# This is the local DX companion to the CI TIA gate (#8084 D1). The CI version
|
||||
# builds a full import-graph impact map; for local dev we use a fast heuristic:
|
||||
# This is the local DX companion to the CI TIA gate (#8084 D1). It uses the SAME
|
||||
# selector as CI (scripts/quality/select-impacted-tests.mjs) against the import-graph
|
||||
# impact map (config/quality/test-impact-map.json, gitignored):
|
||||
# - Changed test files → run those directly
|
||||
# - Changed source files → run tests that share the file's directory/name prefix
|
||||
# - Hub files (tsconfig, package.json, etc.) → suggest full suite
|
||||
# - Changed source files → run every unit test whose import graph reaches them
|
||||
# - Hub files (tsconfig, package.json, …) or unmapped sources → full suite (fail-safe)
|
||||
#
|
||||
# For the full TIA (import-graph based), use: npm run test:scoped:full
|
||||
# (requires a pre-built impact map via: node scripts/quality/build-test-impact-map.mjs)
|
||||
# The map is a snapshot of the import graph: rebuild it (`--full`) after adding tests,
|
||||
# moving files, or pulling a big base update — a stale map falls back to __RUN_ALL__
|
||||
# for unknown sources, never to a silent skip.
|
||||
#
|
||||
# Loader parity with `npm run test:unit` / CI (#6787): tests/unit/dashboard/** runs
|
||||
# under `--import tsx` (CJS transform — required for ESM-only deep imports such as
|
||||
# @lobehub/icons/es/*), tests/unit/serial/** at --test-concurrency=1, everything else
|
||||
# under `--import tsx/esm`. A single tsx/esm invocation false-reds every dashboard
|
||||
# test the map selects ("Unexpected token 'export'").
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
MAP_FILE="$REPO_ROOT/config/quality/test-impact-map.json"
|
||||
|
||||
STAGED=false
|
||||
FULL=false
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--staged) STAGED=true ;;
|
||||
--full) FULL=true ;;
|
||||
-h|--help) sed -n '2,25p' "${BASH_SOURCE[0]}"; exit 0 ;;
|
||||
*) echo "[test:scoped] unknown argument: $arg (use --staged, --full)"; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# ── 1. Determine changed files ───────────────────────────────────────────────
|
||||
if [[ "${1:-}" == "--staged" ]]; then
|
||||
if [ "$STAGED" = true ]; then
|
||||
CHANGED=$(git -C "$REPO_ROOT" diff --name-only --diff-filter=ACMR --cached)
|
||||
else
|
||||
CHANGED=$(git -C "$REPO_ROOT" diff --name-only --diff-filter=ACMR HEAD~1...HEAD 2>/dev/null || \
|
||||
@@ -32,80 +53,51 @@ if [ -z "$CHANGED" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ── 2. Classify changes ──────────────────────────────────────────────────────
|
||||
HUB_RE="(setupPolyfill|tsconfig|package\\.json|package-lock\\.json|\\.env|vitest\\.config|stryker\\.conf)"
|
||||
TEST_FILES=()
|
||||
SRC_FILES=()
|
||||
HIT_HUB=false
|
||||
# ── 2. Impact map (build on --full or when missing) ──────────────────────────
|
||||
if [ "$FULL" = true ] || [ ! -f "$MAP_FILE" ]; then
|
||||
echo "[test:scoped] Building the import-graph impact map (config/quality/test-impact-map.json)…"
|
||||
(cd "$REPO_ROOT" && node scripts/quality/build-test-impact-map.mjs)
|
||||
fi
|
||||
|
||||
while IFS= read -r f; do
|
||||
[ -z "$f" ] && continue
|
||||
if echo "$f" | grep -qE "$HUB_RE"; then
|
||||
HIT_HUB=true
|
||||
elif echo "$f" | grep -qE '^tests/unit/.*\.test\.(ts|mjs)$'; then
|
||||
TEST_FILES+=("$f")
|
||||
elif echo "$f" | grep -qE '^(src|open-sse)/'; then
|
||||
SRC_FILES+=("$f")
|
||||
fi
|
||||
done <<< "$CHANGED"
|
||||
# ── 3. Select impacted tests (same selector as the CI TIA gate) ──────────────
|
||||
SEL=$(printf '%s\n' "$CHANGED" | node "$REPO_ROOT/scripts/quality/select-impacted-tests.mjs" --stdin)
|
||||
|
||||
# ── 3. Hub file changed → full suite ─────────────────────────────────────────
|
||||
if [ "$HIT_HUB" = true ]; then
|
||||
echo "[test:scoped] Hub file changed — run full suite: npm run test:unit"
|
||||
if echo "$SEL" | grep -q "__RUN_ALL__"; then
|
||||
echo "[test:scoped] Hub file or unmapped source changed — run the full suite: npm run test:unit"
|
||||
echo "[test:scoped] (if you just added a source file, rebuild the map: npm run test:scoped:full)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ── 4. Collect tests to run ──────────────────────────────────────────────────
|
||||
RUN_TESTS=()
|
||||
mapfile -t RUN_TESTS < <(printf '%s\n' "$SEL" | grep -v '^$' | sort -u)
|
||||
|
||||
# Direct test file changes always run
|
||||
for tf in "${TEST_FILES[@]}"; do
|
||||
RUN_TESTS+=("$tf")
|
||||
done
|
||||
|
||||
# For source files, try the impact map first; fall back to heuristic
|
||||
MAP_FILE="$REPO_ROOT/config/quality/test-impact-map.json"
|
||||
if [ ${#SRC_FILES[@]} -gt 0 ] && [ -f "$MAP_FILE" ]; then
|
||||
# Use the TIA selection with the impact map
|
||||
SEL=$(printf '%s\n' "${SRC_FILES[@]}" | node "$REPO_ROOT/scripts/quality/select-impacted-tests.mjs" 2>/dev/null || echo "__RUN_ALL__")
|
||||
if echo "$SEL" | grep -q "__RUN_ALL__"; then
|
||||
echo "[test:scoped] Unmapped source change — run full suite: npm run test:unit"
|
||||
exit 1
|
||||
fi
|
||||
while IFS= read -r t; do
|
||||
[ -n "$t" ] && RUN_TESTS+=("$t")
|
||||
done <<< "$SEL"
|
||||
elif [ ${#SRC_FILES[@]} -gt 0 ]; then
|
||||
# No impact map — heuristic: suggest building it
|
||||
echo "[test:scoped] No impact map found. Build it with: node scripts/quality/build-test-impact-map.mjs"
|
||||
echo "[test:scoped] Or run the full suite: npm run test:unit"
|
||||
echo ""
|
||||
echo "[test:scoped] Changed source files:"
|
||||
printf ' %s\n' "${SRC_FILES[@]}"
|
||||
if [ ${#TEST_FILES[@]} -gt 0 ]; then
|
||||
echo "[test:scoped] Running changed test files only..."
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Deduplicate
|
||||
IFS=$'\n' SORTED=($(printf '%s\n' "${RUN_TESTS[@]}" | sort -u)); unset IFS
|
||||
|
||||
if [ ${#SORTED[@]} -eq 0 ]; then
|
||||
echo "[test:scoped] No impacted tests — source changes don't map to any unit test."
|
||||
if [ ${#RUN_TESTS[@]} -eq 0 ]; then
|
||||
echo "[test:scoped] No impacted unit tests — the change does not reach any node:test file."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "[test:scoped] Running ${#SORTED[@]} impacted test(s)..."
|
||||
echo "[test:scoped] Running ${#RUN_TESTS[@]} impacted test(s)..."
|
||||
|
||||
# ── 4. Split by loader (mirror package.json test:unit / quality.yml TIA step) ──
|
||||
DASH=(); SERIAL=(); REST=()
|
||||
for f in "${RUN_TESTS[@]}"; do
|
||||
case "$f" in
|
||||
tests/unit/dashboard/*) DASH+=("$f") ;;
|
||||
tests/unit/serial/*) SERIAL+=("$f") ;;
|
||||
*) REST+=("$f") ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# ── 5. Run selected tests ────────────────────────────────────────────────────
|
||||
cd "$REPO_ROOT"
|
||||
exec cross-env \
|
||||
DISABLE_SQLITE_AUTO_BACKUP=true \
|
||||
node --max-old-space-size=8192 \
|
||||
--import tsx/esm \
|
||||
--import ./open-sse/utils/setupPolyfill.ts \
|
||||
--import ./tests/_setup/isolateDataDir.ts \
|
||||
--test --test-force-exit --test-concurrency=4 \
|
||||
"${SORTED[@]}"
|
||||
NODE_COMMON=(--max-old-space-size=8192 --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit)
|
||||
export DISABLE_SQLITE_AUTO_BACKUP=true
|
||||
RC=0
|
||||
if [ ${#REST[@]} -gt 0 ]; then
|
||||
node --import tsx/esm "${NODE_COMMON[@]}" --test-concurrency=4 "${REST[@]}" || RC=$?
|
||||
fi
|
||||
if [ ${#DASH[@]} -gt 0 ]; then
|
||||
node --import tsx "${NODE_COMMON[@]}" --test-concurrency=4 "${DASH[@]}" || RC=$?
|
||||
fi
|
||||
if [ ${#SERIAL[@]} -gt 0 ]; then
|
||||
node --import tsx/esm "${NODE_COMMON[@]}" --test-concurrency=1 "${SERIAL[@]}" || RC=$?
|
||||
fi
|
||||
exit $RC
|
||||
|
||||
@@ -21,9 +21,7 @@ const MAP = {
|
||||
"tests/unit/api/chat-route.test.ts",
|
||||
"tests/unit/combo/combo-strategy.test.ts",
|
||||
],
|
||||
"src/shared/constants/routingStrategies.ts": [
|
||||
"tests/unit/combo/combo-strategy.test.ts",
|
||||
],
|
||||
"src/shared/constants/routingStrategies.ts": ["tests/unit/combo/combo-strategy.test.ts"],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -88,3 +86,54 @@ test("selectImpacted: non-source files are ignored (no __RUN_ALL__)", () => {
|
||||
});
|
||||
assert.deepEqual(sel, []);
|
||||
});
|
||||
|
||||
// ── #8084 D1 completion: stdin mode + loader parity ─────────────────────────
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { changedFilesFromStdin } from "../../scripts/quality/select-impacted-tests.mjs";
|
||||
|
||||
test("changedFilesFromStdin: one path per line, trimmed, blanks dropped", () => {
|
||||
assert.deepEqual(changedFilesFromStdin(" src/a.ts \n\nopen-sse/b.ts\r\n\n"), [
|
||||
"src/a.ts",
|
||||
"open-sse/b.ts",
|
||||
]);
|
||||
assert.deepEqual(changedFilesFromStdin(""), []);
|
||||
assert.deepEqual(changedFilesFromStdin(undefined), []);
|
||||
});
|
||||
|
||||
const SCRIPT = fs.readFileSync(
|
||||
path.resolve(import.meta.dirname, "../../scripts/quality/test-scoped.sh"),
|
||||
"utf8"
|
||||
);
|
||||
|
||||
test("test-scoped.sh feeds the selector via --stdin (staged mode must not read git commits)", () => {
|
||||
assert.match(SCRIPT, /select-impacted-tests\.mjs" --stdin/);
|
||||
});
|
||||
|
||||
test("test-scoped.sh mirrors the CI loader split (#6787): dashboard→tsx, serial→concurrency=1, rest→tsx/esm", () => {
|
||||
assert.match(SCRIPT, /tests\/unit\/dashboard\/\*\) DASH\+=/);
|
||||
assert.match(SCRIPT, /tests\/unit\/serial\/\*\) SERIAL\+=/);
|
||||
assert.match(
|
||||
SCRIPT,
|
||||
/node --import tsx "\$\{NODE_COMMON\[@\]\}" --test-concurrency=4 "\$\{DASH\[@\]\}"/
|
||||
);
|
||||
assert.match(
|
||||
SCRIPT,
|
||||
/node --import tsx\/esm "\$\{NODE_COMMON\[@\]\}" --test-concurrency=1 "\$\{SERIAL\[@\]\}"/
|
||||
);
|
||||
assert.match(
|
||||
SCRIPT,
|
||||
/node --import tsx\/esm "\$\{NODE_COMMON\[@\]\}" --test-concurrency=4 "\$\{REST\[@\]\}"/
|
||||
);
|
||||
});
|
||||
|
||||
test("package.json exposes every mode the script header documents", () => {
|
||||
const pkg = JSON.parse(
|
||||
fs.readFileSync(path.resolve(import.meta.dirname, "../../package.json"), "utf8")
|
||||
);
|
||||
for (const name of ["test:scoped", "test:scoped:staged", "test:scoped:full"]) {
|
||||
assert.ok(pkg.scripts[name], `missing script ${name}`);
|
||||
assert.match(pkg.scripts[name], /scripts\/quality\/test-scoped\.sh/);
|
||||
}
|
||||
assert.match(pkg.scripts["test:scoped:full"], /--full/);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user