From 3a781b16adbe219cc53f2a4944de51598e31cd70 Mon Sep 17 00:00:00 2001 From: bodapatisaikrishna Date: Thu, 17 Sep 2026 14:28:50 +0530 Subject: [PATCH] fix(testController): exclude .bundle and vendor/bundle from test discovery Fixes #3949. When gems are installed locally into .bundle/gems or vendor/bundle (e.g. via bundle install --path or --deployment), test discovery scanned and added all gem test files into the test explorer. This: 1. Defines TEST_FILE_EXCLUDE_PATTERN for '**/{.bundle,vendor/bundle}/**'. 2. Passes testExcludePattern to vscode.workspace.findFiles so excluded directories are skipped during initial and full test collection. 3. Checks for .bundle and vendor/bundle in shouldSkipTestFile as an additional safeguard. 4. Adds unit test asserting findFiles is called with the exclude pattern. --- vscode/src/test/suite/testController.test.ts | 11 +++++++++++ vscode/src/testController.ts | 17 +++++++++++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/vscode/src/test/suite/testController.test.ts b/vscode/src/test/suite/testController.test.ts index be904e49a1..fd82d13f77 100644 --- a/vscode/src/test/suite/testController.test.ts +++ b/vscode/src/test/suite/testController.test.ts @@ -927,4 +927,15 @@ suite("TestController", () => { assert.ok(spy.calledOnce); }); + + test("findFiles excludes .bundle and vendor/bundle from test discovery", async () => { + const spy = sandbox.spy(vscode.workspace, "findFiles"); + await controller.testController.resolveHandler!(undefined); + + assert.ok(spy.called); + const excludeArg = spy.firstCall.args[1] as vscode.RelativePattern; + assert.ok(excludeArg); + assert.match(excludeArg.pattern, /\.bundle/); + assert.match(excludeArg.pattern, /vendor\/bundle/); + }); }); diff --git a/vscode/src/testController.ts b/vscode/src/testController.ts index 4d2c8507df..e8349a453f 100644 --- a/vscode/src/testController.ts +++ b/vscode/src/testController.ts @@ -15,6 +15,7 @@ const asyncExec = promisify(exec); const NESTED_TEST_DIR_PATTERN = "**/{test,spec,features}/**/"; const TEST_FILE_PATTERN = `${NESTED_TEST_DIR_PATTERN}{*_test.rb,test_*.rb,*_spec.rb,*.feature}`; +const TEST_FILE_EXCLUDE_PATTERN = "**/{.bundle,vendor/bundle}/**"; interface CodeLensData { type: string; @@ -981,7 +982,8 @@ export class TestController { for (const workspaceFolder of workspaceFolders) { // Check if there is at least one Ruby test file in the workspace, otherwise we don't consider it const pattern = this.testPattern(workspaceFolder); - const files = await vscode.workspace.findFiles(pattern, undefined, 1); + const excludePattern = this.testExcludePattern(workspaceFolder); + const files = await vscode.workspace.findFiles(pattern, excludePattern, 1); if (files.length === 0) { continue; } @@ -1002,7 +1004,8 @@ export class TestController { ) { const initialCollection = item ? item.children : this.testController.items; const pattern = this.testPattern(workspaceFolder); - const filePaths = await vscode.workspace.findFiles(pattern); + const excludePattern = this.testExcludePattern(workspaceFolder); + const filePaths = await vscode.workspace.findFiles(pattern, excludePattern); const increment = Math.floor(filePaths.length / 100); for (const uri of filePaths) { @@ -1143,6 +1146,12 @@ export class TestController { return true; } + // Bundler can install gems into `.bundle/gems` or `vendor/bundle`. Tests inside those gem directories + // must not be discovered as workspace tests. + if (pathParts.includes(".bundle") || (pathParts.includes("vendor") && pathParts.includes("bundle"))) { + return true; + } + return false; } @@ -1150,6 +1159,10 @@ export class TestController { return new vscode.RelativePattern(workspaceFolder, TEST_FILE_PATTERN); } + private testExcludePattern(workspaceFolder: vscode.WorkspaceFolder) { + return new vscode.RelativePattern(workspaceFolder, TEST_FILE_EXCLUDE_PATTERN); + } + private testDirectoryPosition(pathParts: string[]) { let index = pathParts.indexOf("test"); if (index !== -1) {