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

Dynamically handle test adapter activation #23276

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
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
2 changes: 1 addition & 1 deletion src/client/testing/testController/common/resultResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import { Deferred } from '../../../common/utils/async';
export class PythonResultResolver implements ITestResultResolver {
testController: TestController;

testProvider: TestProvider;
public testProvider: TestProvider;

public runIdToTestItem: Map<string, TestItem>;

Expand Down
155 changes: 82 additions & 73 deletions src/client/testing/testController/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,9 @@ import { traceError, traceInfo, traceVerbose } from '../../logging';
import { IEventNamePropertyMapping, sendTelemetryEvent } from '../../telemetry';
import { EventName } from '../../telemetry/constants';
import { PYTEST_PROVIDER, UNITTEST_PROVIDER } from '../common/constants';
import { TestProvider } from '../types';
import { DebugTestTag, getNodeByUri, RunTestTag } from './common/testItemUtilities';
import { pythonTestAdapterRewriteEnabled } from './common/utils';
import {
ITestController,
ITestDiscoveryAdapter,
ITestFrameworkController,
TestRefreshOptions,
ITestExecutionAdapter,
ITestResultResolver,
} from './common/types';
import { ITestController, ITestFrameworkController, TestRefreshOptions } from './common/types';
import { UnittestTestDiscoveryAdapter } from './unittest/testDiscoveryAdapter';
import { UnittestTestExecutionAdapter } from './unittest/testExecutionAdapter';
import { PytestTestDiscoveryAdapter } from './pytest/pytestDiscoveryAdapter';
Expand Down Expand Up @@ -157,49 +149,8 @@ export class PythonTestController implements ITestController, IExtensionSingleAc
workspaces.forEach((workspace) => {
const settings = this.configSettings.getSettings(workspace.uri);

let discoveryAdapter: ITestDiscoveryAdapter;
let executionAdapter: ITestExecutionAdapter;
let testProvider: TestProvider;
let resultResolver: ITestResultResolver;
if (settings.testing.unittestEnabled) {
testProvider = UNITTEST_PROVIDER;
resultResolver = new PythonResultResolver(this.testController, testProvider, workspace.uri);
discoveryAdapter = new UnittestTestDiscoveryAdapter(
this.configSettings,
this.testOutputChannel,
resultResolver,
this.envVarsService,
);
executionAdapter = new UnittestTestExecutionAdapter(
this.configSettings,
this.testOutputChannel,
resultResolver,
this.envVarsService,
);
} else {
testProvider = PYTEST_PROVIDER;
resultResolver = new PythonResultResolver(this.testController, testProvider, workspace.uri);
discoveryAdapter = new PytestTestDiscoveryAdapter(
this.configSettings,
this.testOutputChannel,
resultResolver,
this.envVarsService,
);
executionAdapter = new PytestTestExecutionAdapter(
this.configSettings,
this.testOutputChannel,
resultResolver,
this.envVarsService,
);
}

const workspaceTestAdapter = new WorkspaceTestAdapter(
testProvider,
discoveryAdapter,
executionAdapter,
workspace.uri,
resultResolver,
);
const resultResolver = new PythonResultResolver(this.testController, PYTEST_PROVIDER, workspace.uri);
const workspaceTestAdapter = new WorkspaceTestAdapter(workspace.uri, resultResolver);

this.testAdapters.set(workspace.uri, workspaceTestAdapter);

Expand Down Expand Up @@ -257,12 +208,25 @@ export class PythonTestController implements ITestController, IExtensionSingleAc
// ** experiment to roll out NEW test discovery mechanism
if (settings.testing.pytestEnabled) {
if (pythonTestAdapterRewriteEnabled(this.serviceContainer)) {
// create test adapter for the given framework and type

traceInfo(`Running discovery for pytest using the new test adapter.`);
if (workspace && workspace.uri) {
const testAdapter = this.testAdapters.get(workspace.uri);
if (testAdapter) {
testAdapter.discoverTests(
const workspaceTestAdapter = this.testAdapters.get(workspace.uri);
if (workspaceTestAdapter) {
// update test provider
workspaceTestAdapter.resultResolver.testProvider = PYTEST_PROVIDER;
// create test adapter for the given framework and type
const pytestDiscoveryAdapter = new PytestTestDiscoveryAdapter(
this.configSettings,
this.testOutputChannel,
workspaceTestAdapter.resultResolver,
this.envVarsService,
);
workspaceTestAdapter.discoverTests(
this.testController,
pytestDiscoveryAdapter,
PYTEST_PROVIDER,
this.refreshCancellation.token,
this.pythonExecFactory,
);
Expand All @@ -279,11 +243,24 @@ export class PythonTestController implements ITestController, IExtensionSingleAc
} else if (settings.testing.unittestEnabled) {
if (pythonTestAdapterRewriteEnabled(this.serviceContainer)) {
traceInfo(`Running discovery for unittest using the new test adapter.`);
// create test adapter for the given framework and type

if (workspace && workspace.uri) {
const testAdapter = this.testAdapters.get(workspace.uri);
if (testAdapter) {
// update test provider
testAdapter.resultResolver.testProvider = UNITTEST_PROVIDER;
// create test adapter for the given framework and type
const unittestDiscoveryAdapter = new UnittestTestDiscoveryAdapter(
this.configSettings,
this.testOutputChannel,
testAdapter.resultResolver,
this.envVarsService,
);
testAdapter.discoverTests(
this.testController,
unittestDiscoveryAdapter,
UNITTEST_PROVIDER,
this.refreshCancellation.token,
this.pythonExecFactory,
);
Expand Down Expand Up @@ -414,18 +391,34 @@ export class PythonTestController implements ITestController, IExtensionSingleAc
});
// ** experiment to roll out NEW test discovery mechanism
if (pythonTestAdapterRewriteEnabled(this.serviceContainer)) {
// create test adapter for the given framework and type
const testAdapter =
this.testAdapters.get(workspace.uri) ||
(this.testAdapters.values().next().value as WorkspaceTestAdapter);
return testAdapter.executeTests(
this.testController,
runInstance,
testItems,
token,
request.profile?.kind === TestRunProfileKind.Debug,
this.pythonExecFactory,
this.debugLauncher,
);
if (testAdapter) {
// update test provider
testAdapter.resultResolver.testProvider = PYTEST_PROVIDER;
// create test adapter for the given framework and type
const pytestExecutionAdapter = new PytestTestExecutionAdapter(
this.configSettings,
this.testOutputChannel,
testAdapter.resultResolver,
this.envVarsService,
);
return testAdapter.executeTests(
this.testController,
pytestExecutionAdapter,
PYTEST_PROVIDER,
runInstance,
testItems,
token,
request.profile?.kind === TestRunProfileKind.Debug,
this.pythonExecFactory,
this.debugLauncher,
);
}
traceError('Unable to find test adapter for workspace.');
return Promise.resolve();
}
return this.pytest.runTests(
{
Expand All @@ -448,15 +441,31 @@ export class PythonTestController implements ITestController, IExtensionSingleAc
const testAdapter =
this.testAdapters.get(workspace.uri) ||
(this.testAdapters.values().next().value as WorkspaceTestAdapter);
return testAdapter.executeTests(
this.testController,
runInstance,
testItems,
token,
request.profile?.kind === TestRunProfileKind.Debug,
this.pythonExecFactory,
this.debugLauncher,
);

if (testAdapter) {
// update test provider
testAdapter.resultResolver.testProvider = UNITTEST_PROVIDER;
// create test adapter for the given framework and type
const unittestExecutionAdapter = new UnittestTestExecutionAdapter(
this.configSettings,
this.testOutputChannel,
testAdapter.resultResolver,
this.envVarsService,
);
return testAdapter.executeTests(
this.testController,
unittestExecutionAdapter,
UNITTEST_PROVIDER,
runInstance,
testItems,
token,
request.profile?.kind === TestRunProfileKind.Debug,
this.pythonExecFactory,
this.debugLauncher,
);
}
traceError('Unable to find test adapter for workspace.');
return Promise.resolve();
}
// below is old way of running unittest execution
return this.unittest.runTests(
Expand Down
42 changes: 22 additions & 20 deletions src/client/testing/testController/workspaceTestAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ import { Testing } from '../../common/utils/localize';
import { traceError } from '../../logging';
import { sendTelemetryEvent } from '../../telemetry';
import { EventName } from '../../telemetry/constants';
import { TestProvider } from '../types';
import { createErrorTestItem, getTestCaseNodes } from './common/testItemUtilities';
import { ITestDiscoveryAdapter, ITestExecutionAdapter, ITestResultResolver } from './common/types';
import { IPythonExecutionFactory } from '../../common/process/types';
import { ITestDebugLauncher } from '../common/types';
import { buildErrorNodeOptions } from './common/utils';
import { ITestDiscoveryAdapter, ITestExecutionAdapter } from './common/types';
import { TestProvider } from '../types';
import { PythonResultResolver } from './common/resultResolver';

/**
* This class exposes a test-provider-agnostic way of discovering tests.
Expand All @@ -29,16 +30,12 @@ export class WorkspaceTestAdapter {

private executing: Deferred<void> | undefined;

constructor(
private testProvider: TestProvider,
private discoveryAdapter: ITestDiscoveryAdapter,
private executionAdapter: ITestExecutionAdapter,
private workspaceUri: Uri,
private resultResolver: ITestResultResolver,
) {}
constructor(private workspaceUri: Uri, public resultResolver: PythonResultResolver) {}

public async executeTests(
testController: TestController,
executionAdapter: ITestExecutionAdapter,
testProvider: TestProvider,
runInstance: TestRun,
includes: TestItem[],
token?: CancellationToken,
Expand Down Expand Up @@ -73,7 +70,7 @@ export class WorkspaceTestAdapter {
const testCaseIds = Array.from(testCaseIdsSet);
// ** execution factory only defined for new rewrite way
if (executionFactory !== undefined) {
await this.executionAdapter.runTests(
await executionAdapter.runTests(
this.workspaceUri,
testCaseIds,
debugBool,
Expand All @@ -82,7 +79,7 @@ export class WorkspaceTestAdapter {
debugLauncher,
);
} else {
await this.executionAdapter.runTests(this.workspaceUri, testCaseIds, debugBool);
await executionAdapter.runTests(this.workspaceUri, testCaseIds, debugBool);
}
deferred.resolve();
} catch (ex) {
Expand All @@ -92,14 +89,14 @@ export class WorkspaceTestAdapter {
let cancel = token?.isCancellationRequested
? Testing.cancelUnittestExecution
: Testing.errorUnittestExecution;
if (this.testProvider === 'pytest') {
if (testProvider === 'pytest') {
cancel = token?.isCancellationRequested ? Testing.cancelPytestExecution : Testing.errorPytestExecution;
}
traceError(`${cancel}\r\n`, ex);

// Also report on the test view
const message = util.format(`${cancel} ${Testing.seePythonOutput}\r\n`, ex);
const options = buildErrorNodeOptions(this.workspaceUri, message, this.testProvider);
const options = buildErrorNodeOptions(this.workspaceUri, message, testProvider);
const errorNode = createErrorTestItem(testController, options);
testController.items.add(errorNode);

Expand All @@ -113,10 +110,12 @@ export class WorkspaceTestAdapter {

public async discoverTests(
testController: TestController,
discoveryAdapter: ITestDiscoveryAdapter,
testProvider: TestProvider,
token?: CancellationToken,
executionFactory?: IPythonExecutionFactory,
): Promise<void> {
sendTelemetryEvent(EventName.UNITTEST_DISCOVERING, undefined, { tool: this.testProvider });
sendTelemetryEvent(EventName.UNITTEST_DISCOVERING, undefined, { tool: testProvider });

// Discovery is expensive. If it is already running, use the existing promise.
if (this.discovering) {
Expand All @@ -130,26 +129,29 @@ export class WorkspaceTestAdapter {
try {
// ** execution factory only defined for new rewrite way
if (executionFactory !== undefined) {
await this.discoveryAdapter.discoverTests(this.workspaceUri, executionFactory);
await discoveryAdapter.discoverTests(this.workspaceUri, executionFactory);
} else {
await this.discoveryAdapter.discoverTests(this.workspaceUri);
await discoveryAdapter.discoverTests(this.workspaceUri);
}
deferred.resolve();
} catch (ex) {
sendTelemetryEvent(EventName.UNITTEST_DISCOVERY_DONE, undefined, { tool: this.testProvider, failed: true });
sendTelemetryEvent(EventName.UNITTEST_DISCOVERY_DONE, undefined, {
tool: testProvider,
failed: true,
});

let cancel = token?.isCancellationRequested
? Testing.cancelUnittestDiscovery
: Testing.errorUnittestDiscovery;
if (this.testProvider === 'pytest') {
if (testProvider === 'pytest') {
cancel = token?.isCancellationRequested ? Testing.cancelPytestDiscovery : Testing.errorPytestDiscovery;
}

traceError(`${cancel} for workspace: ${this.workspaceUri} \r\n`, ex);

// Report also on the test view.
const message = util.format(`${cancel} ${Testing.seePythonOutput}\r\n`, ex);
const options = buildErrorNodeOptions(this.workspaceUri, message, this.testProvider);
const options = buildErrorNodeOptions(this.workspaceUri, message, testProvider);
const errorNode = createErrorTestItem(testController, options);
testController.items.add(errorNode);

Expand All @@ -160,7 +162,7 @@ export class WorkspaceTestAdapter {
this.discovering = undefined;
}

sendTelemetryEvent(EventName.UNITTEST_DISCOVERY_DONE, undefined, { tool: this.testProvider, failed: false });
sendTelemetryEvent(EventName.UNITTEST_DISCOVERY_DONE, undefined, { tool: testProvider, failed: false });
return Promise.resolve();
}
}
Loading
Loading