From 79334155a8c8731fcc26e08fca1a391e2d079370 Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Fri, 14 Jul 2023 13:15:28 -0700 Subject: [PATCH 01/17] current progress stuck --- .../testing/testController/common/server.ts | 29 ++++++++++++++---- .../testing/testController/common/types.ts | 7 ++++- .../pytest/pytestDiscoveryAdapter.ts | 24 ++++++++------- .../pytest/pytestExecutionAdapter.ts | 30 +++++++++++++++---- .../unittest/testDiscoveryAdapter.ts | 18 +++++++---- .../unittest/testExecutionAdapter.ts | 23 ++++++++++---- 6 files changed, 96 insertions(+), 35 deletions(-) diff --git a/src/client/testing/testController/common/server.ts b/src/client/testing/testController/common/server.ts index f854371ffc35..999a2a8b371e 100644 --- a/src/client/testing/testController/common/server.ts +++ b/src/client/testing/testController/common/server.ts @@ -3,7 +3,7 @@ import * as net from 'net'; import * as crypto from 'crypto'; -import { Disposable, Event, EventEmitter } from 'vscode'; +import { Disposable, Event, EventEmitter, TestRun } from 'vscode'; import * as path from 'path'; import { ExecutionFactoryCreateWithEnvironmentOptions, @@ -40,6 +40,7 @@ export class PythonTestServer implements ITestServer, Disposable { const rpcHeaders = jsonRPCHeaders(buffer.toString()); const uuid = rpcHeaders.headers.get(JSONRPC_UUID_HEADER); const totalContentLength = rpcHeaders.headers.get('Content-Length'); + traceLog('ABCDEFG::: testing for UUID, if no error then good', uuid); if (!uuid) { traceError('On data received: Error occurred because payload UUID is undefined'); this._onDataReceived.fire({ uuid: '', data: '' }); @@ -120,6 +121,7 @@ export class PythonTestServer implements ITestServer, Disposable { } public deleteUUID(uuid: string): void { + traceLog('ABCDEFG::: DELETE IS OCCURRRING'); this.uuids = this.uuids.filter((u) => u !== uuid); } @@ -140,7 +142,12 @@ export class PythonTestServer implements ITestServer, Disposable { return this._onDataReceived.event; } - async sendCommand(options: TestCommandOptions, runTestIdPort?: string, callback?: () => void): Promise { + async sendCommand( + options: TestCommandOptions, + runTestIdPort?: string, + runInstance?: TestRun, + callback?: () => void, + ): Promise { const { uuid } = options; const pythonPathParts: string[] = process.env.PYTHONPATH?.split(path.delimiter) ?? []; @@ -154,7 +161,7 @@ export class PythonTestServer implements ITestServer, Disposable { }; if (spawnOptions.extraVariables) spawnOptions.extraVariables.RUN_TEST_IDS_PORT = runTestIdPort; - const isRun = !options.testIds; + const isRun = runTestIdPort !== undefined; // Create the Python environment in which to execute the command. const creationOptions: ExecutionFactoryCreateWithEnvironmentOptions = { allowEnvironmentFetchExceptions: false, @@ -190,12 +197,20 @@ export class PythonTestServer implements ITestServer, Disposable { } else { if (isRun) { // This means it is running the test - traceInfo(`Running unittests with arguments: ${args}\r\n`); + traceInfo(`EJFB Running unittests with arguments: ${args}\r\n`); } else { // This means it is running discovery - traceLog(`Discovering unittest tests with arguments: ${args}\r\n`); + traceLog(`EJFB Discovering unittest tests with arguments: ${args}\r\n`); } - await execService.exec(args, spawnOptions); + const result = await execService.execObservable(args, spawnOptions); + + runInstance?.token.onCancellationRequested(() => { + result?.proc?.kill(); + }); + result?.proc?.on('close', () => { + traceLog('ABCDEFG::: callback on proc close, server.', uuid); + callback?.(); + }); } } catch (ex) { this.uuids = this.uuids.filter((u) => u !== uuid); @@ -206,6 +221,8 @@ export class PythonTestServer implements ITestServer, Disposable { errors: [(ex as Error).message], }), }); + } finally { + traceLog('ABCDEFG::: done with send command, NO deletion :)'); } } } diff --git a/src/client/testing/testController/common/types.ts b/src/client/testing/testController/common/types.ts index d4e54951bfd7..16c0bd0e3cee 100644 --- a/src/client/testing/testController/common/types.ts +++ b/src/client/testing/testController/common/types.ts @@ -174,7 +174,12 @@ export interface ITestServer { readonly onDataReceived: Event; readonly onRunDataReceived: Event; readonly onDiscoveryDataReceived: Event; - sendCommand(options: TestCommandOptions, runTestIdsPort?: string, callback?: () => void): Promise; + sendCommand( + options: TestCommandOptions, + runTestIdsPort?: string, + runInstance?: TestRun, + callback?: () => void, + ): Promise; serverReady(): Promise; getPort(): number; createUUID(cwd: string): string; diff --git a/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts b/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts index b83224d4161b..a72101588034 100644 --- a/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts +++ b/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts @@ -10,7 +10,7 @@ import { import { IConfigurationService, ITestOutputChannel } from '../../../common/types'; import { createDeferred } from '../../../common/utils/async'; import { EXTENSION_ROOT_DIR } from '../../../constants'; -import { traceError, traceVerbose } from '../../../logging'; +import { traceError, traceLog, traceVerbose } from '../../../logging'; import { DataReceivedEvent, DiscoveredTestPayload, @@ -78,17 +78,21 @@ export class PytestTestDiscoveryAdapter implements ITestDiscoveryAdapter { }; const execService = await executionFactory?.createActivatedEnvironment(creationOptions); // delete UUID following entire discovery finishing. - execService - ?.exec(['-m', 'pytest', '-p', 'vscode_pytest', '--collect-only'].concat(pytestArgs), spawnOptions) - .then(() => { - this.testServer.deleteUUID(uuid); - return deferred.resolve(); - }) - .catch((err) => { - traceError(`Error while trying to run pytest discovery, \n${err}\r\n\r\n`); + try { + const result = execService?.execObservable( + ['-m', 'pytest', '-p', 'vscode_pytest', '--collect-only'].concat(pytestArgs), + spawnOptions, + ); + result?.proc?.on('close', () => { + traceLog('ABCDEFG::: callback on proc close.'); this.testServer.deleteUUID(uuid); - return deferred.reject(err); + deferred.resolve(); }); + } catch (ex) { + traceError(`Error while trying to run pytest discovery, \n${ex}\r\n\r\n`); + this.testServer.deleteUUID(uuid); + deferred.reject(ex); + } return deferred.promise; } } diff --git a/src/client/testing/testController/pytest/pytestExecutionAdapter.ts b/src/client/testing/testController/pytest/pytestExecutionAdapter.ts index a75a6089627c..6aa3b6f96e1f 100644 --- a/src/client/testing/testController/pytest/pytestExecutionAdapter.ts +++ b/src/client/testing/testController/pytest/pytestExecutionAdapter.ts @@ -47,18 +47,25 @@ export class PytestTestExecutionAdapter implements ITestExecutionAdapter { debugLauncher?: ITestDebugLauncher, ): Promise { const uuid = this.testServer.createUUID(uri.fsPath); + traceLog('ABCDEFG::: create UUID for run', uuid); traceVerbose(uri, testIds, debugBool); - const disposable = this.testServer.onRunDataReceived((e: DataReceivedEvent) => { + const disposedDataReceived = this.testServer.onRunDataReceived((e: DataReceivedEvent) => { if (runInstance) { this.resultResolver?.resolveExecution(JSON.parse(e.data), runInstance); } }); + const dispose = function (testServer: ITestServer) { + traceLog('ABCDEFG::: dispose w/ uuid', uuid); + testServer.deleteUUID(uuid); + disposedDataReceived.dispose(); + }; + runInstance?.token.onCancellationRequested(() => { + dispose(this.testServer); + }); try { - await this.runTestsNew(uri, testIds, uuid, debugBool, executionFactory, debugLauncher); + await this.runTestsNew(uri, testIds, uuid, runInstance, debugBool, executionFactory, debugLauncher); } finally { - this.testServer.deleteUUID(uuid); - disposable.dispose(); - // confirm with testing that this gets called (it must clean this up) + // dispose(this.testServer); } // placeholder until after the rewrite is adopted // TODO: remove after adoption. @@ -74,6 +81,7 @@ export class PytestTestExecutionAdapter implements ITestExecutionAdapter { uri: Uri, testIds: string[], uuid: string, + runInstance?: TestRun, debugBool?: boolean, executionFactory?: IPythonExecutionFactory, debugLauncher?: ITestDebugLauncher, @@ -150,7 +158,17 @@ export class PytestTestExecutionAdapter implements ITestExecutionAdapter { const runArgs = [scriptPath, ...testArgs]; traceInfo(`Running pytests with arguments: ${runArgs.join(' ')}\r\n`); - await execService?.exec(runArgs, spawnOptions); + const result = await execService?.execObservable(runArgs, spawnOptions); + + runInstance?.token.onCancellationRequested(() => { + result?.proc?.kill(); + }); + + result?.proc?.on('close', () => { + traceLog('ABCDEFG::: callback on proc close, delete UUID.', uuid); + this.testServer.deleteUUID(uuid); + deferred.resolve(); + }); } } catch (ex) { traceError(`Error while running tests: ${testIds}\r\n${ex}\r\n\r\n`); diff --git a/src/client/testing/testController/unittest/testDiscoveryAdapter.ts b/src/client/testing/testController/unittest/testDiscoveryAdapter.ts index 6deca55117ea..489835d454fe 100644 --- a/src/client/testing/testController/unittest/testDiscoveryAdapter.ts +++ b/src/client/testing/testController/unittest/testDiscoveryAdapter.ts @@ -14,6 +14,7 @@ import { TestCommandOptions, TestDiscoveryCommand, } from '../common/types'; +import { traceLog } from '../../../logging'; /** * Wrapper class for unittest test discovery. This is where we call `runTestCommand`. @@ -34,6 +35,7 @@ export class UnittestTestDiscoveryAdapter implements ITestDiscoveryAdapter { const command = buildDiscoveryCommand(unittestArgs); const uuid = this.testServer.createUUID(uri.fsPath); + traceLog(`ABCDEFG: create UUID ${uuid}`); const options: TestCommandOptions = { workspaceFolder: uri, @@ -46,12 +48,16 @@ export class UnittestTestDiscoveryAdapter implements ITestDiscoveryAdapter { const disposable = this.testServer.onDiscoveryDataReceived((e: DataReceivedEvent) => { this.resultResolver?.resolveDiscovery(JSON.parse(e.data)); }); - try { - await this.callSendCommand(options); - } finally { + + await this.callSendCommand(options, () => { this.testServer.deleteUUID(uuid); disposable.dispose(); - } + }); + + // finally { + // this.testServer.deleteUUID(uuid); + // disposable.dispose(); + // } // placeholder until after the rewrite is adopted // TODO: remove after adoption. const discoveryPayload: DiscoveredTestPayload = { @@ -61,8 +67,8 @@ export class UnittestTestDiscoveryAdapter implements ITestDiscoveryAdapter { return discoveryPayload; } - private async callSendCommand(options: TestCommandOptions): Promise { - await this.testServer.sendCommand(options); + private async callSendCommand(options: TestCommandOptions, callback: () => void): Promise { + await this.testServer.sendCommand(options, undefined, undefined, callback); const discoveryPayload: DiscoveredTestPayload = { cwd: '', status: 'success' }; return discoveryPayload; } diff --git a/src/client/testing/testController/unittest/testExecutionAdapter.ts b/src/client/testing/testController/unittest/testExecutionAdapter.ts index 4cab941c2608..42602fc236f8 100644 --- a/src/client/testing/testController/unittest/testExecutionAdapter.ts +++ b/src/client/testing/testController/unittest/testExecutionAdapter.ts @@ -37,17 +37,24 @@ export class UnittestTestExecutionAdapter implements ITestExecutionAdapter { runInstance?: TestRun, ): Promise { const uuid = this.testServer.createUUID(uri.fsPath); - const disposable = this.testServer.onRunDataReceived((e: DataReceivedEvent) => { + traceLog('ABCDEFG::: created UUID', uuid); + const disposedDataReceived = this.testServer.onRunDataReceived((e: DataReceivedEvent) => { if (runInstance) { this.resultResolver?.resolveExecution(JSON.parse(e.data), runInstance); } }); + const dispose = function () { + traceLog('ABCDEFG::: no dispose of uuid :)'); + disposedDataReceived.dispose(); + }; + runInstance?.token.onCancellationRequested(() => { + this.testServer.deleteUUID(uuid); + dispose(); + }); try { - await this.runTestsNew(uri, testIds, uuid, debugBool); + await this.runTestsNew(uri, testIds, uuid, runInstance, debugBool, dispose); } finally { - this.testServer.deleteUUID(uuid); - disposable.dispose(); - // confirm with testing that this gets called (it must clean this up) + // dispose(this.testServer); } const executionPayload: ExecutionTestPayload = { cwd: uri.fsPath, status: 'success', error: '' }; return executionPayload; @@ -57,7 +64,9 @@ export class UnittestTestExecutionAdapter implements ITestExecutionAdapter { uri: Uri, testIds: string[], uuid: string, + runInstance?: TestRun, debugBool?: boolean, + dispose?: () => void, ): Promise { const settings = this.configSettings.getSettings(uri); const { unittestArgs } = settings.testing; @@ -80,8 +89,10 @@ export class UnittestTestExecutionAdapter implements ITestExecutionAdapter { const runTestIdsPort = await startTestIdServer(testIds); - await this.testServer.sendCommand(options, runTestIdsPort.toString(), () => { + await this.testServer.sendCommand(options, runTestIdsPort.toString(), runInstance, () => { + this.testServer.deleteUUID(uuid); deferred.resolve(); + dispose?.(); }); // placeholder until after the rewrite is adopted // TODO: remove after adoption. From 736f4b0fc3e919072035b6c9bc4571324a0ab94d Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Tue, 18 Jul 2023 15:30:32 -0700 Subject: [PATCH 02/17] stuck p2 --- .../testing/testController/common/server.ts | 11 +++++++-- .../pytest/pytestDiscoveryAdapter.ts | 24 ++++++++----------- .../pytest/pytestExecutionAdapter.ts | 6 ++++- .../unittest/testExecutionAdapter.ts | 1 + 4 files changed, 25 insertions(+), 17 deletions(-) diff --git a/src/client/testing/testController/common/server.ts b/src/client/testing/testController/common/server.ts index 999a2a8b371e..c10d63ebfdda 100644 --- a/src/client/testing/testController/common/server.ts +++ b/src/client/testing/testController/common/server.ts @@ -7,6 +7,7 @@ import { Disposable, Event, EventEmitter, TestRun } from 'vscode'; import * as path from 'path'; import { ExecutionFactoryCreateWithEnvironmentOptions, + ExecutionResult, IPythonExecutionFactory, SpawnOptions, } from '../../../common/process/types'; @@ -15,6 +16,7 @@ import { DataReceivedEvent, ITestServer, TestCommandOptions } from './types'; import { ITestDebugLauncher, LaunchOptions } from '../../common/types'; import { UNITTEST_PROVIDER } from '../../common/constants'; import { jsonRPCHeaders, jsonRPCContent, JSONRPC_UUID_HEADER } from './utils'; +import { createDeferred } from '../../../common/utils/async'; export class PythonTestServer implements ITestServer, Disposable { private _onDataReceived: EventEmitter = new EventEmitter(); @@ -30,6 +32,7 @@ export class PythonTestServer implements ITestServer, Disposable { private _onDiscoveryDataReceived: EventEmitter = new EventEmitter(); constructor(private executionFactory: IPythonExecutionFactory, private debugLauncher: ITestDebugLauncher) { + traceLog('ABCDEFG::: testing for UUID, if no error then good'); this.server = net.createServer((socket: net.Socket) => { let buffer: Buffer = Buffer.alloc(0); // Buffer to accumulate received data socket.on('data', (data: Buffer) => { @@ -202,15 +205,19 @@ export class PythonTestServer implements ITestServer, Disposable { // This means it is running discovery traceLog(`EJFB Discovering unittest tests with arguments: ${args}\r\n`); } - const result = await execService.execObservable(args, spawnOptions); + const deferred = createDeferred>(); + + const result = execService.execObservable(args, spawnOptions); runInstance?.token.onCancellationRequested(() => { result?.proc?.kill(); }); result?.proc?.on('close', () => { - traceLog('ABCDEFG::: callback on proc close, server.', uuid); + traceLog('Exec server closed.', uuid); + deferred.resolve({ stdout: '', stderr: '' }); callback?.(); }); + await deferred.promise; } } catch (ex) { this.uuids = this.uuids.filter((u) => u !== uuid); diff --git a/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts b/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts index a72101588034..b83224d4161b 100644 --- a/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts +++ b/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts @@ -10,7 +10,7 @@ import { import { IConfigurationService, ITestOutputChannel } from '../../../common/types'; import { createDeferred } from '../../../common/utils/async'; import { EXTENSION_ROOT_DIR } from '../../../constants'; -import { traceError, traceLog, traceVerbose } from '../../../logging'; +import { traceError, traceVerbose } from '../../../logging'; import { DataReceivedEvent, DiscoveredTestPayload, @@ -78,21 +78,17 @@ export class PytestTestDiscoveryAdapter implements ITestDiscoveryAdapter { }; const execService = await executionFactory?.createActivatedEnvironment(creationOptions); // delete UUID following entire discovery finishing. - try { - const result = execService?.execObservable( - ['-m', 'pytest', '-p', 'vscode_pytest', '--collect-only'].concat(pytestArgs), - spawnOptions, - ); - result?.proc?.on('close', () => { - traceLog('ABCDEFG::: callback on proc close.'); + execService + ?.exec(['-m', 'pytest', '-p', 'vscode_pytest', '--collect-only'].concat(pytestArgs), spawnOptions) + .then(() => { + this.testServer.deleteUUID(uuid); + return deferred.resolve(); + }) + .catch((err) => { + traceError(`Error while trying to run pytest discovery, \n${err}\r\n\r\n`); this.testServer.deleteUUID(uuid); - deferred.resolve(); + return deferred.reject(err); }); - } catch (ex) { - traceError(`Error while trying to run pytest discovery, \n${ex}\r\n\r\n`); - this.testServer.deleteUUID(uuid); - deferred.reject(ex); - } return deferred.promise; } } diff --git a/src/client/testing/testController/pytest/pytestExecutionAdapter.ts b/src/client/testing/testController/pytest/pytestExecutionAdapter.ts index 6aa3b6f96e1f..ccaaeafdcfb2 100644 --- a/src/client/testing/testController/pytest/pytestExecutionAdapter.ts +++ b/src/client/testing/testController/pytest/pytestExecutionAdapter.ts @@ -15,6 +15,7 @@ import { } from '../common/types'; import { ExecutionFactoryCreateWithEnvironmentOptions, + ExecutionResult, IPythonExecutionFactory, SpawnOptions, } from '../../../common/process/types'; @@ -158,7 +159,8 @@ export class PytestTestExecutionAdapter implements ITestExecutionAdapter { const runArgs = [scriptPath, ...testArgs]; traceInfo(`Running pytests with arguments: ${runArgs.join(' ')}\r\n`); - const result = await execService?.execObservable(runArgs, spawnOptions); + const deferred2 = createDeferred>(); + const result = execService?.execObservable(runArgs, spawnOptions); runInstance?.token.onCancellationRequested(() => { result?.proc?.kill(); @@ -166,9 +168,11 @@ export class PytestTestExecutionAdapter implements ITestExecutionAdapter { result?.proc?.on('close', () => { traceLog('ABCDEFG::: callback on proc close, delete UUID.', uuid); + deferred2.resolve({ stdout: '', stderr: '' }); this.testServer.deleteUUID(uuid); deferred.resolve(); }); + await deferred2.promise; } } catch (ex) { traceError(`Error while running tests: ${testIds}\r\n${ex}\r\n\r\n`); diff --git a/src/client/testing/testController/unittest/testExecutionAdapter.ts b/src/client/testing/testController/unittest/testExecutionAdapter.ts index 42602fc236f8..289d9daa86f3 100644 --- a/src/client/testing/testController/unittest/testExecutionAdapter.ts +++ b/src/client/testing/testController/unittest/testExecutionAdapter.ts @@ -90,6 +90,7 @@ export class UnittestTestExecutionAdapter implements ITestExecutionAdapter { const runTestIdsPort = await startTestIdServer(testIds); await this.testServer.sendCommand(options, runTestIdsPort.toString(), runInstance, () => { + traceLog('ABCDEFG::: resolve deferred, of send command in execution to then call delete UUID'); this.testServer.deleteUUID(uuid); deferred.resolve(); dispose?.(); From 302a7b26f3b996dd5fe0632779dbf71b6ab3436b Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Wed, 19 Jul 2023 11:02:12 -0700 Subject: [PATCH 03/17] working now for all 4 --- .../pytest/pytestDiscoveryAdapter.ts | 37 ++++++++++++------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts b/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts index b83224d4161b..e9c22b6026b5 100644 --- a/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts +++ b/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts @@ -4,13 +4,14 @@ import * as path from 'path'; import { Uri } from 'vscode'; import { ExecutionFactoryCreateWithEnvironmentOptions, + ExecutionResult, IPythonExecutionFactory, SpawnOptions, } from '../../../common/process/types'; import { IConfigurationService, ITestOutputChannel } from '../../../common/types'; import { createDeferred } from '../../../common/utils/async'; import { EXTENSION_ROOT_DIR } from '../../../constants'; -import { traceError, traceVerbose } from '../../../logging'; +import { traceError, traceLog, traceVerbose } from '../../../logging'; import { DataReceivedEvent, DiscoveredTestPayload, @@ -78,17 +79,27 @@ export class PytestTestDiscoveryAdapter implements ITestDiscoveryAdapter { }; const execService = await executionFactory?.createActivatedEnvironment(creationOptions); // delete UUID following entire discovery finishing. - execService - ?.exec(['-m', 'pytest', '-p', 'vscode_pytest', '--collect-only'].concat(pytestArgs), spawnOptions) - .then(() => { - this.testServer.deleteUUID(uuid); - return deferred.resolve(); - }) - .catch((err) => { - traceError(`Error while trying to run pytest discovery, \n${err}\r\n\r\n`); - this.testServer.deleteUUID(uuid); - return deferred.reject(err); - }); - return deferred.promise; + const deferred2 = createDeferred>(); + const execArgs = ['-m', 'pytest', '-p', 'vscode_pytest', '--collect-only'].concat(pytestArgs); + const result = execService?.execObservable(execArgs, spawnOptions); + + result?.proc?.on('close', () => { + traceLog('ABCDEFG::: callback on proc close, delete UUID.', uuid); + deferred2.resolve({ stdout: '', stderr: '' }); + this.testServer.deleteUUID(uuid); + deferred.resolve(); + }); + await deferred2.promise; + + // .then(() => { + // this.testServer.deleteUUID(uuid); + // return deferred.resolve(); + // }) + // .catch((err) => { + // traceError(`Error while trying to run pytest discovery, \n${err}\r\n\r\n`); + // this.testServer.deleteUUID(uuid); + // return deferred.reject(err); + // }); + // return deferred.promise; } } From 16cc307a27e450e0240ec1b661a4789b4cc687fe Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Wed, 19 Jul 2023 11:53:53 -0700 Subject: [PATCH 04/17] pytest work --- .../testing/testController/pytest/pytestDiscoveryAdapter.ts | 4 ++-- .../testing/testController/pytest/pytestExecutionAdapter.ts | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts b/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts index e9c22b6026b5..e371aa12dd01 100644 --- a/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts +++ b/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts @@ -11,7 +11,7 @@ import { import { IConfigurationService, ITestOutputChannel } from '../../../common/types'; import { createDeferred } from '../../../common/utils/async'; import { EXTENSION_ROOT_DIR } from '../../../constants'; -import { traceError, traceLog, traceVerbose } from '../../../logging'; +import { traceLog, traceVerbose } from '../../../logging'; import { DataReceivedEvent, DiscoveredTestPayload, @@ -49,7 +49,7 @@ export class PytestTestDiscoveryAdapter implements ITestDiscoveryAdapter { return discoveryPayload; } - async runPytestDiscovery(uri: Uri, executionFactory?: IPythonExecutionFactory): Promise { + async runPytestDiscovery(uri: Uri, executionFactory?: IPythonExecutionFactory): Promise { const deferred = createDeferred(); const relativePathToPytest = 'pythonFiles'; const fullPluginPath = path.join(EXTENSION_ROOT_DIR, relativePathToPytest); diff --git a/src/client/testing/testController/pytest/pytestExecutionAdapter.ts b/src/client/testing/testController/pytest/pytestExecutionAdapter.ts index ccaaeafdcfb2..f20ca133efcb 100644 --- a/src/client/testing/testController/pytest/pytestExecutionAdapter.ts +++ b/src/client/testing/testController/pytest/pytestExecutionAdapter.ts @@ -152,6 +152,7 @@ export class PytestTestExecutionAdapter implements ITestExecutionAdapter { traceInfo(`Running DEBUG pytest with arguments: ${testArgs.join(' ')}\r\n`); await debugLauncher!.launchDebugger(launchOptions, () => { deferred.resolve(); + this.testServer.deleteUUID(uuid); }); } else { // combine path to run script with run args From 6d32bf79cdcd86ee7467600ba7d43aa133fade10 Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Wed, 19 Jul 2023 13:40:06 -0700 Subject: [PATCH 05/17] remove debug logging --- src/client/testing/testController/common/server.ts | 5 ----- .../testing/testController/pytest/pytestDiscoveryAdapter.ts | 1 - .../testing/testController/pytest/pytestExecutionAdapter.ts | 3 --- .../testing/testController/unittest/testDiscoveryAdapter.ts | 2 -- .../testing/testController/unittest/testExecutionAdapter.ts | 3 --- 5 files changed, 14 deletions(-) diff --git a/src/client/testing/testController/common/server.ts b/src/client/testing/testController/common/server.ts index c10d63ebfdda..fb54fdd649e7 100644 --- a/src/client/testing/testController/common/server.ts +++ b/src/client/testing/testController/common/server.ts @@ -32,7 +32,6 @@ export class PythonTestServer implements ITestServer, Disposable { private _onDiscoveryDataReceived: EventEmitter = new EventEmitter(); constructor(private executionFactory: IPythonExecutionFactory, private debugLauncher: ITestDebugLauncher) { - traceLog('ABCDEFG::: testing for UUID, if no error then good'); this.server = net.createServer((socket: net.Socket) => { let buffer: Buffer = Buffer.alloc(0); // Buffer to accumulate received data socket.on('data', (data: Buffer) => { @@ -43,7 +42,6 @@ export class PythonTestServer implements ITestServer, Disposable { const rpcHeaders = jsonRPCHeaders(buffer.toString()); const uuid = rpcHeaders.headers.get(JSONRPC_UUID_HEADER); const totalContentLength = rpcHeaders.headers.get('Content-Length'); - traceLog('ABCDEFG::: testing for UUID, if no error then good', uuid); if (!uuid) { traceError('On data received: Error occurred because payload UUID is undefined'); this._onDataReceived.fire({ uuid: '', data: '' }); @@ -124,7 +122,6 @@ export class PythonTestServer implements ITestServer, Disposable { } public deleteUUID(uuid: string): void { - traceLog('ABCDEFG::: DELETE IS OCCURRRING'); this.uuids = this.uuids.filter((u) => u !== uuid); } @@ -228,8 +225,6 @@ export class PythonTestServer implements ITestServer, Disposable { errors: [(ex as Error).message], }), }); - } finally { - traceLog('ABCDEFG::: done with send command, NO deletion :)'); } } } diff --git a/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts b/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts index e371aa12dd01..3d43fd639d8b 100644 --- a/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts +++ b/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts @@ -84,7 +84,6 @@ export class PytestTestDiscoveryAdapter implements ITestDiscoveryAdapter { const result = execService?.execObservable(execArgs, spawnOptions); result?.proc?.on('close', () => { - traceLog('ABCDEFG::: callback on proc close, delete UUID.', uuid); deferred2.resolve({ stdout: '', stderr: '' }); this.testServer.deleteUUID(uuid); deferred.resolve(); diff --git a/src/client/testing/testController/pytest/pytestExecutionAdapter.ts b/src/client/testing/testController/pytest/pytestExecutionAdapter.ts index f20ca133efcb..7cc718cfce24 100644 --- a/src/client/testing/testController/pytest/pytestExecutionAdapter.ts +++ b/src/client/testing/testController/pytest/pytestExecutionAdapter.ts @@ -48,7 +48,6 @@ export class PytestTestExecutionAdapter implements ITestExecutionAdapter { debugLauncher?: ITestDebugLauncher, ): Promise { const uuid = this.testServer.createUUID(uri.fsPath); - traceLog('ABCDEFG::: create UUID for run', uuid); traceVerbose(uri, testIds, debugBool); const disposedDataReceived = this.testServer.onRunDataReceived((e: DataReceivedEvent) => { if (runInstance) { @@ -56,7 +55,6 @@ export class PytestTestExecutionAdapter implements ITestExecutionAdapter { } }); const dispose = function (testServer: ITestServer) { - traceLog('ABCDEFG::: dispose w/ uuid', uuid); testServer.deleteUUID(uuid); disposedDataReceived.dispose(); }; @@ -168,7 +166,6 @@ export class PytestTestExecutionAdapter implements ITestExecutionAdapter { }); result?.proc?.on('close', () => { - traceLog('ABCDEFG::: callback on proc close, delete UUID.', uuid); deferred2.resolve({ stdout: '', stderr: '' }); this.testServer.deleteUUID(uuid); deferred.resolve(); diff --git a/src/client/testing/testController/unittest/testDiscoveryAdapter.ts b/src/client/testing/testController/unittest/testDiscoveryAdapter.ts index 489835d454fe..acf973b7be07 100644 --- a/src/client/testing/testController/unittest/testDiscoveryAdapter.ts +++ b/src/client/testing/testController/unittest/testDiscoveryAdapter.ts @@ -14,7 +14,6 @@ import { TestCommandOptions, TestDiscoveryCommand, } from '../common/types'; -import { traceLog } from '../../../logging'; /** * Wrapper class for unittest test discovery. This is where we call `runTestCommand`. @@ -35,7 +34,6 @@ export class UnittestTestDiscoveryAdapter implements ITestDiscoveryAdapter { const command = buildDiscoveryCommand(unittestArgs); const uuid = this.testServer.createUUID(uri.fsPath); - traceLog(`ABCDEFG: create UUID ${uuid}`); const options: TestCommandOptions = { workspaceFolder: uri, diff --git a/src/client/testing/testController/unittest/testExecutionAdapter.ts b/src/client/testing/testController/unittest/testExecutionAdapter.ts index 289d9daa86f3..9492b0fb5b04 100644 --- a/src/client/testing/testController/unittest/testExecutionAdapter.ts +++ b/src/client/testing/testController/unittest/testExecutionAdapter.ts @@ -37,14 +37,12 @@ export class UnittestTestExecutionAdapter implements ITestExecutionAdapter { runInstance?: TestRun, ): Promise { const uuid = this.testServer.createUUID(uri.fsPath); - traceLog('ABCDEFG::: created UUID', uuid); const disposedDataReceived = this.testServer.onRunDataReceived((e: DataReceivedEvent) => { if (runInstance) { this.resultResolver?.resolveExecution(JSON.parse(e.data), runInstance); } }); const dispose = function () { - traceLog('ABCDEFG::: no dispose of uuid :)'); disposedDataReceived.dispose(); }; runInstance?.token.onCancellationRequested(() => { @@ -90,7 +88,6 @@ export class UnittestTestExecutionAdapter implements ITestExecutionAdapter { const runTestIdsPort = await startTestIdServer(testIds); await this.testServer.sendCommand(options, runTestIdsPort.toString(), runInstance, () => { - traceLog('ABCDEFG::: resolve deferred, of send command in execution to then call delete UUID'); this.testServer.deleteUUID(uuid); deferred.resolve(); dispose?.(); From c6dc362610ef44c3a2913e805bb70a3829d58d6e Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Wed, 19 Jul 2023 14:42:58 -0700 Subject: [PATCH 06/17] small cleanup --- .../testing/testController/common/server.ts | 4 ++-- .../pytest/pytestDiscoveryAdapter.ts | 17 +++-------------- .../pytest/pytestExecutionAdapter.ts | 19 +++++-------------- .../unittest/testDiscoveryAdapter.ts | 5 ----- .../unittest/testExecutionAdapter.ts | 6 +----- 5 files changed, 11 insertions(+), 40 deletions(-) diff --git a/src/client/testing/testController/common/server.ts b/src/client/testing/testController/common/server.ts index fb54fdd649e7..564bd82f2ef6 100644 --- a/src/client/testing/testController/common/server.ts +++ b/src/client/testing/testController/common/server.ts @@ -197,10 +197,10 @@ export class PythonTestServer implements ITestServer, Disposable { } else { if (isRun) { // This means it is running the test - traceInfo(`EJFB Running unittests with arguments: ${args}\r\n`); + traceInfo(`Running unittests with arguments: ${args}\r\n`); } else { // This means it is running discovery - traceLog(`EJFB Discovering unittest tests with arguments: ${args}\r\n`); + traceLog(`Discovering unittest tests with arguments: ${args}\r\n`); } const deferred = createDeferred>(); diff --git a/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts b/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts index 3d43fd639d8b..a70fcdc5a16e 100644 --- a/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts +++ b/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts @@ -79,26 +79,15 @@ export class PytestTestDiscoveryAdapter implements ITestDiscoveryAdapter { }; const execService = await executionFactory?.createActivatedEnvironment(creationOptions); // delete UUID following entire discovery finishing. - const deferred2 = createDeferred>(); + const deferredExec = createDeferred>(); const execArgs = ['-m', 'pytest', '-p', 'vscode_pytest', '--collect-only'].concat(pytestArgs); const result = execService?.execObservable(execArgs, spawnOptions); result?.proc?.on('close', () => { - deferred2.resolve({ stdout: '', stderr: '' }); + deferredExec.resolve({ stdout: '', stderr: '' }); this.testServer.deleteUUID(uuid); deferred.resolve(); }); - await deferred2.promise; - - // .then(() => { - // this.testServer.deleteUUID(uuid); - // return deferred.resolve(); - // }) - // .catch((err) => { - // traceError(`Error while trying to run pytest discovery, \n${err}\r\n\r\n`); - // this.testServer.deleteUUID(uuid); - // return deferred.reject(err); - // }); - // return deferred.promise; + await deferredExec.promise; } } diff --git a/src/client/testing/testController/pytest/pytestExecutionAdapter.ts b/src/client/testing/testController/pytest/pytestExecutionAdapter.ts index 7cc718cfce24..576c4fd38d06 100644 --- a/src/client/testing/testController/pytest/pytestExecutionAdapter.ts +++ b/src/client/testing/testController/pytest/pytestExecutionAdapter.ts @@ -25,12 +25,6 @@ import { PYTEST_PROVIDER } from '../../common/constants'; import { EXTENSION_ROOT_DIR } from '../../../common/constants'; import { startTestIdServer } from '../common/utils'; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -// (global as any).EXTENSION_ROOT_DIR = EXTENSION_ROOT_DIR; -/** - * Wrapper Class for pytest test execution.. - */ - export class PytestTestExecutionAdapter implements ITestExecutionAdapter { constructor( public testServer: ITestServer, @@ -61,11 +55,8 @@ export class PytestTestExecutionAdapter implements ITestExecutionAdapter { runInstance?.token.onCancellationRequested(() => { dispose(this.testServer); }); - try { - await this.runTestsNew(uri, testIds, uuid, runInstance, debugBool, executionFactory, debugLauncher); - } finally { - // dispose(this.testServer); - } + await this.runTestsNew(uri, testIds, uuid, runInstance, debugBool, executionFactory, debugLauncher); + // placeholder until after the rewrite is adopted // TODO: remove after adoption. const executionPayload: ExecutionTestPayload = { @@ -158,7 +149,7 @@ export class PytestTestExecutionAdapter implements ITestExecutionAdapter { const runArgs = [scriptPath, ...testArgs]; traceInfo(`Running pytests with arguments: ${runArgs.join(' ')}\r\n`); - const deferred2 = createDeferred>(); + const deferredExec = createDeferred>(); const result = execService?.execObservable(runArgs, spawnOptions); runInstance?.token.onCancellationRequested(() => { @@ -166,11 +157,11 @@ export class PytestTestExecutionAdapter implements ITestExecutionAdapter { }); result?.proc?.on('close', () => { - deferred2.resolve({ stdout: '', stderr: '' }); + deferredExec.resolve({ stdout: '', stderr: '' }); this.testServer.deleteUUID(uuid); deferred.resolve(); }); - await deferred2.promise; + await deferredExec.promise; } } catch (ex) { traceError(`Error while running tests: ${testIds}\r\n${ex}\r\n\r\n`); diff --git a/src/client/testing/testController/unittest/testDiscoveryAdapter.ts b/src/client/testing/testController/unittest/testDiscoveryAdapter.ts index acf973b7be07..b49ac3dabd0e 100644 --- a/src/client/testing/testController/unittest/testDiscoveryAdapter.ts +++ b/src/client/testing/testController/unittest/testDiscoveryAdapter.ts @@ -51,11 +51,6 @@ export class UnittestTestDiscoveryAdapter implements ITestDiscoveryAdapter { this.testServer.deleteUUID(uuid); disposable.dispose(); }); - - // finally { - // this.testServer.deleteUUID(uuid); - // disposable.dispose(); - // } // placeholder until after the rewrite is adopted // TODO: remove after adoption. const discoveryPayload: DiscoveredTestPayload = { diff --git a/src/client/testing/testController/unittest/testExecutionAdapter.ts b/src/client/testing/testController/unittest/testExecutionAdapter.ts index 9492b0fb5b04..4cd392f93a43 100644 --- a/src/client/testing/testController/unittest/testExecutionAdapter.ts +++ b/src/client/testing/testController/unittest/testExecutionAdapter.ts @@ -49,11 +49,7 @@ export class UnittestTestExecutionAdapter implements ITestExecutionAdapter { this.testServer.deleteUUID(uuid); dispose(); }); - try { - await this.runTestsNew(uri, testIds, uuid, runInstance, debugBool, dispose); - } finally { - // dispose(this.testServer); - } + await this.runTestsNew(uri, testIds, uuid, runInstance, debugBool, dispose); const executionPayload: ExecutionTestPayload = { cwd: uri.fsPath, status: 'success', error: '' }; return executionPayload; } From 31c80f0689bead7327eacdffa9bc34602051b5d7 Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Mon, 24 Jul 2023 09:49:22 -0700 Subject: [PATCH 07/17] linting --- .../testing/testController/pytest/pytestDiscoveryAdapter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts b/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts index a70fcdc5a16e..810fae0fa11c 100644 --- a/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts +++ b/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts @@ -11,7 +11,7 @@ import { import { IConfigurationService, ITestOutputChannel } from '../../../common/types'; import { createDeferred } from '../../../common/utils/async'; import { EXTENSION_ROOT_DIR } from '../../../constants'; -import { traceLog, traceVerbose } from '../../../logging'; +import { traceVerbose } from '../../../logging'; import { DataReceivedEvent, DiscoveredTestPayload, From 6ddc92507e87f5fe3d7af44da46f00384b012221 Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Fri, 28 Jul 2023 10:02:40 -0700 Subject: [PATCH 08/17] work in progress --- .../pytestDiscoveryAdapter.unit.test.ts | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/test/testing/testController/pytest/pytestDiscoveryAdapter.unit.test.ts b/src/test/testing/testController/pytest/pytestDiscoveryAdapter.unit.test.ts index 18212b2d1032..14a3d03ca0c4 100644 --- a/src/test/testing/testController/pytest/pytestDiscoveryAdapter.unit.test.ts +++ b/src/test/testing/testController/pytest/pytestDiscoveryAdapter.unit.test.ts @@ -5,16 +5,20 @@ import * as assert from 'assert'; import { Uri } from 'vscode'; import * as typeMoq from 'typemoq'; import * as path from 'path'; +import { ChildProcess } from 'child_process'; +import { observable } from 'rxjs'; import { IConfigurationService, ITestOutputChannel } from '../../../../client/common/types'; import { PytestTestDiscoveryAdapter } from '../../../../client/testing/testController/pytest/pytestDiscoveryAdapter'; import { ITestServer } from '../../../../client/testing/testController/common/types'; import { IPythonExecutionFactory, IPythonExecutionService, + ObservableExecutionResult, SpawnOptions, } from '../../../../client/common/process/types'; import { createDeferred, Deferred } from '../../../../client/common/utils/async'; import { EXTENSION_ROOT_DIR } from '../../../../client/constants'; +import { execObservable } from '../../../../client/common/process/rawProcessApis'; suite('pytest test discovery adapter', () => { let testServer: typeMoq.IMock; @@ -29,6 +33,8 @@ suite('pytest test discovery adapter', () => { let expectedPath: string; let uri: Uri; let expectedExtraVariables: Record; + let execObservableMock: typeMoq.IMock>; + let mockProc: typeMoq.IMock; setup(() => { const mockExtensionRootDir = typeMoq.Mock.ofType(); @@ -73,14 +79,18 @@ suite('pytest test discovery adapter', () => { .returns(() => Promise.resolve(execService.object)); // set up exec service + mockProc = typeMoq.Mock.ofType(); execService = typeMoq.Mock.ofType(); deferred = createDeferred(); execService - .setup((x) => x.exec(typeMoq.It.isAny(), typeMoq.It.isAny())) - .returns(() => { - deferred.resolve(); - return Promise.resolve({ stdout: '{}' }); - }); + .setup((x) => x.execObservable(typeMoq.It.isAny(), typeMoq.It.isAny())) + .returns(() => ({ + proc: mockProc.object, + out: deferred.promise, + dispose: undefined, + })); + mockProc.setup((p) => p.on('close', typeMoq.It.isAny())).returns(() => undefined); + execObservableMock.object.dispose(); execService.setup((p) => ((p as unknown) as any).then).returns(() => undefined); outputChannel = typeMoq.Mock.ofType(); }); From 3b0079e2e4cdec20c844abec4f46c40f06a6c12a Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Fri, 28 Jul 2023 11:43:56 -0700 Subject: [PATCH 09/17] beginning of mock child process --- src/test/mocks/mockChildProcess.ts | 190 +++++++++++++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 src/test/mocks/mockChildProcess.ts diff --git a/src/test/mocks/mockChildProcess.ts b/src/test/mocks/mockChildProcess.ts new file mode 100644 index 000000000000..01c10372c9f7 --- /dev/null +++ b/src/test/mocks/mockChildProcess.ts @@ -0,0 +1,190 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { Serializable, SendHandle } from 'child_process'; +import { Writable, Readable, Pipe } from 'stream'; +import { EventEmitter, MessageOptions } from 'vscode'; + +export class MockChildProcess extends EventEmitter { + constructor(spawnfile: string, spawnargs: string[]) { + super(); + this.spawnfile = spawnfile; + this.spawnargs = spawnargs; + this.stdin = null; + this.stdout = null; + this.stderr = null; + this.channel = null; + this.stdio = [null, this.stdin, this.stdout, this.stderr, null]; + this.killed = false; + this.connected = false; + this.exitCode = null; + this.signalCode = null; + this.listenerList = []; + } + + stdin: Writable | null; + + stdout: Readable | null; + + stderr: Readable | null; + + listenerList: unknown[]; + + readonly channel?: Pipe | null | undefined; + + readonly stdio: [ + Writable | null, + // stdin + Readable | null, + // stdout + Readable | null, + // stderr + Readable | Writable | null | undefined, + // extra + Readable | Writable | null | undefined, // extra + ]; + + readonly killed: boolean; + + readonly pid?: number | undefined; + + readonly connected: boolean; + + readonly exitCode: number | null; + + readonly signalCode: NodeJS.Signals | null; + + readonly spawnargs: string[]; + + readonly spawnfile: string; + + signal?: NodeJS.Signals | number; + + send(message: Serializable, callback?: (error: Error | null) => void): boolean; + + send(message: Serializable, sendHandle?: SendHandle, callback?: (error: Error | null) => void): boolean; + + send( + message: Serializable, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + sendHandle?: SendHandle, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + options?: MessageOptions, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + callback?: (error: Error | null) => void, + ): boolean { + this.stdin = new Writable(); + this.stdin.write(message.toString()); + return true; + } + + // eslint-disable-next-line class-methods-use-this + disconnect(): void { + /* noop */ + } + + // eslint-disable-next-line class-methods-use-this + unref(): void { + /* noop */ + } + + // eslint-disable-next-line class-methods-use-this + ref(): void { + /* noop */ + } + + addListener(event: string, listener: (...args: any[]) => void): this { + return this; + } + + addListener(event: 'close', listener: (code: number | null, signal: NodeJS.Signals | null) => void): this; + + addListener(event: 'disconnect', listener: () => void): this; + + addListener(event: 'error', listener: (err: Error) => void): this; + + addListener(event: 'exit', listener: (code: number | null, signal: NodeJS.Signals | null) => void): this; + + addListener(event: 'message', listener: (message: Serializable, sendHandle: SendHandle) => void): this; + + addListener(event: 'spawn', listener: () => void): this; + + emit(event: string | symbol, ...args: unknown[]): boolean; + + emit(event: 'close', code: number | null, signal: NodeJS.Signals | null): boolean; + + emit(event: 'disconnect'): boolean; + + emit(event: 'error', err: Error): boolean; + + emit(event: 'exit', code: number | null, signal: NodeJS.Signals | null): boolean; + + emit(event: 'message', message: Serializable, sendHandle: SendHandle): boolean; + + emit(event: 'spawn', listener: () => void): boolean { + /* noop */ + this.emit(event); + return true; + } + + on(event: string, listener: (...args: any[]) => void): this { + this.listenerList.push({ event, listener }); + return this; + } + + on(event: 'close', listener: (code: number | null, signal: NodeJS.Signals | null) => void): this; + + on(event: 'disconnect', listener: () => void): this; + + on(event: 'error', listener: (err: Error) => void): this; + + on(event: 'exit', listener: (code: number | null, signal: NodeJS.Signals | null) => void): this; + + on(event: 'message', listener: (message: Serializable, sendHandle: SendHandle) => void): this; + + on(event: 'spawn', listener: () => void): this; + + once(event: string, listener: (...args: any[]) => void): this { + this.listenerList.push({ event, listener }); + return this; + } + + once(event: 'close', listener: (code: number | null, signal: NodeJS.Signals | null) => void): this; + + once(event: 'disconnect', listener: () => void): this; + + once(event: 'error', listener: (err: Error) => void): this; + + once(event: 'exit', listener: (code: number | null, signal: NodeJS.Signals | null) => void): this; + + once(event: 'message', listener: (message: Serializable, sendHandle: SendHandle) => void): this; + + once(event: 'spawn', listener: () => void): this; + + prependListener(event: string, listener: (...args: any[]) => void): this; + + prependListener(event: 'close', listener: (code: number | null, signal: NodeJS.Signals | null) => void): this; + + prependListener(event: 'disconnect', listener: () => void): this; + + prependListener(event: 'error', listener: (err: Error) => void): this; + + prependListener(event: 'exit', listener: (code: number | null, signal: NodeJS.Signals | null) => void): this; + + prependListener(event: 'message', listener: (message: Serializable, sendHandle: SendHandle) => void): this; + + prependListener(event: 'spawn', listener: () => void): this; + + prependOnceListener(event: string, listener: (...args: any[]) => void): this; + + prependOnceListener(event: 'close', listener: (code: number | null, signal: NodeJS.Signals | null) => void): this; + + prependOnceListener(event: 'disconnect', listener: () => void): this; + + prependOnceListener(event: 'error', listener: (err: Error) => void): this; + + prependOnceListener(event: 'exit', listener: (code: number | null, signal: NodeJS.Signals | null) => void): this; + + prependOnceListener(event: 'message', listener: (message: Serializable, sendHandle: SendHandle) => void): this; + + prependOnceListener(event: 'spawn', listener: () => void): this; +} From 9015a5305860db69fb1eb38fc90b2d46eb07dd40 Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Fri, 28 Jul 2023 12:13:28 -0700 Subject: [PATCH 10/17] new update --- src/test/mocks/mockChildProcess.ts | 87 +++++++++++++++++++++--------- 1 file changed, 62 insertions(+), 25 deletions(-) diff --git a/src/test/mocks/mockChildProcess.ts b/src/test/mocks/mockChildProcess.ts index 01c10372c9f7..f5c9434a996a 100644 --- a/src/test/mocks/mockChildProcess.ts +++ b/src/test/mocks/mockChildProcess.ts @@ -4,21 +4,21 @@ import { Serializable, SendHandle } from 'child_process'; import { Writable, Readable, Pipe } from 'stream'; import { EventEmitter, MessageOptions } from 'vscode'; -export class MockChildProcess extends EventEmitter { +export class MockChildProcess extends EventEmitter { constructor(spawnfile: string, spawnargs: string[]) { super(); this.spawnfile = spawnfile; this.spawnargs = spawnargs; - this.stdin = null; - this.stdout = null; + this.stdin = new Writable(); + this.stdout = new Readable(); this.stderr = null; this.channel = null; - this.stdio = [null, this.stdin, this.stdout, this.stderr, null]; + this.stdio = [this.stdin, this.stdout, this.stdout, this.stderr, null]; this.killed = false; this.connected = false; this.exitCode = null; this.signalCode = null; - this.listenerList = []; + this.eventMap = new Map(); } stdin: Writable | null; @@ -27,7 +27,7 @@ export class MockChildProcess extends EventEmitter { stderr: Readable | null; - listenerList: unknown[]; + eventMap: Map; readonly channel?: Pipe | null | undefined; @@ -72,8 +72,7 @@ export class MockChildProcess extends EventEmitter { // eslint-disable-next-line @typescript-eslint/no-unused-vars callback?: (error: Error | null) => void, ): boolean { - this.stdin = new Writable(); - this.stdin.write(message.toString()); + this.stdin?.write(message.toString()); return true; } @@ -92,10 +91,6 @@ export class MockChildProcess extends EventEmitter { /* noop */ } - addListener(event: string, listener: (...args: any[]) => void): this { - return this; - } - addListener(event: 'close', listener: (code: number | null, signal: NodeJS.Signals | null) => void): this; addListener(event: 'disconnect', listener: () => void): this; @@ -108,7 +103,14 @@ export class MockChildProcess extends EventEmitter { addListener(event: 'spawn', listener: () => void): this; - emit(event: string | symbol, ...args: unknown[]): boolean; + addListener(event: string, listener: (...args: any[]) => void): this { + if (this.eventMap.has(event)) { + this.eventMap.get(event).push(listener); + } else { + this.eventMap.set(event, [listener]); + } + return this; + } emit(event: 'close', code: number | null, signal: NodeJS.Signals | null): boolean; @@ -120,15 +122,16 @@ export class MockChildProcess extends EventEmitter { emit(event: 'message', message: Serializable, sendHandle: SendHandle): boolean; - emit(event: 'spawn', listener: () => void): boolean { - /* noop */ - this.emit(event); - return true; - } + emit(event: 'spawn', listener: () => void): boolean; - on(event: string, listener: (...args: any[]) => void): this { - this.listenerList.push({ event, listener }); - return this; + emit(event: string | symbol, ...args: unknown[]): boolean { + if (this.eventMap.has(event.toString())) { + this.eventMap.get(event.toString()).forEach((listener: (arg0: unknown) => void) => { + const argsArray = Array.isArray(args) ? args : [args]; + listener(argsArray); + }); + } + return true; } on(event: 'close', listener: (code: number | null, signal: NodeJS.Signals | null) => void): this; @@ -143,8 +146,12 @@ export class MockChildProcess extends EventEmitter { on(event: 'spawn', listener: () => void): this; - once(event: string, listener: (...args: any[]) => void): this { - this.listenerList.push({ event, listener }); + on(event: string, listener: (...args: any[]) => void): this { + if (this.eventMap.has(event)) { + this.eventMap.get(event).push(listener); + } else { + this.eventMap.set(event, [listener]); + } return this; } @@ -160,7 +167,14 @@ export class MockChildProcess extends EventEmitter { once(event: 'spawn', listener: () => void): this; - prependListener(event: string, listener: (...args: any[]) => void): this; + once(event: string, listener: (...args: any[]) => void): this { + if (this.eventMap.has(event)) { + this.eventMap.get(event).push(listener); + } else { + this.eventMap.set(event, [listener]); + } + return this; + } prependListener(event: 'close', listener: (code: number | null, signal: NodeJS.Signals | null) => void): this; @@ -174,7 +188,14 @@ export class MockChildProcess extends EventEmitter { prependListener(event: 'spawn', listener: () => void): this; - prependOnceListener(event: string, listener: (...args: any[]) => void): this; + prependListener(event: string, listener: (...args: any[]) => void): this { + if (this.eventMap.has(event)) { + this.eventMap.get(event).push(listener); + } else { + this.eventMap.set(event, [listener]); + } + return this; + } prependOnceListener(event: 'close', listener: (code: number | null, signal: NodeJS.Signals | null) => void): this; @@ -187,4 +208,20 @@ export class MockChildProcess extends EventEmitter { prependOnceListener(event: 'message', listener: (message: Serializable, sendHandle: SendHandle) => void): this; prependOnceListener(event: 'spawn', listener: () => void): this; + + prependOnceListener(event: string, listener: (...args: any[]) => void): this { + if (this.eventMap.has(event)) { + this.eventMap.get(event).push(listener); + } else { + this.eventMap.set(event, [listener]); + } + return this; + } + + trigger(event: string): Array { + if (this.eventMap.has(event)) { + return this.eventMap.get(event); + } + return []; + } } From b7dd1a7dc4bb879cff6328140c91835c9ffe34ad Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Fri, 28 Jul 2023 13:42:54 -0700 Subject: [PATCH 11/17] passing pytest discovery --- src/test/mocks/mockChildProcess.ts | 23 +++-- .../pytestDiscoveryAdapter.unit.test.ts | 83 +++++++++++++------ 2 files changed, 74 insertions(+), 32 deletions(-) diff --git a/src/test/mocks/mockChildProcess.ts b/src/test/mocks/mockChildProcess.ts index f5c9434a996a..c038c0f845ab 100644 --- a/src/test/mocks/mockChildProcess.ts +++ b/src/test/mocks/mockChildProcess.ts @@ -1,8 +1,8 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { Serializable, SendHandle } from 'child_process'; +import { Serializable, SendHandle, MessageOptions } from 'child_process'; import { Writable, Readable, Pipe } from 'stream'; -import { EventEmitter, MessageOptions } from 'vscode'; +import { EventEmitter } from 'node:events'; export class MockChildProcess extends EventEmitter { constructor(spawnfile: string, spawnargs: string[]) { @@ -65,14 +65,20 @@ export class MockChildProcess extends EventEmitter { send( message: Serializable, - // eslint-disable-next-line @typescript-eslint/no-unused-vars sendHandle?: SendHandle, - // eslint-disable-next-line @typescript-eslint/no-unused-vars options?: MessageOptions, - // eslint-disable-next-line @typescript-eslint/no-unused-vars callback?: (error: Error | null) => void, + ): boolean; + + send( + message: Serializable, + _sendHandleOrCallback?: SendHandle | ((error: Error | null) => void), + _optionsOrCallback?: MessageOptions | ((error: Error | null) => void), + _callback?: (error: Error | null) => void, ): boolean { - this.stdin?.write(message.toString()); + // Implementation of the send method + // For example, you might want to emit a 'message' event + this.stdout?.push(message.toString()); return true; } @@ -224,4 +230,9 @@ export class MockChildProcess extends EventEmitter { } return []; } + + kill(_signal?: NodeJS.Signals | number): boolean { + this.stdout?.destroy(); + return true; + } } diff --git a/src/test/testing/testController/pytest/pytestDiscoveryAdapter.unit.test.ts b/src/test/testing/testController/pytest/pytestDiscoveryAdapter.unit.test.ts index 14a3d03ca0c4..12916b494ab6 100644 --- a/src/test/testing/testController/pytest/pytestDiscoveryAdapter.unit.test.ts +++ b/src/test/testing/testController/pytest/pytestDiscoveryAdapter.unit.test.ts @@ -5,20 +5,19 @@ import * as assert from 'assert'; import { Uri } from 'vscode'; import * as typeMoq from 'typemoq'; import * as path from 'path'; -import { ChildProcess } from 'child_process'; -import { observable } from 'rxjs'; +import { Observable } from 'rxjs/Observable'; import { IConfigurationService, ITestOutputChannel } from '../../../../client/common/types'; import { PytestTestDiscoveryAdapter } from '../../../../client/testing/testController/pytest/pytestDiscoveryAdapter'; import { ITestServer } from '../../../../client/testing/testController/common/types'; import { IPythonExecutionFactory, IPythonExecutionService, - ObservableExecutionResult, SpawnOptions, + Output, } from '../../../../client/common/process/types'; -import { createDeferred, Deferred } from '../../../../client/common/utils/async'; import { EXTENSION_ROOT_DIR } from '../../../../client/constants'; -import { execObservable } from '../../../../client/common/process/rawProcessApis'; +import { MockChildProcess } from '../../../mocks/mockChildProcess'; +import { Deferred, createDeferred } from '../../../../client/common/utils/async'; suite('pytest test discovery adapter', () => { let testServer: typeMoq.IMock; @@ -33,8 +32,8 @@ suite('pytest test discovery adapter', () => { let expectedPath: string; let uri: Uri; let expectedExtraVariables: Record; - let execObservableMock: typeMoq.IMock>; - let mockProc: typeMoq.IMock; + // let execObservableMock: typeMoq.IMock>; + let mockProc: MockChildProcess; setup(() => { const mockExtensionRootDir = typeMoq.Mock.ofType(); @@ -72,36 +71,49 @@ suite('pytest test discovery adapter', () => { }), } as unknown) as IConfigurationService; - // set up exec factory - execFactory = typeMoq.Mock.ofType(); - execFactory - .setup((x) => x.createActivatedEnvironment(typeMoq.It.isAny())) - .returns(() => Promise.resolve(execService.object)); - // set up exec service - mockProc = typeMoq.Mock.ofType(); + mockProc = new MockChildProcess('', ['']); execService = typeMoq.Mock.ofType(); - deferred = createDeferred(); + // deferred = createDeferred(); + // create Observable> + const output = new Observable>(() => { + /* no op */ + }); execService .setup((x) => x.execObservable(typeMoq.It.isAny(), typeMoq.It.isAny())) .returns(() => ({ - proc: mockProc.object, - out: deferred.promise, - dispose: undefined, + proc: mockProc, + out: output, + dispose: () => { + /* no-body */ + }, })); - mockProc.setup((p) => p.on('close', typeMoq.It.isAny())).returns(() => undefined); - execObservableMock.object.dispose(); execService.setup((p) => ((p as unknown) as any).then).returns(() => undefined); outputChannel = typeMoq.Mock.ofType(); }); test('Discovery should call exec with correct basic args', async () => { + // set up exec mock + deferred = createDeferred(); + execFactory = typeMoq.Mock.ofType(); + execFactory + .setup((x) => x.createActivatedEnvironment(typeMoq.It.isAny())) + .returns(() => { + console.log('hello'); + deferred.resolve(); + return Promise.resolve(execService.object); + }); + adapter = new PytestTestDiscoveryAdapter(testServer.object, configService, outputChannel.object); - await adapter.discoverTests(uri, execFactory.object); - const expectedArgs = ['-m', 'pytest', '-p', 'vscode_pytest', '--collect-only', '.']; + adapter.discoverTests(uri, execFactory.object); + // add in await and trigger + await deferred.promise; + mockProc.trigger('close'); + // verification + const expectedArgs = ['-m', 'pytest', '-p', 'vscode_pytest', '--collect-only', '.']; execService.verify( (x) => - x.exec( + x.execObservable( expectedArgs, typeMoq.It.is((options) => { assert.deepEqual(options.extraVariables, expectedExtraVariables); @@ -118,16 +130,35 @@ suite('pytest test discovery adapter', () => { const expectedPathNew = path.join('other', 'path'); const configServiceNew: IConfigurationService = ({ getSettings: () => ({ - testing: { pytestArgs: ['.', 'abc', 'xyz'], cwd: expectedPathNew }, + testing: { + pytestArgs: ['.', 'abc', 'xyz'], + cwd: expectedPathNew, + }, }), } as unknown) as IConfigurationService; + // set up exec mock + deferred = createDeferred(); + execFactory = typeMoq.Mock.ofType(); + execFactory + .setup((x) => x.createActivatedEnvironment(typeMoq.It.isAny())) + .returns(() => { + console.log('hello'); + deferred.resolve(); + return Promise.resolve(execService.object); + }); + adapter = new PytestTestDiscoveryAdapter(testServer.object, configServiceNew, outputChannel.object); - await adapter.discoverTests(uri, execFactory.object); + adapter.discoverTests(uri, execFactory.object); + // add in await and trigger + await deferred.promise; + mockProc.trigger('close'); + + // verification const expectedArgs = ['-m', 'pytest', '-p', 'vscode_pytest', '--collect-only', '.', 'abc', 'xyz']; execService.verify( (x) => - x.exec( + x.execObservable( expectedArgs, typeMoq.It.is((options) => { assert.deepEqual(options.extraVariables, expectedExtraVariables); From 212dbaf58b307abbda1af78eb89c6a50c3f303fc Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Fri, 28 Jul 2023 14:47:48 -0700 Subject: [PATCH 12/17] pytest run tests --- .../pytest/pytestExecutionAdapter.ts | 4 +- .../pytestDiscoveryAdapter.unit.test.ts | 5 +- .../pytestExecutionAdapter.unit.test.ts | 125 +++++++++++++++--- 3 files changed, 109 insertions(+), 25 deletions(-) diff --git a/src/client/testing/testController/pytest/pytestExecutionAdapter.ts b/src/client/testing/testController/pytest/pytestExecutionAdapter.ts index 576c4fd38d06..b05fa21fc046 100644 --- a/src/client/testing/testController/pytest/pytestExecutionAdapter.ts +++ b/src/client/testing/testController/pytest/pytestExecutionAdapter.ts @@ -23,7 +23,7 @@ import { removePositionalFoldersAndFiles } from './arguments'; import { ITestDebugLauncher, LaunchOptions } from '../../common/types'; import { PYTEST_PROVIDER } from '../../common/constants'; import { EXTENSION_ROOT_DIR } from '../../../common/constants'; -import { startTestIdServer } from '../common/utils'; +import * as utils from '../common/utils'; export class PytestTestExecutionAdapter implements ITestExecutionAdapter { constructor( @@ -122,7 +122,7 @@ export class PytestTestExecutionAdapter implements ITestExecutionAdapter { } traceLog(`Running PYTEST execution for the following test ids: ${testIds}`); - const pytestRunTestIdsPort = await startTestIdServer(testIds); + const pytestRunTestIdsPort = await utils.startTestIdServer(testIds); if (spawnOptions.extraVariables) spawnOptions.extraVariables.RUN_TEST_IDS_PORT = pytestRunTestIdsPort.toString(); diff --git a/src/test/testing/testController/pytest/pytestDiscoveryAdapter.unit.test.ts b/src/test/testing/testController/pytest/pytestDiscoveryAdapter.unit.test.ts index 12916b494ab6..9cc933b9f3bd 100644 --- a/src/test/testing/testController/pytest/pytestDiscoveryAdapter.unit.test.ts +++ b/src/test/testing/testController/pytest/pytestDiscoveryAdapter.unit.test.ts @@ -32,7 +32,6 @@ suite('pytest test discovery adapter', () => { let expectedPath: string; let uri: Uri; let expectedExtraVariables: Record; - // let execObservableMock: typeMoq.IMock>; let mockProc: MockChildProcess; setup(() => { @@ -71,11 +70,9 @@ suite('pytest test discovery adapter', () => { }), } as unknown) as IConfigurationService; - // set up exec service + // set up exec service with child process mockProc = new MockChildProcess('', ['']); execService = typeMoq.Mock.ofType(); - // deferred = createDeferred(); - // create Observable> const output = new Observable>(() => { /* no op */ }); diff --git a/src/test/testing/testController/pytest/pytestExecutionAdapter.unit.test.ts b/src/test/testing/testController/pytest/pytestExecutionAdapter.unit.test.ts index 44116fd753b0..ccc4a5e30e9b 100644 --- a/src/test/testing/testController/pytest/pytestExecutionAdapter.unit.test.ts +++ b/src/test/testing/testController/pytest/pytestExecutionAdapter.unit.test.ts @@ -6,11 +6,13 @@ import { TestRun, Uri } from 'vscode'; import * as typeMoq from 'typemoq'; import * as sinon from 'sinon'; import * as path from 'path'; +import { Observable } from 'rxjs/Observable'; import { IConfigurationService, ITestOutputChannel } from '../../../../client/common/types'; import { ITestServer } from '../../../../client/testing/testController/common/types'; import { IPythonExecutionFactory, IPythonExecutionService, + Output, SpawnOptions, } from '../../../../client/common/process/types'; import { createDeferred, Deferred } from '../../../../client/common/utils/async'; @@ -18,6 +20,7 @@ import { PytestTestExecutionAdapter } from '../../../../client/testing/testContr import { ITestDebugLauncher, LaunchOptions } from '../../../../client/testing/common/types'; import * as util from '../../../../client/testing/testController/common/utils'; import { EXTENSION_ROOT_DIR } from '../../../../client/constants'; +import { MockChildProcess } from '../../../mocks/mockChildProcess'; suite('pytest test execution adapter', () => { let testServer: typeMoq.IMock; @@ -29,8 +32,8 @@ suite('pytest test execution adapter', () => { let debugLauncher: typeMoq.IMock; (global as any).EXTENSION_ROOT_DIR = EXTENSION_ROOT_DIR; let myTestPath: string; - let startTestIdServerStub: sinon.SinonStub; - + let mockProc: MockChildProcess; + let utilsStub: sinon.SinonStub; setup(() => { testServer = typeMoq.Mock.ofType(); testServer.setup((t) => t.getPort()).returns(() => 12345); @@ -47,8 +50,24 @@ suite('pytest test execution adapter', () => { }), isTestExecution: () => false, } as unknown) as IConfigurationService; - execFactory = typeMoq.Mock.ofType(); + + // set up exec service with child process + mockProc = new MockChildProcess('', ['']); + const output = new Observable>(() => { + /* no op */ + }); execService = typeMoq.Mock.ofType(); + execService + .setup((x) => x.execObservable(typeMoq.It.isAny(), typeMoq.It.isAny())) + .returns(() => ({ + proc: mockProc, + out: output, + dispose: () => { + /* no-body */ + }, + })); + execFactory = typeMoq.Mock.ofType(); + utilsStub = sinon.stub(util, 'startTestIdServer'); debugLauncher = typeMoq.Mock.ofType(); execFactory .setup((x) => x.createActivatedEnvironment(typeMoq.It.isAny())) @@ -66,7 +85,6 @@ suite('pytest test execution adapter', () => { deferred.resolve(); return Promise.resolve(); }); - startTestIdServerStub = sinon.stub(util, 'startTestIdServer').returns(Promise.resolve(54321)); execFactory.setup((p) => ((p as unknown) as any).then).returns(() => undefined); execService.setup((p) => ((p as unknown) as any).then).returns(() => undefined); @@ -77,10 +95,27 @@ suite('pytest test execution adapter', () => { sinon.restore(); }); test('startTestIdServer called with correct testIds', async () => { + const deferred2 = createDeferred(); + const deferred3 = createDeferred(); + execFactory = typeMoq.Mock.ofType(); + execFactory + .setup((x) => x.createActivatedEnvironment(typeMoq.It.isAny())) + .returns(() => { + console.log('hello'); + deferred2.resolve(); + return Promise.resolve(execService.object); + }); + utilsStub.callsFake(() => { + console.log('hi'); + deferred3.resolve(); + return Promise.resolve(54321); + }); + const testRun = typeMoq.Mock.ofType(); + testRun.setup((t) => t.token).returns(() => ({ onCancellationRequested: () => undefined } as any)); const uri = Uri.file(myTestPath); const uuid = 'uuid123'; testServer - .setup((t) => t.onDiscoveryDataReceived(typeMoq.It.isAny(), typeMoq.It.isAny())) + .setup((t) => t.onRunDataReceived(typeMoq.It.isAny(), typeMoq.It.isAny())) .returns(() => ({ dispose: () => { /* no-body */ @@ -88,19 +123,40 @@ suite('pytest test execution adapter', () => { })); testServer.setup((t) => t.createUUID(typeMoq.It.isAny())).returns(() => uuid); const outputChannel = typeMoq.Mock.ofType(); - const testRun = typeMoq.Mock.ofType(); adapter = new PytestTestExecutionAdapter(testServer.object, configService, outputChannel.object); - const testIds = ['test1id', 'test2id']; - await adapter.runTests(uri, testIds, false, testRun.object, execFactory.object); + adapter.runTests(uri, testIds, false, testRun.object, execFactory.object); - sinon.assert.calledWithExactly(startTestIdServerStub, testIds); + // add in await and trigger + await deferred2.promise; + await deferred3.promise; + mockProc.trigger('close'); + + // assert + sinon.assert.calledWithExactly(utilsStub, testIds); }); test('pytest execution called with correct args', async () => { + const deferred2 = createDeferred(); + const deferred3 = createDeferred(); + execFactory = typeMoq.Mock.ofType(); + execFactory + .setup((x) => x.createActivatedEnvironment(typeMoq.It.isAny())) + .returns(() => { + console.log('hello'); + deferred2.resolve(); + return Promise.resolve(execService.object); + }); + utilsStub.callsFake(() => { + console.log('hi'); + deferred3.resolve(); + return Promise.resolve(54321); + }); + const testRun = typeMoq.Mock.ofType(); + testRun.setup((t) => t.token).returns(() => ({ onCancellationRequested: () => undefined } as any)); const uri = Uri.file(myTestPath); const uuid = 'uuid123'; testServer - .setup((t) => t.onDiscoveryDataReceived(typeMoq.It.isAny(), typeMoq.It.isAny())) + .setup((t) => t.onRunDataReceived(typeMoq.It.isAny(), typeMoq.It.isAny())) .returns(() => ({ dispose: () => { /* no-body */ @@ -108,9 +164,12 @@ suite('pytest test execution adapter', () => { })); testServer.setup((t) => t.createUUID(typeMoq.It.isAny())).returns(() => uuid); const outputChannel = typeMoq.Mock.ofType(); - const testRun = typeMoq.Mock.ofType(); adapter = new PytestTestExecutionAdapter(testServer.object, configService, outputChannel.object); - await adapter.runTests(uri, [], false, testRun.object, execFactory.object); + adapter.runTests(uri, [], false, testRun.object, execFactory.object); + + await deferred2.promise; + await deferred3.promise; + mockProc.trigger('close'); const pathToPythonFiles = path.join(EXTENSION_ROOT_DIR, 'pythonFiles'); const pathToPythonScript = path.join(pathToPythonFiles, 'vscode_pytest', 'run_pytest_script.py'); @@ -123,7 +182,7 @@ suite('pytest test execution adapter', () => { // execService.verify((x) => x.exec(expectedArgs, typeMoq.It.isAny()), typeMoq.Times.once()); execService.verify( (x) => - x.exec( + x.execObservable( expectedArgs, typeMoq.It.is((options) => { assert.equal(options.extraVariables?.PYTHONPATH, expectedExtraVariables.PYTHONPATH); @@ -139,6 +198,23 @@ suite('pytest test execution adapter', () => { ); }); test('pytest execution respects settings.testing.cwd when present', async () => { + const deferred2 = createDeferred(); + const deferred3 = createDeferred(); + execFactory = typeMoq.Mock.ofType(); + execFactory + .setup((x) => x.createActivatedEnvironment(typeMoq.It.isAny())) + .returns(() => { + console.log('hello'); + deferred2.resolve(); + return Promise.resolve(execService.object); + }); + utilsStub.callsFake(() => { + console.log('hi'); + deferred3.resolve(); + return Promise.resolve(54321); + }); + const testRun = typeMoq.Mock.ofType(); + testRun.setup((t) => t.token).returns(() => ({ onCancellationRequested: () => undefined } as any)); const newCwd = path.join('new', 'path'); configService = ({ getSettings: () => ({ @@ -149,7 +225,7 @@ suite('pytest test execution adapter', () => { const uri = Uri.file(myTestPath); const uuid = 'uuid123'; testServer - .setup((t) => t.onDiscoveryDataReceived(typeMoq.It.isAny(), typeMoq.It.isAny())) + .setup((t) => t.onRunDataReceived(typeMoq.It.isAny(), typeMoq.It.isAny())) .returns(() => ({ dispose: () => { /* no-body */ @@ -157,9 +233,12 @@ suite('pytest test execution adapter', () => { })); testServer.setup((t) => t.createUUID(typeMoq.It.isAny())).returns(() => uuid); const outputChannel = typeMoq.Mock.ofType(); - const testRun = typeMoq.Mock.ofType(); adapter = new PytestTestExecutionAdapter(testServer.object, configService, outputChannel.object); - await adapter.runTests(uri, [], false, testRun.object, execFactory.object); + adapter.runTests(uri, [], false, testRun.object, execFactory.object); + + await deferred2.promise; + await deferred3.promise; + mockProc.trigger('close'); const pathToPythonFiles = path.join(EXTENSION_ROOT_DIR, 'pythonFiles'); const pathToPythonScript = path.join(pathToPythonFiles, 'vscode_pytest', 'run_pytest_script.py'); @@ -172,7 +251,7 @@ suite('pytest test execution adapter', () => { execService.verify( (x) => - x.exec( + x.execObservable( expectedArgs, typeMoq.It.is((options) => { assert.equal(options.extraVariables?.PYTHONPATH, expectedExtraVariables.PYTHONPATH); @@ -188,10 +267,18 @@ suite('pytest test execution adapter', () => { ); }); test('Debug launched correctly for pytest', async () => { + const deferred3 = createDeferred(); + utilsStub.callsFake(() => { + console.log('hi'); + deferred3.resolve(); + return Promise.resolve(54321); + }); + const testRun = typeMoq.Mock.ofType(); + testRun.setup((t) => t.token).returns(() => ({ onCancellationRequested: () => undefined } as any)); const uri = Uri.file(myTestPath); const uuid = 'uuid123'; testServer - .setup((t) => t.onDiscoveryDataReceived(typeMoq.It.isAny(), typeMoq.It.isAny())) + .setup((t) => t.onRunDataReceived(typeMoq.It.isAny(), typeMoq.It.isAny())) .returns(() => ({ dispose: () => { /* no-body */ @@ -199,9 +286,9 @@ suite('pytest test execution adapter', () => { })); testServer.setup((t) => t.createUUID(typeMoq.It.isAny())).returns(() => uuid); const outputChannel = typeMoq.Mock.ofType(); - const testRun = typeMoq.Mock.ofType(); adapter = new PytestTestExecutionAdapter(testServer.object, configService, outputChannel.object); await adapter.runTests(uri, [], true, testRun.object, execFactory.object, debugLauncher.object); + await deferred3.promise; debugLauncher.verify( (x) => x.launchDebugger( From 71239b83579f03a8c79c929076a1dfc16ebac054 Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Mon, 31 Jul 2023 10:30:24 -0700 Subject: [PATCH 13/17] first two pass --- .../testController/server.unit.test.ts | 213 ++++++++++++------ 1 file changed, 147 insertions(+), 66 deletions(-) diff --git a/src/test/testing/testController/server.unit.test.ts b/src/test/testing/testController/server.unit.test.ts index 1131c26c6444..711299e85d16 100644 --- a/src/test/testing/testController/server.unit.test.ts +++ b/src/test/testing/testController/server.unit.test.ts @@ -1,15 +1,24 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +/* eslint-disable @typescript-eslint/no-explicit-any */ import * as assert from 'assert'; import * as net from 'net'; import * as sinon from 'sinon'; import * as crypto from 'crypto'; import { OutputChannel, Uri } from 'vscode'; -import { IPythonExecutionFactory, IPythonExecutionService, SpawnOptions } from '../../../client/common/process/types'; +import { Observable } from 'rxjs'; +import * as typeMoq from 'typemoq'; +import { + IPythonExecutionFactory, + IPythonExecutionService, + Output, + SpawnOptions, +} from '../../../client/common/process/types'; import { PythonTestServer } from '../../../client/testing/testController/common/server'; import { ITestDebugLauncher } from '../../../client/testing/common/types'; -import { createDeferred } from '../../../client/common/utils/async'; +import { Deferred, createDeferred } from '../../../client/common/utils/async'; +import { MockChildProcess } from '../../mocks/mockChildProcess'; suite('Python Test Server', () => { const fakeUuid = 'fake-uuid'; @@ -22,6 +31,10 @@ suite('Python Test Server', () => { let spawnOptions: SpawnOptions; let v4Stub: sinon.SinonStub; let debugLauncher: ITestDebugLauncher; + let mockProc: MockChildProcess; + let execService: typeMoq.IMock; + let deferred: Deferred; + let execFactory = typeMoq.Mock.ofType(); setup(() => { sandbox = sinon.createSandbox(); @@ -29,7 +42,7 @@ suite('Python Test Server', () => { v4Stub.returns(fakeUuid); stubExecutionService = ({ - exec: (args: string[], spawnOptionsProvided: SpawnOptions) => { + execObservable: (args: string[], spawnOptionsProvided: SpawnOptions) => { execArgs = args; spawnOptions = spawnOptionsProvided; return Promise.resolve({ stdout: '', stderr: '' }); @@ -39,6 +52,23 @@ suite('Python Test Server', () => { stubExecutionFactory = ({ createActivatedEnvironment: () => Promise.resolve(stubExecutionService), } as unknown) as IPythonExecutionFactory; + + // set up exec service with child process + mockProc = new MockChildProcess('', ['']); + execService = typeMoq.Mock.ofType(); + const output = new Observable>(() => { + /* no op */ + }); + execService + .setup((x) => x.execObservable(typeMoq.It.isAny(), typeMoq.It.isAny())) + .returns(() => ({ + proc: mockProc, + out: output, + dispose: () => { + /* no-body */ + }, + })); + execService.setup((p) => ((p as unknown) as any).then).returns(() => undefined); }); teardown(() => { @@ -49,27 +79,46 @@ suite('Python Test Server', () => { test('sendCommand should add the port to the command being sent and add the correct extra spawn variables', async () => { const options = { - command: { script: 'myscript', args: ['-foo', 'foo'] }, + command: { + script: 'myscript', + args: ['-foo', 'foo'], + }, workspaceFolder: Uri.file('/foo/bar'), cwd: '/foo/bar', uuid: fakeUuid, }; + console.log('hello', execArgs, spawnOptions); const expectedSpawnOptions = { cwd: '/foo/bar', outputChannel: undefined, token: undefined, throwOnStdErr: true, - extraVariables: { PYTHONPATH: '/foo/bar', RUN_TEST_IDS_PORT: '56789' }, + extraVariables: { + PYTHONPATH: '/foo/bar', + RUN_TEST_IDS_PORT: '56789', + }, } as SpawnOptions; + const deferred2 = createDeferred(); + execFactory = typeMoq.Mock.ofType(); + execFactory + .setup((x) => x.createActivatedEnvironment(typeMoq.It.isAny())) + .returns(() => { + console.log('hello'); + deferred2.resolve(); + return Promise.resolve(execService.object); + }); - server = new PythonTestServer(stubExecutionFactory, debugLauncher); + server = new PythonTestServer(execFactory.object, debugLauncher); await server.serverReady(); - await server.sendCommand(options, '56789'); - const port = server.getPort(); + server.sendCommand(options, '56789'); + // add in await and trigger + await deferred2.promise; + mockProc.trigger('close'); - assert.deepStrictEqual(execArgs, ['myscript', '--port', `${port}`, '--uuid', fakeUuid, '-foo', 'foo']); - assert.deepStrictEqual(spawnOptions, expectedSpawnOptions); + const port = server.getPort(); + const expectedArgs = ['myscript', '--port', `${port}`, '--uuid', fakeUuid, '-foo', 'foo']; + execService.verify((x) => x.execObservable(expectedArgs, expectedSpawnOptions), typeMoq.Times.once()); }); test('sendCommand should write to an output channel if it is provided as an option', async () => { @@ -80,17 +129,32 @@ suite('Python Test Server', () => { }, } as OutputChannel; const options = { - command: { script: 'myscript', args: ['-foo', 'foo'] }, + command: { + script: 'myscript', + args: ['-foo', 'foo'], + }, workspaceFolder: Uri.file('/foo/bar'), cwd: '/foo/bar', uuid: fakeUuid, outChannel, }; + deferred = createDeferred(); + execFactory = typeMoq.Mock.ofType(); + execFactory + .setup((x) => x.createActivatedEnvironment(typeMoq.It.isAny())) + .returns(() => { + console.log('hello'); + deferred.resolve(); + return Promise.resolve(execService.object); + }); - server = new PythonTestServer(stubExecutionFactory, debugLauncher); + server = new PythonTestServer(execFactory.object, debugLauncher); await server.serverReady(); - await server.sendCommand(options); + server.sendCommand(options); + // add in await and trigger + await deferred.promise; + mockProc.trigger('close'); const port = server.getPort(); const expected = ['python', 'myscript', '--port', `${port}`, '--uuid', fakeUuid, '-foo', 'foo'].join(' '); @@ -101,11 +165,11 @@ suite('Python Test Server', () => { test('If script execution fails during sendCommand, an onDataReceived event should be fired with the "error" status', async () => { let eventData: { status: string; errors: string[] }; stubExecutionService = ({ - exec: () => { + execObservable: () => { throw new Error('Failed to execute'); }, } as unknown) as IPythonExecutionService; - + // console.log('ugh', stubExecutionService); const options = { command: { script: 'myscript', args: ['-foo', 'foo'] }, workspaceFolder: Uri.file('/foo/bar'), @@ -129,8 +193,16 @@ suite('Python Test Server', () => { test('If the server receives malformed data, it should display a log message, and not fire an event', async () => { let eventData: string | undefined; const client = new net.Socket(); - const deferred = createDeferred(); + deferred = createDeferred(); + execFactory = typeMoq.Mock.ofType(); + execFactory + .setup((x) => x.createActivatedEnvironment(typeMoq.It.isAny())) + .returns(() => { + console.log('hello'); + deferred.resolve(); + return Promise.resolve(execService.object); + }); const options = { command: { script: 'myscript', args: ['-foo', 'foo'] }, workspaceFolder: Uri.file('/foo/bar'), @@ -138,19 +210,21 @@ suite('Python Test Server', () => { uuid: fakeUuid, }; - stubExecutionService = ({ - exec: async () => { - client.connect(server.getPort()); - return Promise.resolve({ stdout: '', stderr: '' }); - }, - } as unknown) as IPythonExecutionService; - - server = new PythonTestServer(stubExecutionFactory, debugLauncher); + server = new PythonTestServer(execFactory.object, debugLauncher); await server.serverReady(); server.onDataReceived(({ data }) => { eventData = data; deferred.resolve(); }); + deferred = createDeferred(); + execFactory = typeMoq.Mock.ofType(); + execFactory + .setup((x) => x.createActivatedEnvironment(typeMoq.It.isAny())) + .returns(() => { + console.log('hello'); + deferred.resolve(); + return Promise.resolve(execService.object); + }); client.on('connect', () => { console.log('Socket connected, local port:', client.localPort); @@ -161,7 +235,11 @@ suite('Python Test Server', () => { console.log('Socket connection error:', error); }); - await server.sendCommand(options); + server.sendCommand(options); + // add in await and trigger + await deferred.promise; + mockProc.trigger('close'); + await deferred.promise; assert.deepStrictEqual(eventData, ''); }); @@ -169,23 +247,33 @@ suite('Python Test Server', () => { test('If the server doesnt recognize the UUID it should ignore it', async () => { let eventData: string | undefined; const client = new net.Socket(); - const deferred = createDeferred(); + deferred = createDeferred(); + execFactory = typeMoq.Mock.ofType(); + execFactory + .setup((x) => x.createActivatedEnvironment(typeMoq.It.isAny())) + .returns(() => { + console.log('hello'); + deferred.resolve(); + return Promise.resolve(execService.object); + }); const options = { command: { script: 'myscript', args: ['-foo', 'foo'] }, workspaceFolder: Uri.file('/foo/bar'), cwd: '/foo/bar', uuid: fakeUuid, }; + deferred = createDeferred(); + execFactory = typeMoq.Mock.ofType(); + execFactory + .setup((x) => x.createActivatedEnvironment(typeMoq.It.isAny())) + .returns(() => { + console.log('hello'); + deferred.resolve(); + return Promise.resolve(execService.object); + }); - stubExecutionService = ({ - exec: async () => { - client.connect(server.getPort()); - return Promise.resolve({ stdout: '', stderr: '' }); - }, - } as unknown) as IPythonExecutionService; - - server = new PythonTestServer(stubExecutionFactory, debugLauncher); + server = new PythonTestServer(execFactory.object, debugLauncher); await server.serverReady(); server.onDataReceived(({ data }) => { eventData = data; @@ -201,7 +289,7 @@ suite('Python Test Server', () => { console.log('Socket connection error:', error); }); - await server.sendCommand(options); + server.sendCommand(options); await deferred.promise; assert.deepStrictEqual(eventData, ''); }); @@ -212,23 +300,23 @@ suite('Python Test Server', () => { test('Error if payload does not have a content length header', async () => { let eventData: string | undefined; const client = new net.Socket(); - const deferred = createDeferred(); - const options = { command: { script: 'myscript', args: ['-foo', 'foo'] }, workspaceFolder: Uri.file('/foo/bar'), cwd: '/foo/bar', uuid: fakeUuid, }; + deferred = createDeferred(); + execFactory = typeMoq.Mock.ofType(); + execFactory + .setup((x) => x.createActivatedEnvironment(typeMoq.It.isAny())) + .returns(() => { + console.log('hello'); + deferred.resolve(); + return Promise.resolve(execService.object); + }); - stubExecutionService = ({ - exec: async () => { - client.connect(server.getPort()); - return Promise.resolve({ stdout: '', stderr: '' }); - }, - } as unknown) as IPythonExecutionService; - - server = new PythonTestServer(stubExecutionFactory, debugLauncher); + server = new PythonTestServer(execFactory.object, debugLauncher); await server.serverReady(); server.onDataReceived(({ data }) => { eventData = data; @@ -244,7 +332,7 @@ suite('Python Test Server', () => { console.log('Socket connection error:', error); }); - await server.sendCommand(options); + server.sendCommand(options); await deferred.promise; assert.deepStrictEqual(eventData, ''); }); @@ -267,7 +355,6 @@ Request-uuid: UUID_HERE // Your test logic here let eventData: string | undefined; const client = new net.Socket(); - const deferred = createDeferred(); const options = { command: { script: 'myscript', args: ['-foo', 'foo'] }, @@ -276,14 +363,7 @@ Request-uuid: UUID_HERE uuid: fakeUuid, }; - stubExecutionService = ({ - exec: async () => { - client.connect(server.getPort()); - return Promise.resolve({ stdout: '', stderr: '' }); - }, - } as unknown) as IPythonExecutionService; - - server = new PythonTestServer(stubExecutionFactory, debugLauncher); + server = new PythonTestServer(execFactory.object, debugLauncher); await server.serverReady(); const uuid = server.createUUID(); payload = payload.replace('UUID_HERE', uuid); @@ -301,7 +381,7 @@ Request-uuid: UUID_HERE console.log('Socket connection error:', error); }); - await server.sendCommand(options); + server.sendCommand(options); await deferred.promise; assert.deepStrictEqual(eventData, expectedResult); }); @@ -310,8 +390,16 @@ Request-uuid: UUID_HERE test('Calls run resolver if the result header is in the payload', async () => { let eventData: string | undefined; const client = new net.Socket(); - const deferred = createDeferred(); + deferred = createDeferred(); + execFactory = typeMoq.Mock.ofType(); + execFactory + .setup((x) => x.createActivatedEnvironment(typeMoq.It.isAny())) + .returns(() => { + console.log('hello'); + deferred.resolve(); + return Promise.resolve(execService.object); + }); const options = { command: { script: 'myscript', args: ['-foo', 'foo'] }, workspaceFolder: Uri.file('/foo/bar'), @@ -319,14 +407,7 @@ Request-uuid: UUID_HERE uuid: fakeUuid, }; - stubExecutionService = ({ - exec: async () => { - client.connect(server.getPort()); - return Promise.resolve({ stdout: '', stderr: '' }); - }, - } as unknown) as IPythonExecutionService; - - server = new PythonTestServer(stubExecutionFactory, debugLauncher); + server = new PythonTestServer(execFactory.object, debugLauncher); await server.serverReady(); const uuid = server.createUUID(); server.onRunDataReceived(({ data }) => { @@ -349,7 +430,7 @@ Request-uuid: ${uuid} console.log('Socket connection error:', error); }); - await server.sendCommand(options); + server.sendCommand(options); await deferred.promise; console.log('event data', eventData); const expectedResult = From f4db306be5e6775c42f3f62ba2c9883f15ae515f Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Mon, 31 Jul 2023 11:04:51 -0700 Subject: [PATCH 14/17] server tests and cleanup --- .../pytestExecutionAdapter.unit.test.ts | 7 - .../testController/server.unit.test.ts | 186 +++++++++++------- .../workspaceTestAdapter.unit.test.ts | 3 +- 3 files changed, 120 insertions(+), 76 deletions(-) diff --git a/src/test/testing/testController/pytest/pytestExecutionAdapter.unit.test.ts b/src/test/testing/testController/pytest/pytestExecutionAdapter.unit.test.ts index ccc4a5e30e9b..43b763f56e6c 100644 --- a/src/test/testing/testController/pytest/pytestExecutionAdapter.unit.test.ts +++ b/src/test/testing/testController/pytest/pytestExecutionAdapter.unit.test.ts @@ -101,12 +101,10 @@ suite('pytest test execution adapter', () => { execFactory .setup((x) => x.createActivatedEnvironment(typeMoq.It.isAny())) .returns(() => { - console.log('hello'); deferred2.resolve(); return Promise.resolve(execService.object); }); utilsStub.callsFake(() => { - console.log('hi'); deferred3.resolve(); return Promise.resolve(54321); }); @@ -142,12 +140,10 @@ suite('pytest test execution adapter', () => { execFactory .setup((x) => x.createActivatedEnvironment(typeMoq.It.isAny())) .returns(() => { - console.log('hello'); deferred2.resolve(); return Promise.resolve(execService.object); }); utilsStub.callsFake(() => { - console.log('hi'); deferred3.resolve(); return Promise.resolve(54321); }); @@ -204,12 +200,10 @@ suite('pytest test execution adapter', () => { execFactory .setup((x) => x.createActivatedEnvironment(typeMoq.It.isAny())) .returns(() => { - console.log('hello'); deferred2.resolve(); return Promise.resolve(execService.object); }); utilsStub.callsFake(() => { - console.log('hi'); deferred3.resolve(); return Promise.resolve(54321); }); @@ -269,7 +263,6 @@ suite('pytest test execution adapter', () => { test('Debug launched correctly for pytest', async () => { const deferred3 = createDeferred(); utilsStub.callsFake(() => { - console.log('hi'); deferred3.resolve(); return Promise.resolve(54321); }); diff --git a/src/test/testing/testController/server.unit.test.ts b/src/test/testing/testController/server.unit.test.ts index 711299e85d16..4cc86c4851d1 100644 --- a/src/test/testing/testController/server.unit.test.ts +++ b/src/test/testing/testController/server.unit.test.ts @@ -87,7 +87,6 @@ suite('Python Test Server', () => { cwd: '/foo/bar', uuid: fakeUuid, }; - console.log('hello', execArgs, spawnOptions); const expectedSpawnOptions = { cwd: '/foo/bar', outputChannel: undefined, @@ -103,7 +102,6 @@ suite('Python Test Server', () => { execFactory .setup((x) => x.createActivatedEnvironment(typeMoq.It.isAny())) .returns(() => { - console.log('hello'); deferred2.resolve(); return Promise.resolve(execService.object); }); @@ -143,7 +141,6 @@ suite('Python Test Server', () => { execFactory .setup((x) => x.createActivatedEnvironment(typeMoq.It.isAny())) .returns(() => { - console.log('hello'); deferred.resolve(); return Promise.resolve(execService.object); }); @@ -163,13 +160,12 @@ suite('Python Test Server', () => { }); test('If script execution fails during sendCommand, an onDataReceived event should be fired with the "error" status', async () => { - let eventData: { status: string; errors: string[] }; + let eventData: { status: string; errors: string[] } | undefined; stubExecutionService = ({ execObservable: () => { throw new Error('Failed to execute'); }, } as unknown) as IPythonExecutionService; - // console.log('ugh', stubExecutionService); const options = { command: { script: 'myscript', args: ['-foo', 'foo'] }, workspaceFolder: Uri.file('/foo/bar'), @@ -186,45 +182,50 @@ suite('Python Test Server', () => { await server.sendCommand(options); - assert.deepStrictEqual(eventData!.status, 'error'); - assert.deepStrictEqual(eventData!.errors, ['Failed to execute']); + assert.notEqual(eventData, undefined); + assert.deepStrictEqual(eventData?.status, 'error'); + assert.deepStrictEqual(eventData?.errors, ['Failed to execute']); }); test('If the server receives malformed data, it should display a log message, and not fire an event', async () => { let eventData: string | undefined; const client = new net.Socket(); - - deferred = createDeferred(); - execFactory = typeMoq.Mock.ofType(); - execFactory - .setup((x) => x.createActivatedEnvironment(typeMoq.It.isAny())) - .returns(() => { - console.log('hello'); - deferred.resolve(); - return Promise.resolve(execService.object); - }); const options = { command: { script: 'myscript', args: ['-foo', 'foo'] }, workspaceFolder: Uri.file('/foo/bar'), cwd: '/foo/bar', uuid: fakeUuid, }; + mockProc = new MockChildProcess('', ['']); + const output = new Observable>(() => { + /* no op */ + }); + const stubExecutionService2 = ({ + execObservable: (args: string[], spawnOptionsProvided: SpawnOptions) => { + client.connect(server.getPort()); + execArgs = args; + spawnOptions = spawnOptionsProvided; + return ({ + proc: mockProc, + out: output, + dispose: () => { + /* no-body */ + }, + }) + }, + } as unknown) as IPythonExecutionService; - server = new PythonTestServer(execFactory.object, debugLauncher); + const stubExecutionFactory2 = ({ + createActivatedEnvironment: () => Promise.resolve(stubExecutionService2), + } as unknown) as IPythonExecutionFactory; + + deferred = createDeferred(); + server = new PythonTestServer(stubExecutionFactory2, debugLauncher); await server.serverReady(); server.onDataReceived(({ data }) => { eventData = data; deferred.resolve(); }); - deferred = createDeferred(); - execFactory = typeMoq.Mock.ofType(); - execFactory - .setup((x) => x.createActivatedEnvironment(typeMoq.It.isAny())) - .returns(() => { - console.log('hello'); - deferred.resolve(); - return Promise.resolve(execService.object); - }); client.on('connect', () => { console.log('Socket connected, local port:', client.localPort); @@ -240,40 +241,43 @@ suite('Python Test Server', () => { await deferred.promise; mockProc.trigger('close'); - await deferred.promise; assert.deepStrictEqual(eventData, ''); }); test('If the server doesnt recognize the UUID it should ignore it', async () => { let eventData: string | undefined; const client = new net.Socket(); - - deferred = createDeferred(); - execFactory = typeMoq.Mock.ofType(); - execFactory - .setup((x) => x.createActivatedEnvironment(typeMoq.It.isAny())) - .returns(() => { - console.log('hello'); - deferred.resolve(); - return Promise.resolve(execService.object); - }); const options = { command: { script: 'myscript', args: ['-foo', 'foo'] }, workspaceFolder: Uri.file('/foo/bar'), cwd: '/foo/bar', uuid: fakeUuid, }; + deferred = createDeferred(); - execFactory = typeMoq.Mock.ofType(); - execFactory - .setup((x) => x.createActivatedEnvironment(typeMoq.It.isAny())) - .returns(() => { - console.log('hello'); - deferred.resolve(); - return Promise.resolve(execService.object); - }); + mockProc = new MockChildProcess('', ['']); + const output = new Observable>(() => { + /* no op */ + }); + const stubExecutionService2 = ({ + execObservable: (args: string[], spawnOptionsProvided: SpawnOptions) => { + client.connect(server.getPort()); + execArgs = args; + spawnOptions = spawnOptionsProvided; + return ({ + proc: mockProc, + out: output, + dispose: () => { + /* no-body */ + }, + }) + }, + } as unknown) as IPythonExecutionService; - server = new PythonTestServer(execFactory.object, debugLauncher); + const stubExecutionFactory2 = ({ + createActivatedEnvironment: () => Promise.resolve(stubExecutionService2), + } as unknown) as IPythonExecutionFactory; + server = new PythonTestServer(stubExecutionFactory2, debugLauncher); await server.serverReady(); server.onDataReceived(({ data }) => { eventData = data; @@ -307,16 +311,29 @@ suite('Python Test Server', () => { uuid: fakeUuid, }; deferred = createDeferred(); - execFactory = typeMoq.Mock.ofType(); - execFactory - .setup((x) => x.createActivatedEnvironment(typeMoq.It.isAny())) - .returns(() => { - console.log('hello'); - deferred.resolve(); - return Promise.resolve(execService.object); - }); + mockProc = new MockChildProcess('', ['']); + const output = new Observable>(() => { + /* no op */ + }); + const stubExecutionService2 = ({ + execObservable: (args: string[], spawnOptionsProvided: SpawnOptions) => { + client.connect(server.getPort()); + execArgs = args; + spawnOptions = spawnOptionsProvided; + return ({ + proc: mockProc, + out: output, + dispose: () => { + /* no-body */ + }, + }) + }, + } as unknown) as IPythonExecutionService; - server = new PythonTestServer(execFactory.object, debugLauncher); + const stubExecutionFactory2 = ({ + createActivatedEnvironment: () => Promise.resolve(stubExecutionService2), + } as unknown) as IPythonExecutionFactory; + server = new PythonTestServer(stubExecutionFactory2, debugLauncher); await server.serverReady(); server.onDataReceived(({ data }) => { eventData = data; @@ -362,8 +379,30 @@ Request-uuid: UUID_HERE cwd: '/foo/bar', uuid: fakeUuid, }; + deferred = createDeferred(); + mockProc = new MockChildProcess('', ['']); + const output = new Observable>(() => { + /* no op */ + }); + const stubExecutionService2 = ({ + execObservable: (args: string[], spawnOptionsProvided: SpawnOptions) => { + client.connect(server.getPort()); + execArgs = args; + spawnOptions = spawnOptionsProvided; + return ({ + proc: mockProc, + out: output, + dispose: () => { + /* no-body */ + }, + }) + }, + } as unknown) as IPythonExecutionService; + const stubExecutionFactory2 = ({ + createActivatedEnvironment: () => Promise.resolve(stubExecutionService2), + } as unknown) as IPythonExecutionFactory; - server = new PythonTestServer(execFactory.object, debugLauncher); + server = new PythonTestServer(stubExecutionFactory2, debugLauncher); await server.serverReady(); const uuid = server.createUUID(); payload = payload.replace('UUID_HERE', uuid); @@ -392,14 +431,29 @@ Request-uuid: UUID_HERE const client = new net.Socket(); deferred = createDeferred(); - execFactory = typeMoq.Mock.ofType(); - execFactory - .setup((x) => x.createActivatedEnvironment(typeMoq.It.isAny())) - .returns(() => { - console.log('hello'); - deferred.resolve(); - return Promise.resolve(execService.object); - }); + mockProc = new MockChildProcess('', ['']); + const output = new Observable>(() => { + /* no op */ + }); + const stubExecutionService2 = ({ + execObservable: (args: string[], spawnOptionsProvided: SpawnOptions) => { + client.connect(server.getPort()); + execArgs = args; + spawnOptions = spawnOptionsProvided; + return ({ + proc: mockProc, + out: output, + dispose: () => { + /* no-body */ + }, + }) + }, + } as unknown) as IPythonExecutionService; + + const stubExecutionFactory2 = ({ + createActivatedEnvironment: () => Promise.resolve(stubExecutionService2), + } as unknown) as IPythonExecutionFactory; + server = new PythonTestServer(stubExecutionFactory2, debugLauncher); const options = { command: { script: 'myscript', args: ['-foo', 'foo'] }, workspaceFolder: Uri.file('/foo/bar'), @@ -407,7 +461,6 @@ Request-uuid: UUID_HERE uuid: fakeUuid, }; - server = new PythonTestServer(execFactory.object, debugLauncher); await server.serverReady(); const uuid = server.createUUID(); server.onRunDataReceived(({ data }) => { @@ -432,7 +485,6 @@ Request-uuid: ${uuid} server.sendCommand(options); await deferred.promise; - console.log('event data', eventData); const expectedResult = '{"cwd": "path", "status": "success", "result": "xyz", "not_found": null, "error": null}'; assert.deepStrictEqual(eventData, expectedResult); diff --git a/src/test/testing/testController/workspaceTestAdapter.unit.test.ts b/src/test/testing/testController/workspaceTestAdapter.unit.test.ts index 5a2e48130746..41cd1bbd7ef2 100644 --- a/src/test/testing/testController/workspaceTestAdapter.unit.test.ts +++ b/src/test/testing/testController/workspaceTestAdapter.unit.test.ts @@ -164,8 +164,7 @@ suite('Workspace test adapter', () => { const buildErrorNodeOptionsStub = sinon.stub(util, 'buildErrorNodeOptions').returns(errorTestItemOptions); const testProvider = 'unittest'; - const abc = await workspaceTestAdapter.discoverTests(testController); - console.log(abc); + await workspaceTestAdapter.discoverTests(testController); sinon.assert.calledWithMatch(createErrorTestItemStub, sinon.match.any, sinon.match.any); sinon.assert.calledWithMatch(buildErrorNodeOptionsStub, Uri.parse('foo'), sinon.match.any, testProvider); From 79185c3cf4932fc8908a4e4de596594c82902afe Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Mon, 31 Jul 2023 11:17:36 -0700 Subject: [PATCH 15/17] fix unused vars --- .../testController/server.unit.test.ts | 47 ++++++------------- 1 file changed, 15 insertions(+), 32 deletions(-) diff --git a/src/test/testing/testController/server.unit.test.ts b/src/test/testing/testController/server.unit.test.ts index 4cc86c4851d1..36bd00633f8c 100644 --- a/src/test/testing/testController/server.unit.test.ts +++ b/src/test/testing/testController/server.unit.test.ts @@ -27,8 +27,6 @@ suite('Python Test Server', () => { let stubExecutionService: IPythonExecutionService; let server: PythonTestServer; let sandbox: sinon.SinonSandbox; - let execArgs: string[]; - let spawnOptions: SpawnOptions; let v4Stub: sinon.SinonStub; let debugLauncher: ITestDebugLauncher; let mockProc: MockChildProcess; @@ -42,11 +40,7 @@ suite('Python Test Server', () => { v4Stub.returns(fakeUuid); stubExecutionService = ({ - execObservable: (args: string[], spawnOptionsProvided: SpawnOptions) => { - execArgs = args; - spawnOptions = spawnOptionsProvided; - return Promise.resolve({ stdout: '', stderr: '' }); - }, + execObservable: () => Promise.resolve({ stdout: '', stderr: '' }), } as unknown) as IPythonExecutionService; stubExecutionFactory = ({ @@ -73,7 +67,6 @@ suite('Python Test Server', () => { teardown(() => { sandbox.restore(); - execArgs = []; server.dispose(); }); @@ -201,10 +194,8 @@ suite('Python Test Server', () => { /* no op */ }); const stubExecutionService2 = ({ - execObservable: (args: string[], spawnOptionsProvided: SpawnOptions) => { + execObservable: () => { client.connect(server.getPort()); - execArgs = args; - spawnOptions = spawnOptionsProvided; return ({ proc: mockProc, out: output, @@ -260,10 +251,8 @@ suite('Python Test Server', () => { /* no op */ }); const stubExecutionService2 = ({ - execObservable: (args: string[], spawnOptionsProvided: SpawnOptions) => { + execObservable: () => { client.connect(server.getPort()); - execArgs = args; - spawnOptions = spawnOptionsProvided; return ({ proc: mockProc, out: output, @@ -316,10 +305,8 @@ suite('Python Test Server', () => { /* no op */ }); const stubExecutionService2 = ({ - execObservable: (args: string[], spawnOptionsProvided: SpawnOptions) => { + execObservable: () => { client.connect(server.getPort()); - execArgs = args; - spawnOptions = spawnOptionsProvided; return ({ proc: mockProc, out: output, @@ -385,19 +372,17 @@ Request-uuid: UUID_HERE /* no op */ }); const stubExecutionService2 = ({ - execObservable: (args: string[], spawnOptionsProvided: SpawnOptions) => { - client.connect(server.getPort()); - execArgs = args; - spawnOptions = spawnOptionsProvided; - return ({ - proc: mockProc, - out: output, - dispose: () => { - /* no-body */ - }, - }) + execObservable: () => { + client.connect(server.getPort()); + return ({ + proc: mockProc, + out: output, + dispose: () => { + /* no-body */ }, - } as unknown) as IPythonExecutionService; + }) + }, + } as unknown) as IPythonExecutionService; const stubExecutionFactory2 = ({ createActivatedEnvironment: () => Promise.resolve(stubExecutionService2), } as unknown) as IPythonExecutionFactory; @@ -436,10 +421,8 @@ Request-uuid: UUID_HERE /* no op */ }); const stubExecutionService2 = ({ - execObservable: (args: string[], spawnOptionsProvided: SpawnOptions) => { + execObservable: () => { client.connect(server.getPort()); - execArgs = args; - spawnOptions = spawnOptionsProvided; return ({ proc: mockProc, out: output, From c0f7be8db23ae3a2417e0099756c0fcca908d8e5 Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Mon, 31 Jul 2023 11:20:23 -0700 Subject: [PATCH 16/17] remove console logs --- .../testController/pytest/pytestDiscoveryAdapter.unit.test.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/test/testing/testController/pytest/pytestDiscoveryAdapter.unit.test.ts b/src/test/testing/testController/pytest/pytestDiscoveryAdapter.unit.test.ts index 9cc933b9f3bd..8ba7dd9a6f00 100644 --- a/src/test/testing/testController/pytest/pytestDiscoveryAdapter.unit.test.ts +++ b/src/test/testing/testController/pytest/pytestDiscoveryAdapter.unit.test.ts @@ -95,7 +95,6 @@ suite('pytest test discovery adapter', () => { execFactory .setup((x) => x.createActivatedEnvironment(typeMoq.It.isAny())) .returns(() => { - console.log('hello'); deferred.resolve(); return Promise.resolve(execService.object); }); @@ -140,7 +139,6 @@ suite('pytest test discovery adapter', () => { execFactory .setup((x) => x.createActivatedEnvironment(typeMoq.It.isAny())) .returns(() => { - console.log('hello'); deferred.resolve(); return Promise.resolve(execService.object); }); From 78d87cd4f7b4dc957b80a7e37566a1caa5d8e3d1 Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Mon, 31 Jul 2023 11:57:03 -0700 Subject: [PATCH 17/17] prettier --- .../testController/server.unit.test.ts | 78 +++++++++---------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/src/test/testing/testController/server.unit.test.ts b/src/test/testing/testController/server.unit.test.ts index 36bd00633f8c..53c2b72e40f7 100644 --- a/src/test/testing/testController/server.unit.test.ts +++ b/src/test/testing/testController/server.unit.test.ts @@ -196,13 +196,13 @@ suite('Python Test Server', () => { const stubExecutionService2 = ({ execObservable: () => { client.connect(server.getPort()); - return ({ - proc: mockProc, - out: output, - dispose: () => { - /* no-body */ - }, - }) + return { + proc: mockProc, + out: output, + dispose: () => { + /* no-body */ + }, + }; }, } as unknown) as IPythonExecutionService; @@ -253,13 +253,13 @@ suite('Python Test Server', () => { const stubExecutionService2 = ({ execObservable: () => { client.connect(server.getPort()); - return ({ - proc: mockProc, - out: output, - dispose: () => { - /* no-body */ - }, - }) + return { + proc: mockProc, + out: output, + dispose: () => { + /* no-body */ + }, + }; }, } as unknown) as IPythonExecutionService; @@ -307,13 +307,13 @@ suite('Python Test Server', () => { const stubExecutionService2 = ({ execObservable: () => { client.connect(server.getPort()); - return ({ - proc: mockProc, - out: output, - dispose: () => { - /* no-body */ - }, - }) + return { + proc: mockProc, + out: output, + dispose: () => { + /* no-body */ + }, + }; }, } as unknown) as IPythonExecutionService; @@ -372,19 +372,19 @@ Request-uuid: UUID_HERE /* no op */ }); const stubExecutionService2 = ({ - execObservable: () => { - client.connect(server.getPort()); - return ({ - proc: mockProc, - out: output, - dispose: () => { - /* no-body */ + execObservable: () => { + client.connect(server.getPort()); + return { + proc: mockProc, + out: output, + dispose: () => { + /* no-body */ + }, + }; }, - }) - }, - } as unknown) as IPythonExecutionService; + } as unknown) as IPythonExecutionService; const stubExecutionFactory2 = ({ - createActivatedEnvironment: () => Promise.resolve(stubExecutionService2), + createActivatedEnvironment: () => Promise.resolve(stubExecutionService2), } as unknown) as IPythonExecutionFactory; server = new PythonTestServer(stubExecutionFactory2, debugLauncher); @@ -423,13 +423,13 @@ Request-uuid: UUID_HERE const stubExecutionService2 = ({ execObservable: () => { client.connect(server.getPort()); - return ({ - proc: mockProc, - out: output, - dispose: () => { - /* no-body */ - }, - }) + return { + proc: mockProc, + out: output, + dispose: () => { + /* no-body */ + }, + }; }, } as unknown) as IPythonExecutionService;