diff --git a/config/max-lines-baseline.txt b/config/max-lines-baseline.txt index d92d91ebc04..fa89d01bfad 100644 --- a/config/max-lines-baseline.txt +++ b/config/max-lines-baseline.txt @@ -511,13 +511,13 @@ src/agents/model-selection.test.ts src/agents/models.profiles.live.test.ts src/agents/openai-completions-transport.ts src/agents/openai-responses-transport.ts -src/agents/openai-transport-stream.base.test-utils.ts -src/agents/openai-transport-stream.deepseek-and-shaping.test-utils.ts -src/agents/openai-transport-stream.inline-reasoning-and-tool-calls.test-utils.ts -src/agents/openai-transport-stream.reasoning-and-cache.test-utils.ts -src/agents/openai-transport-stream.replay-and-tools.test-utils.ts -src/agents/openai-transport-stream.streaming.test-utils.ts -src/agents/openai-transport-stream.usage-and-calls.test-utils.ts +src/agents/openai-transport-stream.base.test.ts +src/agents/openai-transport-stream.deepseek-and-shaping.test.ts +src/agents/openai-transport-stream.inline-reasoning-and-tool-calls.test.ts +src/agents/openai-transport-stream.reasoning-and-cache.test.ts +src/agents/openai-transport-stream.replay-and-tools.test.ts +src/agents/openai-transport-stream.streaming.test.ts +src/agents/openai-transport-stream.usage-and-calls.test.ts src/agents/openclaw-tools.media-factory-plan.test.ts src/agents/openclaw-tools.session-status.test.ts src/agents/openclaw-tools.sessions.test.ts diff --git a/scripts/test-projects.test-support.mjs b/scripts/test-projects.test-support.mjs index bdd6ce43499..800ff1c7336 100644 --- a/scripts/test-projects.test-support.mjs +++ b/scripts/test-projects.test-support.mjs @@ -2700,7 +2700,8 @@ function isPathLikeTargetArg(arg, cwd) { isGlobTarget(arg) || isFileLikeTarget(arg) || isVitestConfigPathLikeTarget(relative) || - isExistingPathTarget(arg, cwd) + isExistingPathTarget(arg, cwd) || + Boolean(resolveExplicitTestPrefixTargets(arg, cwd)?.length) ); } @@ -2767,6 +2768,26 @@ function listExplicitTestTargetFilesForCwd(cwd) { return cachedExplicitTestTargetFiles; } +function resolveExplicitTestPrefixTargets(targetArg, cwd) { + if (isExistingPathTarget(targetArg, cwd) || isGlobTarget(targetArg)) { + return null; + } + const relative = toRepoRelativeTarget(targetArg, cwd).replace(/\/+$/u, ""); + if (!relative || isLikelyFileTarget(relative)) { + return null; + } + const directory = path.posix.dirname(relative); + const prefix = `${relative}.`; + const targets = listExplicitTestTargetFilesForCwd(cwd).filter( + (file) => + fs.existsSync(path.join(cwd, file)) && + path.posix.dirname(file) === directory && + file.startsWith(prefix) && + isTestFileTarget(file), + ); + return targets.length > 0 ? targets.toSorted((left, right) => left.localeCompare(right)) : null; +} + function includePatternMatchesAnyFile(pattern, files) { return files.some((file) => file === pattern || path.matchesGlob(file, pattern)); } @@ -2810,6 +2831,10 @@ function expandExplicitSourceTestTargets(targetArgs, cwd) { const forceFullImportGraph = sourceTargetCount > EXPLICIT_SOURCE_FULL_IMPORT_GRAPH_THRESHOLD; return targetArgs.flatMap((targetArg) => { const relative = toRepoRelativeTarget(targetArg, cwd); + const prefixTargets = resolveExplicitTestPrefixTargets(targetArg, cwd); + if (prefixTargets) { + return prefixTargets; + } if (relative === "src/commands" && isExistingDirectoryTarget(targetArg, cwd)) { return [COMMANDS_LIGHT_VITEST_CONFIG, COMMANDS_VITEST_CONFIG]; } @@ -2928,6 +2953,9 @@ export function findUnmatchedExplicitTestTargets(args, cwd = process.cwd()) { const absolute = path.resolve(cwd, targetArg); if (!fs.existsSync(absolute)) { + if (resolveExplicitTestPrefixTargets(targetArg, cwd)) { + continue; + } unmatched.push({ target: targetArg, reason: "path-does-not-exist", diff --git a/src/agents/openai-transport-stream.base.test-utils.ts b/src/agents/openai-transport-stream.base.test.ts similarity index 99% rename from src/agents/openai-transport-stream.base.test-utils.ts rename to src/agents/openai-transport-stream.base.test.ts index aad7561f1a8..83cabb8660f 100644 --- a/src/agents/openai-transport-stream.base.test-utils.ts +++ b/src/agents/openai-transport-stream.base.test.ts @@ -1,4 +1,3 @@ -// Imported by openai-transport-stream.test.ts to keep its mocked suite in one Vitest module graph. import type { ChatCompletionChunk } from "openai/resources/chat/completions.js"; import type { Api, Model } from "openclaw/plugin-sdk/llm"; import { describe, expect, it, vi } from "vitest"; diff --git a/src/agents/openai-transport-stream.deepseek-and-shaping.test-utils.ts b/src/agents/openai-transport-stream.deepseek-and-shaping.test.ts similarity index 99% rename from src/agents/openai-transport-stream.deepseek-and-shaping.test-utils.ts rename to src/agents/openai-transport-stream.deepseek-and-shaping.test.ts index 525f763619e..4f004477616 100644 --- a/src/agents/openai-transport-stream.deepseek-and-shaping.test-utils.ts +++ b/src/agents/openai-transport-stream.deepseek-and-shaping.test.ts @@ -1,4 +1,3 @@ -// Imported by openai-transport-stream.test.ts to keep its mocked suite in one Vitest module graph. import { createServer } from "node:http"; import { SYSTEM_PROMPT_CACHE_BOUNDARY } from "@openclaw/ai/internal/shared"; import type { ChatCompletionChunk } from "openai/resources/chat/completions.js"; diff --git a/src/agents/openai-transport-stream.inline-reasoning-and-tool-calls.test-utils.ts b/src/agents/openai-transport-stream.inline-reasoning-and-tool-calls.test.ts similarity index 99% rename from src/agents/openai-transport-stream.inline-reasoning-and-tool-calls.test-utils.ts rename to src/agents/openai-transport-stream.inline-reasoning-and-tool-calls.test.ts index 183b7780ead..8e89477c8fd 100644 --- a/src/agents/openai-transport-stream.inline-reasoning-and-tool-calls.test-utils.ts +++ b/src/agents/openai-transport-stream.inline-reasoning-and-tool-calls.test.ts @@ -1,4 +1,3 @@ -// Imported by openai-transport-stream.test.ts to keep its mocked suite in one Vitest module graph. import { createServer } from "node:http"; import { expectDefined } from "@openclaw/normalization-core"; import { describe, expect, it } from "vitest"; diff --git a/src/agents/openai-transport-stream.reasoning-and-cache.test-utils.ts b/src/agents/openai-transport-stream.reasoning-and-cache.test.ts similarity index 99% rename from src/agents/openai-transport-stream.reasoning-and-cache.test-utils.ts rename to src/agents/openai-transport-stream.reasoning-and-cache.test.ts index 42f999f2b32..30b9a004146 100644 --- a/src/agents/openai-transport-stream.reasoning-and-cache.test-utils.ts +++ b/src/agents/openai-transport-stream.reasoning-and-cache.test.ts @@ -1,4 +1,3 @@ -// Imported by openai-transport-stream.test.ts to keep its mocked suite in one Vitest module graph. import { SYSTEM_PROMPT_CACHE_BOUNDARY } from "@openclaw/ai/internal/shared"; import { expectDefined } from "@openclaw/normalization-core"; import type { Model } from "openclaw/plugin-sdk/llm"; diff --git a/src/agents/openai-transport-stream.replay-and-tools.test-utils.ts b/src/agents/openai-transport-stream.replay-and-tools.test.ts similarity index 99% rename from src/agents/openai-transport-stream.replay-and-tools.test-utils.ts rename to src/agents/openai-transport-stream.replay-and-tools.test.ts index eb0a721d319..3a02dd15280 100644 --- a/src/agents/openai-transport-stream.replay-and-tools.test-utils.ts +++ b/src/agents/openai-transport-stream.replay-and-tools.test.ts @@ -1,4 +1,3 @@ -// Imported by openai-transport-stream.test.ts to keep its mocked suite in one Vitest module graph. import { SYSTEM_PROMPT_CACHE_BOUNDARY } from "@openclaw/ai/internal/shared"; import { expectDefined } from "@openclaw/normalization-core"; import OpenAI from "openai"; diff --git a/src/agents/openai-transport-stream.replay-sanitization.test-utils.ts b/src/agents/openai-transport-stream.replay-sanitization.test.ts similarity index 99% rename from src/agents/openai-transport-stream.replay-sanitization.test-utils.ts rename to src/agents/openai-transport-stream.replay-sanitization.test.ts index bcfaf8e3a18..7e8d41a2394 100644 --- a/src/agents/openai-transport-stream.replay-sanitization.test-utils.ts +++ b/src/agents/openai-transport-stream.replay-sanitization.test.ts @@ -1,4 +1,3 @@ -// Imported by openai-transport-stream.test.ts to keep its mocked suite in one Vitest module graph. import type { Model } from "openclaw/plugin-sdk/llm"; import { describe, expect, it } from "vitest"; import { buildOpenAICompletionsParams } from "./openai-transport-stream.js"; diff --git a/src/agents/openai-transport-stream.streaming.test-utils.ts b/src/agents/openai-transport-stream.streaming.test.ts similarity index 99% rename from src/agents/openai-transport-stream.streaming.test-utils.ts rename to src/agents/openai-transport-stream.streaming.test.ts index d9d6d9fc396..476ce195590 100644 --- a/src/agents/openai-transport-stream.streaming.test-utils.ts +++ b/src/agents/openai-transport-stream.streaming.test.ts @@ -1,4 +1,3 @@ -// Imported by openai-transport-stream.test.ts to keep its mocked suite in one Vitest module graph. import { createServer } from "node:http"; import type { Model } from "openclaw/plugin-sdk/llm"; import { describe, expect, it, vi } from "vitest"; diff --git a/src/agents/openai-transport-stream.test.ts b/src/agents/openai-transport-stream.test.ts deleted file mode 100644 index 5c35bceb870..00000000000 --- a/src/agents/openai-transport-stream.test.ts +++ /dev/null @@ -1,8 +0,0 @@ -import "./openai-transport-stream.base.test-utils.js"; -import "./openai-transport-stream.streaming.test-utils.js"; -import "./openai-transport-stream.deepseek-and-shaping.test-utils.js"; -import "./openai-transport-stream.replay-and-tools.test-utils.js"; -import "./openai-transport-stream.reasoning-and-cache.test-utils.js"; -import "./openai-transport-stream.usage-and-calls.test-utils.js"; -import "./openai-transport-stream.inline-reasoning-and-tool-calls.test-utils.js"; -import "./openai-transport-stream.replay-sanitization.test-utils.js"; diff --git a/src/agents/openai-transport-stream.usage-and-calls.test-utils.ts b/src/agents/openai-transport-stream.usage-and-calls.test.ts similarity index 99% rename from src/agents/openai-transport-stream.usage-and-calls.test-utils.ts rename to src/agents/openai-transport-stream.usage-and-calls.test.ts index 4a78ec01916..69bea852708 100644 --- a/src/agents/openai-transport-stream.usage-and-calls.test-utils.ts +++ b/src/agents/openai-transport-stream.usage-and-calls.test.ts @@ -1,4 +1,3 @@ -// Imported by openai-transport-stream.test.ts to keep its mocked suite in one Vitest module graph. import type { Model } from "openclaw/plugin-sdk/llm"; import { describe, expect, it } from "vitest"; import { buildOpenAICompletionsParams } from "./openai-transport-stream.js"; diff --git a/src/scripts/test-projects.test.ts b/src/scripts/test-projects.test.ts index 78ac48fbf2b..273b5ae0dd4 100644 --- a/src/scripts/test-projects.test.ts +++ b/src/scripts/test-projects.test.ts @@ -94,12 +94,12 @@ describe("test-projects args", () => { ]); }); - it("keeps extracted test entries in their owner configs", () => { - expect(buildVitestRunPlans(["src/agents/openai-transport-stream.test.ts"])).toEqual([ + it("keeps split test entries in their owner configs", () => { + expect(buildVitestRunPlans(["src/agents/openai-transport-stream.base.test.ts"])).toEqual([ { config: "test/vitest/vitest.agents.config.ts", forwardedArgs: [], - includePatterns: ["src/agents/openai-transport-stream.test.ts"], + includePatterns: ["src/agents/openai-transport-stream.base.test.ts"], watchMode: false, }, ]); @@ -113,6 +113,26 @@ describe("test-projects args", () => { ]); }); + it("expands a test filename prefix into standalone sibling suites", () => { + expect(buildVitestRunPlans(["src/agents/openai-transport-stream"])).toEqual([ + { + config: "test/vitest/vitest.agents.config.ts", + forwardedArgs: [], + includePatterns: [ + "src/agents/openai-transport-stream.base.test.ts", + "src/agents/openai-transport-stream.deepseek-and-shaping.test.ts", + "src/agents/openai-transport-stream.inline-reasoning-and-tool-calls.test.ts", + "src/agents/openai-transport-stream.reasoning-and-cache.test.ts", + "src/agents/openai-transport-stream.replay-and-tools.test.ts", + "src/agents/openai-transport-stream.replay-sanitization.test.ts", + "src/agents/openai-transport-stream.streaming.test.ts", + "src/agents/openai-transport-stream.usage-and-calls.test.ts", + ], + watchMode: false, + }, + ]); + }); + it("routes top-level repo tests to the contracts config", () => { expect(buildVitestRunPlans(["test/appcast.test.ts"])).toEqual([ { diff --git a/test/vitest-unit-fast-config.test.ts b/test/vitest-unit-fast-config.test.ts index aac46d013dd..6d0b5d97d78 100644 --- a/test/vitest-unit-fast-config.test.ts +++ b/test/vitest-unit-fast-config.test.ts @@ -197,7 +197,7 @@ describe("unit-fast vitest lane", () => { it("keeps obvious stateful files out of the unit-fast lane", () => { expect(isUnitFastTestFile("src/plugin-sdk/temp-path.test.ts")).toBe(false); - expect(isUnitFastTestFile("src/agents/openai-transport-stream.test.ts")).toBe(false); + expect(isUnitFastTestFile("src/agents/openai-transport-stream.base.test.ts")).toBe(false); expect(isUnitFastTestFile("src/auto-reply/reply/dispatch-from-config.test.ts")).toBe(false); expect(isUnitFastTestFile("src/agents/sandbox.resolveSandboxContext.test.ts")).toBe(false); expect(isUnitFastTestFile("src/acp/runtime/session-meta.test.ts")).toBe(false); diff --git a/test/vitest/vitest.unit-fast-paths.mjs b/test/vitest/vitest.unit-fast-paths.mjs index 59a93c0c4d1..0bf2613b37e 100644 --- a/test/vitest/vitest.unit-fast-paths.mjs +++ b/test/vitest/vitest.unit-fast-paths.mjs @@ -221,8 +221,8 @@ const broadUnitFastCandidateGlobs = [ "packages/**/*.test.ts", "test/**/*.test.ts", ]; -const ownerRoutedUnitTestFiles = [ - "src/agents/openai-transport-stream.test.ts", +const ownerRoutedUnitTestPatterns = [ + "src/agents/openai-transport-stream.*.test.ts", "src/auto-reply/reply/dispatch-from-config.test.ts", ]; const broadUnitFastCandidateSkipGlobs = [ @@ -233,9 +233,8 @@ const broadUnitFastCandidateSkipGlobs = [ // Explicit bundled ownership outranks content-based discovery. Otherwise extracting // a test body can silently move its entry to a config with the wrong mocked setup. ...bundledPluginDependentUnitTestFiles, - // These entries register tests from imported utility modules. Their tiny entry files - // cannot carry the stateful-content signals that keep them in their owner configs. - ...ownerRoutedUnitTestFiles, + // Keep these suites in owner configs even when content-based discovery changes. + ...ownerRoutedUnitTestPatterns, "src/agents/sandbox.resolveSandboxContext.test.ts", "src/acp/runtime/session-meta.test.ts", "src/channels/plugins/contracts/**/*.test.ts",