diff --git a/pythonFiles/tests/pytestadapter/helpers.py b/pythonFiles/tests/pytestadapter/helpers.py index 013e4bb31fca..c3e01d52170a 100644 --- a/pythonFiles/tests/pytestadapter/helpers.py +++ b/pythonFiles/tests/pytestadapter/helpers.py @@ -1,5 +1,6 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. + import io import json import os @@ -9,7 +10,7 @@ import sys import threading import uuid -from typing import Any, Dict, List, Optional, Tuple +from typing import Any, Dict, List, Optional, Tuple, Union TEST_DATA_PATH = pathlib.Path(__file__).parent / ".data" from typing_extensions import TypedDict @@ -72,21 +73,34 @@ def process_rpc_message(data: str) -> Tuple[Dict[str, Any], str]: str_stream: io.StringIO = io.StringIO(data) length: int = 0 + while True: line: str = str_stream.readline() if CONTENT_LENGTH.lower() in line.lower(): length = int(line[len(CONTENT_LENGTH) :]) break + if not line or line.isspace(): raise ValueError("Header does not contain Content-Length") + while True: line: str = str_stream.readline() if not line or line.isspace(): break raw_json: str = str_stream.read(length) - dict_json: Dict[str, Any] = json.loads(raw_json) - return dict_json, str_stream.read() + return json.loads(raw_json), str_stream.read() + + +def process_rpc_json(data: str) -> List[Dict[str, Any]]: + """Process the JSON data which comes from the server which runs the pytest discovery.""" + json_messages = [] + remaining = data + while remaining: + json_data, remaining = process_rpc_message(remaining) + json_messages.append(json_data) + + return json_messages def runner(args: List[str]) -> Optional[List[Dict[str, Any]]]: @@ -110,53 +124,58 @@ def runner(args: List[str]) -> Optional[List[Dict[str, Any]]]: "PYTHONPATH": os.fspath(pathlib.Path(__file__).parent.parent.parent), } ) + completed = threading.Event() - result: list = [] + result = [] t1: threading.Thread = threading.Thread( - target=_listen_on_socket, args=(listener, result) + target=_listen_on_socket, args=(listener, result, completed) ) t1.start() t2 = threading.Thread( - target=lambda proc_args, proc_env, proc_cwd: subprocess.run( - proc_args, env=proc_env, cwd=proc_cwd - ), - args=(process_args, env, TEST_DATA_PATH), + target=_run_test_code, + args=(process_args, env, TEST_DATA_PATH, completed), ) t2.start() t1.join() t2.join() - a = process_rpc_json(result[0]) - return a if result else None + return process_rpc_json(result[0]) if result else None -def process_rpc_json(data: str) -> List[Dict[str, Any]]: - """Process the JSON data which comes from the server which runs the pytest discovery.""" - json_messages = [] - remaining = data - while remaining: - json_data, remaining = process_rpc_message(remaining) - json_messages.append(json_data) - - return json_messages - - -def _listen_on_socket(listener: socket.socket, result: List[str]): +def _listen_on_socket( + listener: socket.socket, result: List[str], completed: threading.Event +): """Listen on the socket for the JSON data from the server. - Created as a seperate function for clarity in threading. + Created as a separate function for clarity in threading. """ sock, (other_host, other_port) = listener.accept() + listener.settimeout(1) all_data: list = [] while True: data: bytes = sock.recv(1024 * 1024) if not data: - break + if completed.is_set(): + break + else: + try: + sock, (other_host, other_port) = listener.accept() + except socket.timeout: + result.append("".join(all_data)) + return all_data.append(data.decode("utf-8")) result.append("".join(all_data)) +def _run_test_code( + proc_args: List[str], proc_env, proc_cwd: str, completed: threading.Event +): + result = subprocess.run(proc_args, env=proc_env, cwd=proc_cwd) + completed.set() + return result + + def find_test_line_number(test_name: str, test_file_path) -> str: """Function which finds the correct line number for a test by looking for the "test_marker--[test_name]" string. diff --git a/pythonFiles/tests/pytestadapter/test_discovery.py b/pythonFiles/tests/pytestadapter/test_discovery.py index 3cbaa270cab9..7f2355129c65 100644 --- a/pythonFiles/tests/pytestadapter/test_discovery.py +++ b/pythonFiles/tests/pytestadapter/test_discovery.py @@ -59,11 +59,10 @@ def test_syntax_error(tmp_path): temp_dir.mkdir() p = temp_dir / "error_syntax_discovery.py" shutil.copyfile(file_path, p) - actual_list: Optional[List[Dict[str, Any]]] = runner( - ["--collect-only", os.fspath(p)] - ) - assert actual_list - for actual in actual_list: + actual = runner(["--collect-only", os.fspath(p)]) + if actual: + actual = actual[0] + assert actual assert all(item in actual for item in ("status", "cwd", "error")) assert actual["status"] == "error" assert actual["cwd"] == os.fspath(TEST_DATA_PATH) @@ -76,11 +75,9 @@ def test_parameterized_error_collect(): The json should still be returned but the errors list should be present. """ file_path_str = "error_parametrize_discovery.py" - actual_list: Optional[List[Dict[str, Any]]] = runner( - ["--collect-only", file_path_str] - ) - assert actual_list - for actual in actual_list: + actual = runner(["--collect-only", file_path_str]) + if actual: + actual = actual[0] assert all(item in actual for item in ("status", "cwd", "error")) assert actual["status"] == "error" assert actual["cwd"] == os.fspath(TEST_DATA_PATH) @@ -135,14 +132,15 @@ def test_pytest_collect(file, expected_const): file -- a string with the file or folder to run pytest discovery on. expected_const -- the expected output from running pytest discovery on the file. """ - actual_list: Optional[List[Dict[str, Any]]] = runner( + actual = runner( [ "--collect-only", os.fspath(TEST_DATA_PATH / file), ] ) - assert actual_list - for actual in actual_list: + if actual: + actual = actual[0] + assert actual assert all(item in actual for item in ("status", "cwd", "tests")) assert actual["status"] == "success" assert actual["cwd"] == os.fspath(TEST_DATA_PATH) diff --git a/pythonFiles/tests/pytestadapter/test_execution.py b/pythonFiles/tests/pytestadapter/test_execution.py index 5b2cbddb1644..d11766027a91 100644 --- a/pythonFiles/tests/pytestadapter/test_execution.py +++ b/pythonFiles/tests/pytestadapter/test_execution.py @@ -2,7 +2,6 @@ # Licensed under the MIT License. import os import shutil -from typing import Any, Dict, List, Optional import pytest from tests.pytestadapter import expected_execution_test_output @@ -29,11 +28,10 @@ def test_syntax_error_execution(tmp_path): temp_dir.mkdir() p = temp_dir / "error_syntax_discovery.py" shutil.copyfile(file_path, p) - actual_list: Optional[List[Dict[str, Any]]] = runner( - ["error_syntax_discover.py::test_function"] - ) - assert actual_list - for actual in actual_list: + actual = runner(["error_syntax_discover.py::test_function"]) + if actual: + actual = actual[0] + assert actual assert all(item in actual for item in ("status", "cwd", "error")) assert actual["status"] == "error" assert actual["cwd"] == os.fspath(TEST_DATA_PATH) @@ -45,9 +43,10 @@ def test_bad_id_error_execution(): The json should still be returned but the errors list should be present. """ - actual_list: Optional[List[Dict[str, Any]]] = runner(["not/a/real::test_id"]) - assert actual_list - for actual in actual_list: + actual = runner(["not/a/real::test_id"]) + if actual: + actual = actual[0] + assert actual assert all(item in actual for item in ("status", "cwd", "error")) assert actual["status"] == "error" assert actual["cwd"] == os.fspath(TEST_DATA_PATH) @@ -156,14 +155,17 @@ def test_pytest_execution(test_ids, expected_const): expected_const -- a dictionary of the expected output from running pytest discovery on the files. """ args = test_ids - actual_list: Optional[List[Dict[str, Any]]] = runner(args) - assert actual_list - for actual in actual_list: - assert all(item in actual for item in ("status", "cwd", "result")) - assert actual["status"] == "success" - assert actual["cwd"] == os.fspath(TEST_DATA_PATH) - result_data = actual["result"] - for key in result_data: - if result_data[key]["outcome"] == "failure": - result_data[key]["message"] = "ERROR MESSAGE" - assert result_data == expected_const + actual = runner(args) + assert actual + print(actual) + assert len(actual) == len(expected_const) + actual_result_dict = dict() + for a in actual: + assert all(item in a for item in ("status", "cwd", "result")) + assert a["status"] == "success" + assert a["cwd"] == os.fspath(TEST_DATA_PATH) + actual_result_dict.update(a["result"]) + for key in actual_result_dict: + if actual_result_dict[key]["outcome"] == "failure": + actual_result_dict[key]["message"] = "ERROR MESSAGE" + assert actual_result_dict == expected_const diff --git a/pythonFiles/tests/unittestadapter/test_execution.py b/pythonFiles/tests/unittestadapter/test_execution.py index d461ead9ad94..c7a5cb0eef61 100644 --- a/pythonFiles/tests/unittestadapter/test_execution.py +++ b/pythonFiles/tests/unittestadapter/test_execution.py @@ -3,9 +3,14 @@ import os import pathlib +import sys from typing import List import pytest + +PYTHON_FILES = pathlib.Path(__file__).parent.parent + +sys.path.insert(0, os.fspath(PYTHON_FILES)) from unittestadapter.execution import parse_execution_cli_args, run_tests TEST_DATA_PATH = pathlib.Path(__file__).parent / ".data" diff --git a/pythonFiles/unittestadapter/discovery.py b/pythonFiles/unittestadapter/discovery.py index bcc2fd967f78..02490024b217 100644 --- a/pythonFiles/unittestadapter/discovery.py +++ b/pythonFiles/unittestadapter/discovery.py @@ -17,8 +17,8 @@ from typing_extensions import Literal # 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 = pathlib.Path(__file__).parent.parent +sys.path.insert(0, os.fspath(PYTHON_FILES)) from testing_tools import socket_manager diff --git a/pythonFiles/unittestadapter/execution.py b/pythonFiles/unittestadapter/execution.py index 17c125e5843a..d9d5f41d624b 100644 --- a/pythonFiles/unittestadapter/execution.py +++ b/pythonFiles/unittestadapter/execution.py @@ -13,17 +13,14 @@ from types import TracebackType from typing import Dict, List, Optional, Tuple, Type, Union -script_dir = pathlib.Path(__file__).parent.parent -sys.path.append(os.fspath(script_dir)) -sys.path.append(os.fspath(script_dir / "lib" / "python")) -from testing_tools import process_json_util - +directory_path = pathlib.Path(__file__).parent.parent / "lib" / "python" # 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 = pathlib.Path(__file__).parent.parent + +sys.path.insert(0, os.fspath(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 testing_tools import process_json_util, socket_manager from typing_extensions import NotRequired, TypeAlias, TypedDict from unittestadapter.utils import parse_unittest_args @@ -54,6 +51,9 @@ def parse_execution_cli_args( ErrorType = Union[ Tuple[Type[BaseException], BaseException, TracebackType], Tuple[None, None, None] ] +PORT = 0 +UUID = 0 +START_DIR = "" class TestOutcomeEnum(str, enum.Enum): @@ -148,8 +148,10 @@ def formatResult( "traceback": tb, "subtest": subtest.id() if subtest else None, } - self.formatted[test_id] = result + if PORT == 0 or UUID == 0: + print("Error sending response, port or uuid unknown to python server.") + send_run_data(result, PORT, UUID) class TestExecutionStatus(str, enum.Enum): @@ -225,6 +227,33 @@ def run_tests( return payload +def send_run_data(raw_data, port, uuid): + # Build the request data (it has to be a POST request or the Node side will not process it), and send it. + status = raw_data["outcome"] + cwd = os.path.abspath(START_DIR) + if raw_data["subtest"]: + test_id = raw_data["subtest"] + else: + test_id = raw_data["test"] + test_dict = {} + test_dict[test_id] = raw_data + payload: PayloadDict = {"cwd": cwd, "status": status, "result": test_dict} + addr = ("localhost", port) + data = json.dumps(payload) + request = f"""Content-Length: {len(data)} +Content-Type: application/json +Request-uuid: {uuid} + +{data}""" + try: + with socket_manager.SocketManager(addr) as s: + if s.socket is not None: + s.socket.sendall(request.encode("utf-8")) + except Exception as e: + print(f"Error sending response: {e}") + print(f"Request data: {request}") + + if __name__ == "__main__": # Get unittest test execution arguments. argv = sys.argv[1:] @@ -270,11 +299,11 @@ def run_tests( print(f"Error: Could not connect to runTestIdsPort: {e}") print("Error: Could not connect to runTestIdsPort") - port, uuid = parse_execution_cli_args(argv[:index]) + PORT, UUID = parse_execution_cli_args(argv[:index]) if test_ids_from_buffer: # Perform test execution. payload = run_tests( - start_dir, test_ids_from_buffer, pattern, top_level_dir, uuid + start_dir, test_ids_from_buffer, pattern, top_level_dir, UUID ) else: cwd = os.path.abspath(start_dir) @@ -284,19 +313,3 @@ def run_tests( "status": status, "error": "No test ids received from buffer", } - - # Build the request data and send it. - addr = ("localhost", port) - data = json.dumps(payload) - request = f"""Content-Length: {len(data)} -Content-Type: application/json -Request-uuid: {uuid} - -{data}""" - try: - with socket_manager.SocketManager(addr) as s: - if s.socket is not None: - s.socket.sendall(request.encode("utf-8")) - except Exception as e: - print(f"Error sending response: {e}") - print(f"Request data: {request}") diff --git a/pythonFiles/vscode_pytest/__init__.py b/pythonFiles/vscode_pytest/__init__.py index 726dba8c9a97..1178abdb93d6 100644 --- a/pythonFiles/vscode_pytest/__init__.py +++ b/pythonFiles/vscode_pytest/__init__.py @@ -56,7 +56,7 @@ def pytest_internalerror(excrepr, excinfo): excinfo -- the exception information of type ExceptionInfo. """ # call.excinfo.exconly() returns the exception as a string. - ERRORS.append(excinfo.exconly()) + ERRORS.append(excinfo.exconly() + "\n Check Python Test Logs for more details.") def pytest_exception_interact(node, call, report): @@ -70,7 +70,13 @@ def pytest_exception_interact(node, call, report): # call.excinfo is the captured exception of the call, if it raised as type ExceptionInfo. # call.excinfo.exconly() returns the exception as a string. if call.excinfo and call.excinfo.typename != "AssertionError": - ERRORS.append(call.excinfo.exconly()) + ERRORS.append( + call.excinfo.exconly() + "\n Check Python Test Logs for more details." + ) + else: + ERRORS.append( + report.longreprtext + "\n Check Python Test Logs for more details." + ) def pytest_keyboard_interrupt(excinfo): @@ -79,8 +85,8 @@ def pytest_keyboard_interrupt(excinfo): Keyword arguments: excinfo -- the exception information of type ExceptionInfo. """ - # The function exconly() returns the exception as a string. - ERRORS.append(excinfo.exconly()) + # The function execonly() returns the exception as a string. + ERRORS.append(excinfo.exconly() + "\n Check Python Test Logs for more details.") class TestOutcome(Dict): @@ -120,7 +126,6 @@ class testRunResultDict(Dict[str, Dict[str, TestOutcome]]): tests: Dict[str, TestOutcome] -collected_tests = testRunResultDict() IS_DISCOVERY = False @@ -130,6 +135,9 @@ def pytest_load_initial_conftests(early_config, parser, args): IS_DISCOVERY = True +collected_tests_so_far = list() + + def pytest_report_teststatus(report, config): """ A pytest hook that is called when a test is called. It is called 3 times per test, @@ -138,6 +146,7 @@ def pytest_report_teststatus(report, config): report -- the report on the test setup, call, and teardown. config -- configuration object. """ + cwd = pathlib.Path.cwd() if report.when == "call": traceback = None @@ -148,13 +157,22 @@ def pytest_report_teststatus(report, config): elif report.failed: report_value = "failure" message = report.longreprtext - item_result = create_test_outcome( - report.nodeid, - report_value, - message, - traceback, - ) - collected_tests[report.nodeid] = item_result + node_id = str(report.nodeid) + if node_id not in collected_tests_so_far: + collected_tests_so_far.append(node_id) + item_result = create_test_outcome( + node_id, + report_value, + message, + traceback, + ) + collected_test = testRunResultDict() + collected_test[node_id] = item_result + execution_post( + os.fsdecode(cwd), + "success", + collected_test if collected_test else None, + ) ERROR_MESSAGE_CONST = { @@ -187,6 +205,15 @@ def pytest_sessionfinish(session, exitstatus): ) cwd = pathlib.Path.cwd() if IS_DISCOVERY: + if not (exitstatus == 0 or exitstatus == 1 or exitstatus == 5): + errorNode: TestNode = { + "name": "", + "path": cwd, + "type_": "error", + "children": [], + "id_": "", + } + post_response(os.fsdecode(cwd), errorNode) try: session_node: Union[TestNode, None] = build_test_tree(session) if not session_node: @@ -196,7 +223,9 @@ def pytest_sessionfinish(session, exitstatus): ) post_response(os.fsdecode(cwd), session_node) except Exception as e: - f"Error Occurred, description: {e.args[0] if e.args and e.args[0] else ''} traceback: {(traceback.format_exc() if e.__traceback__ else '')}" + ERRORS.append( + f"Error Occurred, traceback: {(traceback.format_exc() if e.__traceback__ else '')}" + ) errorNode: TestNode = { "name": "", "path": cwd, @@ -213,11 +242,12 @@ def pytest_sessionfinish(session, exitstatus): f"Pytest exited with error status: {exitstatus}, {ERROR_MESSAGE_CONST[exitstatus]}" ) exitstatus_bool = "error" - execution_post( - os.fsdecode(cwd), - exitstatus_bool, - collected_tests if collected_tests else None, - ) + + execution_post( + os.fsdecode(cwd), + exitstatus_bool, + None, + ) def build_test_tree(session: pytest.Session) -> TestNode: diff --git a/src/client/testing/testController/common/resultResolver.ts b/src/client/testing/testController/common/resultResolver.ts index 49243390ad0f..9b2ad6abdf78 100644 --- a/src/client/testing/testController/common/resultResolver.ts +++ b/src/client/testing/testController/common/resultResolver.ts @@ -24,6 +24,8 @@ export class PythonResultResolver implements ITestResultResolver { public vsIdToRunId: Map; + public subTestStats: Map = new Map(); + constructor(testController: TestController, testProvider: TestProvider, private workspaceUri: Uri) { this.testController = testController; this.testProvider = testProvider; @@ -47,13 +49,13 @@ export class PythonResultResolver implements ITestResultResolver { if (rawTestData.status === 'error') { const testingErrorConst = this.testProvider === 'pytest' ? Testing.errorPytestDiscovery : Testing.errorUnittestDiscovery; - const { errors } = rawTestData; - traceError(testingErrorConst, '\r\n', errors!.join('\r\n\r\n')); + const { error } = rawTestData; + traceError(testingErrorConst, '\r\n', error?.join('\r\n\r\n') ?? ''); let errorNode = this.testController.items.get(`DiscoveryError:${workspacePath}`); const message = util.format( `${testingErrorConst} ${Testing.seePythonOutput}\r\n`, - errors!.join('\r\n\r\n'), + error?.join('\r\n\r\n') ?? '', ); if (errorNode === undefined) { @@ -88,7 +90,6 @@ export class PythonResultResolver implements ITestResultResolver { const rawTestExecData = payload; if (rawTestExecData !== undefined && rawTestExecData.result !== undefined) { // Map which holds the subtest information for each test item. - const subTestStats: Map = new Map(); // iterate through payload and update the UI accordingly. for (const keyTemp of Object.keys(rawTestExecData.result)) { @@ -163,11 +164,11 @@ export class PythonResultResolver implements ITestResultResolver { const data = rawTestExecData.result[keyTemp]; // find the subtest's parent test item if (parentTestItem) { - const subtestStats = subTestStats.get(parentTestCaseId); + const subtestStats = this.subTestStats.get(parentTestCaseId); if (subtestStats) { subtestStats.failed += 1; } else { - subTestStats.set(parentTestCaseId, { failed: 1, passed: 0 }); + this.subTestStats.set(parentTestCaseId, { failed: 1, passed: 0 }); runInstance.appendOutput(fixLogLines(`${parentTestCaseId} [subtests]:\r\n`)); // clear since subtest items don't persist between runs clearAllChildren(parentTestItem); @@ -200,11 +201,11 @@ export class PythonResultResolver implements ITestResultResolver { // find the subtest's parent test item if (parentTestItem) { - const subtestStats = subTestStats.get(parentTestCaseId); + const subtestStats = this.subTestStats.get(parentTestCaseId); if (subtestStats) { subtestStats.passed += 1; } else { - subTestStats.set(parentTestCaseId, { failed: 0, passed: 1 }); + this.subTestStats.set(parentTestCaseId, { failed: 0, passed: 1 }); runInstance.appendOutput(fixLogLines(`${parentTestCaseId} [subtests]:\r\n`)); // clear since subtest items don't persist between runs clearAllChildren(parentTestItem); @@ -229,5 +230,3 @@ export class PythonResultResolver implements ITestResultResolver { return Promise.resolve(); } } - -// had to switch the order of the original parameter since required param cannot follow optional. diff --git a/src/client/testing/testController/common/types.ts b/src/client/testing/testController/common/types.ts index cb7fda797c4a..d4e54951bfd7 100644 --- a/src/client/testing/testController/common/types.ts +++ b/src/client/testing/testController/common/types.ts @@ -231,7 +231,7 @@ export type DiscoveredTestPayload = { cwd: string; tests?: DiscoveredTestNode; status: 'success' | 'error'; - errors?: string[]; + error?: string[]; }; export type ExecutionTestPayload = { diff --git a/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts b/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts index 4378c68b534c..211c322d67f1 100644 --- a/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts +++ b/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts @@ -8,7 +8,7 @@ import { SpawnOptions, } from '../../../common/process/types'; import { IConfigurationService, ITestOutputChannel } from '../../../common/types'; -import { createDeferred, Deferred } from '../../../common/utils/async'; +import { createDeferred } from '../../../common/utils/async'; import { EXTENSION_ROOT_DIR } from '../../../constants'; import { traceError, traceVerbose } from '../../../logging'; import { @@ -23,8 +23,6 @@ import { * Wrapper class for unittest test discovery. This is where we call `runTestCommand`. #this seems incorrectly copied */ export class PytestTestDiscoveryAdapter implements ITestDiscoveryAdapter { - private promiseMap: Map> = new Map(); - constructor( public testServer: ITestServer, public configSettings: IConfigurationService, @@ -55,7 +53,6 @@ export class PytestTestDiscoveryAdapter implements ITestDiscoveryAdapter { const relativePathToPytest = 'pythonFiles'; const fullPluginPath = path.join(EXTENSION_ROOT_DIR, relativePathToPytest); const uuid = this.testServer.createUUID(uri.fsPath); - this.promiseMap.set(uuid, deferred); const settings = this.configSettings.getSettings(uri); const { pytestArgs } = settings.testing; diff --git a/src/client/testing/testController/pytest/pytestExecutionAdapter.ts b/src/client/testing/testController/pytest/pytestExecutionAdapter.ts index 1bf032b9b594..b68b80945ef3 100644 --- a/src/client/testing/testController/pytest/pytestExecutionAdapter.ts +++ b/src/client/testing/testController/pytest/pytestExecutionAdapter.ts @@ -4,7 +4,7 @@ import { TestRun, Uri } from 'vscode'; import * as path from 'path'; import { IConfigurationService, ITestOutputChannel } from '../../../common/types'; -import { createDeferred, Deferred } from '../../../common/utils/async'; +import { createDeferred } from '../../../common/utils/async'; import { traceError, traceInfo, traceLog, traceVerbose } from '../../../logging'; import { DataReceivedEvent, @@ -31,8 +31,6 @@ import { startTestIdServer } from '../common/utils'; */ export class PytestTestExecutionAdapter implements ITestExecutionAdapter { - private promiseMap: Map> = new Map(); - constructor( public testServer: ITestServer, public configSettings: IConfigurationService, @@ -48,6 +46,7 @@ export class PytestTestExecutionAdapter implements ITestExecutionAdapter { executionFactory?: IPythonExecutionFactory, debugLauncher?: ITestDebugLauncher, ): Promise { + const uuid = this.testServer.createUUID(uri.fsPath); traceVerbose(uri, testIds, debugBool); const disposable = this.testServer.onRunDataReceived((e: DataReceivedEvent) => { if (runInstance) { @@ -55,18 +54,26 @@ export class PytestTestExecutionAdapter implements ITestExecutionAdapter { } }); try { - await this.runTestsNew(uri, testIds, debugBool, executionFactory, debugLauncher); + await this.runTestsNew(uri, testIds, uuid, debugBool, executionFactory, debugLauncher); } finally { + this.testServer.deleteUUID(uuid); disposable.dispose(); // confirm with testing that this gets called (it must clean this up) } - const executionPayload: ExecutionTestPayload = { cwd: uri.fsPath, status: 'success', error: '' }; + // placeholder until after the rewrite is adopted + // TODO: remove after adoption. + const executionPayload: ExecutionTestPayload = { + cwd: uri.fsPath, + status: 'success', + error: '', + }; return executionPayload; } private async runTestsNew( uri: Uri, testIds: string[], + uuid: string, debugBool?: boolean, executionFactory?: IPythonExecutionFactory, debugLauncher?: ITestDebugLauncher, @@ -75,8 +82,6 @@ export class PytestTestExecutionAdapter implements ITestExecutionAdapter { const relativePathToPytest = 'pythonFiles'; const fullPluginPath = path.join(EXTENSION_ROOT_DIR, relativePathToPytest); this.configSettings.isTestExecution(); - const uuid = this.testServer.createUUID(uri.fsPath); - this.promiseMap.set(uuid, deferred); const settings = this.configSettings.getSettings(uri); const { pytestArgs } = settings.testing; diff --git a/src/client/testing/testController/unittest/testDiscoveryAdapter.ts b/src/client/testing/testController/unittest/testDiscoveryAdapter.ts index 8d393a8da18d..69438e341f14 100644 --- a/src/client/testing/testController/unittest/testDiscoveryAdapter.ts +++ b/src/client/testing/testController/unittest/testDiscoveryAdapter.ts @@ -4,7 +4,6 @@ import * as path from 'path'; import { Uri } from 'vscode'; import { IConfigurationService, ITestOutputChannel } from '../../../common/types'; -import { createDeferred, Deferred } from '../../../common/utils/async'; import { EXTENSION_ROOT_DIR } from '../../../constants'; import { DataReceivedEvent, @@ -20,8 +19,6 @@ import { * Wrapper class for unittest test discovery. This is where we call `runTestCommand`. */ export class UnittestTestDiscoveryAdapter implements ITestDiscoveryAdapter { - private promiseMap: Map> = new Map(); - private cwd: string | undefined; constructor( @@ -32,7 +29,6 @@ export class UnittestTestDiscoveryAdapter implements ITestDiscoveryAdapter { ) {} public async discoverTests(uri: Uri): Promise { - const deferred = createDeferred(); const settings = this.configSettings.getSettings(uri); const { unittestArgs } = settings.testing; @@ -49,18 +45,21 @@ export class UnittestTestDiscoveryAdapter implements ITestDiscoveryAdapter { outChannel: this.outputChannel, }; - this.promiseMap.set(uuid, deferred); - const disposable = this.testServer.onDiscoveryDataReceived((e: DataReceivedEvent) => { this.resultResolver?.resolveDiscovery(JSON.parse(e.data)); }); try { await this.callSendCommand(options); } finally { + this.testServer.deleteUUID(uuid); disposable.dispose(); - // confirm with testing that this gets called (it must clean this up) } - const discoveryPayload: DiscoveredTestPayload = { cwd: uri.fsPath, status: 'success' }; + // placeholder until after the rewrite is adopted + // TODO: remove after adoption. + const discoveryPayload: DiscoveredTestPayload = { + cwd: uri.fsPath, + status: 'success', + }; return discoveryPayload; } diff --git a/src/client/testing/testController/unittest/testExecutionAdapter.ts b/src/client/testing/testController/unittest/testExecutionAdapter.ts index ca88d3871706..bf3038817f8b 100644 --- a/src/client/testing/testController/unittest/testExecutionAdapter.ts +++ b/src/client/testing/testController/unittest/testExecutionAdapter.ts @@ -4,7 +4,7 @@ import * as path from 'path'; import { TestRun, Uri } from 'vscode'; import { IConfigurationService, ITestOutputChannel } from '../../../common/types'; -import { Deferred, createDeferred } from '../../../common/utils/async'; +import { createDeferred } from '../../../common/utils/async'; import { EXTENSION_ROOT_DIR } from '../../../constants'; import { DataReceivedEvent, @@ -23,8 +23,6 @@ import { startTestIdServer } from '../common/utils'; */ export class UnittestTestExecutionAdapter implements ITestExecutionAdapter { - private promiseMap: Map> = new Map(); - private cwd: string | undefined; constructor( @@ -40,14 +38,16 @@ export class UnittestTestExecutionAdapter implements ITestExecutionAdapter { debugBool?: boolean, runInstance?: TestRun, ): Promise { + const uuid = this.testServer.createUUID(uri.fsPath); const disposable = this.testServer.onRunDataReceived((e: DataReceivedEvent) => { if (runInstance) { this.resultResolver?.resolveExecution(JSON.parse(e.data), runInstance); } }); try { - await this.runTestsNew(uri, testIds, debugBool); + await this.runTestsNew(uri, testIds, uuid, debugBool); } finally { + this.testServer.deleteUUID(uuid); disposable.dispose(); // confirm with testing that this gets called (it must clean this up) } @@ -55,13 +55,17 @@ export class UnittestTestExecutionAdapter implements ITestExecutionAdapter { return executionPayload; } - private async runTestsNew(uri: Uri, testIds: string[], debugBool?: boolean): Promise { + private async runTestsNew( + uri: Uri, + testIds: string[], + uuid: string, + debugBool?: boolean, + ): Promise { const settings = this.configSettings.getSettings(uri); const { cwd, unittestArgs } = settings.testing; const command = buildExecutionCommand(unittestArgs); this.cwd = cwd || uri.fsPath; - const uuid = this.testServer.createUUID(uri.fsPath); const options: TestCommandOptions = { workspaceFolder: uri, @@ -74,16 +78,15 @@ export class UnittestTestExecutionAdapter implements ITestExecutionAdapter { }; const deferred = createDeferred(); - this.promiseMap.set(uuid, deferred); traceLog(`Running UNITTEST execution for the following test ids: ${testIds}`); const runTestIdsPort = await startTestIdServer(testIds); await this.testServer.sendCommand(options, runTestIdsPort.toString(), () => { - // disposable.dispose(); deferred.resolve(); }); - // return deferred.promise; + // placeholder until after the rewrite is adopted + // TODO: remove after adoption. const executionPayload: ExecutionTestPayload = { cwd: uri.fsPath, status: 'success', error: '' }; return executionPayload; } diff --git a/src/test/testing/testController/resultResolver.unit.test.ts b/src/test/testing/testController/resultResolver.unit.test.ts index 57b321c2e36c..94ec1cc10a9a 100644 --- a/src/test/testing/testController/resultResolver.unit.test.ts +++ b/src/test/testing/testController/resultResolver.unit.test.ts @@ -113,7 +113,7 @@ suite('Result Resolver tests', () => { const payload: DiscoveredTestPayload = { cwd: workspaceUri.fsPath, status: 'error', - errors: [errorMessage], + error: [errorMessage], }; const errorTestItemOptions: testItemUtilities.ErrorTestItemOptions = { id: 'id',