Skip to content

Commit f53a64a

Browse files
committed
unittest execution 1
1 parent 3b87651 commit f53a64a

File tree

5 files changed

+148
-14
lines changed

5 files changed

+148
-14
lines changed

src/client/testing/testController/controller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -794,7 +794,7 @@ export class PythonTestController implements ITestController, IExtensionSingleAc
794794
}
795795

796796
// Check if we're in project-based mode and should use project-specific execution
797-
if (this.projectRegistry.hasProjects(workspace.uri) && settings.testing.pytestEnabled) {
797+
if (this.projectRegistry.hasProjects(workspace.uri)) {
798798
const projects = this.projectRegistry.getProjectsArray(workspace.uri);
799799
await executeTestsForProjects(projects, testItems, runInstance, request, token, {
800800
projectRegistry: this.projectRegistry,

src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ export class PytestTestDiscoveryAdapter implements ITestDiscoveryAdapter {
109109
// Execute using environment extension if available
110110
if (useEnvExtension()) {
111111
traceInfo(`Using environment extension for pytest discovery in workspace ${uri.fsPath}`);
112-
const pythonEnv = await getEnvironment(uri);
112+
const pythonEnv = project?.pythonEnvironment ?? (await getEnvironment(uri));
113113
if (!pythonEnv) {
114114
traceError(
115115
`Python environment not found for workspace ${uri.fsPath}. Cannot proceed with test discovery.`,

src/client/testing/testController/unittest/testDiscoveryAdapter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ export class UnittestTestDiscoveryAdapter implements ITestDiscoveryAdapter {
9494
// Execute using environment extension if available
9595
if (useEnvExtension()) {
9696
traceInfo(`Using environment extension for unittest discovery in workspace ${uri.fsPath}`);
97-
const pythonEnv = await getEnvironment(uri);
97+
const pythonEnv = project?.pythonEnvironment ?? (await getEnvironment(uri));
9898
if (!pythonEnv) {
9999
traceError(
100100
`Python environment not found for workspace ${uri.fsPath}. Cannot proceed with test discovery.`,

src/client/testing/testController/unittest/testExecutionAdapter.ts

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,18 +48,9 @@ export class UnittestTestExecutionAdapter implements ITestExecutionAdapter {
4848
runInstance: TestRun,
4949
executionFactory: IPythonExecutionFactory,
5050
debugLauncher?: ITestDebugLauncher,
51-
interpreter?: PythonEnvironment,
51+
_interpreter?: PythonEnvironment,
5252
project?: ProjectAdapter,
5353
): Promise<void> {
54-
// Note: project parameter is currently unused for unittest.
55-
// Project-based unittest execution will be implemented in a future PR.
56-
console.log(
57-
'interpreter, project parameters are currently unused in UnittestTestExecutionAdapter, they will be used in a future implementation of project-based unittest execution.:',
58-
{
59-
interpreter,
60-
project,
61-
},
62-
);
6354
// deferredTillServerClose awaits named pipe server close
6455
const deferredTillServerClose: Deferred<void> = utils.createTestingDeferred();
6556

@@ -189,6 +180,8 @@ export class UnittestTestExecutionAdapter implements ITestExecutionAdapter {
189180
testProvider: UNITTEST_PROVIDER,
190181
runTestIdsPort: testIdsFileName,
191182
pytestPort: resultNamedPipeName, // change this from pytest
183+
// Pass project for project-based debugging (Python path and session name derived from this)
184+
project: project?.pythonProject,
192185
};
193186
const sessionOptions: DebugSessionOptions = {
194187
testRun: runInstance,
@@ -207,7 +200,7 @@ export class UnittestTestExecutionAdapter implements ITestExecutionAdapter {
207200
sessionOptions,
208201
);
209202
} else if (useEnvExtension()) {
210-
const pythonEnv = await getEnvironment(uri);
203+
const pythonEnv = project?.pythonEnvironment ?? (await getEnvironment(uri));
211204
if (pythonEnv) {
212205
traceInfo(`Running unittest with arguments: ${args.join(' ')} for workspace ${uri.fsPath} \r\n`);
213206
const deferredTillExecClose = createDeferred();

src/test/testing/testController/unittest/testExecutionAdapter.unit.test.ts

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { traceInfo } from '../../../../client/logging';
2323
import { UnittestTestExecutionAdapter } from '../../../../client/testing/testController/unittest/testExecutionAdapter';
2424
import * as extapi from '../../../../client/envExt/api.internal';
2525
import { ProjectAdapter } from '../../../../client/testing/testController/common/projectAdapter';
26+
import { createMockProjectAdapter } from '../testMocks';
2627

2728
suite('Unittest test execution adapter', () => {
2829
let configService: IConfigurationService;
@@ -434,4 +435,144 @@ suite('Unittest test execution adapter', () => {
434435
typeMoq.Times.once(),
435436
);
436437
});
438+
439+
test('Debug mode with project should pass project.pythonProject to debug launcher', async () => {
440+
const deferred3 = createDeferred();
441+
utilsWriteTestIdsFileStub.callsFake(() => Promise.resolve('testIdPipe-mockName'));
442+
443+
debugLauncher
444+
.setup((dl) => dl.launchDebugger(typeMoq.It.isAny(), typeMoq.It.isAny(), typeMoq.It.isAny()))
445+
.returns(async (_opts, callback) => {
446+
traceInfo('stubs launch debugger');
447+
if (typeof callback === 'function') {
448+
deferred3.resolve();
449+
callback();
450+
}
451+
});
452+
453+
const testRun = typeMoq.Mock.ofType<TestRun>();
454+
testRun
455+
.setup((t) => t.token)
456+
.returns(
457+
() =>
458+
({
459+
onCancellationRequested: () => undefined,
460+
} as any),
461+
);
462+
463+
const projectPath = path.join('/', 'workspace', 'myproject');
464+
const mockProject = createMockProjectAdapter({
465+
projectPath,
466+
projectName: 'myproject (Python 3.11)',
467+
pythonPath: '/custom/python/path',
468+
testProvider: 'unittest',
469+
});
470+
471+
const uri = Uri.file(myTestPath);
472+
adapter = new UnittestTestExecutionAdapter(configService);
473+
adapter.runTests(
474+
uri,
475+
[],
476+
TestRunProfileKind.Debug,
477+
testRun.object,
478+
execFactory.object,
479+
debugLauncher.object,
480+
undefined,
481+
mockProject,
482+
);
483+
484+
await deferred3.promise;
485+
486+
debugLauncher.verify(
487+
(x) =>
488+
x.launchDebugger(
489+
typeMoq.It.is<LaunchOptions>((launchOptions) => {
490+
// Project should be passed for project-based debugging
491+
assert.ok(launchOptions.project, 'project should be defined');
492+
assert.equal(launchOptions.project?.name, 'myproject (Python 3.11)');
493+
assert.equal(launchOptions.project?.uri.fsPath, projectPath);
494+
return true;
495+
}),
496+
typeMoq.It.isAny(),
497+
typeMoq.It.isAny(),
498+
),
499+
typeMoq.Times.once(),
500+
);
501+
});
502+
503+
test('useEnvExtension mode with project should use project pythonEnvironment', async () => {
504+
// Enable the useEnvExtension path
505+
useEnvExtensionStub.returns(true);
506+
507+
utilsWriteTestIdsFileStub.callsFake(() => Promise.resolve('testIdPipe-mockName'));
508+
509+
// Store the deferredTillServerClose so we can resolve it
510+
let serverCloseDeferred: Deferred<void> | undefined;
511+
utilsStartRunResultNamedPipeStub.callsFake((_callback: unknown, deferred: Deferred<void>, _token: unknown) => {
512+
serverCloseDeferred = deferred;
513+
return Promise.resolve('runResultPipe-mockName');
514+
});
515+
516+
const projectPath = path.join('/', 'workspace', 'myproject');
517+
const mockProject = createMockProjectAdapter({
518+
projectPath,
519+
projectName: 'myproject (Python 3.11)',
520+
pythonPath: '/custom/python/path',
521+
testProvider: 'unittest',
522+
});
523+
524+
// Stub runInBackground to capture which environment was used
525+
const runInBackgroundStub = sinon.stub(extapi, 'runInBackground');
526+
const exitCallbacks: ((code: number, signal: string | null) => void)[] = [];
527+
const mockProc2 = {
528+
stdout: { on: sinon.stub() },
529+
stderr: { on: sinon.stub() },
530+
onExit: (cb: (code: number, signal: string | null) => void) => {
531+
exitCallbacks.push(cb);
532+
},
533+
kill: sinon.stub(),
534+
};
535+
runInBackgroundStub.resolves(mockProc2 as any);
536+
537+
const testRun = typeMoq.Mock.ofType<TestRun>();
538+
testRun
539+
.setup((t) => t.token)
540+
.returns(
541+
() =>
542+
({
543+
onCancellationRequested: () => undefined,
544+
} as any),
545+
);
546+
547+
const uri = Uri.file(myTestPath);
548+
adapter = new UnittestTestExecutionAdapter(configService);
549+
const runPromise = adapter.runTests(
550+
uri,
551+
[],
552+
TestRunProfileKind.Run,
553+
testRun.object,
554+
execFactory.object,
555+
debugLauncher.object,
556+
undefined,
557+
mockProject,
558+
);
559+
560+
// Wait for the runInBackground to be called
561+
await new Promise((resolve) => setTimeout(resolve, 10));
562+
563+
// Simulate process exit to complete the test
564+
exitCallbacks.forEach((cb) => cb(0, null));
565+
566+
// Resolve the server close deferred to allow the runTests to complete
567+
serverCloseDeferred?.resolve();
568+
569+
await runPromise;
570+
571+
// Verify runInBackground was called with the project's Python environment
572+
sinon.assert.calledOnce(runInBackgroundStub);
573+
const envArg = runInBackgroundStub.firstCall.args[0];
574+
// The environment should be the project's pythonEnvironment
575+
assert.ok(envArg, 'runInBackground should be called with an environment');
576+
assert.equal(envArg.execInfo?.run?.executable, '/custom/python/path');
577+
});
437578
});

0 commit comments

Comments
 (0)