diff --git a/__tests__/cli-affected-paths.test.ts b/__tests__/cli-affected-paths.test.ts index 88b06cd49..f85d6eba2 100644 --- a/__tests__/cli-affected-paths.test.ts +++ b/__tests__/cli-affected-paths.test.ts @@ -18,8 +18,8 @@ import { CodeGraph } from '../src'; const BIN = path.resolve(__dirname, '../dist/bin/codegraph.js'); -function affected(cwd: string, arg: string): string[] { - const out = execFileSync(process.execPath, [BIN, 'affected', arg, '--quiet', '-p', cwd], { +function affected(cwd: string, arg: string, extraArgs: string[] = []): string[] { + const out = execFileSync(process.execPath, [BIN, 'affected', arg, '--quiet', '-p', cwd, ...extraArgs], { encoding: 'utf-8', env: { ...process.env, CODEGRAPH_NO_DAEMON: '1', CODEGRAPH_WASM_RELAUNCHED: '1' }, stdio: ['ignore', 'pipe', 'pipe'], @@ -43,6 +43,13 @@ describe('codegraph affected — input path normalization (#825)', () => { path.join(tempDir, 'src/helper.test.ts'), "import { helper } from './helper';\ntest('t', () => helper());\n", ); + fs.mkdirSync(path.join(tempDir, 'python')); + fs.mkdirSync(path.join(tempDir, 'tests')); + fs.writeFileSync(path.join(tempDir, 'python/math_utils.py'), 'def add_one(value):\n return value + 1\n'); + fs.writeFileSync( + path.join(tempDir, 'tests/test_math_utils.py'), + 'from python.math_utils import add_one\n\ndef test_add_one():\n assert add_one(1) == 2\n', + ); const cg = CodeGraph.initSync(tempDir); await cg.indexAll(); cg.close(); @@ -60,4 +67,13 @@ describe('codegraph affected — input path normalization (#825)', () => { expect(affected(tempDir, './src/util.ts')).toEqual(expected); expect(affected(tempDir, path.join(tempDir, 'src/util.ts'))).toEqual(expected); }); + + it('recognizes pytest files in a root-level tests directory', () => { + expect(affected(tempDir, 'python/math_utils.py')).toEqual(['tests/test_math_utils.py']); + }); + + it('treats globstar as zero or more directories in custom filters', () => { + expect(affected(tempDir, 'python/math_utils.py', ['--filter', 'tests/**/*.py'])) + .toEqual(['tests/test_math_utils.py']); + }); }); diff --git a/src/bin/codegraph.ts b/src/bin/codegraph.ts index 4d85e204f..fe3dea2c7 100644 --- a/src/bin/codegraph.ts +++ b/src/bin/codegraph.ts @@ -33,6 +33,8 @@ import * as path from 'path'; import * as fs from 'fs'; import { getCodeGraphDir, isInitialized, unsafeIndexRootReason, findNearestCodeGraphRoot, planFrontload, hasStructuralKeyword, extractCodeTokens } from '../directory'; import { extractProseCandidates } from '../search/identifier-segments'; +import { isTestFile as isDefaultTestFile } from '../search/query-utils'; +import picomatch from 'picomatch'; import { detectWorktreeIndexMismatch, worktreeMismatchWarning } from '../sync/worktree'; import { createShimmerProgress } from '../ui/shimmer-progress'; import { getGlyphs } from '../ui/glyphs'; @@ -2090,31 +2092,11 @@ program const cg = await CodeGraph.open(projectPath); const maxDepth = parseInt(options.depth || '5', 10); - // Common test file patterns - const defaultTestPatterns = [ - /\.spec\./, - /\.test\./, - /\/__tests__\//, - /\/tests?\//, - /\/e2e\//, - /\/spec\//, - ]; - - // Custom filter pattern - let customFilter: RegExp | null = null; - if (options.filter) { - // Convert glob to regex: ** → .+, * → [^/]*, . → \. - const regex = options.filter - .replace(/[+[\]{}()^$|\\]/g, '\\$&') - .replace(/\./g, '\\.') - .replace(/\*\*/g, '.+') - .replace(/\*/g, '[^/]*'); - customFilter = new RegExp(regex); - } + const customFilter = options.filter ? picomatch(options.filter) : null; - function isTestFile(filePath: string): boolean { - if (customFilter) return customFilter.test(filePath); - return defaultTestPatterns.some(p => p.test(filePath)); + function matchesTestFile(filePath: string): boolean { + if (customFilter) return customFilter(filePath); + return isDefaultTestFile(filePath); } // BFS to find all transitive dependents of changed files, filtered to test files @@ -2123,7 +2105,7 @@ program for (const file of changedFiles) { // If the changed file is itself a test file, include it - if (isTestFile(file)) { + if (matchesTestFile(file)) { affectedTests.add(file); continue; } @@ -2143,7 +2125,7 @@ program visited.add(dep); allDependents.add(dep); - if (isTestFile(dep)) { + if (matchesTestFile(dep)) { affectedTests.add(dep); } else { queue.push({ file: dep, depth: current.depth + 1 });