diff --git a/scripts/check-changed.mjs b/scripts/check-changed.mjs index 6bfdebfb0cb..d855babe5b8 100644 --- a/scripts/check-changed.mjs +++ b/scripts/check-changed.mjs @@ -145,9 +145,9 @@ export async function runChangedCheck(result, options = {}) { } } else if (plan.runChangedTestsBroad) { const testArgs = options.explicitPaths - ? ["scripts/test-projects.mjs"] - : ["scripts/test-projects.mjs", "--changed", options.base ?? "origin/main"]; - const status = await runNode( + ? ["test"] + : ["test", "--changed", options.base ?? "origin/main"]; + const status = await runPnpm( { name: options.explicitPaths ? "tests all" : "tests changed broad", args: testArgs, @@ -159,10 +159,10 @@ export async function runChangedCheck(result, options = {}) { return status; } } else if (plan.testTargets.length > 0) { - const status = await runNode( + const status = await runPnpm( { name: "tests changed", - args: ["scripts/test-projects.mjs", ...plan.testTargets], + args: ["test", ...plan.testTargets], }, timings, ); @@ -209,10 +209,6 @@ async function runPnpm(command, timings) { return await runCommand({ ...command, bin: "pnpm" }, timings); } -async function runNode(command, timings) { - return await runCommand({ ...command, bin: process.execPath }, timings); -} - async function runCommand(command, timings) { const startedAt = performance.now(); console.error(`\n[check:changed] ${command.name}`); diff --git a/scripts/run-vitest.mjs b/scripts/run-vitest.mjs index 5006f3abe4f..bc3645cc3c0 100644 --- a/scripts/run-vitest.mjs +++ b/scripts/run-vitest.mjs @@ -180,7 +180,13 @@ export function forwardVitestOutput(stream, target, shouldSuppressLine = () => f }); } -export function spawnWatchedVitestProcess({ pnpmArgs, spawnParams, env, label }) { +export function spawnWatchedVitestProcess({ + pnpmArgs, + spawnParams, + env, + label, + onNoOutputTimeout, +}) { const child = spawnVitestProcess({ pnpmArgs, spawnParams, @@ -194,6 +200,7 @@ export function spawnWatchedVitestProcess({ pnpmArgs, spawnParams, env, label }) console.error(message); }, onTimeout: () => { + onNoOutputTimeout?.(); forwardSignalToVitestProcessGroup({ child, signal: "SIGTERM", diff --git a/scripts/test-projects.mjs b/scripts/test-projects.mjs index 4bc0f675d84..459166cd8de 100644 --- a/scripts/test-projects.mjs +++ b/scripts/test-projects.mjs @@ -189,11 +189,15 @@ function runVitestSpec(spec) { if (spec.includeFilePath && spec.includePatterns) { writeVitestIncludeFile(spec.includeFilePath, spec.includePatterns); } + let noOutputTimedOut = false; return new Promise((resolve, reject) => { const { child, teardown } = spawnWatchedVitestProcess({ pnpmArgs: spec.pnpmArgs, env: spec.env, label: spec.config, + onNoOutputTimeout: () => { + noOutputTimedOut = true; + }, spawnParams: { cwd: process.cwd(), ...resolveVitestSpawnParams(spec.env), @@ -203,7 +207,7 @@ function runVitestSpec(spec) { child.on("exit", (code, signal) => { teardown(); cleanupVitestRunSpec(spec); - resolve({ code: code ?? 1, signal }); + resolve({ code: code ?? (signal ? 143 : 1), noOutputTimedOut, signal }); }); child.on("error", (error) => { @@ -231,8 +235,21 @@ function applyDefaultParallelVitestWorkerBudget(specs, env) { async function runLoggedVitestSpec(spec) { console.error(`[test] starting ${spec.config}`); const startedAt = performance.now(); - const result = await runVitestSpec(spec); + let result = await runVitestSpec(spec); + if (result.noOutputTimedOut && !spec.watchMode) { + console.error(`[test] retrying ${spec.config} after no-output timeout`); + result = await runVitestSpec(spec); + } const durationMs = performance.now() - startedAt; + if (result.noOutputTimedOut && result.signal) { + console.error(`[test] ${spec.config} exceeded no-output timeout`); + return { + ...result, + code: result.code || 143, + signal: null, + timing: null, + }; + } if (result.signal) { console.error(`[test] ${spec.config} exited by signal ${result.signal}`); releaseLockOnce(); diff --git a/src/config/config.pruning-defaults.test.ts b/src/config/config.pruning-defaults.test.ts index a5c7922ff4c..896b31dd3ed 100644 --- a/src/config/config.pruning-defaults.test.ts +++ b/src/config/config.pruning-defaults.test.ts @@ -1,4 +1,6 @@ -import { describe, expect, it } from "vitest"; +import path from "node:path"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { clearBundledProviderPolicySurfaceCache } from "../plugins/provider-public-artifacts.js"; import type { OpenClawConfig } from "./config.js"; import { applyProviderConfigDefaultsForConfig } from "./provider-policy.js"; @@ -13,6 +15,19 @@ function applyAnthropicDefaultsForTest(config: OpenClawConfig) { } describe("config pruning defaults", () => { + beforeEach(() => { + clearBundledProviderPolicySurfaceCache(); + vi.stubEnv( + "OPENCLAW_BUNDLED_PLUGINS_DIR", + path.resolve(import.meta.dirname, "../../extensions"), + ); + }); + + afterEach(() => { + vi.unstubAllEnvs(); + clearBundledProviderPolicySurfaceCache(); + }); + it("does not enable contextPruning by default", () => { const cfg = applyAnthropicDefaultsForTest({ agents: { defaults: {} } }); diff --git a/src/config/model-alias-defaults.test.ts b/src/config/model-alias-defaults.test.ts index 4386163056e..36a90ae069a 100644 --- a/src/config/model-alias-defaults.test.ts +++ b/src/config/model-alias-defaults.test.ts @@ -1,9 +1,24 @@ -import { describe, expect, it } from "vitest"; +import path from "node:path"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { DEFAULT_CONTEXT_TOKENS } from "../agents/defaults.js"; +import { clearBundledProviderPolicySurfaceCache } from "../plugins/provider-public-artifacts.js"; import { applyModelDefaults } from "./defaults.js"; import type { OpenClawConfig } from "./types.js"; describe("applyModelDefaults", () => { + beforeEach(() => { + clearBundledProviderPolicySurfaceCache(); + vi.stubEnv( + "OPENCLAW_BUNDLED_PLUGINS_DIR", + path.resolve(import.meta.dirname, "../../extensions"), + ); + }); + + afterEach(() => { + vi.unstubAllEnvs(); + clearBundledProviderPolicySurfaceCache(); + }); + function buildProxyProviderConfig(overrides?: { contextWindow?: number; maxTokens?: number }) { return { models: { diff --git a/test/extension-test-boundary.test.ts b/test/extension-test-boundary.test.ts index 466a5dac765..2c9dd653d3f 100644 --- a/test/extension-test-boundary.test.ts +++ b/test/extension-test-boundary.test.ts @@ -8,6 +8,8 @@ const repoRoot = path.resolve(import.meta.dirname, ".."); const ALLOWED_EXTENSION_PUBLIC_SURFACE_BASENAMES = new Set( GUARDED_EXTENSION_PUBLIC_SURFACE_BASENAMES, ); +const ROOTDIR_BOUNDARY_CANARY_RE = + /(^|\/)__rootdir_boundary_canary__\.(?:[cm]?ts|[cm]?js|tsx|jsx)$/u; function walk(dir: string, entries: string[] = []): string[] { for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { @@ -40,7 +42,11 @@ function walkCode(dir: string, entries: string[] = []): string[] { if (!entry.name.endsWith(".ts") && !entry.name.endsWith(".tsx")) { continue; } - entries.push(path.relative(repoRoot, fullPath).replaceAll(path.sep, "/")); + const relativePath = path.relative(repoRoot, fullPath).replaceAll(path.sep, "/"); + if (ROOTDIR_BOUNDARY_CANARY_RE.test(relativePath)) { + continue; + } + entries.push(relativePath); } return entries; } diff --git a/test/vitest/vitest.extension-telegram.config.ts b/test/vitest/vitest.extension-telegram.config.ts index 470f8d2d569..20c36ca04a6 100644 --- a/test/vitest/vitest.extension-telegram.config.ts +++ b/test/vitest/vitest.extension-telegram.config.ts @@ -17,6 +17,7 @@ export function createExtensionTelegramVitestConfig( { dir: "extensions", env, + fileParallelism: false, name: "extension-telegram", passWithNoTests: true, setupFiles: ["test/setup.extensions.ts"],