From f449c03b2adaa80ed0ab4adbd5ecb58116701700 Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Tue, 24 Jan 2023 15:15:20 -0800 Subject: [PATCH 01/21] attempted to port over all necessary files --- pythonFiles/vscode_pytest/__init__.py | 218 ++++++++++++++++++ .../vscode-pytest.egg-info/entry_points.txt | 2 + .../testing/testController/common/server.ts | 10 +- .../testing/testController/common/types.ts | 16 +- .../testing/testController/controller.ts | 76 ++++-- .../testController/pytest/arguments.ts | 2 +- .../pytest/pytestDiscoveryAdapter.ts | 80 +++++++ .../pytest/pytestExecutionAdapter.ts | 73 ++++++ .../testController/workspaceTestAdapter.ts | 23 +- 9 files changed, 471 insertions(+), 29 deletions(-) create mode 100644 pythonFiles/vscode_pytest/__init__.py create mode 100644 pythonFiles/vscode_pytest/vscode-pytest.egg-info/entry_points.txt create mode 100644 src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts create mode 100644 src/client/testing/testController/pytest/pytestExecutionAdapter.ts diff --git a/pythonFiles/vscode_pytest/__init__.py b/pythonFiles/vscode_pytest/__init__.py new file mode 100644 index 000000000000..4341c17c68ae --- /dev/null +++ b/pythonFiles/vscode_pytest/__init__.py @@ -0,0 +1,218 @@ +# -*- coding: utf-8 -*- + +# this file taken from 71636e91930c9905604577db7e1e9a1cffa05a6e +# multi class actually working on Nov 9th + +import enum +import json +import os +import pathlib +import sys +from dbm.ndbm import library +from typing import KeysView, List, Literal, Optional, Tuple, TypedDict, Union +from unittest import TestCase + +import pytest + + +# Inherit from str so it's JSON serializable. +class TestNodeTypeEnum(str, enum.Enum): + class_ = "class" + file = "file" + folder = "folder" + test = "test" + + +class TestData(TypedDict): + name: str + path: str + type_: TestNodeTypeEnum + id_: str + + +class TestItem(TestData): + lineno: str + runID: str + + +class TestNode(TestData): + children: "List[TestNode | TestItem]" + + +# Add the path to pythonFiles to sys.path to find testing_tools.socket_manager. +PYTHON_FILES = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.insert(0, PYTHON_FILES) + +# Add the lib path to sys.path to find the typing_extensions module. +sys.path.insert(0, os.path.join(PYTHON_FILES, "lib", "python")) +from testing_tools import socket_manager +from typing_extensions import NotRequired + +DEFAULT_PORT = "45454" + +# session +# test Case + +# modules folders1/folders2 (can be in classes) +# test cases + +# module +# class +# test case + + +def pytest_collection_finish(session): + node, error = build_test_tree(session) + cwd = os.getcwd() + # add error check + sendPost(cwd, node) + + +def build_test_tree(session) -> Tuple[Union[TestNode, None], List[str]]: + errors: List[str] = [] # TODO: how do I check for errors + session_test_node = createSessionTestNode(session) + testNode_file_dict: dict[ + pytest.Module, TestNode + ] = dict() # a dictionary of all files in the session + session_children_dict: dict[ + str, TestNode + ] = dict() # a dictionary of all direct children of the session + testNode_class_dict: dict[ + str, TestNode + ] = dict() # a dictionary of all direct children of the session + # iterate through all the test items in the session + for test_case in session.items: + testNode_test = createTestItem(test_case) + # if the parent object file doesn't already exist + if type(test_case.parent) == pytest.Module: + test_case_parent_node = testNode_file_dict.setdefault( + test_case.parent, createFileTestNode(test_case.parent) + ) + test_case_parent_node["children"].append(testNode_test) + else: + # this means its a unittest class + # create class + test_class_node = testNode_class_dict.setdefault( + test_case.parent.name, + createClassTestNode(test_case.parent), + ) + test_class_node["children"].append(testNode_test) + parent_module = test_case.parent.parent + # create file that wraps class + test_file_node = testNode_file_dict.setdefault( + parent_module, createFileTestNode(parent_module) + ) + if test_class_node not in test_file_node["children"]: + test_file_node["children"].append(test_class_node) + + created_filesfolder_dict: dict[str, TestNode] = {} + for file_module, testNode_file in testNode_file_dict.items(): + name = str(file_module.name) + prev_folder_test_node: TestNode = testNode_file + if "/" in name: + # it is a nested folder structure and so new objects need to be created + nested_folder_list = name.split("/") + path_iterator = ( + str(session.path) + + "/" + + "/".join( + nested_folder_list[0:-1] + ) # check to see if windows style (more fancy stuff path lib if windows or posix via API in os module) + ) + for i in range(len(nested_folder_list) - 2, -1, -1): # reverse and slice + folderName = nested_folder_list[i] + test_folder_node = created_filesfolder_dict.setdefault( + folderName, createFolderTestNode(folderName, path_iterator) + ) + if prev_folder_test_node not in test_folder_node["children"]: + test_folder_node["children"].append(prev_folder_test_node) + # TestNode_test before + # increase iteration through path + prev_folder_test_node = test_folder_node + path_iterator = str(session.path) + "/".join(nested_folder_list[0:i]) + + # the final folder we get to is the highest folder in the path and therefore we add this as a child to the session + if (prev_folder_test_node is not None) and ( + prev_folder_test_node.get("id_") not in session_children_dict + ): + session_children_dict[ + prev_folder_test_node.get("id_") + ] = prev_folder_test_node + session_test_node["children"] = list(session_children_dict.values()) + return session_test_node, errors + + +def createTestItem(test_case) -> TestItem: + return { + "name": test_case.name, + "path": str(test_case.path), + "lineno": test_case.location[1] + 1, + "type_": TestNodeTypeEnum.test, + "id_": str(test_case.nodeid), + "runID": test_case.nodeid, # can I use this two times? + } + + +def createSessionTestNode(session) -> TestNode: + return { + "name": session.name, + "path": str(session.path), + "type_": TestNodeTypeEnum.folder, # check if this is a file or a folder + "children": [], + "id_": str(session.path), + } + + +def createClassTestNode(class_module) -> TestNode: + return { + "name": class_module.name, + "path": str(class_module.path), + "type_": TestNodeTypeEnum.class_, + "children": [], + "id_": str(class_module.nodeid), + } + + +def createFileTestNode(file_module) -> TestNode: + return { + "name": str(file_module.path.name), # check + "path": str(file_module.path), + "type_": TestNodeTypeEnum.file, + "id_": str(file_module.path), + "children": [], + } + + +def createFolderTestNode(folderName, path_iterator) -> TestNode: + return { + "name": folderName, + "path": str(path_iterator), + "type_": TestNodeTypeEnum.folder, # check if this is a file or a folder + "id_": str(path_iterator), + "children": [], + } + + +class PayloadDict(TypedDict): + cwd: str + status: Literal["success", "error"] + tests: NotRequired[TestNode] + errors: NotRequired[List[str]] + + +def sendPost(cwd, tests): + payload: PayloadDict = {"cwd": cwd, "status": "success", "tests": tests} + testPort = os.getenv("TEST_PORT", 45454) + testuuid = os.getenv("TEST_UUID") + addr = ("localhost", int(testPort)) + print("sending post", addr, cwd) + # socket_manager.send_post("Hello from pytest") # type: ignore + with socket_manager.SocketManager(addr) as s: + data = json.dumps(payload) + request = f"""POST / HTTP/1.1 +Host: localhost:{testPort} +Content-Length: {len(data)} +Content-Type: application/json +Request-uuid: {testuuid} +{data}""" + result = s.socket.sendall(request.encode("utf-8")) # type: ignore diff --git a/pythonFiles/vscode_pytest/vscode-pytest.egg-info/entry_points.txt b/pythonFiles/vscode_pytest/vscode-pytest.egg-info/entry_points.txt new file mode 100644 index 000000000000..8aafa6eb93a3 --- /dev/null +++ b/pythonFiles/vscode_pytest/vscode-pytest.egg-info/entry_points.txt @@ -0,0 +1,2 @@ +[pytest11] +vscode-integration = vscode-pytest diff --git a/src/client/testing/testController/common/server.ts b/src/client/testing/testController/common/server.ts index adf5bba1a33c..48c0b81972af 100644 --- a/src/client/testing/testController/common/server.ts +++ b/src/client/testing/testController/common/server.ts @@ -71,6 +71,12 @@ export class PythonTestServer implements ITestServer, Disposable { return (this.server.address() as net.AddressInfo).port; } + 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(); @@ -81,15 +87,13 @@ export class PythonTestServer implements ITestServer, Disposable { } async sendCommand(options: TestCommandOptions): Promise { - 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, diff --git a/src/client/testing/testController/common/types.ts b/src/client/testing/testController/common/types.ts index 064307ca8d9a..b61fad1c9167 100644 --- a/src/client/testing/testController/common/types.ts +++ b/src/client/testing/testController/common/types.ts @@ -12,6 +12,7 @@ import { Uri, WorkspaceFolder, } from 'vscode'; +import { IPythonExecutionFactory } from '../../../common/process/types'; import { TestDiscoveryOptions } from '../../common/types'; export type TestRunInstanceOptions = TestRunOptions & { @@ -151,6 +152,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. * @@ -161,10 +173,12 @@ export interface ITestServer { readonly onDataReceived: Event; sendCommand(options: TestCommandOptions): Promise; serverReady(): Promise; + getPort(): number; + createUUID(cwd: string): string; } export interface ITestDiscoveryAdapter { - discoverTests(uri: Uri): Promise; + discoverTests(uri: Uri, executionFactory: IPythonExecutionFactory): Promise; } // interface for execution/runner adapter diff --git a/src/client/testing/testController/controller.ts b/src/client/testing/testController/controller.ts index fafdd3fafe7e..7add7eae6d80 100644 --- a/src/client/testing/testController/controller.ts +++ b/src/client/testing/testController/controller.ts @@ -38,9 +38,12 @@ import { TestRefreshOptions, ITestExecutionAdapter, } from './common/types'; +// TODO: create pytest and add to import 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. @@ -141,7 +144,6 @@ export class PythonTestController implements ITestController, IExtensionSingleAc }); return this.refreshTestData(undefined, { forceRefresh: true }); }; - this.pythonTestServer = new PythonTestServer(this.pythonExecFactory, this.debugLauncher); } @@ -149,6 +151,7 @@ export class PythonTestController implements ITestController, IExtensionSingleAc traceVerbose('Waiting for test server to start...'); await this.pythonTestServer.serverReady(); traceVerbose('Test server started.'); + console.debug('Test server started'); const workspaces: readonly WorkspaceFolder[] = this.workspaceService.workspaceFolders || []; workspaces.forEach((workspace) => { const settings = this.configSettings.getSettings(workspace.uri); @@ -156,14 +159,19 @@ export class PythonTestController implements ITestController, IExtensionSingleAc let discoveryAdapter: ITestDiscoveryAdapter; let executionAdapter: ITestExecutionAdapter; let testProvider: TestProvider; - if (settings.testing.unittestEnabled) { + if (settings.testing.pytestEnabled) { + console.log('settings.testing.pytestEnabled = true'); + discoveryAdapter = new PytestTestDiscoveryAdapter(this.pythonTestServer, this.configSettings); // what is the ... for + executionAdapter = new PytestTestExecutionAdapter(this.pythonTestServer, this.configSettings); + testProvider = PYTEST_PROVIDER; + } else if (settings.testing.unittestEnabled) { + console.log('settings.testing.unittestEnabled = true'); discoveryAdapter = new UnittestTestDiscoveryAdapter(this.pythonTestServer, this.configSettings); 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 }); + // this would be an error because neither is enabled? + discoveryAdapter = new UnittestTestDiscoveryAdapter(this.pythonTestServer, this.configSettings); executionAdapter = new UnittestTestExecutionAdapter(this.pythonTestServer, this.configSettings); testProvider = PYTEST_PROVIDER; } @@ -227,27 +235,42 @@ export class PythonTestController implements ITestController, IExtensionSingleAc if (settings.testing.pytestEnabled) { traceVerbose(`Testing: Refreshing test data for ${uri.fsPath}`); + // can I move these out of the if statement + 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( + this.testController, + this.refreshCancellation.token, + this.testAdapters.size > 1, + this.workspaceService.workspaceFile?.fsPath, + this.pythonExecFactory, + ); // 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); - await this.pytest.refreshTestData(this.testController, uri, this.refreshCancellation.token); + // 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( - // 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; + 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( + this.testController, + this.refreshCancellation.token, + this.testAdapters.size > 1, + this.workspaceService.workspaceFile?.fsPath, + this.pythonExecFactory, + ); + // 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); + // await this.unittest.refreshTestData(this.testController, uri, this.refreshCancellation.token); } else { if (this.sendTestDisabledTelemetry) { this.sendTestDisabledTelemetry = false; @@ -293,6 +316,7 @@ export class PythonTestController implements ITestController, IExtensionSingleAc const settings = this.configSettings.getSettings(item.uri); if (settings.testing.pytestEnabled) { return this.pytest.resolveChildren(this.testController, item, this.refreshCancellation.token); + // ** check resolve children functionality } if (settings.testing.unittestEnabled) { return this.unittest.resolveChildren(this.testController, item, this.refreshCancellation.token); @@ -311,6 +335,11 @@ export class PythonTestController implements ITestController, IExtensionSingleAc }), ); } + console.log('HERE2'); + this.testController.items.forEach((element) => console.log(element)); + + console.log(this.testController.items); + console.log('size', this.testController.items.size); return Promise.resolve(); } @@ -360,9 +389,11 @@ export class PythonTestController implements ITestController, IExtensionSingleAc if (testItems.length > 0) { if (settings.testing.pytestEnabled) { sendTelemetryEvent(EventName.UNITTEST_RUN, undefined, { + // seems like this telemetry is named incorrectly? tool: 'pytest', debugging: request.profile?.kind === TestRunProfileKind.Debug, }); + // ** update this to reflect the nwe execution style before return this.pytest.runTests( { includes: testItems, @@ -409,6 +440,7 @@ export class PythonTestController implements ITestController, IExtensionSingleAc } if (!settings.testing.pytestEnabled && !settings.testing.unittestEnabled) { + // ** this could be the logic I am looking for unconfiguredWorkspaces.push(workspace); } return Promise.resolve(); @@ -492,6 +524,8 @@ export class PythonTestController implements ITestController, IExtensionSingleAc ); } + // ** not sure about the telemetry + /** * Send UNITTEST_DISCOVERY_TRIGGER telemetry event only once per trigger type. * diff --git a/src/client/testing/testController/pytest/arguments.ts b/src/client/testing/testController/pytest/arguments.ts index 78b451acdd6b..25b2853539e7 100644 --- a/src/client/testing/testController/pytest/arguments.ts +++ b/src/client/testing/testController/pytest/arguments.ts @@ -263,7 +263,7 @@ export function preparePytestArgumentsForDiscovery(options: TestDiscoveryOptions // Remove unwanted arguments (which happen to be test directories & test specific args). const args = pytestFilterArguments(options.args, TestFilter.discovery); if (options.ignoreCache && args.indexOf('--cache-clear') === -1) { - args.splice(0, 0, '--cache-clear'); + args.splice(0, 0, 'bbbbb--cache-clear'); } if (args.indexOf('-s') === -1) { args.splice(0, 0, '-s'); diff --git a/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts b/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts new file mode 100644 index 000000000000..8cdd96def619 --- /dev/null +++ b/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts @@ -0,0 +1,80 @@ +// 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 { 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 | 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: DiscoveredTestPayload = JSON.parse(data); + + this.deferred.resolve(testData); + this.deferred = undefined; + } + } + + public async discoverTests(uri: Uri, executionFactory: IPythonExecutionFactory): Promise { + const settings = this.configSettings.getSettings(uri); + const { pytestArgs } = settings.testing; + console.debug(pytestArgs); // do we use pytestArgs anywhere? + + this.cwd = uri.fsPath; + return this.runPytestDiscovery(uri, executionFactory); + } + + async runPytestDiscovery(uri: Uri, executionFactory: IPythonExecutionFactory): Promise { + if (!this.deferred) { + this.deferred = createDeferred(); + 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; + } +} diff --git a/src/client/testing/testController/pytest/pytestExecutionAdapter.ts b/src/client/testing/testController/pytest/pytestExecutionAdapter.ts new file mode 100644 index 000000000000..35d62c50e774 --- /dev/null +++ b/src/client/testing/testController/pytest/pytestExecutionAdapter.ts @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as path from 'path'; +import { Uri } from 'vscode'; +import { IConfigurationService } from '../../../common/types'; +import { createDeferred, Deferred } from '../../../common/utils/async'; +import { EXTENSION_ROOT_DIR } from '../../../constants'; +import { + DataReceivedEvent, + ExecutionTestPayload, + ITestExecutionAdapter, + ITestServer, + TestCommandOptions, + TestExecutionCommand, +} from '../common/types'; + +/** + * Wrapper Class for unittest test execution. This is where we call `runTestCommand`? + */ + +export class PytestTestExecutionAdapter implements ITestExecutionAdapter { + private deferred: Deferred | 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 { + if (!this.deferred) { + const settings = this.configSettings.getSettings(uri); + const { unittestArgs } = settings.testing; + + const command = buildExecutionCommand(unittestArgs); + this.cwd = uri.fsPath; + + const options: TestCommandOptions = { + workspaceFolder: uri, + command, + cwd: this.cwd, + debugBool, + testIds, + }; + + this.deferred = createDeferred(); + + // 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 = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'unittestadapter', 'execution.py'); + + return { + script: executionScript, + args: ['--udiscovery', ...args], + }; +} diff --git a/src/client/testing/testController/workspaceTestAdapter.ts b/src/client/testing/testController/workspaceTestAdapter.ts index 0ecab7649745..e83e2f6579e3 100644 --- a/src/client/testing/testController/workspaceTestAdapter.ts +++ b/src/client/testing/testController/workspaceTestAdapter.ts @@ -14,6 +14,7 @@ import { Uri, Location, } from 'vscode'; +import { IPythonExecutionFactory } from '../../common/process/types'; import { createDeferred, Deferred } from '../../common/utils/async'; import { Testing } from '../../common/utils/localize'; import { traceError } from '../../logging'; @@ -201,6 +202,7 @@ export class WorkspaceTestAdapter { token?: CancellationToken, isMultiroot?: boolean, workspaceFilePath?: string, + executionFactory?: IPythonExecutionFactory, ): Promise { sendTelemetryEvent(EventName.UNITTEST_DISCOVERING, undefined, { tool: this.testProvider }); @@ -216,7 +218,13 @@ export class WorkspaceTestAdapter { let rawTestData; try { - rawTestData = await this.discoveryAdapter.discoverTests(this.workspaceUri); + if (executionFactory !== undefined) { + rawTestData = await this.discoveryAdapter.discoverTests(this.workspaceUri, executionFactory); + console.debug('here'); + console.debug('rawTestData: ', rawTestData); + } else { + console.log('executionFactory is undefined'); + } deferred.resolve(); } catch (ex) { @@ -339,6 +347,10 @@ function populateTestTree( } // Recursively populate the tree with test data. + for (let i = 0; i < testTreeData.children.length; i = i + 1) { + // console.debug('testTreeData.children i= ', i, '', testTreeData.children[i]); + } + testTreeData.children.forEach((child) => { if (!token?.isCancellationRequested) { if (isTestItem(child)) { @@ -352,6 +364,7 @@ function populateTestTree( testItem.canResolveChildren = false; testItem.range = range; testItem.tags = [RunTestTag, DebugTestTag]; + console.debug('adding test item: ', testItem?.id, 'to root item: ', testRoot?.id); testRoot!.children.add(testItem); // add to our map wstAdapter.runIdToTestItem.set(child.runID, testItem); @@ -359,14 +372,18 @@ function populateTestTree( wstAdapter.vsIdToRunId.set(child.id_, child.runID); } else { let node = testController.items.get(child.path); - + console.debug('AA node = ', node?.id, ' child.path = ', child.path); if (!node) { node = testController.createTestItem(child.id_, child.name, Uri.file(child.path)); - + console.debug('!node BB node', node.id, 'child.path', child.name); node.canResolveChildren = true; node.tags = [RunTestTag, DebugTestTag]; testRoot!.children.add(node); + console.debug('test root ', testRoot?.id, testRoot?.children.size); + testRoot?.children.forEach((child1) => { + console.debug('testRoot.children = ', child1.id); + }); } populateTestTree(testController, child, node, wstAdapter, token); } From 2b3c34331f20117f4ac685c451fd2ae72206df52 Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Tue, 24 Jan 2023 15:16:50 -0800 Subject: [PATCH 02/21] path updates --- pythonFiles/vscode_pytest/__init__.py | 185 ++++++++++++-------------- 1 file changed, 87 insertions(+), 98 deletions(-) diff --git a/pythonFiles/vscode_pytest/__init__.py b/pythonFiles/vscode_pytest/__init__.py index 4341c17c68ae..842f54485a79 100644 --- a/pythonFiles/vscode_pytest/__init__.py +++ b/pythonFiles/vscode_pytest/__init__.py @@ -1,19 +1,21 @@ # -*- coding: utf-8 -*- - -# this file taken from 71636e91930c9905604577db7e1e9a1cffa05a6e -# multi class actually working on Nov 9th - import enum import json import os import pathlib import sys -from dbm.ndbm import library -from typing import KeysView, List, Literal, Optional, Tuple, TypedDict, Union -from unittest import TestCase +import traceback +from typing import List, Literal, Tuple, TypedDict, Union import pytest +script_dir = pathlib.Path(__file__).parent.parent +sys.path.append(os.fspath(script_dir)) +sys.path.append(os.fspath(script_dir / "lib" / "python")) + +import debugpy + +debugpy.breakpoint() # Inherit from str so it's JSON serializable. class TestNodeTypeEnum(str, enum.Enum): @@ -40,142 +42,121 @@ class TestNode(TestData): # Add the path to pythonFiles to sys.path to find testing_tools.socket_manager. -PYTHON_FILES = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -sys.path.insert(0, PYTHON_FILES) +# PYTHON_FILES = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +# sys.path.insert(0, PYTHON_FILES) -# Add the lib path to sys.path to find the typing_extensions module. -sys.path.insert(0, os.path.join(PYTHON_FILES, "lib", "python")) +# # Add the lib path to sys.path to find the typing_extensions module. +# sys.path.insert(0, os.path.join(PYTHON_FILES, "lib", "python")) from testing_tools import socket_manager from typing_extensions import NotRequired DEFAULT_PORT = "45454" -# session -# test Case - -# modules folders1/folders2 (can be in classes) -# test cases - -# module -# class -# test case - def pytest_collection_finish(session): + print("hello") node, error = build_test_tree(session) cwd = os.getcwd() - # add error check + # TODO: add error checking. sendPost(cwd, node) def build_test_tree(session) -> Tuple[Union[TestNode, None], List[str]]: - errors: List[str] = [] # TODO: how do I check for errors - session_test_node = createSessionTestNode(session) - testNode_file_dict: dict[ - pytest.Module, TestNode - ] = dict() # a dictionary of all files in the session - session_children_dict: dict[ - str, TestNode - ] = dict() # a dictionary of all direct children of the session - testNode_class_dict: dict[ - str, TestNode - ] = dict() # a dictionary of all direct children of the session - # iterate through all the test items in the session + errors: List[str] = [] + session_node = create_session_node(session) + # a dictionary of all direct children of the session. + session_children_dict: dict[str, TestNode] = dict() + # a dictionary of all files in the session. + file_nodes_dict: dict[pytest.Module, TestNode] = dict() + # a dictionary of all classes in the session. + class_nodes_dict: dict[str, TestNode] = dict() + # iterate through all the test items in the session. for test_case in session.items: - testNode_test = createTestItem(test_case) - # if the parent object file doesn't already exist + test_node = create_test_node(test_case) + # Check parent node type, either Module or UnitTest class. if type(test_case.parent) == pytest.Module: - test_case_parent_node = testNode_file_dict.setdefault( - test_case.parent, createFileTestNode(test_case.parent) - ) - test_case_parent_node["children"].append(testNode_test) + file_nodes_dict.setdefault( + test_case.parent, create_file_node(test_case.parent) + )["children"].append(test_node) else: - # this means its a unittest class - # create class - test_class_node = testNode_class_dict.setdefault( + test_class_node = class_nodes_dict.setdefault( test_case.parent.name, - createClassTestNode(test_case.parent), + create_class_node(test_case.parent), ) - test_class_node["children"].append(testNode_test) + test_class_node["children"].append(test_node) parent_module = test_case.parent.parent - # create file that wraps class - test_file_node = testNode_file_dict.setdefault( - parent_module, createFileTestNode(parent_module) + # Create a file node that has the class as a child. + test_file_node = file_nodes_dict.setdefault( + parent_module, create_file_node(parent_module) ) + # Check if the class is already a child of the file node. if test_class_node not in test_file_node["children"]: test_file_node["children"].append(test_class_node) - created_filesfolder_dict: dict[str, TestNode] = {} - for file_module, testNode_file in testNode_file_dict.items(): - name = str(file_module.name) - prev_folder_test_node: TestNode = testNode_file - if "/" in name: - # it is a nested folder structure and so new objects need to be created - nested_folder_list = name.split("/") - path_iterator = ( - str(session.path) - + "/" - + "/".join( - nested_folder_list[0:-1] - ) # check to see if windows style (more fancy stuff path lib if windows or posix via API in os module) - ) - for i in range(len(nested_folder_list) - 2, -1, -1): # reverse and slice - folderName = nested_folder_list[i] - test_folder_node = created_filesfolder_dict.setdefault( - folderName, createFolderTestNode(folderName, path_iterator) - ) - if prev_folder_test_node not in test_folder_node["children"]: - test_folder_node["children"].append(prev_folder_test_node) - # TestNode_test before - # increase iteration through path - prev_folder_test_node = test_folder_node - path_iterator = str(session.path) + "/".join(nested_folder_list[0:i]) - - # the final folder we get to is the highest folder in the path and therefore we add this as a child to the session - if (prev_folder_test_node is not None) and ( - prev_folder_test_node.get("id_") not in session_children_dict - ): - session_children_dict[ - prev_folder_test_node.get("id_") - ] = prev_folder_test_node - session_test_node["children"] = list(session_children_dict.values()) - return session_test_node, errors - - -def createTestItem(test_case) -> TestItem: + created_files_folders_dict: dict[str, TestNode] = {} + for file_module, file_node in file_nodes_dict.items(): + root_folder_node = build_nested_folders( + file_module, file_node, created_files_folders_dict, session + ) + # the final folder we get to is the highest folder in the path and therefore we add this as a child to the session. + if root_folder_node.get("id_") not in session_children_dict: + session_children_dict[root_folder_node.get("id_")] = root_folder_node + session_node["children"] = list(session_children_dict.values()) + return session_node, errors + + +def build_nested_folders( + file_module, file_node, created_files_folders_dict, session +) -> TestNode: + prev_folder_node: TestNode = file_node + # Begin the i_path iteration one level above the current file. + iterator_path = file_module.path.parent + while iterator_path != session.path: + curr_folder_name = iterator_path.name + curr_folder_node = created_files_folders_dict.setdefault( + curr_folder_name, create_folder_node(curr_folder_name, iterator_path) + ) + if prev_folder_node not in curr_folder_node["children"]: + curr_folder_node["children"].append(prev_folder_node) + iterator_path = iterator_path.parent + prev_folder_node = curr_folder_node + return prev_folder_node + + +def create_test_node(test_case) -> TestItem: return { "name": test_case.name, "path": str(test_case.path), "lineno": test_case.location[1] + 1, "type_": TestNodeTypeEnum.test, - "id_": str(test_case.nodeid), - "runID": test_case.nodeid, # can I use this two times? + "id_": test_case.nodeid, # remove cast + "runID": test_case.nodeid, } -def createSessionTestNode(session) -> TestNode: +def create_session_node(session) -> TestNode: return { "name": session.name, "path": str(session.path), - "type_": TestNodeTypeEnum.folder, # check if this is a file or a folder + "type_": TestNodeTypeEnum.folder, "children": [], "id_": str(session.path), } -def createClassTestNode(class_module) -> TestNode: +def create_class_node(class_module) -> TestNode: return { "name": class_module.name, "path": str(class_module.path), "type_": TestNodeTypeEnum.class_, "children": [], - "id_": str(class_module.nodeid), + "id_": class_module.nodeid, } -def createFileTestNode(file_module) -> TestNode: +def create_file_node(file_module) -> TestNode: return { - "name": str(file_module.path.name), # check + "name": str(file_module.path.name), "path": str(file_module.path), "type_": TestNodeTypeEnum.file, "id_": str(file_module.path), @@ -183,11 +164,11 @@ def createFileTestNode(file_module) -> TestNode: } -def createFolderTestNode(folderName, path_iterator) -> TestNode: +def create_folder_node(folderName, path_iterator) -> TestNode: return { "name": folderName, "path": str(path_iterator), - "type_": TestNodeTypeEnum.folder, # check if this is a file or a folder + "type_": TestNodeTypeEnum.folder, "id_": str(path_iterator), "children": [], } @@ -206,7 +187,6 @@ def sendPost(cwd, tests): testuuid = os.getenv("TEST_UUID") addr = ("localhost", int(testPort)) print("sending post", addr, cwd) - # socket_manager.send_post("Hello from pytest") # type: ignore with socket_manager.SocketManager(addr) as s: data = json.dumps(payload) request = f"""POST / HTTP/1.1 @@ -214,5 +194,14 @@ def sendPost(cwd, tests): Content-Length: {len(data)} Content-Type: application/json Request-uuid: {testuuid} + {data}""" - result = s.socket.sendall(request.encode("utf-8")) # type: ignore + with open( + "/Users/eleanorboyd/vscode-python/pythonFiles/vscode_pytest/test_logs.log", + "w", + ) as f: + f.write(request) + try: + s.socket.sendall(request.encode("utf-8")) # type: ignore + except Exception as ex: + f.write(traceback.format_exc()) From 4138b0978d3c8a23ce89d0c37b4f012f176f3dc5 Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Tue, 24 Jan 2023 15:21:38 -0800 Subject: [PATCH 03/21] functioning with egg file --- .../pytest/pytestDiscoveryAdapter.ts | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts b/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts index 8cdd96def619..df27adbcf13b 100644 --- a/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts +++ b/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts @@ -45,12 +45,15 @@ export class PytestTestDiscoveryAdapter implements ITestDiscoveryAdapter { async runPytestDiscovery(uri: Uri, executionFactory: IPythonExecutionFactory): Promise { if (!this.deferred) { this.deferred = createDeferred(); - const relativePathToPytest = 'pythonFiles/pytest-vscode-integration'; + const relativePathToPytest = 'pythonFiles'; 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 pythonPathParts: string[] = process.env.PYTHONPATH?.split(path.delimiter) ?? []; + const pythonPathCommand = [fullPluginPath, ...pythonPathParts].join(path.delimiter); + console.log('port', this.testServer.getPort().toString(), uuid.toString()); const spawnOptions: SpawnOptions = { cwd: uri.fsPath, @@ -70,7 +73,18 @@ export class PytestTestDiscoveryAdapter implements ITestDiscoveryAdapter { const execService = await executionFactory.createActivatedEnvironment(creationOptions); try { - execService.exec(['-m', 'pytest', '--collect-only'].concat(pytestArgs), spawnOptions); + // const p = await execService.exec(['os.getenv("TEST_PORT",5555)'], spawnOptions); + // console.log(await execService.exec(['echo', '$PYTHONPATH'], spawnOptions));\--trace-config + // const p = await execService.exec( + // ['-m', 'pytest', '-p', 'vscode_pytest', '--trace-config'].concat(pytestArgs), + // spawnOptions, + // ); + execService.exec( + ['-m', 'pytest', '-p', 'vscode_pytest', '--collect-only'].concat(pytestArgs), + spawnOptions, + ); + // console.log(p.stdout); + console.log('finish'); } catch (ex) { console.error(ex); } From b943414c1bc4b30a0b09049ffb47747d8953df45 Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Tue, 24 Jan 2023 15:24:35 -0800 Subject: [PATCH 04/21] remove entry point --- .../vscode_pytest/vscode-pytest.egg-info/entry_points.txt | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 pythonFiles/vscode_pytest/vscode-pytest.egg-info/entry_points.txt diff --git a/pythonFiles/vscode_pytest/vscode-pytest.egg-info/entry_points.txt b/pythonFiles/vscode_pytest/vscode-pytest.egg-info/entry_points.txt deleted file mode 100644 index 8aafa6eb93a3..000000000000 --- a/pythonFiles/vscode_pytest/vscode-pytest.egg-info/entry_points.txt +++ /dev/null @@ -1,2 +0,0 @@ -[pytest11] -vscode-integration = vscode-pytest From cb2a812ffaad5ca6b97bfb070eec47df995f2a1f Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Tue, 24 Jan 2023 15:31:49 -0800 Subject: [PATCH 05/21] remove logging from testing --- pythonFiles/vscode_pytest/__init__.py | 10 +--------- .../testController/pytest/pytestDiscoveryAdapter.ts | 8 -------- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/pythonFiles/vscode_pytest/__init__.py b/pythonFiles/vscode_pytest/__init__.py index 842f54485a79..5e57ac850578 100644 --- a/pythonFiles/vscode_pytest/__init__.py +++ b/pythonFiles/vscode_pytest/__init__.py @@ -196,12 +196,4 @@ def sendPost(cwd, tests): Request-uuid: {testuuid} {data}""" - with open( - "/Users/eleanorboyd/vscode-python/pythonFiles/vscode_pytest/test_logs.log", - "w", - ) as f: - f.write(request) - try: - s.socket.sendall(request.encode("utf-8")) # type: ignore - except Exception as ex: - f.write(traceback.format_exc()) + result = s.socket.sendall(request.encode("utf-8")) # type: ignore diff --git a/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts b/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts index df27adbcf13b..48718fbed7a1 100644 --- a/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts +++ b/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts @@ -73,18 +73,10 @@ export class PytestTestDiscoveryAdapter implements ITestDiscoveryAdapter { const execService = await executionFactory.createActivatedEnvironment(creationOptions); try { - // const p = await execService.exec(['os.getenv("TEST_PORT",5555)'], spawnOptions); - // console.log(await execService.exec(['echo', '$PYTHONPATH'], spawnOptions));\--trace-config - // const p = await execService.exec( - // ['-m', 'pytest', '-p', 'vscode_pytest', '--trace-config'].concat(pytestArgs), - // spawnOptions, - // ); execService.exec( ['-m', 'pytest', '-p', 'vscode_pytest', '--collect-only'].concat(pytestArgs), spawnOptions, ); - // console.log(p.stdout); - console.log('finish'); } catch (ex) { console.error(ex); } From e73d1e83ff5df451262c28334087aa4688317df8 Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Tue, 24 Jan 2023 15:41:07 -0800 Subject: [PATCH 06/21] step 1 --- pythonFiles/vscode_pytest/__init__.py | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/pythonFiles/vscode_pytest/__init__.py b/pythonFiles/vscode_pytest/__init__.py index 5e57ac850578..399423642f4c 100644 --- a/pythonFiles/vscode_pytest/__init__.py +++ b/pythonFiles/vscode_pytest/__init__.py @@ -4,7 +4,6 @@ import os import pathlib import sys -import traceback from typing import List, Literal, Tuple, TypedDict, Union import pytest @@ -13,10 +12,6 @@ sys.path.append(os.fspath(script_dir)) sys.path.append(os.fspath(script_dir / "lib" / "python")) -import debugpy - -debugpy.breakpoint() - # Inherit from str so it's JSON serializable. class TestNodeTypeEnum(str, enum.Enum): class_ = "class" @@ -38,15 +33,9 @@ class TestItem(TestData): class TestNode(TestData): - children: "List[TestNode | TestItem]" - + children: "List[Union[TestNode, TestItem]]" -# Add the path to pythonFiles to sys.path to find testing_tools.socket_manager. -# PYTHON_FILES = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -# sys.path.insert(0, PYTHON_FILES) -# # Add the lib path to sys.path to find the typing_extensions module. -# sys.path.insert(0, os.path.join(PYTHON_FILES, "lib", "python")) from testing_tools import socket_manager from typing_extensions import NotRequired @@ -54,8 +43,8 @@ class TestNode(TestData): def pytest_collection_finish(session): - print("hello") - node, error = build_test_tree(session) + # Called after collection has been performed. + node: Union[TestNode, None] = build_test_tree(session)[0] cwd = os.getcwd() # TODO: add error checking. sendPost(cwd, node) From ac1185bc9d2518bc18983eb9dfb0e9148139be18 Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Tue, 24 Jan 2023 15:47:16 -0800 Subject: [PATCH 07/21] edits 2 --- pythonFiles/vscode_pytest/__init__.py | 75 ++++++++++++++++----------- 1 file changed, 44 insertions(+), 31 deletions(-) diff --git a/pythonFiles/vscode_pytest/__init__.py b/pythonFiles/vscode_pytest/__init__.py index 399423642f4c..09c23f2c0395 100644 --- a/pythonFiles/vscode_pytest/__init__.py +++ b/pythonFiles/vscode_pytest/__init__.py @@ -45,46 +45,52 @@ class TestNode(TestData): def pytest_collection_finish(session): # Called after collection has been performed. node: Union[TestNode, None] = build_test_tree(session)[0] - cwd = os.getcwd() + cwd = pathlib.Path.cwd() + if node: + sendPost(str(cwd), node) # TODO: add error checking. - sendPost(cwd, node) def build_test_tree(session) -> Tuple[Union[TestNode, None], List[str]]: + # Builds a tree of tests from the pytest session. errors: List[str] = [] - session_node = create_session_node(session) - # a dictionary of all direct children of the session. - session_children_dict: dict[str, TestNode] = dict() - # a dictionary of all files in the session. - file_nodes_dict: dict[pytest.Module, TestNode] = dict() - # a dictionary of all classes in the session. - class_nodes_dict: dict[str, TestNode] = dict() - # iterate through all the test items in the session. + session_node: TestNode = create_session_node(session) + session_children_dict: dict[str, TestNode] = {} + file_nodes_dict: dict[pytest.Module, TestNode] = {} + class_nodes_dict: dict[str, TestNode] = {} + for test_case in session.items: - test_node = create_test_node(test_case) + test_node: TestItem = create_test_node(test_case) # Check parent node type, either Module or UnitTest class. - if type(test_case.parent) == pytest.Module: - file_nodes_dict.setdefault( - test_case.parent, create_file_node(test_case.parent) - )["children"].append(test_node) - else: - test_class_node = class_nodes_dict.setdefault( - test_case.parent.name, - create_class_node(test_case.parent), - ) + if type(test_case.parent) is pytest.Module: + try: + parent_test_case: TestNode = file_nodes_dict[test_case.parent] + except KeyError: + parent_test_case: TestNode = create_file_node(test_case.parent) + file_nodes_dict[test_case.parent] = parent_test_case + parent_test_case["children"].append(test_node) + else: # should be a pytest.Class + try: + test_class_node: TestNode = class_nodes_dict[test_case.parent.name] + except KeyError: + test_class_node: TestNode = create_class_node(test_case.parent) + class_nodes_dict[test_case.parent.name] = test_class_node test_class_node["children"].append(test_node) - parent_module = test_case.parent.parent + parent_module: pytest.Module = test_case.parent.parent # Create a file node that has the class as a child. - test_file_node = file_nodes_dict.setdefault( - parent_module, create_file_node(parent_module) - ) + try: + test_file_node: TestNode = file_nodes_dict[parent_module] + except KeyError: + test_file_node: TestNode = create_file_node(parent_module) + file_nodes_dict[parent_module] = test_file_node + test_file_node["children"].append(test_node) # Check if the class is already a child of the file node. if test_class_node not in test_file_node["children"]: test_file_node["children"].append(test_class_node) created_files_folders_dict: dict[str, TestNode] = {} for file_module, file_node in file_nodes_dict.items(): - root_folder_node = build_nested_folders( + root_folder_node: TestNode = build_nested_folders( file_module, file_node, created_files_folders_dict, session ) # the final folder we get to is the highest folder in the path and therefore we add this as a child to the session. @@ -95,16 +101,23 @@ def build_test_tree(session) -> Tuple[Union[TestNode, None], List[str]]: def build_nested_folders( - file_module, file_node, created_files_folders_dict, session + file_module: pytest.Module, + file_node: TestNode, + created_files_folders_dict: dict[str, TestNode], + session: pytest.Session, ) -> TestNode: prev_folder_node: TestNode = file_node # Begin the i_path iteration one level above the current file. - iterator_path = file_module.path.parent + iterator_path: pathlib.Path = file_module.path.parent while iterator_path != session.path: - curr_folder_name = iterator_path.name - curr_folder_node = created_files_folders_dict.setdefault( - curr_folder_name, create_folder_node(curr_folder_name, iterator_path) - ) + curr_folder_name: str = iterator_path.name + try: + curr_folder_node: TestNode = created_files_folders_dict[curr_folder_name] + except KeyError: + curr_folder_node: TestNode = create_folder_node( + curr_folder_name, iterator_path + ) + created_files_folders_dict[curr_folder_name] = curr_folder_node if prev_folder_node not in curr_folder_node["children"]: curr_folder_node["children"].append(prev_folder_node) iterator_path = iterator_path.parent From 0c6daa3518e194d9bae4b382d53e697cd16cd8ba Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Tue, 24 Jan 2023 15:56:23 -0800 Subject: [PATCH 08/21] finish round 1 brett comments --- pythonFiles/vscode_pytest/__init__.py | 36 ++++++++++++++++----------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/pythonFiles/vscode_pytest/__init__.py b/pythonFiles/vscode_pytest/__init__.py index 09c23f2c0395..c3fb4620afe4 100644 --- a/pythonFiles/vscode_pytest/__init__.py +++ b/pythonFiles/vscode_pytest/__init__.py @@ -125,18 +125,23 @@ def build_nested_folders( return prev_folder_node -def create_test_node(test_case) -> TestItem: +def create_test_node( + test_case: pytest.Item, +) -> TestItem: # stickynote what is this type + test_case_loc: str = ( + "" if test_case.location[1] is None else str(test_case.location[1] + 1) + ) return { "name": test_case.name, "path": str(test_case.path), - "lineno": test_case.location[1] + 1, + "lineno": test_case_loc, "type_": TestNodeTypeEnum.test, "id_": test_case.nodeid, # remove cast "runID": test_case.nodeid, } -def create_session_node(session) -> TestNode: +def create_session_node(session: pytest.Session) -> TestNode: return { "name": session.name, "path": str(session.path), @@ -146,7 +151,7 @@ def create_session_node(session) -> TestNode: } -def create_class_node(class_module) -> TestNode: +def create_class_node(class_module: pytest.Class) -> TestNode: return { "name": class_module.name, "path": str(class_module.path), @@ -156,7 +161,7 @@ def create_class_node(class_module) -> TestNode: } -def create_file_node(file_module) -> TestNode: +def create_file_node(file_module: pytest.Module) -> TestNode: return { "name": str(file_module.path.name), "path": str(file_module.path), @@ -166,7 +171,7 @@ def create_file_node(file_module) -> TestNode: } -def create_folder_node(folderName, path_iterator) -> TestNode: +def create_folder_node(folderName: str, path_iterator: pathlib.Path) -> TestNode: return { "name": folderName, "path": str(path_iterator), @@ -183,19 +188,20 @@ class PayloadDict(TypedDict): errors: NotRequired[List[str]] -def sendPost(cwd, tests): +def sendPost(cwd: str, tests: TestNode) -> None: + # Sends a post request as a response to the server. payload: PayloadDict = {"cwd": cwd, "status": "success", "tests": tests} - testPort = os.getenv("TEST_PORT", 45454) - testuuid = os.getenv("TEST_UUID") - addr = ("localhost", int(testPort)) - print("sending post", addr, cwd) - with socket_manager.SocketManager(addr) as s: - data = json.dumps(payload) - request = f"""POST / HTTP/1.1 + testPort: Union[str, int] = os.getenv("TEST_PORT", 45454) + testuuid: Union[str, None] = os.getenv("TEST_UUID") + addr = "localhost", int(testPort) + data = json.dumps(payload) + request = f"""POST / HTTP/1.1 Host: localhost:{testPort} Content-Length: {len(data)} Content-Type: application/json Request-uuid: {testuuid} {data}""" - result = s.socket.sendall(request.encode("utf-8")) # type: ignore + with socket_manager.SocketManager(addr) as s: + if s.socket is not None: + s.socket.sendall(request.encode("utf-8")) # type: ignore From 81c61f36e59add97e83989c6f2d51ec9d44083ec Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Tue, 24 Jan 2023 17:20:56 -0800 Subject: [PATCH 09/21] fix commenting msgs --- .../testing/testController/controller.ts | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/client/testing/testController/controller.ts b/src/client/testing/testController/controller.ts index 7add7eae6d80..13f20105b74b 100644 --- a/src/client/testing/testController/controller.ts +++ b/src/client/testing/testController/controller.ts @@ -232,10 +232,11 @@ 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}`); if (settings.testing.pytestEnabled) { - traceVerbose(`Testing: Refreshing test data for ${uri.fsPath}`); - - // can I move these out of the if statement + // Ensure we send test telemetry if it gets disabled again + this.sendTestDisabledTelemetry = true; + // uncomment 240 - 250 to NEW new test discovery mechanism const workspace = this.workspaceService.getWorkspaceFolder(uri); console.warn(`Discover tests for workspace name: ${workspace?.name} - uri: ${uri.fsPath}`); const testAdapter = @@ -247,15 +248,12 @@ export class PythonTestController implements ITestController, IExtensionSingleAc this.workspaceService.workspaceFile?.fsPath, this.pythonExecFactory, ); - // 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 + // uncomment 252 to use OLD test discovery mechanism // await this.unittest.refreshTestData(this.testController, uri, this.refreshCancellation.token); - - // 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}`); + // Ensure we send test telemetry if it gets disabled again + this.sendTestDisabledTelemetry = true; + // uncomment 257 - 267 to NEW new test discovery mechanism const workspace = this.workspaceService.getWorkspaceFolder(uri); console.warn(`Discover tests for workspace name: ${workspace?.name} - uri: ${uri.fsPath}`); const testAdapter = @@ -267,9 +265,7 @@ export class PythonTestController implements ITestController, IExtensionSingleAc this.workspaceService.workspaceFile?.fsPath, this.pythonExecFactory, ); - // 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 + // uncomment 269 to use OLD test discovery mechanism // await this.unittest.refreshTestData(this.testController, uri, this.refreshCancellation.token); } else { if (this.sendTestDisabledTelemetry) { From dbfa5e7ef563db10ee52ea3ef287af871a222eb0 Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Tue, 24 Jan 2023 17:25:48 -0800 Subject: [PATCH 10/21] hide new logic --- .../testing/testController/controller.ts | 48 +++++++++---------- .../testController/pytest/arguments.ts | 2 +- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/client/testing/testController/controller.ts b/src/client/testing/testController/controller.ts index 13f20105b74b..1eab02ee5671 100644 --- a/src/client/testing/testController/controller.ts +++ b/src/client/testing/testController/controller.ts @@ -237,36 +237,36 @@ export class PythonTestController implements ITestController, IExtensionSingleAc // Ensure we send test telemetry if it gets disabled again this.sendTestDisabledTelemetry = true; // uncomment 240 - 250 to NEW new test discovery mechanism - 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( - this.testController, - this.refreshCancellation.token, - this.testAdapters.size > 1, - this.workspaceService.workspaceFile?.fsPath, - this.pythonExecFactory, - ); + // 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( + // this.testController, + // this.refreshCancellation.token, + // this.testAdapters.size > 1, + // this.workspaceService.workspaceFile?.fsPath, + // this.pythonExecFactory, + // ); // uncomment 252 to use OLD test discovery mechanism - // await this.unittest.refreshTestData(this.testController, uri, this.refreshCancellation.token); + await this.pytest.refreshTestData(this.testController, uri, this.refreshCancellation.token); } else if (settings.testing.unittestEnabled) { // Ensure we send test telemetry if it gets disabled again this.sendTestDisabledTelemetry = true; // uncomment 257 - 267 to NEW new test discovery mechanism - 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( - this.testController, - this.refreshCancellation.token, - this.testAdapters.size > 1, - this.workspaceService.workspaceFile?.fsPath, - this.pythonExecFactory, - ); + // 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( + // this.testController, + // this.refreshCancellation.token, + // this.testAdapters.size > 1, + // this.workspaceService.workspaceFile?.fsPath, + // this.pythonExecFactory, + // ); // uncomment 269 to use OLD test discovery mechanism - // await this.unittest.refreshTestData(this.testController, uri, this.refreshCancellation.token); + await this.unittest.refreshTestData(this.testController, uri, this.refreshCancellation.token); } else { if (this.sendTestDisabledTelemetry) { this.sendTestDisabledTelemetry = false; diff --git a/src/client/testing/testController/pytest/arguments.ts b/src/client/testing/testController/pytest/arguments.ts index 25b2853539e7..78b451acdd6b 100644 --- a/src/client/testing/testController/pytest/arguments.ts +++ b/src/client/testing/testController/pytest/arguments.ts @@ -263,7 +263,7 @@ export function preparePytestArgumentsForDiscovery(options: TestDiscoveryOptions // Remove unwanted arguments (which happen to be test directories & test specific args). const args = pytestFilterArguments(options.args, TestFilter.discovery); if (options.ignoreCache && args.indexOf('--cache-clear') === -1) { - args.splice(0, 0, 'bbbbb--cache-clear'); + args.splice(0, 0, '--cache-clear'); } if (args.indexOf('-s') === -1) { args.splice(0, 0, '-s'); From 90f8554af2676efad48f41c8b76159c16544659d Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Tue, 24 Jan 2023 18:08:04 -0800 Subject: [PATCH 11/21] remove log --- src/client/testing/testController/controller.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/client/testing/testController/controller.ts b/src/client/testing/testController/controller.ts index 1eab02ee5671..d633af100f0f 100644 --- a/src/client/testing/testController/controller.ts +++ b/src/client/testing/testController/controller.ts @@ -151,7 +151,6 @@ export class PythonTestController implements ITestController, IExtensionSingleAc traceVerbose('Waiting for test server to start...'); await this.pythonTestServer.serverReady(); traceVerbose('Test server started.'); - console.debug('Test server started'); const workspaces: readonly WorkspaceFolder[] = this.workspaceService.workspaceFolders || []; workspaces.forEach((workspace) => { const settings = this.configSettings.getSettings(workspace.uri); From 9348dcd17b75bac510fa5fe4e847ec75568bc8da Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Tue, 24 Jan 2023 18:13:30 -0800 Subject: [PATCH 12/21] revert to old execution provider line --- src/client/testing/testController/controller.ts | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/client/testing/testController/controller.ts b/src/client/testing/testController/controller.ts index d633af100f0f..656de0604208 100644 --- a/src/client/testing/testController/controller.ts +++ b/src/client/testing/testController/controller.ts @@ -158,19 +158,14 @@ export class PythonTestController implements ITestController, IExtensionSingleAc let discoveryAdapter: ITestDiscoveryAdapter; let executionAdapter: ITestExecutionAdapter; let testProvider: TestProvider; - if (settings.testing.pytestEnabled) { - console.log('settings.testing.pytestEnabled = true'); - discoveryAdapter = new PytestTestDiscoveryAdapter(this.pythonTestServer, this.configSettings); // what is the ... for - executionAdapter = new PytestTestExecutionAdapter(this.pythonTestServer, this.configSettings); - testProvider = PYTEST_PROVIDER; - } else if (settings.testing.unittestEnabled) { - console.log('settings.testing.unittestEnabled = true'); + if (settings.testing.unittestEnabled) { discoveryAdapter = new UnittestTestDiscoveryAdapter(this.pythonTestServer, this.configSettings); executionAdapter = new UnittestTestExecutionAdapter(this.pythonTestServer, this.configSettings); testProvider = UNITTEST_PROVIDER; } else { - // this would be an error because neither is enabled? - discoveryAdapter = new UnittestTestDiscoveryAdapter(this.pythonTestServer, this.configSettings); + // TODO: PYTEST DISCOVERY ADAPTER + // this is a placeholder for now + discoveryAdapter = new UnittestTestDiscoveryAdapter(this.pythonTestServer, { ...this.configSettings }); executionAdapter = new UnittestTestExecutionAdapter(this.pythonTestServer, this.configSettings); testProvider = PYTEST_PROVIDER; } From 216bba50321bdc1f03a4738060546273ac3b7fed Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Tue, 24 Jan 2023 18:24:49 -0800 Subject: [PATCH 13/21] disabling the new method --- .../testing/testController/common/types.ts | 5 ++-- .../testing/testController/controller.ts | 14 ++--------- .../pytest/pytestDiscoveryAdapter.ts | 16 ++++++------- .../testController/workspaceTestAdapter.ts | 24 ++++--------------- 4 files changed, 17 insertions(+), 42 deletions(-) diff --git a/src/client/testing/testController/common/types.ts b/src/client/testing/testController/common/types.ts index b61fad1c9167..d8c9bcf5ce35 100644 --- a/src/client/testing/testController/common/types.ts +++ b/src/client/testing/testController/common/types.ts @@ -12,7 +12,6 @@ import { Uri, WorkspaceFolder, } from 'vscode'; -import { IPythonExecutionFactory } from '../../../common/process/types'; import { TestDiscoveryOptions } from '../../common/types'; export type TestRunInstanceOptions = TestRunOptions & { @@ -178,7 +177,9 @@ export interface ITestServer { } export interface ITestDiscoveryAdapter { - discoverTests(uri: Uri, executionFactory: IPythonExecutionFactory): Promise; + // Uncomment line 182 and comment out line 181 to use the new discovery method. + discoverTests(uri: Uri): Promise; + // discoverTests(uri: Uri, executionFactory: IPythonExecutionFactory): Promise; testing rewrite } // interface for execution/runner adapter diff --git a/src/client/testing/testController/controller.ts b/src/client/testing/testController/controller.ts index 656de0604208..f57e3600dc34 100644 --- a/src/client/testing/testController/controller.ts +++ b/src/client/testing/testController/controller.ts @@ -165,8 +165,8 @@ export class PythonTestController implements ITestController, IExtensionSingleAc } 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; } @@ -325,11 +325,6 @@ export class PythonTestController implements ITestController, IExtensionSingleAc }), ); } - console.log('HERE2'); - this.testController.items.forEach((element) => console.log(element)); - - console.log(this.testController.items); - console.log('size', this.testController.items.size); return Promise.resolve(); } @@ -379,11 +374,9 @@ export class PythonTestController implements ITestController, IExtensionSingleAc if (testItems.length > 0) { if (settings.testing.pytestEnabled) { sendTelemetryEvent(EventName.UNITTEST_RUN, undefined, { - // seems like this telemetry is named incorrectly? tool: 'pytest', debugging: request.profile?.kind === TestRunProfileKind.Debug, }); - // ** update this to reflect the nwe execution style before return this.pytest.runTests( { includes: testItems, @@ -430,7 +423,6 @@ export class PythonTestController implements ITestController, IExtensionSingleAc } if (!settings.testing.pytestEnabled && !settings.testing.unittestEnabled) { - // ** this could be the logic I am looking for unconfiguredWorkspaces.push(workspace); } return Promise.resolve(); @@ -514,8 +506,6 @@ export class PythonTestController implements ITestController, IExtensionSingleAc ); } - // ** not sure about the telemetry - /** * Send UNITTEST_DISCOVERY_TRIGGER telemetry event only once per trigger type. * diff --git a/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts b/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts index 48718fbed7a1..4b0a0a46f00b 100644 --- a/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts +++ b/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts @@ -33,14 +33,15 @@ export class PytestTestDiscoveryAdapter implements ITestDiscoveryAdapter { } } - public async discoverTests(uri: Uri, executionFactory: IPythonExecutionFactory): Promise { - const settings = this.configSettings.getSettings(uri); - const { pytestArgs } = settings.testing; - console.debug(pytestArgs); // do we use pytestArgs anywhere? + // Uncomment the function discoverTests to use the new discovery method. + // public async discoverTests(uri: Uri, executionFactory: IPythonExecutionFactory): Promise { + // const settings = this.configSettings.getSettings(uri); + // const { pytestArgs } = settings.testing; + // console.debug(pytestArgs); - this.cwd = uri.fsPath; - return this.runPytestDiscovery(uri, executionFactory); - } + // this.cwd = uri.fsPath; + // return this.runPytestDiscovery(uri, executionFactory); + // } async runPytestDiscovery(uri: Uri, executionFactory: IPythonExecutionFactory): Promise { if (!this.deferred) { @@ -53,7 +54,6 @@ export class PytestTestDiscoveryAdapter implements ITestDiscoveryAdapter { const pythonPathParts: string[] = process.env.PYTHONPATH?.split(path.delimiter) ?? []; const pythonPathCommand = [fullPluginPath, ...pythonPathParts].join(path.delimiter); - console.log('port', this.testServer.getPort().toString(), uuid.toString()); const spawnOptions: SpawnOptions = { cwd: uri.fsPath, diff --git a/src/client/testing/testController/workspaceTestAdapter.ts b/src/client/testing/testController/workspaceTestAdapter.ts index e83e2f6579e3..5c4a9bec45c5 100644 --- a/src/client/testing/testController/workspaceTestAdapter.ts +++ b/src/client/testing/testController/workspaceTestAdapter.ts @@ -218,14 +218,7 @@ export class WorkspaceTestAdapter { let rawTestData; try { - if (executionFactory !== undefined) { - rawTestData = await this.discoveryAdapter.discoverTests(this.workspaceUri, executionFactory); - console.debug('here'); - console.debug('rawTestData: ', rawTestData); - } else { - console.log('executionFactory is undefined'); - } - + rawTestData = await this.discoveryAdapter.discoverTests(this.workspaceUri, executionFactory); deferred.resolve(); } catch (ex) { sendTelemetryEvent(EventName.UNITTEST_DISCOVERY_DONE, undefined, { tool: this.testProvider, failed: true }); @@ -347,10 +340,6 @@ function populateTestTree( } // Recursively populate the tree with test data. - for (let i = 0; i < testTreeData.children.length; i = i + 1) { - // console.debug('testTreeData.children i= ', i, '', testTreeData.children[i]); - } - testTreeData.children.forEach((child) => { if (!token?.isCancellationRequested) { if (isTestItem(child)) { @@ -364,7 +353,7 @@ function populateTestTree( testItem.canResolveChildren = false; testItem.range = range; testItem.tags = [RunTestTag, DebugTestTag]; - console.debug('adding test item: ', testItem?.id, 'to root item: ', testRoot?.id); + testRoot!.children.add(testItem); // add to our map wstAdapter.runIdToTestItem.set(child.runID, testItem); @@ -372,18 +361,13 @@ function populateTestTree( wstAdapter.vsIdToRunId.set(child.id_, child.runID); } else { let node = testController.items.get(child.path); - console.debug('AA node = ', node?.id, ' child.path = ', child.path); + if (!node) { node = testController.createTestItem(child.id_, child.name, Uri.file(child.path)); - console.debug('!node BB node', node.id, 'child.path', child.name); + node.canResolveChildren = true; node.tags = [RunTestTag, DebugTestTag]; - testRoot!.children.add(node); - console.debug('test root ', testRoot?.id, testRoot?.children.size); - testRoot?.children.forEach((child1) => { - console.debug('testRoot.children = ', child1.id); - }); } populateTestTree(testController, child, node, wstAdapter, token); } From a01fd9827c82e223aa3b87126903632513b3b38d Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Tue, 24 Jan 2023 18:36:26 -0800 Subject: [PATCH 14/21] add back old version of discover tests --- .../testController/pytest/pytestDiscoveryAdapter.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts b/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts index 4b0a0a46f00b..0cfaa8cf893d 100644 --- a/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts +++ b/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts @@ -33,7 +33,13 @@ export class PytestTestDiscoveryAdapter implements ITestDiscoveryAdapter { } } - // Uncomment the function discoverTests to use the new discovery method. + // Old version of discover tests. + discoverTests(uri: Uri): Promise { + console.log(uri); + this.deferred = createDeferred(); + return this.deferred.promise; + } + // Uncomment this version of the function discoverTests to use the new discovery method. // public async discoverTests(uri: Uri, executionFactory: IPythonExecutionFactory): Promise { // const settings = this.configSettings.getSettings(uri); // const { pytestArgs } = settings.testing; From 3a7a10bd5f23f87acc6edae145acf44e107c9a88 Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Tue, 24 Jan 2023 18:39:15 -0800 Subject: [PATCH 15/21] fix build errors- now it builds --- src/client/testing/testController/workspaceTestAdapter.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/client/testing/testController/workspaceTestAdapter.ts b/src/client/testing/testController/workspaceTestAdapter.ts index 5c4a9bec45c5..a922809355be 100644 --- a/src/client/testing/testController/workspaceTestAdapter.ts +++ b/src/client/testing/testController/workspaceTestAdapter.ts @@ -14,7 +14,6 @@ import { Uri, Location, } from 'vscode'; -import { IPythonExecutionFactory } from '../../common/process/types'; import { createDeferred, Deferred } from '../../common/utils/async'; import { Testing } from '../../common/utils/localize'; import { traceError } from '../../logging'; @@ -197,12 +196,12 @@ export class WorkspaceTestAdapter { return Promise.resolve(); } + // add `executionFactory?: IPythonExecutionFactory,` to the function for new pytest method public async discoverTests( testController: TestController, token?: CancellationToken, isMultiroot?: boolean, workspaceFilePath?: string, - executionFactory?: IPythonExecutionFactory, ): Promise { sendTelemetryEvent(EventName.UNITTEST_DISCOVERING, undefined, { tool: this.testProvider }); @@ -218,7 +217,9 @@ export class WorkspaceTestAdapter { let rawTestData; try { - rawTestData = await this.discoveryAdapter.discoverTests(this.workspaceUri, executionFactory); + // First line is old way, second line is new way. + rawTestData = await this.discoveryAdapter.discoverTests(this.workspaceUri); + // rawTestData = await this.discoveryAdapter.discoverTests(this.workspaceUri, executionFactory); deferred.resolve(); } catch (ex) { sendTelemetryEvent(EventName.UNITTEST_DISCOVERY_DONE, undefined, { tool: this.testProvider, failed: true }); From e9b5398813e560a02d970c9431e1b5380a764f01 Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Tue, 24 Jan 2023 19:27:39 -0800 Subject: [PATCH 16/21] remove discovery logic --- pythonFiles/vscode_pytest/__init__.py | 207 -------------------------- 1 file changed, 207 deletions(-) delete mode 100644 pythonFiles/vscode_pytest/__init__.py diff --git a/pythonFiles/vscode_pytest/__init__.py b/pythonFiles/vscode_pytest/__init__.py deleted file mode 100644 index c3fb4620afe4..000000000000 --- a/pythonFiles/vscode_pytest/__init__.py +++ /dev/null @@ -1,207 +0,0 @@ -# -*- coding: utf-8 -*- -import enum -import json -import os -import pathlib -import sys -from typing import List, Literal, Tuple, TypedDict, Union - -import pytest - -script_dir = pathlib.Path(__file__).parent.parent -sys.path.append(os.fspath(script_dir)) -sys.path.append(os.fspath(script_dir / "lib" / "python")) - -# Inherit from str so it's JSON serializable. -class TestNodeTypeEnum(str, enum.Enum): - class_ = "class" - file = "file" - folder = "folder" - test = "test" - - -class TestData(TypedDict): - name: str - path: str - type_: TestNodeTypeEnum - id_: str - - -class TestItem(TestData): - lineno: str - runID: str - - -class TestNode(TestData): - children: "List[Union[TestNode, TestItem]]" - - -from testing_tools import socket_manager -from typing_extensions import NotRequired - -DEFAULT_PORT = "45454" - - -def pytest_collection_finish(session): - # Called after collection has been performed. - node: Union[TestNode, None] = build_test_tree(session)[0] - cwd = pathlib.Path.cwd() - if node: - sendPost(str(cwd), node) - # TODO: add error checking. - - -def build_test_tree(session) -> Tuple[Union[TestNode, None], List[str]]: - # Builds a tree of tests from the pytest session. - errors: List[str] = [] - session_node: TestNode = create_session_node(session) - session_children_dict: dict[str, TestNode] = {} - file_nodes_dict: dict[pytest.Module, TestNode] = {} - class_nodes_dict: dict[str, TestNode] = {} - - for test_case in session.items: - test_node: TestItem = create_test_node(test_case) - # Check parent node type, either Module or UnitTest class. - if type(test_case.parent) is pytest.Module: - try: - parent_test_case: TestNode = file_nodes_dict[test_case.parent] - except KeyError: - parent_test_case: TestNode = create_file_node(test_case.parent) - file_nodes_dict[test_case.parent] = parent_test_case - parent_test_case["children"].append(test_node) - else: # should be a pytest.Class - try: - test_class_node: TestNode = class_nodes_dict[test_case.parent.name] - except KeyError: - test_class_node: TestNode = create_class_node(test_case.parent) - class_nodes_dict[test_case.parent.name] = test_class_node - test_class_node["children"].append(test_node) - parent_module: pytest.Module = test_case.parent.parent - # Create a file node that has the class as a child. - try: - test_file_node: TestNode = file_nodes_dict[parent_module] - except KeyError: - test_file_node: TestNode = create_file_node(parent_module) - file_nodes_dict[parent_module] = test_file_node - test_file_node["children"].append(test_node) - # Check if the class is already a child of the file node. - if test_class_node not in test_file_node["children"]: - test_file_node["children"].append(test_class_node) - - created_files_folders_dict: dict[str, TestNode] = {} - for file_module, file_node in file_nodes_dict.items(): - root_folder_node: TestNode = build_nested_folders( - file_module, file_node, created_files_folders_dict, session - ) - # the final folder we get to is the highest folder in the path and therefore we add this as a child to the session. - if root_folder_node.get("id_") not in session_children_dict: - session_children_dict[root_folder_node.get("id_")] = root_folder_node - session_node["children"] = list(session_children_dict.values()) - return session_node, errors - - -def build_nested_folders( - file_module: pytest.Module, - file_node: TestNode, - created_files_folders_dict: dict[str, TestNode], - session: pytest.Session, -) -> TestNode: - prev_folder_node: TestNode = file_node - # Begin the i_path iteration one level above the current file. - iterator_path: pathlib.Path = file_module.path.parent - while iterator_path != session.path: - curr_folder_name: str = iterator_path.name - try: - curr_folder_node: TestNode = created_files_folders_dict[curr_folder_name] - except KeyError: - curr_folder_node: TestNode = create_folder_node( - curr_folder_name, iterator_path - ) - created_files_folders_dict[curr_folder_name] = curr_folder_node - if prev_folder_node not in curr_folder_node["children"]: - curr_folder_node["children"].append(prev_folder_node) - iterator_path = iterator_path.parent - prev_folder_node = curr_folder_node - return prev_folder_node - - -def create_test_node( - test_case: pytest.Item, -) -> TestItem: # stickynote what is this type - test_case_loc: str = ( - "" if test_case.location[1] is None else str(test_case.location[1] + 1) - ) - return { - "name": test_case.name, - "path": str(test_case.path), - "lineno": test_case_loc, - "type_": TestNodeTypeEnum.test, - "id_": test_case.nodeid, # remove cast - "runID": test_case.nodeid, - } - - -def create_session_node(session: pytest.Session) -> TestNode: - return { - "name": session.name, - "path": str(session.path), - "type_": TestNodeTypeEnum.folder, - "children": [], - "id_": str(session.path), - } - - -def create_class_node(class_module: pytest.Class) -> TestNode: - return { - "name": class_module.name, - "path": str(class_module.path), - "type_": TestNodeTypeEnum.class_, - "children": [], - "id_": class_module.nodeid, - } - - -def create_file_node(file_module: pytest.Module) -> TestNode: - return { - "name": str(file_module.path.name), - "path": str(file_module.path), - "type_": TestNodeTypeEnum.file, - "id_": str(file_module.path), - "children": [], - } - - -def create_folder_node(folderName: str, path_iterator: pathlib.Path) -> TestNode: - return { - "name": folderName, - "path": str(path_iterator), - "type_": TestNodeTypeEnum.folder, - "id_": str(path_iterator), - "children": [], - } - - -class PayloadDict(TypedDict): - cwd: str - status: Literal["success", "error"] - tests: NotRequired[TestNode] - errors: NotRequired[List[str]] - - -def sendPost(cwd: str, tests: TestNode) -> None: - # Sends a post request as a response to the server. - payload: PayloadDict = {"cwd": cwd, "status": "success", "tests": tests} - testPort: Union[str, int] = os.getenv("TEST_PORT", 45454) - testuuid: Union[str, None] = os.getenv("TEST_UUID") - addr = "localhost", int(testPort) - data = json.dumps(payload) - request = f"""POST / HTTP/1.1 -Host: localhost:{testPort} -Content-Length: {len(data)} -Content-Type: application/json -Request-uuid: {testuuid} - -{data}""" - with socket_manager.SocketManager(addr) as s: - if s.socket is not None: - s.socket.sendall(request.encode("utf-8")) # type: ignore From 36a90d31dcf107e78484d059aaa1387cc94689f2 Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Wed, 25 Jan 2023 14:34:16 -0800 Subject: [PATCH 17/21] remove unneeded comments --- src/client/testing/testController/controller.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/client/testing/testController/controller.ts b/src/client/testing/testController/controller.ts index f57e3600dc34..7e35918d8ada 100644 --- a/src/client/testing/testController/controller.ts +++ b/src/client/testing/testController/controller.ts @@ -38,7 +38,6 @@ import { TestRefreshOptions, ITestExecutionAdapter, } from './common/types'; -// TODO: create pytest and add to import import { UnittestTestDiscoveryAdapter } from './unittest/testDiscoveryAdapter'; import { UnittestTestExecutionAdapter } from './unittest/testExecutionAdapter'; import { PytestTestDiscoveryAdapter } from './pytest/pytestDiscoveryAdapter'; @@ -163,8 +162,6 @@ 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 PytestTestDiscoveryAdapter(this.pythonTestServer, { ...this.configSettings }); executionAdapter = new PytestTestExecutionAdapter(this.pythonTestServer, this.configSettings); testProvider = PYTEST_PROVIDER; @@ -306,7 +303,6 @@ export class PythonTestController implements ITestController, IExtensionSingleAc const settings = this.configSettings.getSettings(item.uri); if (settings.testing.pytestEnabled) { return this.pytest.resolveChildren(this.testController, item, this.refreshCancellation.token); - // ** check resolve children functionality } if (settings.testing.unittestEnabled) { return this.unittest.resolveChildren(this.testController, item, this.refreshCancellation.token); From f94494cc927a9e59dcd1488d2f47511a5ba91bde Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Wed, 25 Jan 2023 15:10:21 -0800 Subject: [PATCH 18/21] updating comments --- src/client/testing/testController/common/types.ts | 4 ++-- src/client/testing/testController/controller.ts | 12 ++++++------ .../testController/pytest/pytestDiscoveryAdapter.ts | 2 +- .../testing/testController/workspaceTestAdapter.ts | 8 ++++++-- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/client/testing/testController/common/types.ts b/src/client/testing/testController/common/types.ts index d8c9bcf5ce35..579c11d5ef25 100644 --- a/src/client/testing/testController/common/types.ts +++ b/src/client/testing/testController/common/types.ts @@ -177,9 +177,9 @@ export interface ITestServer { } export interface ITestDiscoveryAdapter { - // Uncomment line 182 and comment out line 181 to use the new discovery method. + // ** Uncomment second line and comment out first line to use the new discovery method. discoverTests(uri: Uri): Promise; - // discoverTests(uri: Uri, executionFactory: IPythonExecutionFactory): Promise; testing rewrite + // discoverTests(uri: Uri, executionFactory: IPythonExecutionFactory): Promise } // interface for execution/runner adapter diff --git a/src/client/testing/testController/controller.ts b/src/client/testing/testController/controller.ts index 7e35918d8ada..4466700353ee 100644 --- a/src/client/testing/testController/controller.ts +++ b/src/client/testing/testController/controller.ts @@ -227,7 +227,7 @@ export class PythonTestController implements ITestController, IExtensionSingleAc if (settings.testing.pytestEnabled) { // Ensure we send test telemetry if it gets disabled again this.sendTestDisabledTelemetry = true; - // uncomment 240 - 250 to NEW new test discovery mechanism + // ** uncomment ~231 - 241 to NEW new test discovery mechanism // const workspace = this.workspaceService.getWorkspaceFolder(uri); // console.warn(`Discover tests for workspace name: ${workspace?.name} - uri: ${uri.fsPath}`); // const testAdapter = @@ -239,12 +239,12 @@ export class PythonTestController implements ITestController, IExtensionSingleAc // this.workspaceService.workspaceFile?.fsPath, // this.pythonExecFactory, // ); - // uncomment 252 to use OLD test discovery mechanism + // uncomment ~243 to use OLD test discovery mechanism await this.pytest.refreshTestData(this.testController, uri, this.refreshCancellation.token); } else if (settings.testing.unittestEnabled) { - // Ensure we send test telemetry if it gets disabled again + // ** Ensure we send test telemetry if it gets disabled again this.sendTestDisabledTelemetry = true; - // uncomment 257 - 267 to NEW new test discovery mechanism + // uncomment ~248 - 258 to NEW new test discovery mechanism // const workspace = this.workspaceService.getWorkspaceFolder(uri); // console.warn(`Discover tests for workspace name: ${workspace?.name} - uri: ${uri.fsPath}`); // const testAdapter = @@ -256,7 +256,7 @@ export class PythonTestController implements ITestController, IExtensionSingleAc // this.workspaceService.workspaceFile?.fsPath, // this.pythonExecFactory, // ); - // uncomment 269 to use OLD test discovery mechanism + // uncomment ~260 to use OLD test discovery mechanism await this.unittest.refreshTestData(this.testController, uri, this.refreshCancellation.token); } else { if (this.sendTestDisabledTelemetry) { @@ -385,7 +385,7 @@ export class PythonTestController implements ITestController, IExtensionSingleAc ); } if (settings.testing.unittestEnabled) { - // potentially sqeeze in the new exeuction way here? + // potentially squeeze in the new execution way here? sendTelemetryEvent(EventName.UNITTEST_RUN, undefined, { tool: 'unittest', debugging: request.profile?.kind === TestRunProfileKind.Debug, diff --git a/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts b/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts index 0cfaa8cf893d..385a406bd703 100644 --- a/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts +++ b/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts @@ -33,7 +33,7 @@ export class PytestTestDiscoveryAdapter implements ITestDiscoveryAdapter { } } - // Old version of discover tests. + // ** Old version of discover tests. discoverTests(uri: Uri): Promise { console.log(uri); this.deferred = createDeferred(); diff --git a/src/client/testing/testController/workspaceTestAdapter.ts b/src/client/testing/testController/workspaceTestAdapter.ts index a922809355be..77296b4c63b7 100644 --- a/src/client/testing/testController/workspaceTestAdapter.ts +++ b/src/client/testing/testController/workspaceTestAdapter.ts @@ -217,9 +217,13 @@ export class WorkspaceTestAdapter { let rawTestData; try { - // First line is old way, second line is new way. + // ** First line is old way, section with if statement below is new way. rawTestData = await this.discoveryAdapter.discoverTests(this.workspaceUri); - // rawTestData = await this.discoveryAdapter.discoverTests(this.workspaceUri, executionFactory); + // if (executionFactory !== undefined) { + // rawTestData = await this.discoveryAdapter.discoverTests(this.workspaceUri, executionFactory); + // } else { + // console.log('executionFactory is undefined'); + // } deferred.resolve(); } catch (ex) { sendTelemetryEvent(EventName.UNITTEST_DISCOVERY_DONE, undefined, { tool: this.testProvider, failed: true }); From a8989dac753807cb3dd45485bee0d67af0d1eb97 Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Wed, 25 Jan 2023 15:11:08 -0800 Subject: [PATCH 19/21] fix un-intentional add to unittest --- src/client/testing/testController/controller.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/client/testing/testController/controller.ts b/src/client/testing/testController/controller.ts index 4466700353ee..eb69deb897d7 100644 --- a/src/client/testing/testController/controller.ts +++ b/src/client/testing/testController/controller.ts @@ -253,8 +253,7 @@ export class PythonTestController implements ITestController, IExtensionSingleAc // this.testController, // this.refreshCancellation.token, // this.testAdapters.size > 1, - // this.workspaceService.workspaceFile?.fsPath, - // this.pythonExecFactory, + // this.workspaceService.workspaceFile?.fsPath // ); // uncomment ~260 to use OLD test discovery mechanism await this.unittest.refreshTestData(this.testController, uri, this.refreshCancellation.token); From 55fe674035a6e24a8ced6836a421d61054b4cd33 Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Wed, 25 Jan 2023 15:11:55 -0800 Subject: [PATCH 20/21] comma --- src/client/testing/testController/controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/testing/testController/controller.ts b/src/client/testing/testController/controller.ts index eb69deb897d7..c93b9f8a1e33 100644 --- a/src/client/testing/testController/controller.ts +++ b/src/client/testing/testController/controller.ts @@ -253,7 +253,7 @@ export class PythonTestController implements ITestController, IExtensionSingleAc // this.testController, // this.refreshCancellation.token, // this.testAdapters.size > 1, - // this.workspaceService.workspaceFile?.fsPath + // this.workspaceService.workspaceFile?.fsPath, // ); // uncomment ~260 to use OLD test discovery mechanism await this.unittest.refreshTestData(this.testController, uri, this.refreshCancellation.token); From 405584e8152d74ef26fcb1999a68c3c8a43e01f8 Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Tue, 31 Jan 2023 13:57:40 -0800 Subject: [PATCH 21/21] switch to traceVerbose --- src/client/testing/testController/controller.ts | 4 ++-- .../testing/testController/pytest/pytestDiscoveryAdapter.ts | 5 +++-- src/client/testing/testController/workspaceTestAdapter.ts | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/client/testing/testController/controller.ts b/src/client/testing/testController/controller.ts index c93b9f8a1e33..8cba671277d0 100644 --- a/src/client/testing/testController/controller.ts +++ b/src/client/testing/testController/controller.ts @@ -229,7 +229,7 @@ export class PythonTestController implements ITestController, IExtensionSingleAc this.sendTestDisabledTelemetry = true; // ** uncomment ~231 - 241 to NEW new test discovery mechanism // const workspace = this.workspaceService.getWorkspaceFolder(uri); - // console.warn(`Discover tests for workspace name: ${workspace?.name} - uri: ${uri.fsPath}`); + // traceVerbose(`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( @@ -246,7 +246,7 @@ export class PythonTestController implements ITestController, IExtensionSingleAc this.sendTestDisabledTelemetry = true; // uncomment ~248 - 258 to NEW new test discovery mechanism // const workspace = this.workspaceService.getWorkspaceFolder(uri); - // console.warn(`Discover tests for workspace name: ${workspace?.name} - uri: ${uri.fsPath}`); + // traceVerbose(`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( diff --git a/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts b/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts index 385a406bd703..e2108b872845 100644 --- a/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts +++ b/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts @@ -10,6 +10,7 @@ import { 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'; /** @@ -35,7 +36,7 @@ export class PytestTestDiscoveryAdapter implements ITestDiscoveryAdapter { // ** Old version of discover tests. discoverTests(uri: Uri): Promise { - console.log(uri); + traceVerbose(uri); this.deferred = createDeferred(); return this.deferred.promise; } @@ -43,7 +44,7 @@ export class PytestTestDiscoveryAdapter implements ITestDiscoveryAdapter { // public async discoverTests(uri: Uri, executionFactory: IPythonExecutionFactory): Promise { // const settings = this.configSettings.getSettings(uri); // const { pytestArgs } = settings.testing; - // console.debug(pytestArgs); + // traceVerbose(pytestArgs); // this.cwd = uri.fsPath; // return this.runPytestDiscovery(uri, executionFactory); diff --git a/src/client/testing/testController/workspaceTestAdapter.ts b/src/client/testing/testController/workspaceTestAdapter.ts index 77296b4c63b7..f42152438cfb 100644 --- a/src/client/testing/testController/workspaceTestAdapter.ts +++ b/src/client/testing/testController/workspaceTestAdapter.ts @@ -222,7 +222,7 @@ export class WorkspaceTestAdapter { // if (executionFactory !== undefined) { // rawTestData = await this.discoveryAdapter.discoverTests(this.workspaceUri, executionFactory); // } else { - // console.log('executionFactory is undefined'); + // traceVerbose('executionFactory is undefined'); // } deferred.resolve(); } catch (ex) {