diff --git a/src/client/testing/common/debugLauncher.ts b/src/client/testing/common/debugLauncher.ts index aef1a9fd9197..cd4b7181f447 100644 --- a/src/client/testing/common/debugLauncher.ts +++ b/src/client/testing/common/debugLauncher.ts @@ -1,6 +1,6 @@ import { inject, injectable, named } from 'inversify'; import * as path from 'path'; -import { DebugConfiguration, l10n, Uri, WorkspaceFolder } from 'vscode'; +import { DebugConfiguration, l10n, Uri, WorkspaceFolder, DebugSession } from 'vscode'; import { IApplicationShell, IDebugService } from '../../common/application/types'; import { EXTENSION_ROOT_DIR } from '../../common/constants'; import * as internalScripts from '../../common/process/internal/scripts'; @@ -9,7 +9,7 @@ import { DebuggerTypeName, PythonDebuggerTypeName } from '../../debugger/constan import { IDebugConfigurationResolver } from '../../debugger/extension/configuration/types'; import { DebugPurpose, LaunchRequestArguments } from '../../debugger/types'; import { IServiceContainer } from '../../ioc/types'; -import { traceError } from '../../logging'; +import { traceError, traceVerbose } from '../../logging'; import { TestProvider } from '../types'; import { ITestDebugLauncher, LaunchOptions } from './types'; import { getConfigurationsForWorkspace } from '../../debugger/extension/configuration/launch.json/launchJsonReader'; @@ -34,12 +34,20 @@ export class DebugLauncher implements ITestDebugLauncher { public async launchDebugger(options: LaunchOptions, callback?: () => void): Promise { const deferred = createDeferred(); + let hasCallbackBeenCalled = false; if (options.token && options.token.isCancellationRequested) { + hasCallbackBeenCalled = true; return undefined; deferred.resolve(); callback?.(); } + options.token?.onCancellationRequested(() => { + deferred.resolve(); + callback?.(); + hasCallbackBeenCalled = true; + }); + const workspaceFolder = DebugLauncher.resolveWorkspaceFolder(options.cwd); const launchArgs = await this.getLaunchArgs( options, @@ -48,11 +56,23 @@ export class DebugLauncher implements ITestDebugLauncher { ); const debugManager = this.serviceContainer.get(IDebugService); - debugManager.onDidTerminateDebugSession(() => { - deferred.resolve(); - callback?.(); + let activatedDebugSession: DebugSession | undefined; + debugManager.startDebugging(workspaceFolder, launchArgs).then(() => { + // Save the debug session after it is started so we can check if it is the one that was terminated. + activatedDebugSession = debugManager.activeDebugSession; + }); + debugManager.onDidTerminateDebugSession((session) => { + traceVerbose(`Debug session terminated. sessionId: ${session.id}`); + // Only resolve no callback has been made and the session is the one that was started. + if ( + !hasCallbackBeenCalled && + activatedDebugSession !== undefined && + session.id === activatedDebugSession?.id + ) { + deferred.resolve(); + callback?.(); + } }); - debugManager.startDebugging(workspaceFolder, launchArgs); return deferred.promise; } diff --git a/src/client/testing/testController/controller.ts b/src/client/testing/testController/controller.ts index b55eaa446018..58edfb059666 100644 --- a/src/client/testing/testController/controller.ts +++ b/src/client/testing/testController/controller.ts @@ -378,7 +378,6 @@ export class PythonTestController implements ITestController, IExtensionSingleAc `Running Tests for Workspace(s): ${workspaces.map((w) => w.uri.fsPath).join(';')}`, true, ); - const dispose = token.onCancellationRequested(() => { runInstance.appendOutput(`\nRun instance cancelled.\r\n`); runInstance.end(); diff --git a/src/test/testing/common/debugLauncher.unit.test.ts b/src/test/testing/common/debugLauncher.unit.test.ts index 31ba761eb946..bdcb7b63762c 100644 --- a/src/test/testing/common/debugLauncher.unit.test.ts +++ b/src/test/testing/common/debugLauncher.unit.test.ts @@ -10,7 +10,7 @@ import * as sinon from 'sinon'; import * as TypeMoq from 'typemoq'; import * as fs from 'fs-extra'; import * as workspaceApis from '../../../client/common/vscodeApis/workspaceApis'; -import { CancellationTokenSource, DebugConfiguration, Uri, WorkspaceFolder } from 'vscode'; +import { CancellationTokenSource, DebugConfiguration, DebugSession, Uri, WorkspaceFolder } from 'vscode'; import { IInvalidPythonPathInDebuggerService } from '../../../client/application/diagnostics/types'; import { IApplicationShell, IDebugService } from '../../../client/common/application/types'; import { EXTENSION_ROOT_DIR } from '../../../client/common/constants'; @@ -30,6 +30,7 @@ import { TestProvider } from '../../../client/testing/types'; import { isOs, OSType } from '../../common'; import { IEnvironmentActivationService } from '../../../client/interpreter/activation/types'; import * as util from '../../../client/testing/testController/common/utils'; +import { createDeferred } from '../../../client/common/utils/async'; use(chaiAsPromised); @@ -125,17 +126,31 @@ suite('Unit Tests - Debug Launcher', () => { .setup((x) => x.getEnvironmentVariables(TypeMoq.It.isAny(), TypeMoq.It.isAny())) .returns(() => Promise.resolve(expected.env)); + const deferred = createDeferred(); + debugService .setup((d) => d.startDebugging(TypeMoq.It.isValue(workspaceFolder), TypeMoq.It.isValue(expected))) .returns((_wspc: WorkspaceFolder, _expectedParam: DebugConfiguration) => { + deferred.resolve(); return Promise.resolve(undefined as any); - }) + }); + + // create a fake debug session that the debug service will return on terminate + const fakeDebugSession = TypeMoq.Mock.ofType(); + fakeDebugSession.setup((ds) => ds.id).returns(() => 'id-val'); + const debugSessionInstance = fakeDebugSession.object; + + debugService + .setup((d) => d.activeDebugSession) + .returns(() => debugSessionInstance) .verifiable(TypeMoq.Times.once()); debugService .setup((d) => d.onDidTerminateDebugSession(TypeMoq.It.isAny())) .returns((callback) => { - callback(); + deferred.promise.then(() => { + callback(debugSessionInstance); + }); return undefined as any; }) .verifiable(TypeMoq.Times.once());