Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add debug support in Test Explorer #29

Merged
merged 1 commit into from
Dec 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,23 @@
"type": "boolean",
"default": "true",
"description": "Find all tests within open files, without waiting for the file's target to be expanded in the Test Explorer."
},
"bazelbsp.debug.enabled": {
"type": "boolean",
"default": "false",
"markdownDescription": "Enable debugging integration in the Test Explorer. This adds an additional Debug run profile for each test item.\nSet the bazelFlags, profileName, and readyPattern settings in this section to match your repo's required behavior."
},
"bazelbsp.debug.bazelFlags": {
"type": "array",
"description": "Flags to be added when debugging a target. Include any flags needed to ensure Bazel builds and runs the target in debug mode."
},
"bazelbsp.debug.readyPattern": {
"type": "string",
"description": "Regex pattern in the console output that signals that the target is ready for a debugger to connect. Once this is seen, the configured launch configuration will be triggered."
},
"bazelbsp.debug.launchConfigName": {
"type": "string",
"description": "Name of launch configuration that will be executed to begin the DAP debugging session. This must be a valid launch configuration in the launch.json file, workspace, or contributed by another extension."
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/bsp/bsp-ext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export namespace TestParamsDataKind {
export interface BazelTestParamsData {
coverage?: boolean
testFilter?: string
additionalBazelParams?: string
}

export namespace OnBuildPublishOutput {
Expand Down
11 changes: 11 additions & 0 deletions src/test-info/test-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {BuildTarget, TestParams, TestResult, StatusCode} from '../bsp/bsp'
import {TestParamsDataKind, BazelTestParamsData} from '../bsp/bsp-ext'
import {TestCaseStatus, TestRunTracker} from '../test-runner/run-tracker'
import {DocumentTestItem, LanguageToolManager} from '../language-tools/manager'
import {getExtensionSetting, SettingName} from '../utils/settings'

export enum TestItemType {
Root,
Expand Down Expand Up @@ -113,6 +114,16 @@ export class BuildTargetTestCaseInfo extends TestCaseInfo {
coverage:
currentRun.getRunProfileKind() === vscode.TestRunProfileKind.Coverage,
}

// Includes additional debug-specific flags when necessary.
if (currentRun.getRunProfileKind() === vscode.TestRunProfileKind.Debug) {
const configuredFlags = currentRun.getDebugBazelFlags()
if (configuredFlags && configuredFlags.length > 0) {
// Bazel BSP accepts whitespace separated list of flags.
bazelParams.additionalBazelParams = configuredFlags.join(' ')
}
}

const params = {
targets: [this.target.id],
originId: currentRun.originName,
Expand Down
94 changes: 94 additions & 0 deletions src/test-runner/run-tracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {CoverageTracker} from '../coverage-utils/coverage-tracker'
import {LanguageToolManager} from '../language-tools/manager'
import {TaskEventTracker} from './task-events'
import {ANSI_CODES} from '../utils/utils'
import {getExtensionSetting, SettingName} from '../utils/settings'

export enum TestCaseStatus {
Pending,
Expand All @@ -44,6 +45,12 @@ export interface RunTrackerParams {
languageToolManager: LanguageToolManager
}

type DebugInfo = {
debugFlags?: string[]
launchConfig?: vscode.DebugConfiguration
readyPattern?: RegExp
}

export class TestRunTracker implements TaskOriginHandlers {
// All tests that are included in this run. See iterator definition below.
private allTests: Map<TestItemType, TestCaseInfo[]>
Expand All @@ -63,6 +70,7 @@ export class TestRunTracker implements TaskOriginHandlers {
private languageToolManager: LanguageToolManager
private pending: Thenable<void>[] = []
private buildTaskTracker: TaskEventTracker = new TaskEventTracker()
private debugInfo: DebugInfo | undefined

constructor(params: RunTrackerParams) {
this.allTests = new Map<TestItemType, TestCaseInfo[]>()
Expand All @@ -76,6 +84,7 @@ export class TestRunTracker implements TaskOriginHandlers {
this.languageToolManager = params.languageToolManager

this.prepareCurrentRun()
this.prepareDebugInfo()
}

public get originName(): string {
Expand Down Expand Up @@ -228,6 +237,21 @@ export class TestRunTracker implements TaskOriginHandlers {
this.run.appendOutput(params.message)
this.run.appendOutput('\n\r')
}

// During debug runs, watch each message for indication of debug readiness.
// If the message matches the configured pattern, start the debug session.
if (
this.debugInfo?.launchConfig &&
this.debugInfo.readyPattern?.test(params.message)
) {
this.run.appendOutput(
`Starting remote debug session [Launch config: '${this.debugInfo.launchConfig.name}']\r\n`
)
vscode.debug.startDebugging(
vscode.workspace.workspaceFolders?.[0],
this.debugInfo.launchConfig
)
}
}

/**
Expand All @@ -254,6 +278,10 @@ export class TestRunTracker implements TaskOriginHandlers {
return this.request.profile?.kind
}

public getDebugBazelFlags(): string[] | undefined {
return this.debugInfo?.debugFlags
}

/**
* Collects and stores the parents and all children to be included in this test run.
* Populates maps to group the test items by their TestItemType and current status.
Expand Down Expand Up @@ -284,6 +312,72 @@ export class TestRunTracker implements TaskOriginHandlers {
}
}

/**
* During debug runs, this collects and stores the necessary settings that will be applied through this run.
* In the event that a setting is not found, information will be printed with the test output, but the run will still attempt to proceed.
*/
private prepareDebugInfo() {
if (this.getRunProfileKind() !== vscode.TestRunProfileKind.Debug) {
return
}

// Determine configured launch configuration name.
const configName = getExtensionSetting(SettingName.LAUNCH_CONFIG_NAME)
if (!configName) {
this.run.appendOutput(
'No launch configuration name is configured. Debugger will not connect automatically for this run.\r\n'
)
this.run.appendOutput(
'Check the `bazelbsp.debug.profileName` VS Code setting to ensure it corresponds to a valid launch configuration.\r\n'
)
return
}

// Store the selected launch configuration.
const launchConfigurations = vscode.workspace.getConfiguration('launch')
const configurations =
launchConfigurations.get<any[]>('configurations') || []
const selectedConfig = configurations.find(
config => config.name !== undefined && config.name === configName
)
if (!selectedConfig) {
this.run.appendOutput(
`Unable to find debug profile ${configName}. Debugger will not connect automatically for this run.\r\n`
)
this.run.appendOutput(
'Check the `bazelbsp.debug.profileName` VS Code setting to ensure it corresponds to a valid launch configuration.\r\n'
)
}

// Ensure that matcher pattern is set for the output.
const readyPattern = getExtensionSetting(SettingName.DEBUG_READY_PATTERN)
if (!readyPattern) {
this.run.appendOutput(
'No matcher pattern is set. Debugger will not connect automatically for this run.\r\n'
)
this.run.appendOutput(
'Check the `bazelbsp.debug.readyPattern` VS Code setting to ensure that a pattern is set.\r\n'
)
}

// Ensure that matcher pattern is set for the output.
let debugFlags = getExtensionSetting(SettingName.DEBUG_BAZEL_FLAGS)
if (!debugFlags) {
this.run.appendOutput(
'No additional debug-specific Bazel flags have been found for this run.\r\n'
)
this.run.appendOutput(
'Check the `bazelbsp.debug.bazelFlags` VS Code setting to ensure that necessary flags are set.\r\n'
)
}

this.debugInfo = {
debugFlags: debugFlags,
launchConfig: selectedConfig,
readyPattern: readyPattern ? new RegExp(readyPattern) : undefined,
}
}

/**
* Iterate recursively through all children of the given test item, and collect them in the destination map.
* @param destination Map to be populated with the collected test items, grouped by TestItemType.
Expand Down
12 changes: 12 additions & 0 deletions src/test-runner/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {MessageConnection} from 'vscode-jsonrpc'
import {TestRunTracker} from './run-tracker'
import {RunTrackerFactory} from './run-factory'
import {CoverageTracker} from '../coverage-utils/coverage-tracker'
import {getExtensionSetting, SettingName} from '../utils/settings'

@Injectable()
export class TestRunner implements OnModuleInit, vscode.Disposable {
Expand Down Expand Up @@ -53,6 +54,17 @@ export class TestRunner implements OnModuleInit, vscode.Disposable {
this.runProfiles.set(vscode.TestRunProfileKind.Coverage, coverageRunProfile)
coverageRunProfile.loadDetailedCoverage =
this.coverageTracker.loadDetailedCoverage.bind(this.coverageTracker)

// Debug run profile, added only when enabled.
if (getExtensionSetting(SettingName.DEBUG_ENABLED)) {
const debugRunProfile =
this.testCaseStore.testController.createRunProfile(
'Run with Debug',
vscode.TestRunProfileKind.Debug,
this.runHandler.bind(this)
)
this.runProfiles.set(vscode.TestRunProfileKind.Debug, debugRunProfile)
}
}

private async runHandler(
Expand Down
Loading
Loading