

















@@ -351,6 +351,7 @@ const PRECISE_SOURCE_TEST_TARGETS = new Map([
351351],
352352],
353353]);
354+const BROAD_ONLY_TEST_HELPERS = new Set(["test/helpers/poll.ts"]);
354355const TOOLING_SOURCE_TEST_TARGETS = new Map([
355356["scripts/github/barnacle-auto-response.mjs", ["test/scripts/barnacle-auto-response.test.ts"]],
356357["scripts/changed-lanes.mjs", ["test/scripts/changed-lanes.test.ts"]],
@@ -593,6 +594,9 @@ const SOURCE_ROOTS_FOR_IMPORT_GRAPH = [
593594"test",
594595];
595596const IMPORTABLE_FILE_EXTENSIONS = [".ts", ".tsx", ".mts", ".cts"];
597+const IMPORT_GRAPH_GREP_PATHS = SOURCE_ROOTS_FOR_IMPORT_GRAPH.flatMap((root) =>
598+IMPORTABLE_FILE_EXTENSIONS.map((ext) => `:(glob)${root}/**/*${ext}`),
599+);
596600const IMPORT_SPECIFIER_PATTERN =
597601/\b(?:import|export)\s+(?:type\s+)?(?:[^'"]*?\s+from\s+)?["']([^"']+)["']|\bimport\s*\(\s*["']([^"']+)["']\s*\)/gu;
598602const BROAD_CHANGED_ENV_KEY = "OPENCLAW_TEST_CHANGED_BROAD";
@@ -768,7 +772,7 @@ function isPathLikeTargetArg(arg, cwd) {
768772if (!arg || arg === "--" || arg.startsWith("-")) {
769773return false;
770774}
771-return isExistingPathTarget(arg, cwd) || isGlobTarget(arg) || isFileLikeTarget(arg);
775+return isGlobTarget(arg) || isFileLikeTarget(arg) || isExistingPathTarget(arg, cwd);
772776}
773777774778function toRepoRelativeTarget(arg, cwd) {
@@ -844,7 +848,11 @@ export function findUnmatchedExplicitTestTargets(args, cwd = process.cwd()) {
844848return [];
845849}
846850847-const candidateFiles = listExplicitTestTargetFilesForCwd(cwd);
851+let candidateFiles = null;
852+const getCandidateFiles = () => {
853+candidateFiles ??= listExplicitTestTargetFilesForCwd(cwd);
854+return candidateFiles;
855+};
848856const unmatched = [];
849857for (const targetArg of targetArgs) {
850858const relative = toRepoRelativeTarget(targetArg, cwd);
@@ -856,7 +864,7 @@ export function findUnmatchedExplicitTestTargets(args, cwd = process.cwd()) {
856864continue;
857865}
858866if (isGlobTarget(relative)) {
859-if (!includePatternMatchesAnyFile(relative, candidateFiles)) {
867+if (!includePatternMatchesAnyFile(relative, getCandidateFiles())) {
860868unmatched.push({
861869target: targetArg,
862870reason: "glob-matched-no-files",
@@ -879,7 +887,7 @@ export function findUnmatchedExplicitTestTargets(args, cwd = process.cwd()) {
879887}
880888881889const includePattern = toScopedIncludePattern(targetArg, cwd);
882-if (!includePatternMatchesAnyFile(includePattern, candidateFiles)) {
890+if (!includePatternMatchesAnyFile(includePattern, getCandidateFiles())) {
883891unmatched.push({
884892target: targetArg,
885893reason: "target-matched-no-test-files",
@@ -991,12 +999,22 @@ function stripImportableGraphExtension(relative) {
991999return relative;
9921000}
9931001994-function resolveImportGraphSearchTerm(relative) {
1002+function resolveImportGraphSearchTerms(relative) {
1003+const withoutExtension = stripImportableGraphExtension(relative);
9951004const basename = path.posix.basename(stripImportableGraphExtension(relative));
9961005if (basename === "index" || basename.length < 3) {
997-return null;
1006+return [];
1007+}
1008+const terms = [];
1009+const segments = withoutExtension.split("/");
1010+if (segments.length > 1) {
1011+terms.push(segments.slice(-2).join("/"), withoutExtension);
1012+}
1013+if (relative.startsWith("test/helpers/")) {
1014+return [...new Set(terms)];
9981015}
999-return basename;
1016+terms.push(basename);
1017+return [...new Set(terms)];
10001018}
1001101910021020function listImportGraphGrepMatches(cwd, term) {
@@ -1007,7 +1025,7 @@ function listImportGraphGrepMatches(cwd, term) {
1007102510081026const result = spawnSync(
10091027"git",
1010-["grep", "-l", "--fixed-strings", term, "--", ...SOURCE_ROOTS_FOR_IMPORT_GRAPH],
1028+["grep", "-l", "--fixed-strings", term, "--", ...IMPORT_GRAPH_GREP_PATHS],
10111029{
10121030 cwd,
10131031encoding: "utf8",
@@ -1036,39 +1054,52 @@ function findDirectImportersWithGitGrep(cwd, importedFile, fileSet) {
10361054return cachedDirectImporters.get(cacheKey);
10371055}
103810561039-const term = resolveImportGraphSearchTerm(importedFile);
1040-if (!term) {
1041-cachedDirectImporters.set(cacheKey, null);
1042-return null;
1043-}
1044-1045-const candidates = listImportGraphGrepMatches(cwd, term);
1046-if (!candidates || candidates.length > 800) {
1057+const terms = resolveImportGraphSearchTerms(importedFile);
1058+if (terms.length === 0) {
10471059cachedDirectImporters.set(cacheKey, null);
10481060return null;
10491061}
105010621063+let skippedBroadTerm = false;
10511064const importers = [];
1052-for (const file of candidates) {
1053-if (file === importedFile || !fileSet.has(file)) {
1054-continue;
1065+for (const term of terms) {
1066+const candidates = listImportGraphGrepMatches(cwd, term);
1067+if (!candidates) {
1068+cachedDirectImporters.set(cacheKey, null);
1069+return null;
10551070}
1056-let source = "";
1057-try {
1058-source = fs.readFileSync(path.join(cwd, file), "utf8");
1059-} catch {
1071+if (candidates.length > 800) {
1072+skippedBroadTerm = true;
10601073continue;
10611074}
1062-for (const match of source.matchAll(IMPORT_SPECIFIER_PATTERN)) {
1063-const imported = resolveImportSpecifier(file, match[1] ?? match[2] ?? "", fileSet);
1064-if (imported === importedFile) {
1065-importers.push(file);
1066-break;
1075+for (const file of candidates) {
1076+if (file === importedFile || !fileSet.has(file) || importers.includes(file)) {
1077+continue;
1078+}
1079+let source = "";
1080+try {
1081+source = fs.readFileSync(path.join(cwd, file), "utf8");
1082+} catch {
1083+continue;
1084+}
1085+for (const match of source.matchAll(IMPORT_SPECIFIER_PATTERN)) {
1086+const imported = resolveImportSpecifier(file, match[1] ?? match[2] ?? "", fileSet);
1087+if (imported === importedFile) {
1088+importers.push(file);
1089+break;
1090+}
10671091}
10681092}
1093+if (importedFile.startsWith("test/helpers/") && importers.length > 0 && term.includes("/")) {
1094+break;
1095+}
10691096}
1070-cachedDirectImporters.set(cacheKey, importers);
1071-return importers;
1097+const result =
1098+skippedBroadTerm && importers.length === 0 && !importedFile.startsWith("test/helpers/")
1099+ ? null
1100+ : importers;
1101+cachedDirectImporters.set(cacheKey, result);
1102+return result;
10721103}
1073110410741105function resolveAffectedTestsFromTargetedImportScan(changedPath, cwd) {
@@ -1099,6 +1130,7 @@ function resolveAffectedTestsFromTargetedImportScan(changedPath, cwd) {
10991130seen.add(importer);
11001131if (testFiles.has(importer)) {
11011132targets.push(importer);
1133+continue;
11021134}
11031135queue.push(importer);
11041136}
@@ -1323,6 +1355,13 @@ function resolveSiblingTestTarget(changedPath, cwd) {
13231355return fs.existsSync(path.join(cwd, sibling)) ? sibling : null;
13241356}
132513571358+function shouldRouteChangedTargetWithoutImportGraph(changedPath) {
1359+return (
1360+changedPath.endsWith(".live.test.ts") ||
1361+(changedPath.startsWith("ui/src/") && !changedPath.startsWith("ui/src/ui/"))
1362+);
1363+}
1364+13261365function resolvePreciseChangedTestTargets(changedPath, options) {
13271366const cwd = options.cwd ?? process.cwd();
13281367const mappedTargets =
@@ -1337,6 +1376,12 @@ function resolvePreciseChangedTestTargets(changedPath, options) {
13371376if (siblingTest) {
13381377return [siblingTest];
13391378}
1379+if (BROAD_ONLY_TEST_HELPERS.has(changedPath)) {
1380+return null;
1381+}
1382+if (shouldRouteChangedTargetWithoutImportGraph(changedPath)) {
1383+return changedPath.startsWith("ui/src/") ? [changedPath] : null;
1384+}
13401385if (/^(?:src|test\/helpers|extensions|packages|ui\/src|ui\/config)\//u.test(changedPath)) {
13411386const affectedTests = resolveAffectedTestsFromImportGraph(changedPath, cwd);
13421387if (affectedTests.length > 0) {
@@ -1415,12 +1460,15 @@ function classifyTarget(arg, cwd) {
14151460if (configTargetKind) {
14161461return configTargetKind;
14171462}
1418-if (resolveUnitFastTestIncludePattern(relative)) {
1419-return "unitFast";
1420-}
14211463if (isControlUiE2eTarget(relative)) {
14221464return "uiE2e";
14231465}
1466+if (relative.startsWith("ui/src/")) {
1467+if (isUnitUiTestTarget(relative)) {
1468+return "unitUi";
1469+}
1470+return "ui";
1471+}
14241472if (relative.startsWith("src/tui/tui-pty-")) {
14251473return "tuiPty";
14261474}
@@ -1434,6 +1482,16 @@ function classifyTarget(arg, cwd) {
14341482) {
14351483return "e2e";
14361484}
1485+const channelContractKind = resolveChannelContractTargetKind(relative);
1486+if (channelContractKind) {
1487+return channelContractKind;
1488+}
1489+if (relative.startsWith("src/plugins/contracts/")) {
1490+return "contractsPlugin";
1491+}
1492+if (resolveUnitFastTestIncludePattern(relative)) {
1493+return "unitFast";
1494+}
14371495if (relative === "extensions") {
14381496return "extensionFull";
14391497}
@@ -1508,13 +1566,6 @@ function classifyTarget(arg, cwd) {
15081566}
15091567return isProviderExtensionRoot(extensionRoot) ? "extensionProvider" : "extension";
15101568}
1511-const channelContractKind = resolveChannelContractTargetKind(relative);
1512-if (channelContractKind) {
1513-return channelContractKind;
1514-}
1515-if (relative.startsWith("src/plugins/contracts/")) {
1516-return "contractsPlugin";
1517-}
15181569if (isChannelSurfaceTestFile(relative)) {
15191570return "channel";
15201571}
@@ -1599,15 +1650,6 @@ function classifyTarget(arg, cwd) {
15991650if (relative.startsWith("src/plugins/")) {
16001651return "plugin";
16011652}
1602-if (relative.startsWith("ui/src/")) {
1603-if (isControlUiE2eTarget(relative)) {
1604-return "uiE2e";
1605-}
1606-if (isUnitUiTestTarget(relative)) {
1607-return "unitUi";
1608-}
1609-return "ui";
1610-}
16111653if (relative.startsWith("src/utils/")) {
16121654return "utils";
16131655}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。