From f7ce315aad8f2d872fb6625f64e31227b660074e Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Wed, 1 Nov 2023 10:03:50 -0700 Subject: [PATCH] Add named pipes to pytest discovery and execution --- pythonFiles/vscode_pytest/__init__.py | 70 ++++++++-------- .../testController/common/resultResolver.ts | 46 ++--------- .../testing/testController/common/types.ts | 17 +--- .../testing/testController/common/utils.ts | 82 ++++++++++++++++++- .../testing/testController/controller.ts | 2 - .../pytest/pytestDiscoveryAdapter.ts | 51 +++++------- .../pytest/pytestExecutionAdapter.ts | 62 +++++--------- .../unittest/testDiscoveryAdapter.ts | 2 +- .../unittest/testExecutionAdapter.ts | 2 +- 9 files changed, 170 insertions(+), 164 deletions(-) diff --git a/pythonFiles/vscode_pytest/__init__.py b/pythonFiles/vscode_pytest/__init__.py index a4354179e113b..3040b81381f8f 100644 --- a/pythonFiles/vscode_pytest/__init__.py +++ b/pythonFiles/vscode_pytest/__init__.py @@ -15,11 +15,13 @@ sys.path.append(os.fspath(script_dir / "lib" / "python")) from typing import Any, Dict, List, Optional, Union - -from testing_tools import socket_manager from typing_extensions import Literal, TypedDict -DEFAULT_PORT = 45454 + +# import debugpy + +# debugpy.connect(5678) +# debugpy.wait_for_client() class TestData(TypedDict): @@ -55,19 +57,17 @@ def __init__(self, message): IS_DISCOVERY = False map_id_to_path = dict() collected_tests_so_far = list() -TEST_PORT = os.getenv("TEST_PORT") -TEST_UUID = os.getenv("TEST_UUID") +TEST_RUN_PIPE = os.getenv("TEST_RUN_PIPE") def pytest_load_initial_conftests(early_config, parser, args): - global TEST_PORT - global TEST_UUID - TEST_PORT = os.getenv("TEST_PORT") - TEST_UUID = os.getenv("TEST_UUID") + global TEST_RUN_PIPE + TEST_RUN_PIPE = os.getenv("TEST_RUN_PIPE") error_string = ( - "PYTEST ERROR: TEST_UUID and/or TEST_PORT are not set at the time of pytest starting. Please confirm these environment variables are not being" - " changed or removed as they are required for successful test discovery and execution." - f" \nTEST_UUID = {TEST_UUID}\nTEST_PORT = {TEST_PORT}\n" + "PYTEST ERROR: TEST_RUN_PIPE is not set at the time of pytest starting. " + "Please confirm this environment variable is not being changed or removed " + "as it is required for successful test discovery and execution." + f" \TEST_RUN_PIPE = {TEST_RUN_PIPE}\n" ) print(error_string, file=sys.stderr) if "--collect-only" in args: @@ -636,8 +636,8 @@ def get_node_path(node: Any) -> pathlib.Path: return getattr(node, "path", pathlib.Path(node.fspath)) -__socket = None -atexit.register(lambda: __socket.close() if __socket else None) +__writer = None +atexit.register(lambda: __writer.close() if __writer else None) def execution_post( @@ -700,27 +700,23 @@ def send_post_request( payload -- the payload data to be sent. cls_encoder -- a custom encoder if needed. """ - global TEST_PORT - global TEST_UUID - if TEST_UUID is None or TEST_PORT is None: - # if TEST_UUID or TEST_PORT is None, print an error and fail as these are both critical errors + if not TEST_RUN_PIPE: error_msg = ( - "PYTEST ERROR: TEST_UUID and/or TEST_PORT are not set at the time of pytest starting. Please confirm these environment variables are not being" - " changed or removed as they are required for successful pytest discovery and execution." - f" \nTEST_UUID = {TEST_UUID}\nTEST_PORT = {TEST_PORT}\n" + "PYTEST ERROR: TEST_RUN_PIPE is not set at the time of pytest starting. " + "Please confirm this environment variable is not being changed or removed " + "as it is required for successful test discovery and execution." + f" \TEST_RUN_PIPE = {TEST_RUN_PIPE}\n" ) print(error_msg, file=sys.stderr) raise VSCodePytestError(error_msg) - addr = ("localhost", int(TEST_PORT)) - global __socket + global __writer - if __socket is None: + if __writer is None: try: - __socket = socket_manager.SocketManager(addr) - __socket.connect() + __writer = open(TEST_RUN_PIPE, "wt", encoding="utf-8") except Exception as error: - error_msg = f"Error attempting to connect to extension communication socket[vscode-pytest]: {error}" + error_msg = f"Error attempting to connect to extension named pipe {TEST_RUN_PIPE}[vscode-pytest]: {error}" print(error_msg, file=sys.stderr) print( "If you are on a Windows machine, this error may be occurring if any of your tests clear environment variables" @@ -728,22 +724,26 @@ def send_post_request( "for the correct way to clear environment variables during testing.\n", file=sys.stderr, ) - __socket = None + __writer = None raise VSCodePytestError(error_msg) - data = json.dumps(payload, cls=cls_encoder) - request = f"""Content-Length: {len(data)} -Content-Type: application/json -Request-uuid: {TEST_UUID} + rpc = { + "jsonrpc": "2.0", + "params": payload, + } + data = json.dumps(rpc, cls=cls_encoder) + request = f"""content-length: {len(data)} +content-type: application/json {data}""" try: - if __socket is not None and __socket.socket is not None: - __socket.socket.sendall(request.encode("utf-8")) + if __writer: + __writer.write(request) + __writer.flush() else: print( - f"Plugin error connection error[vscode-pytest], socket is None \n[vscode-pytest] data: \n{request} \n", + f"Plugin error connection error[vscode-pytest], writer is None \n[vscode-pytest] data: \n{request} \n", file=sys.stderr, ) except Exception as error: diff --git a/src/client/testing/testController/common/resultResolver.ts b/src/client/testing/testController/common/resultResolver.ts index 22a13090e1b19..9ff14832344dc 100644 --- a/src/client/testing/testController/common/resultResolver.ts +++ b/src/client/testing/testController/common/resultResolver.ts @@ -14,14 +14,13 @@ import { import * as util from 'util'; import { DiscoveredTestPayload, EOTTestPayload, ExecutionTestPayload, ITestResultResolver } from './types'; import { TestProvider } from '../../types'; -import { traceError, traceLog } from '../../../logging'; +import { traceError } from '../../../logging'; import { Testing } from '../../../common/utils/localize'; import { clearAllChildren, createErrorTestItem, getTestCaseNodes } from './testItemUtilities'; import { sendTelemetryEvent } from '../../../telemetry'; import { EventName } from '../../../telemetry/constants'; import { splitLines } from '../../../common/stringUtils'; import { buildErrorNodeOptions, populateTestTree, splitTestNameWithRegex } from './utils'; -import { Deferred } from '../../../common/utils/async'; export class PythonResultResolver implements ITestResultResolver { testController: TestController; @@ -45,28 +44,16 @@ export class PythonResultResolver implements ITestResultResolver { this.vsIdToRunId = new Map(); } - public resolveDiscovery( - payload: DiscoveredTestPayload | EOTTestPayload, - deferredTillEOT: Deferred, - token?: CancellationToken, - ): Promise { + public resolveDiscovery(payload: DiscoveredTestPayload | EOTTestPayload, token?: CancellationToken): void { if (!payload) { // No test data is available - return Promise.resolve(); + return; } - if ('eot' in payload) { - // the payload is an EOT payload, so resolve the deferred promise. - traceLog('ResultResolver EOT received for discovery.'); - const eotPayload = payload as EOTTestPayload; - if (eotPayload.eot === true) { - deferredTillEOT.resolve(); - return Promise.resolve(); - } - } - return this._resolveDiscovery(payload as DiscoveredTestPayload, token); + + this._resolveDiscovery(payload as DiscoveredTestPayload, token); } - public _resolveDiscovery(payload: DiscoveredTestPayload, token?: CancellationToken): Promise { + public _resolveDiscovery(payload: DiscoveredTestPayload, token?: CancellationToken): void { const workspacePath = this.workspaceUri.fsPath; const rawTestData = payload as DiscoveredTestPayload; // Check if there were any errors in the discovery process. @@ -109,27 +96,13 @@ export class PythonResultResolver implements ITestResultResolver { tool: this.testProvider, failed: false, }); - return Promise.resolve(); } - public resolveExecution( - payload: ExecutionTestPayload | EOTTestPayload, - runInstance: TestRun, - deferredTillEOT: Deferred, - ): Promise { - if (payload !== undefined && 'eot' in payload) { - // the payload is an EOT payload, so resolve the deferred promise. - traceLog('ResultResolver EOT received for execution.'); - const eotPayload = payload as EOTTestPayload; - if (eotPayload.eot === true) { - deferredTillEOT.resolve(); - return Promise.resolve(); - } - } - return this._resolveExecution(payload as ExecutionTestPayload, runInstance); + public resolveExecution(payload: ExecutionTestPayload | EOTTestPayload, runInstance: TestRun): void { + this._resolveExecution(payload as ExecutionTestPayload, runInstance); } - public _resolveExecution(payload: ExecutionTestPayload, runInstance: TestRun): Promise { + public _resolveExecution(payload: ExecutionTestPayload, runInstance: TestRun): void { const rawTestExecData = payload as ExecutionTestPayload; if (rawTestExecData !== undefined && rawTestExecData.result !== undefined) { // Map which holds the subtest information for each test item. @@ -279,6 +252,5 @@ export class PythonResultResolver implements ITestResultResolver { } } } - return Promise.resolve(); } } diff --git a/src/client/testing/testController/common/types.ts b/src/client/testing/testController/common/types.ts index e51270eb4f9e2..b91c12ce6daba 100644 --- a/src/client/testing/testController/common/types.ts +++ b/src/client/testing/testController/common/types.ts @@ -14,7 +14,6 @@ import { } from 'vscode'; import { ITestDebugLauncher, TestDiscoveryOptions } from '../../common/types'; import { IPythonExecutionFactory } from '../../../common/process/types'; -import { Deferred } from '../../../common/utils/async'; import { EnvironmentVariables } from '../../../common/variables/types'; export type TestRunInstanceOptions = TestRunOptions & { @@ -195,18 +194,10 @@ export interface ITestResultResolver { runIdToVSid: Map; runIdToTestItem: Map; vsIdToRunId: Map; - resolveDiscovery( - payload: DiscoveredTestPayload | EOTTestPayload, - deferredTillEOT: Deferred, - token?: CancellationToken, - ): Promise; - resolveExecution( - payload: ExecutionTestPayload | EOTTestPayload, - runInstance: TestRun, - deferredTillEOT: Deferred, - ): Promise; - _resolveDiscovery(payload: DiscoveredTestPayload, token?: CancellationToken): Promise; - _resolveExecution(payload: ExecutionTestPayload, runInstance: TestRun): Promise; + resolveDiscovery(payload: DiscoveredTestPayload | EOTTestPayload, token?: CancellationToken): void; + resolveExecution(payload: ExecutionTestPayload | EOTTestPayload, runInstance: TestRun): void; + _resolveDiscovery(payload: DiscoveredTestPayload, token?: CancellationToken): void; + _resolveExecution(payload: ExecutionTestPayload, runInstance: TestRun): void; } export interface ITestDiscoveryAdapter { // ** first line old method signature, second line new method signature diff --git a/src/client/testing/testController/common/utils.ts b/src/client/testing/testController/common/utils.ts index cf073fdc9c35d..040acc2a88e8f 100644 --- a/src/client/testing/testController/common/utils.ts +++ b/src/client/testing/testController/common/utils.ts @@ -2,7 +2,7 @@ // Licensed under the MIT License. import * as net from 'net'; import * as path from 'path'; -import { CancellationToken, Position, TestController, TestItem, Uri, Range } from 'vscode'; +import { CancellationToken, Position, TestController, TestItem, Uri, Range, Disposable } from 'vscode'; import { Message } from 'vscode-jsonrpc'; import { traceError, traceLog, traceVerbose } from '../../../logging'; @@ -187,6 +187,86 @@ export async function startTestIdsNamedPipe(testIds: string[]): Promise return pipeName; } +interface ExecutionResultMessage extends Message { + params: ExecutionTestPayload | EOTTestPayload; +} + +export async function startRunResultNamedPipe( + callback: (payload: ExecutionTestPayload | EOTTestPayload) => void, + cancellationToken?: CancellationToken, +): Promise<{ name: string } & Disposable> { + const pipeName: string = generateRandomPipeName('python-test-results'); + const server = await createNamedPipeServer(pipeName); + let dispose: () => void = () => { + /* noop */ + }; + server.onConnected().then(([reader, _writer]) => { + traceVerbose(`Test Result named pipe ${pipeName} connected`); + let disposables: (Disposable | undefined)[] = [reader]; + dispose = () => { + traceVerbose(`Test Result named pipe ${pipeName} disposed`); + disposables.forEach((d) => d?.dispose()); + disposables = []; + }; + disposables.push( + cancellationToken?.onCancellationRequested(() => { + traceVerbose(`Test Result named pipe ${pipeName} cancelled`); + dispose(); + }), + reader.listen((data: Message) => { + traceVerbose(`Test Result named pipe ${pipeName} received data`); + callback((data as ExecutionResultMessage).params as ExecutionTestPayload | EOTTestPayload); + }), + reader.onClose(() => { + callback(createEOTPayload(true)); + traceVerbose(`Test Result named pipe ${pipeName} closed`); + dispose(); + }), + ); + }); + return { name: pipeName, dispose }; +} + +interface DiscoveryResultMessage extends Message { + params: DiscoveredTestPayload | EOTTestPayload; +} + +export async function startDiscoveryNamedPipe( + callback: (payload: DiscoveredTestPayload | EOTTestPayload) => void, + cancellationToken?: CancellationToken, +): Promise<{ name: string } & Disposable> { + const pipeName: string = generateRandomPipeName('python-test-discovery'); + const server = await createNamedPipeServer(pipeName); + let dispose: () => void = () => { + /* noop */ + }; + server.onConnected().then(([reader, _writer]) => { + traceVerbose(`Test Discovery named pipe ${pipeName} connected`); + let disposables: (Disposable | undefined)[] = [reader]; + dispose = () => { + traceVerbose(`Test Discovery named pipe ${pipeName} disposed`); + disposables.forEach((d) => d?.dispose()); + disposables = []; + }; + disposables.push( + cancellationToken?.onCancellationRequested(() => { + traceVerbose(`Test Discovery named pipe ${pipeName} cancelled`); + dispose(); + }), + reader.listen((data: Message) => { + traceVerbose(`Test Discovery named pipe ${pipeName} received data`); + callback((data as DiscoveryResultMessage).params as DiscoveredTestPayload | EOTTestPayload); + }), + reader.onClose(() => { + callback(createEOTPayload(true)); + traceVerbose(`Test Discovery named pipe ${pipeName} closed`); + dispose(); + }), + ); + }); + return { name: pipeName, dispose }; +} + export async function startTestIdServer(testIds: string[]): Promise { const startServer = (): Promise => new Promise((resolve, reject) => { diff --git a/src/client/testing/testController/controller.ts b/src/client/testing/testController/controller.ts index bc9d2ca8299f7..29460ee15d53b 100644 --- a/src/client/testing/testController/controller.ts +++ b/src/client/testing/testController/controller.ts @@ -189,14 +189,12 @@ export class PythonTestController implements ITestController, IExtensionSingleAc testProvider = PYTEST_PROVIDER; resultResolver = new PythonResultResolver(this.testController, testProvider, workspace.uri); discoveryAdapter = new PytestTestDiscoveryAdapter( - this.pythonTestServer, this.configSettings, this.testOutputChannel, resultResolver, this.envVarsService, ); executionAdapter = new PytestTestExecutionAdapter( - this.pythonTestServer, this.configSettings, this.testOutputChannel, resultResolver, diff --git a/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts b/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts index 7599513497d72..1274ac3634704 100644 --- a/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts +++ b/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts @@ -11,19 +11,13 @@ import { IConfigurationService, ITestOutputChannel } from '../../../common/types import { Deferred, createDeferred } from '../../../common/utils/async'; import { EXTENSION_ROOT_DIR } from '../../../constants'; import { traceError, traceInfo, traceVerbose } from '../../../logging'; -import { - DataReceivedEvent, - DiscoveredTestPayload, - ITestDiscoveryAdapter, - ITestResultResolver, - ITestServer, -} from '../common/types'; +import { DiscoveredTestPayload, EOTTestPayload, ITestDiscoveryAdapter, ITestResultResolver } from '../common/types'; import { MESSAGE_ON_TESTING_OUTPUT_MOVE, createDiscoveryErrorPayload, - createEOTPayload, createTestingDeferred, fixLogLinesNoTrailing, + startDiscoveryNamedPipe, } from '../common/utils'; import { IEnvironmentVariablesProvider } from '../../../common/variables/types'; @@ -32,7 +26,6 @@ import { IEnvironmentVariablesProvider } from '../../../common/variables/types'; */ export class PytestTestDiscoveryAdapter implements ITestDiscoveryAdapter { constructor( - public testServer: ITestServer, public configSettings: IConfigurationService, private readonly outputChannel: ITestOutputChannel, private readonly resultResolver?: ITestResultResolver, @@ -40,29 +33,33 @@ export class PytestTestDiscoveryAdapter implements ITestDiscoveryAdapter { ) {} async discoverTests(uri: Uri, executionFactory?: IPythonExecutionFactory): Promise { - const uuid = this.testServer.createUUID(uri.fsPath); const deferredTillEOT: Deferred = createDeferred(); - const dataReceivedDisposable = this.testServer.onDiscoveryDataReceived(async (e: DataReceivedEvent) => { - this.resultResolver?.resolveDiscovery(JSON.parse(e.data), deferredTillEOT); + + const { name, dispose } = await startDiscoveryNamedPipe((data: DiscoveredTestPayload | EOTTestPayload) => { + if ('eot' in data && data.eot === true) { + deferredTillEOT.resolve(); + return; + } + this.resultResolver?.resolveDiscovery(data); }); - const disposeDataReceiver = function (testServer: ITestServer) { - traceInfo(`Disposing data receiver for ${uri.fsPath} and deleting UUID; pytest discovery.`); - testServer.deleteUUID(uuid); - dataReceivedDisposable.dispose(); - }; + try { - await this.runPytestDiscovery(uri, uuid, executionFactory); + await this.runPytestDiscovery(uri, name, executionFactory); } finally { await deferredTillEOT.promise; traceVerbose('deferredTill EOT resolved'); - disposeDataReceiver(this.testServer); + dispose(); } // this is only a placeholder to handle function overloading until rewrite is finished const discoveryPayload: DiscoveredTestPayload = { cwd: uri.fsPath, status: 'success' }; return discoveryPayload; } - async runPytestDiscovery(uri: Uri, uuid: string, executionFactory?: IPythonExecutionFactory): Promise { + async runPytestDiscovery( + uri: Uri, + discoveryPipeName: string, + executionFactory?: IPythonExecutionFactory, + ): Promise { const relativePathToPytest = 'pythonFiles'; const fullPluginPath = path.join(EXTENSION_ROOT_DIR, relativePathToPytest); const settings = this.configSettings.getSettings(uri); @@ -77,8 +74,7 @@ export class PytestTestDiscoveryAdapter implements ITestDiscoveryAdapter { const pythonPathParts: string[] = mutableEnv.PYTHONPATH?.split(path.delimiter) ?? []; const pythonPathCommand = [fullPluginPath, ...pythonPathParts].join(path.delimiter); mutableEnv.PYTHONPATH = pythonPathCommand; - mutableEnv.TEST_UUID = uuid.toString(); - mutableEnv.TEST_PORT = this.testServer.getPort().toString(); + mutableEnv.TEST_RUN_PIPE = discoveryPipeName; traceInfo(`All environment variables set for pytest discovery: ${JSON.stringify(mutableEnv)}`); const spawnOptions: SpawnOptions = { cwd, @@ -126,16 +122,7 @@ export class PytestTestDiscoveryAdapter implements ITestDiscoveryAdapter { traceError( `Subprocess exited unsuccessfully with exit code ${code} and signal ${signal} on workspace ${uri.fsPath}. Creating and sending error discovery payload`, ); - // if the child process exited with a non-zero exit code, then we need to send the error payload. - this.testServer.triggerDiscoveryDataReceivedEvent({ - uuid, - data: JSON.stringify(createDiscoveryErrorPayload(code, signal, cwd)), - }); - // then send a EOT payload - this.testServer.triggerDiscoveryDataReceivedEvent({ - uuid, - data: JSON.stringify(createEOTPayload(true)), - }); + this.resultResolver?.resolveDiscovery(createDiscoveryErrorPayload(code, signal, cwd)); } // deferredTillEOT is resolved when all data sent on stdout and stderr is received, close event is only called when this occurs // due to the sync reading of the output. diff --git a/src/client/testing/testController/pytest/pytestExecutionAdapter.ts b/src/client/testing/testController/pytest/pytestExecutionAdapter.ts index 6013b973b7a9c..ff35f8769f562 100644 --- a/src/client/testing/testController/pytest/pytestExecutionAdapter.ts +++ b/src/client/testing/testController/pytest/pytestExecutionAdapter.ts @@ -7,13 +7,7 @@ import { ChildProcess } from 'child_process'; import { IConfigurationService, ITestOutputChannel } from '../../../common/types'; import { Deferred } from '../../../common/utils/async'; import { traceError, traceInfo, traceVerbose } from '../../../logging'; -import { - DataReceivedEvent, - ExecutionTestPayload, - ITestExecutionAdapter, - ITestResultResolver, - ITestServer, -} from '../common/types'; +import { EOTTestPayload, ExecutionTestPayload, ITestExecutionAdapter, ITestResultResolver } from '../common/types'; import { ExecutionFactoryCreateWithEnvironmentOptions, IPythonExecutionFactory, @@ -28,7 +22,6 @@ import { IEnvironmentVariablesProvider } from '../../../common/variables/types'; export class PytestTestExecutionAdapter implements ITestExecutionAdapter { constructor( - public testServer: ITestServer, public configSettings: IConfigurationService, private readonly outputChannel: ITestOutputChannel, private readonly resultResolver?: ITestResultResolver, @@ -43,34 +36,26 @@ export class PytestTestExecutionAdapter implements ITestExecutionAdapter { executionFactory?: IPythonExecutionFactory, debugLauncher?: ITestDebugLauncher, ): Promise { - const uuid = this.testServer.createUUID(uri.fsPath); // deferredTillEOT is resolved when all data sent over payload is received const deferredTillEOT: Deferred = utils.createTestingDeferred(); - const dataReceivedDisposable = this.testServer.onRunDataReceived((e: DataReceivedEvent) => { - runInstance?.token.isCancellationRequested; - if (runInstance) { - const eParsed = JSON.parse(e.data); - this.resultResolver?.resolveExecution(eParsed, runInstance, deferredTillEOT); + const { name, dispose } = await utils.startRunResultNamedPipe((data: ExecutionTestPayload | EOTTestPayload) => { + if ('eot' in data && data.eot === true) { + deferredTillEOT.resolve(); + return; + } + if (runInstance && !runInstance.token.isCancellationRequested) { + this.resultResolver?.resolveExecution(data, runInstance); } else { traceError('No run instance found, cannot resolve execution.'); } - }); - const disposeDataReceiver = function (testServer: ITestServer) { - traceInfo(`Disposing data receiver for ${uri.fsPath} and deleting UUID; pytest execution.`); - testServer.deleteUUID(uuid); - dataReceivedDisposable.dispose(); - }; - runInstance?.token.onCancellationRequested(() => { - traceInfo("Test run cancelled, resolving 'till EOT' deferred."); - deferredTillEOT.resolve(); - }); + }, runInstance?.token); try { await this.runTestsNew( uri, testIds, - uuid, + name, runInstance, debugBool, executionFactory, @@ -79,8 +64,8 @@ export class PytestTestExecutionAdapter implements ITestExecutionAdapter { ); } finally { await deferredTillEOT.promise; + dispose(); traceVerbose('deferredTill EOT resolved'); - disposeDataReceiver(this.testServer); } // placeholder until after the rewrite is adopted @@ -96,7 +81,7 @@ export class PytestTestExecutionAdapter implements ITestExecutionAdapter { private async runTestsNew( uri: Uri, testIds: string[], - uuid: string, + resultNamedPipeName: string, runInstance?: TestRun, debugBool?: boolean, executionFactory?: IPythonExecutionFactory, @@ -116,8 +101,7 @@ export class PytestTestExecutionAdapter implements ITestExecutionAdapter { const pythonPathParts: string[] = mutableEnv.PYTHONPATH?.split(path.delimiter) ?? []; const pythonPathCommand = [fullPluginPath, ...pythonPathParts].join(path.delimiter); mutableEnv.PYTHONPATH = pythonPathCommand; - mutableEnv.TEST_UUID = uuid.toString(); - mutableEnv.TEST_PORT = this.testServer.getPort().toString(); + mutableEnv.TEST_RUN_PIPE = resultNamedPipeName; // Create the Python environment in which to execute the command. const creationOptions: ExecutionFactoryCreateWithEnvironmentOptions = { @@ -154,15 +138,11 @@ export class PytestTestExecutionAdapter implements ITestExecutionAdapter { }; if (debugBool) { - const pytestPort = this.testServer.getPort().toString(); - const pytestUUID = uuid.toString(); const launchOptions: LaunchOptions = { cwd, args: testArgs, token: spawnOptions.token, testProvider: PYTEST_PROVIDER, - pytestPort, - pytestUUID, runTestIdsPort: testIdsPipeName, }; traceInfo(`Running DEBUG pytest with arguments: ${testArgs.join(' ')}\r\n`); @@ -220,15 +200,13 @@ export class PytestTestExecutionAdapter implements ITestExecutionAdapter { traceError( `Subprocess closed unsuccessfully with exit code ${code} and signal ${signal}. Creating and sending error execution payload`, ); - this.testServer.triggerRunDataReceivedEvent({ - uuid, - data: JSON.stringify(utils.createExecutionErrorPayload(code, signal, testIds, cwd)), - }); - // then send a EOT payload - this.testServer.triggerRunDataReceivedEvent({ - uuid, - data: JSON.stringify(utils.createEOTPayload(true)), - }); + + if (runInstance) { + this.resultResolver?.resolveExecution( + utils.createExecutionErrorPayload(code, signal, testIds, cwd), + runInstance, + ); + } } // deferredTillEOT is resolved when all data sent on stdout and stderr is received, close event is only called when this occurs // due to the sync reading of the output. diff --git a/src/client/testing/testController/unittest/testDiscoveryAdapter.ts b/src/client/testing/testController/unittest/testDiscoveryAdapter.ts index 75e29afc9712d..582431eb54b53 100644 --- a/src/client/testing/testController/unittest/testDiscoveryAdapter.ts +++ b/src/client/testing/testController/unittest/testDiscoveryAdapter.ts @@ -50,7 +50,7 @@ export class UnittestTestDiscoveryAdapter implements ITestDiscoveryAdapter { }; const dataReceivedDisposable = this.testServer.onDiscoveryDataReceived((e: DataReceivedEvent) => { - this.resultResolver?.resolveDiscovery(JSON.parse(e.data), deferredTillEOT); + this.resultResolver?.resolveDiscovery(JSON.parse(e.data)); }); const disposeDataReceiver = function (testServer: ITestServer) { testServer.deleteUUID(uuid); diff --git a/src/client/testing/testController/unittest/testExecutionAdapter.ts b/src/client/testing/testController/unittest/testExecutionAdapter.ts index d90581a931104..6ea02898f25bb 100644 --- a/src/client/testing/testController/unittest/testExecutionAdapter.ts +++ b/src/client/testing/testController/unittest/testExecutionAdapter.ts @@ -42,7 +42,7 @@ export class UnittestTestExecutionAdapter implements ITestExecutionAdapter { const deferredTillEOT: Deferred = createDeferred(); const disposedDataReceived = this.testServer.onRunDataReceived((e: DataReceivedEvent) => { if (runInstance) { - this.resultResolver?.resolveExecution(JSON.parse(e.data), runInstance, deferredTillEOT); + this.resultResolver?.resolveExecution(JSON.parse(e.data), runInstance); } else { traceError('No run instance found, cannot resolve execution.'); }