Skip to content

Ignore @SpringBootApplication classes in test source folders - #421

Open
Martin Lippert (martinlippert) wants to merge 2 commits into
microsoft:mainfrom
martinlippert:gh-420-ignore-test-main-classes
Open

Ignore @SpringBootApplication classes in test source folders#421
Martin Lippert (martinlippert) wants to merge 2 commits into
microsoft:mainfrom
martinlippert:gh-420-ignore-test-main-classes

Conversation

@martinlippert

Copy link
Copy Markdown
Collaborator

vscode.java.resolveMainClass searches every source folder of a project, so a @SpringBootApplication class copied into src/test/java — a common pattern for integration tests — was offered as an additional launch candidate. Running an app then popped up a quick pick asking the user to choose between the real main class and one that is irrelevant to launch.

Filter those out by matching each resolved main class against the test source folders of the project's classpath. The Spring Tools classpath listener already reports isTest for each source entry (set from IClasspathEntry.isTest()), so no extra language server round trip is needed; the flag was just missing from the CPE type declaration.

The unfiltered list stays available via getMainClasses() so that live processes launched from a test main class are still associated with their app. When every main class sits in a test source folder, the filter falls back to the full list to keep such a project launchable.

Closes #420

`vscode.java.resolveMainClass` searches every source folder of a project,
so a `@SpringBootApplication` class copied into `src/test/java` — a common
pattern for integration tests — was offered as an additional launch
candidate. Running an app then popped up a quick pick asking the user to
choose between the real main class and one that is irrelevant to launch.

Filter those out by matching each resolved main class against the test
source folders of the project's classpath. The Spring Tools classpath
listener already reports `isTest` for each source entry (set from
`IClasspathEntry.isTest()`), so no extra language server round trip is
needed; the flag was just missing from the `CPE` type declaration.

The unfiltered list stays available via `getMainClasses()` so that live
processes launched from a test main class are still associated with their
app. When every main class sits in a test source folder, the filter falls
back to the full list to keep such a project launchable.

Closes microsoft#420
@martinlippert

Copy link
Copy Markdown
Collaborator Author

@microsoft-github-policy-service agree company="Broadcom"

@chagong Changyong Gong (chagong) left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ordinary Maven/Gradle case is covered, but linked test source folders can still surface as launch candidates because the two path producers use logical and physical resource locations. I added concrete suggestions that preserve the current fast path and use JDT LS classification as a linked-resource-safe fallback. The remaining implementation looks focused; GitHub checks, local compile, lint, and patch hygiene passed.

Comment thread src/utils.ts
Comment on lines +74 to +83
export function excludeTestMainClasses(mainClasses: MainClassData[], classpath: ClassPathData): MainClassData[] {
const testSourceFolders = (classpath?.entries ?? [])
.filter(cpe => cpe.kind === "source" && cpe.isTest)
.map(cpe => cpe.path);
if (testSourceFolders.length === 0) {
return mainClasses;
}
// Keep entries without a file path: they cannot be located, so they cannot be ruled out.
return mainClasses.filter(mc => !mc.filePath || !testSourceFolders.some(folder => isInFolder(mc.filePath, folder)));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Linked test source folders are not filtered here because CPE.path is built from the logical project path while MainClassData.filePath comes from IFile.getLocation(), which resolves to the physical target. Keep the current containment check as the fast path, then let JDT LS resolve unmatched physical URIs back to their logical compilation units; a classification failure should retain the candidate so Run/Debug remains available.

Suggested change
export function excludeTestMainClasses(mainClasses: MainClassData[], classpath: ClassPathData): MainClassData[] {
const testSourceFolders = (classpath?.entries ?? [])
.filter(cpe => cpe.kind === "source" && cpe.isTest)
.map(cpe => cpe.path);
if (testSourceFolders.length === 0) {
return mainClasses;
}
// Keep entries without a file path: they cannot be located, so they cannot be ruled out.
return mainClasses.filter(mc => !mc.filePath || !testSourceFolders.some(folder => isInFolder(mc.filePath, folder)));
}
export async function excludeTestMainClasses(mainClasses: MainClassData[], classpath: ClassPathData): Promise<MainClassData[]> {
const testSourceFolders = (classpath?.entries ?? [])
.filter(cpe => cpe.kind === "source" && cpe.isTest)
.map(cpe => cpe.path);
if (testSourceFolders.length === 0) {
return mainClasses;
}
const testMainClassFlags = await Promise.all(mainClasses.map(async mainClass => {
if (!mainClass.filePath) {
return false;
}
if (testSourceFolders.some(folder => isInFolder(mainClass.filePath, folder))) {
return true;
}
try {
return await vscode.commands.executeCommand<boolean>(
"java.execute.workspaceCommand",
"java.project.isTestFile",
vscode.Uri.file(mainClass.filePath).toString()
) === true;
} catch {
return false;
}
}));
return mainClasses.filter((_, index) => !testMainClassFlags[index]);
}

Please also cover a linked test source whose physical path is outside CPE.path, plus the command-rejection fallback.

Comment thread src/BootApp.ts
* that a project whose only main class lives there stays launchable.
*/
public async getLaunchableMainClasses(): Promise<MainClassData[]> {
const mainClasses = await this.getMainClasses();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the classifier returning a promise, await it before applying the existing all-test fallback.

Suggested change
const mainClasses = await this.getMainClasses();
const launchable = await excludeTestMainClasses(mainClasses, this.classpath);

注册 for free to join this conversation on GitHub. Already have an account? 登录 to comment

标签

None yet

项目

None yet

Development

Successfully merging this pull request may close these issues.

Wrong multiple choices of SpringBootApplication

2 participants