Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
eec4450
setup file structure
Aug 24, 2022
72d5ef0
reverse dummy commit
Aug 24, 2022
470e0be
stash to isolate error
eleanorjboyd Aug 25, 2022
dba9088
fixing merge issues
eleanorjboyd Sep 7, 2022
01cb5e1
pushing changes in order to rebase
eleanorjboyd Sep 7, 2022
494b777
rmv python file
eleanorjboyd Nov 10, 2022
f4df6f5
delete pytest plugin
eleanorjboyd Nov 10, 2022
e0a8adc
rename reverse
eleanorjboyd Nov 10, 2022
101a605
remove files
eleanorjboyd Nov 10, 2022
d14d4fb
reverse uneeded edits
eleanorjboyd Nov 10, 2022
a670f14
change file naming
eleanorjboyd Nov 10, 2022
8eb7612
controler changes
eleanorjboyd Nov 10, 2022
6d45f06
remove testing logic
eleanorjboyd Nov 10, 2022
b30adfd
remove comments and logs
eleanorjboyd Nov 10, 2022
8d3b7b3
comment out new code
eleanorjboyd Nov 10, 2022
e76ddf0
need to confirm it works after vscode isort fix
eleanorjboyd Nov 10, 2022
aaf9900
Merge branch 'main' into pytest_discovery_inactive_addition
eleanorjboyd Nov 10, 2022
aece46d
requested Changes
eleanorjboyd Nov 11, 2022
b035b45
fix comment
eleanorjboyd Nov 11, 2022
0dee36f
reverse error
eleanorjboyd Nov 11, 2022
7ebe875
Merge branch 'main' into pytest_discovery_inactive_addition
eleanorjboyd Nov 11, 2022
51d54a0
remove unneeded code
eleanorjboyd Nov 11, 2022
f8a9911
Merge branch 'main' into pytest_discovery_inactive_addition
eleanorjboyd Nov 15, 2022
c5b0f0e
Merge branch 'main' into pytest_discovery_inactive_addition
eleanorjboyd Nov 21, 2022
79934d3
Merge branch 'main' into pytest_discovery_inactive_addition
eleanorjboyd Dec 19, 2022
de79404
Merge branch 'main' into pytest_discovery_inactive_addition
eleanorjboyd Jan 11, 2023
0692878
kartik review
eleanorjboyd Jan 18, 2023
0c358f2
update discovery adapter file
eleanorjboyd Jan 18, 2023
815652f
updated to handle new discovery code
eleanorjboyd Jan 18, 2023
af357a7
Merge branch 'main' into pytest_discovery_inactive_addition
eleanorjboyd Jan 19, 2023
a65d7d9
Merge branch 'main' into pytest_discovery_inactive_addition
eleanorjboyd Jan 23, 2023
feeeb46
fix line numbers in comment
eleanorjboyd Jan 24, 2023
6d8c29f
remove console log
eleanorjboyd Jan 31, 2023
f5b7b4f
Merge branch 'main' into pytest_discovery_inactive_addition
eleanorjboyd Jan 31, 2023
b7b990a
switch to trace verbose
eleanorjboyd Jan 31, 2023
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
3 changes: 1 addition & 2 deletions pythonFiles/testing_tools/run_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@
),
)

from testing_tools.adapter.__main__ import parse_args, main

from testing_tools.adapter.__main__ import main, parse_args

if __name__ == "__main__":
tool, cmd, subargs, toolargs = parse_args()
Expand Down
15 changes: 12 additions & 3 deletions src/client/testing/testController/common/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,17 @@ export class PythonTestServer implements ITestServer, Disposable {
return (this.server.address() as net.AddressInfo).port;
}

/**
* creates a UUID using crypto library given test command options
* @param options test command options
* @returns a UUID as a string
*/
public createUUID(cwd: string): string {
const uuid = crypto.randomUUID();
this.uuids.set(uuid, cwd);
return uuid;
}

public dispose(): void {
this.server.close();
this._onDataReceived.dispose();
Expand All @@ -81,15 +92,13 @@ export class PythonTestServer implements ITestServer, Disposable {
}

async sendCommand(options: TestCommandOptions): Promise<void> {
const uuid = crypto.randomUUID();
const uuid = this.createUUID(options.cwd);
const spawnOptions: SpawnOptions = {
token: options.token,
cwd: options.cwd,
throwOnStdErr: true,
};

this.uuids.set(uuid, options.cwd);

// Create the Python environment in which to execute the command.
const creationOptions: ExecutionFactoryCreateWithEnvironmentOptions = {
allowEnvironmentFetchExceptions: false,
Expand Down
14 changes: 14 additions & 0 deletions src/client/testing/testController/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,17 @@ export type TestCommandOptions = {
testIds?: string[];
};

export type TestCommandOptionsPytest = {
workspaceFolder: Uri;
cwd: string;
commandStr: string;
token?: CancellationToken;
outChannel?: OutputChannel;
debugBool?: boolean;
testIds?: string[];
env: { [key: string]: string | undefined };
};

/**
* Interface describing the server that will send test commands to the Python side, and process responses.
*
Expand All @@ -161,10 +172,13 @@ export interface ITestServer {
readonly onDataReceived: Event<DataReceivedEvent>;
sendCommand(options: TestCommandOptions): Promise<void>;
serverReady(): Promise<void>;
getPort(): number;
createUUID(cwd: string): string;
}

export interface ITestDiscoveryAdapter {
discoverTests(uri: Uri): Promise<DiscoveredTestPayload>;
// discoverTests(uri: Uri, executionFactory: IPythonExecutionFactory): Promise<DiscoveredTestPayload>; testing rewrite
}

// interface for execution/runner adapter
Expand Down
36 changes: 19 additions & 17 deletions src/client/testing/testController/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,10 @@ import {
ITestExecutionAdapter,
} from './common/types';
import { UnittestTestDiscoveryAdapter } from './unittest/testDiscoveryAdapter';
import { WorkspaceTestAdapter } from './workspaceTestAdapter';
import { UnittestTestExecutionAdapter } from './unittest/testExecutionAdapter';
import { PytestTestDiscoveryAdapter } from './pytest/pytestDiscoveryAdapter';
import { PytestTestExecutionAdapter } from './pytest/pytestExecutionAdapter';
import { WorkspaceTestAdapter } from './workspaceTestAdapter';
import { ITestDebugLauncher } from '../common/types';

// Types gymnastics to make sure that sendTriggerTelemetry only accepts the correct types.
Expand Down Expand Up @@ -141,7 +143,6 @@ export class PythonTestController implements ITestController, IExtensionSingleAc
});
return this.refreshTestData(undefined, { forceRefresh: true });
};

this.pythonTestServer = new PythonTestServer(this.pythonExecFactory, this.debugLauncher);
}

Expand All @@ -161,13 +162,10 @@ export class PythonTestController implements ITestController, IExtensionSingleAc
executionAdapter = new UnittestTestExecutionAdapter(this.pythonTestServer, this.configSettings);
testProvider = UNITTEST_PROVIDER;
} else {
// TODO: PYTEST DISCOVERY ADAPTER
// this is a placeholder for now
discoveryAdapter = new UnittestTestDiscoveryAdapter(this.pythonTestServer, { ...this.configSettings });
executionAdapter = new UnittestTestExecutionAdapter(this.pythonTestServer, this.configSettings);
discoveryAdapter = new PytestTestDiscoveryAdapter(this.pythonTestServer, { ...this.configSettings });
executionAdapter = new PytestTestExecutionAdapter(this.pythonTestServer, this.configSettings);
testProvider = PYTEST_PROVIDER;
}

const workspaceTestAdapter = new WorkspaceTestAdapter(
testProvider,
discoveryAdapter,
Expand Down Expand Up @@ -224,18 +222,23 @@ export class PythonTestController implements ITestController, IExtensionSingleAc
this.refreshingStartedEvent.fire();
if (uri) {
const settings = this.configSettings.getSettings(uri);
traceVerbose(`Testing: Refreshing test data for ${uri.fsPath}`);
const workspace = this.workspaceService.getWorkspaceFolder(uri);
console.warn(`Discover tests for workspace name: ${workspace?.name} - uri: ${uri.fsPath}`);
if (settings.testing.pytestEnabled) {
traceVerbose(`Testing: Refreshing test data for ${uri.fsPath}`);

// const testAdapter =
// this.testAdapters.get(uri) || (this.testAdapters.values().next().value as WorkspaceTestAdapter);
// testAdapter.discoverTests(
// this.testController,
// this.refreshCancellation.token,
// this.testAdapters.size > 1,
// this.workspaceService.workspaceFile?.fsPath,
// );
// Ensure we send test telemetry if it gets disabled again
this.sendTestDisabledTelemetry = true;

// comment the line 240 and uncomment the lines 229-236 to run the new way
await this.pytest.refreshTestData(this.testController, uri, this.refreshCancellation.token);
} else if (settings.testing.unittestEnabled) {
// TODO: Use new test discovery mechanism
// traceVerbose(`Testing: Refreshing test data for ${uri.fsPath}`);
// const workspace = this.workspaceService.getWorkspaceFolder(uri);
// console.warn(`Discover tests for workspace name: ${workspace?.name} - uri: ${uri.fsPath}`);
// const testAdapter =
// this.testAdapters.get(uri) || (this.testAdapters.values().next().value as WorkspaceTestAdapter);
// testAdapter.discoverTests(
Expand All @@ -244,8 +247,8 @@ export class PythonTestController implements ITestController, IExtensionSingleAc
// this.testAdapters.size > 1,
// this.workspaceService.workspaceFile?.fsPath,
// );
// // Ensure we send test telemetry if it gets disabled again
// this.sendTestDisabledTelemetry = true;
// Ensure we send test telemetry if it gets disabled again
this.sendTestDisabledTelemetry = true;
// comment below 229 to run the new way and uncomment above 212 ~ 227
await this.unittest.refreshTestData(this.testController, uri, this.refreshCancellation.token);
} else {
Expand All @@ -256,7 +259,6 @@ export class PythonTestController implements ITestController, IExtensionSingleAc
// If we are here we may have to remove an existing node from the tree
// This handles the case where user removes test settings. Which should remove the
// tests for that particular case from the tree view
const workspace = this.workspaceService.getWorkspaceFolder(uri);
if (workspace) {
const toDelete: string[] = [];
this.testController.items.forEach((i: TestItem) => {
Expand Down
87 changes: 87 additions & 0 deletions src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import * as path from 'path';
import { Uri } from 'vscode';
import {
ExecutionFactoryCreateWithEnvironmentOptions,
IPythonExecutionFactory,
SpawnOptions,
} from '../../../common/process/types';
import { IConfigurationService } from '../../../common/types';
import { createDeferred, Deferred } from '../../../common/utils/async';
import { EXTENSION_ROOT_DIR } from '../../../constants';
import { traceVerbose } from '../../../logging';
import { DataReceivedEvent, DiscoveredTestPayload, ITestDiscoveryAdapter, ITestServer } from '../common/types';

/**
* Wrapper class for unittest test discovery. This is where we call `runTestCommand`. #this seems incorrectly copied
*/
export class PytestTestDiscoveryAdapter implements ITestDiscoveryAdapter {
private deferred: Deferred<DiscoveredTestPayload> | undefined;

private cwd: string | undefined;

constructor(public testServer: ITestServer, public configSettings: IConfigurationService) {
testServer.onDataReceived(this.onDataReceivedHandler, this);
}

discoverTests(uri: Uri): Promise<DiscoveredTestPayload> {
traceVerbose(uri);
this.deferred = createDeferred<DiscoveredTestPayload>();
return this.deferred.promise;
}

public onDataReceivedHandler({ cwd, data }: DataReceivedEvent): void {
if (this.deferred && cwd === this.cwd) {
const testData: DiscoveredTestPayload = JSON.parse(data);

this.deferred.resolve(testData);
this.deferred = undefined;
}
}

// public async discoverTests(uri: Uri, executionFactory: IPythonExecutionFactory): Promise<DiscoveredTestPayload> {
// const settings = this.configSettings.getSettings(uri);
// const { pytestArgs } = settings.testing;
// traceVerbose(pytestArgs); // do we use pytestArgs anywhere?

// this.cwd = uri.fsPath;
// return this.runPytestDiscovery(uri, executionFactory);
// }

async runPytestDiscovery(uri: Uri, executionFactory: IPythonExecutionFactory): Promise<DiscoveredTestPayload> {
if (!this.deferred) {
this.deferred = createDeferred<DiscoveredTestPayload>();
const relativePathToPytest = 'pythonFiles/pytest-vscode-integration';
const fullPluginPath = path.join(EXTENSION_ROOT_DIR, relativePathToPytest);
const uuid = this.testServer.createUUID(uri.fsPath);
const settings = this.configSettings.getSettings(uri);
const { pytestArgs } = settings.testing;
const pythonPathCommand = `${fullPluginPath}${path.delimiter}`.concat(process.env.PYTHONPATH ?? '');

const spawnOptions: SpawnOptions = {
cwd: uri.fsPath,
throwOnStdErr: true,
extraVariables: {
PYTHONPATH: pythonPathCommand,
TEST_UUID: uuid.toString(),
TEST_PORT: this.testServer.getPort().toString(),
},
};

// Create the Python environment in which to execute the command.
const creationOptions: ExecutionFactoryCreateWithEnvironmentOptions = {
allowEnvironmentFetchExceptions: false,
resource: uri,
};
const execService = await executionFactory.createActivatedEnvironment(creationOptions);

try {
execService.exec(['-m', 'pytest', '--collect-only'].concat(pytestArgs), spawnOptions);
} catch (ex) {
console.error(ex);
}
}
return this.deferred.promise;
}
}
71 changes: 71 additions & 0 deletions src/client/testing/testController/pytest/pytestExecutionAdapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import { Uri } from 'vscode';
import { IConfigurationService } from '../../../common/types';
import { createDeferred, Deferred } from '../../../common/utils/async';
import {
DataReceivedEvent,
ExecutionTestPayload,
ITestExecutionAdapter,
ITestServer,
TestCommandOptions,
TestExecutionCommand,
} from '../common/types';

/**
* Wrapper Class for pytest test execution. This is where we call `runTestCommand`?
*/

export class PytestTestExecutionAdapter implements ITestExecutionAdapter {
private deferred: Deferred<ExecutionTestPayload> | undefined;

private cwd: string | undefined;

constructor(public testServer: ITestServer, public configSettings: IConfigurationService) {
testServer.onDataReceived(this.onDataReceivedHandler, this);
}

public onDataReceivedHandler({ cwd, data }: DataReceivedEvent): void {
if (this.deferred && cwd === this.cwd) {
const testData: ExecutionTestPayload = JSON.parse(data);

this.deferred.resolve(testData);
this.deferred = undefined;
}
}

public async runTests(uri: Uri, testIds: string[], debugBool?: boolean): Promise<ExecutionTestPayload> {
if (!this.deferred) {
const settings = this.configSettings.getSettings(uri);
const { pytestArgs } = settings.testing;

const command = buildExecutionCommand(pytestArgs);
this.cwd = uri.fsPath;

const options: TestCommandOptions = {
workspaceFolder: uri,
command,
cwd: this.cwd,
debugBool,
testIds,
};

this.deferred = createDeferred<ExecutionTestPayload>();

// send test command to server
// server fire onDataReceived event once it gets response
this.testServer.sendCommand(options);
}
return this.deferred.promise;
}
}

function buildExecutionCommand(args: string[]): TestExecutionCommand {
const executionScript = '';

return {
script: executionScript,
args: ['--udiscovery', ...args],
};
}