From dd20561bf36a60938e848d1bf6e058883ec7ae69 Mon Sep 17 00:00:00 2001 From: Eleanor Boyd Date: Fri, 4 Aug 2023 10:31:15 -0700 Subject: [PATCH] revert due to buffer overflow on subprocess (#21762) revert https://github.com/microsoft/vscode-python/pull/21667 because it causes buffer overflow in the python testing subprocess when larger repos are used. Specifically seen on pytest discovery with >200 tests. Revert to align with the stable release and put in a fix next week. --- .../testing/testController/common/server.ts | 27 +- .../testing/testController/common/types.ts | 7 +- .../pytest/pytestDiscoveryAdapter.ts | 27 +- .../pytest/pytestExecutionAdapter.ts | 45 ++-- .../unittest/testDiscoveryAdapter.ts | 11 +- .../unittest/testExecutionAdapter.ts | 21 +- src/test/mocks/mockChildProcess.ts | 238 ----------------- .../pytestDiscoveryAdapter.unit.test.ts | 76 ++---- .../pytestExecutionAdapter.unit.test.ts | 118 ++------- .../testController/server.unit.test.ts | 248 +++++------------- .../workspaceTestAdapter.unit.test.ts | 3 +- 11 files changed, 157 insertions(+), 664 deletions(-) delete mode 100644 src/test/mocks/mockChildProcess.ts diff --git a/src/client/testing/testController/common/server.ts b/src/client/testing/testController/common/server.ts index 564bd82f2ef6..f854371ffc35 100644 --- a/src/client/testing/testController/common/server.ts +++ b/src/client/testing/testController/common/server.ts @@ -3,11 +3,10 @@ import * as net from 'net'; import * as crypto from 'crypto'; -import { Disposable, Event, EventEmitter, TestRun } from 'vscode'; +import { Disposable, Event, EventEmitter } from 'vscode'; import * as path from 'path'; import { ExecutionFactoryCreateWithEnvironmentOptions, - ExecutionResult, IPythonExecutionFactory, SpawnOptions, } from '../../../common/process/types'; @@ -16,7 +15,6 @@ 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(); @@ -142,12 +140,7 @@ export class PythonTestServer implements ITestServer, Disposable { return this._onDataReceived.event; } - async sendCommand( - options: TestCommandOptions, - runTestIdPort?: string, - runInstance?: TestRun, - callback?: () => void, - ): Promise { + async sendCommand(options: TestCommandOptions, runTestIdPort?: string, callback?: () => void): Promise { const { uuid } = options; const pythonPathParts: string[] = process.env.PYTHONPATH?.split(path.delimiter) ?? []; @@ -161,7 +154,7 @@ export class PythonTestServer implements ITestServer, Disposable { }; if (spawnOptions.extraVariables) spawnOptions.extraVariables.RUN_TEST_IDS_PORT = runTestIdPort; - const isRun = runTestIdPort !== undefined; + const isRun = !options.testIds; // Create the Python environment in which to execute the command. const creationOptions: ExecutionFactoryCreateWithEnvironmentOptions = { allowEnvironmentFetchExceptions: false, @@ -202,19 +195,7 @@ export class PythonTestServer implements ITestServer, Disposable { // This means it is running discovery traceLog(`Discovering unittest tests with arguments: ${args}\r\n`); } - const deferred = createDeferred>(); - - const result = execService.execObservable(args, spawnOptions); - - runInstance?.token.onCancellationRequested(() => { - result?.proc?.kill(); - }); - result?.proc?.on('close', () => { - traceLog('Exec server closed.', uuid); - deferred.resolve({ stdout: '', stderr: '' }); - callback?.(); - }); - await deferred.promise; + await execService.exec(args, spawnOptions); } } catch (ex) { this.uuids = this.uuids.filter((u) => u !== uuid); diff --git a/src/client/testing/testController/common/types.ts b/src/client/testing/testController/common/types.ts index 16c0bd0e3cee..d4e54951bfd7 100644 --- a/src/client/testing/testController/common/types.ts +++ b/src/client/testing/testController/common/types.ts @@ -174,12 +174,7 @@ export interface ITestServer { readonly onDataReceived: Event; readonly onRunDataReceived: Event; readonly onDiscoveryDataReceived: Event; - sendCommand( - options: TestCommandOptions, - runTestIdsPort?: string, - runInstance?: TestRun, - callback?: () => void, - ): Promise; + sendCommand(options: TestCommandOptions, runTestIdsPort?: string, 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 810fae0fa11c..b83224d4161b 100644 --- a/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts +++ b/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts @@ -4,14 +4,13 @@ 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 { traceVerbose } from '../../../logging'; +import { traceError, traceVerbose } from '../../../logging'; import { DataReceivedEvent, DiscoveredTestPayload, @@ -49,7 +48,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); @@ -79,15 +78,17 @@ export class PytestTestDiscoveryAdapter implements ITestDiscoveryAdapter { }; const execService = await executionFactory?.createActivatedEnvironment(creationOptions); // delete UUID following entire discovery finishing. - const deferredExec = createDeferred>(); - const execArgs = ['-m', 'pytest', '-p', 'vscode_pytest', '--collect-only'].concat(pytestArgs); - const result = execService?.execObservable(execArgs, spawnOptions); - - result?.proc?.on('close', () => { - deferredExec.resolve({ stdout: '', stderr: '' }); - this.testServer.deleteUUID(uuid); - deferred.resolve(); - }); - await deferredExec.promise; + 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; } } diff --git a/src/client/testing/testController/pytest/pytestExecutionAdapter.ts b/src/client/testing/testController/pytest/pytestExecutionAdapter.ts index b05fa21fc046..a75a6089627c 100644 --- a/src/client/testing/testController/pytest/pytestExecutionAdapter.ts +++ b/src/client/testing/testController/pytest/pytestExecutionAdapter.ts @@ -15,7 +15,6 @@ import { } from '../common/types'; import { ExecutionFactoryCreateWithEnvironmentOptions, - ExecutionResult, IPythonExecutionFactory, SpawnOptions, } from '../../../common/process/types'; @@ -23,7 +22,13 @@ import { removePositionalFoldersAndFiles } from './arguments'; import { ITestDebugLauncher, LaunchOptions } from '../../common/types'; import { PYTEST_PROVIDER } from '../../common/constants'; import { EXTENSION_ROOT_DIR } from '../../../common/constants'; -import * as utils from '../common/utils'; +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( @@ -43,20 +48,18 @@ export class PytestTestExecutionAdapter implements ITestExecutionAdapter { ): Promise { const uuid = this.testServer.createUUID(uri.fsPath); traceVerbose(uri, testIds, debugBool); - const disposedDataReceived = this.testServer.onRunDataReceived((e: DataReceivedEvent) => { + const disposable = this.testServer.onRunDataReceived((e: DataReceivedEvent) => { if (runInstance) { this.resultResolver?.resolveExecution(JSON.parse(e.data), runInstance); } }); - const dispose = function (testServer: ITestServer) { - testServer.deleteUUID(uuid); - disposedDataReceived.dispose(); - }; - runInstance?.token.onCancellationRequested(() => { - dispose(this.testServer); - }); - await this.runTestsNew(uri, testIds, uuid, runInstance, debugBool, executionFactory, debugLauncher); - + try { + await this.runTestsNew(uri, testIds, uuid, debugBool, executionFactory, debugLauncher); + } finally { + this.testServer.deleteUUID(uuid); + disposable.dispose(); + // confirm with testing that this gets called (it must clean this up) + } // placeholder until after the rewrite is adopted // TODO: remove after adoption. const executionPayload: ExecutionTestPayload = { @@ -71,7 +74,6 @@ export class PytestTestExecutionAdapter implements ITestExecutionAdapter { uri: Uri, testIds: string[], uuid: string, - runInstance?: TestRun, debugBool?: boolean, executionFactory?: IPythonExecutionFactory, debugLauncher?: ITestDebugLauncher, @@ -122,7 +124,7 @@ export class PytestTestExecutionAdapter implements ITestExecutionAdapter { } traceLog(`Running PYTEST execution for the following test ids: ${testIds}`); - const pytestRunTestIdsPort = await utils.startTestIdServer(testIds); + const pytestRunTestIdsPort = await startTestIdServer(testIds); if (spawnOptions.extraVariables) spawnOptions.extraVariables.RUN_TEST_IDS_PORT = pytestRunTestIdsPort.toString(); @@ -141,7 +143,6 @@ 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 @@ -149,19 +150,7 @@ export class PytestTestExecutionAdapter implements ITestExecutionAdapter { const runArgs = [scriptPath, ...testArgs]; traceInfo(`Running pytests with arguments: ${runArgs.join(' ')}\r\n`); - const deferredExec = createDeferred>(); - const result = execService?.execObservable(runArgs, spawnOptions); - - runInstance?.token.onCancellationRequested(() => { - result?.proc?.kill(); - }); - - result?.proc?.on('close', () => { - deferredExec.resolve({ stdout: '', stderr: '' }); - this.testServer.deleteUUID(uuid); - deferred.resolve(); - }); - await deferredExec.promise; + await execService?.exec(runArgs, spawnOptions); } } 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 b49ac3dabd0e..6deca55117ea 100644 --- a/src/client/testing/testController/unittest/testDiscoveryAdapter.ts +++ b/src/client/testing/testController/unittest/testDiscoveryAdapter.ts @@ -46,11 +46,12 @@ export class UnittestTestDiscoveryAdapter implements ITestDiscoveryAdapter { const disposable = this.testServer.onDiscoveryDataReceived((e: DataReceivedEvent) => { this.resultResolver?.resolveDiscovery(JSON.parse(e.data)); }); - - await this.callSendCommand(options, () => { + try { + await this.callSendCommand(options); + } finally { this.testServer.deleteUUID(uuid); disposable.dispose(); - }); + } // placeholder until after the rewrite is adopted // TODO: remove after adoption. const discoveryPayload: DiscoveredTestPayload = { @@ -60,8 +61,8 @@ export class UnittestTestDiscoveryAdapter implements ITestDiscoveryAdapter { return discoveryPayload; } - private async callSendCommand(options: TestCommandOptions, callback: () => void): Promise { - await this.testServer.sendCommand(options, undefined, undefined, callback); + private async callSendCommand(options: TestCommandOptions): Promise { + await this.testServer.sendCommand(options); 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 4cd392f93a43..4cab941c2608 100644 --- a/src/client/testing/testController/unittest/testExecutionAdapter.ts +++ b/src/client/testing/testController/unittest/testExecutionAdapter.ts @@ -37,19 +37,18 @@ export class UnittestTestExecutionAdapter implements ITestExecutionAdapter { runInstance?: TestRun, ): Promise { const uuid = this.testServer.createUUID(uri.fsPath); - const disposedDataReceived = this.testServer.onRunDataReceived((e: DataReceivedEvent) => { + const disposable = this.testServer.onRunDataReceived((e: DataReceivedEvent) => { if (runInstance) { this.resultResolver?.resolveExecution(JSON.parse(e.data), runInstance); } }); - const dispose = function () { - disposedDataReceived.dispose(); - }; - runInstance?.token.onCancellationRequested(() => { + try { + await this.runTestsNew(uri, testIds, uuid, debugBool); + } finally { this.testServer.deleteUUID(uuid); - dispose(); - }); - await this.runTestsNew(uri, testIds, uuid, runInstance, debugBool, dispose); + disposable.dispose(); + // confirm with testing that this gets called (it must clean this up) + } const executionPayload: ExecutionTestPayload = { cwd: uri.fsPath, status: 'success', error: '' }; return executionPayload; } @@ -58,9 +57,7 @@ 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; @@ -83,10 +80,8 @@ export class UnittestTestExecutionAdapter implements ITestExecutionAdapter { const runTestIdsPort = await startTestIdServer(testIds); - await this.testServer.sendCommand(options, runTestIdsPort.toString(), runInstance, () => { - this.testServer.deleteUUID(uuid); + await this.testServer.sendCommand(options, runTestIdsPort.toString(), () => { deferred.resolve(); - dispose?.(); }); // placeholder until after the rewrite is adopted // TODO: remove after adoption. diff --git a/src/test/mocks/mockChildProcess.ts b/src/test/mocks/mockChildProcess.ts deleted file mode 100644 index c038c0f845ab..000000000000 --- a/src/test/mocks/mockChildProcess.ts +++ /dev/null @@ -1,238 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { Serializable, SendHandle, MessageOptions } from 'child_process'; -import { Writable, Readable, Pipe } from 'stream'; -import { EventEmitter } from 'node:events'; - -export class MockChildProcess extends EventEmitter { - constructor(spawnfile: string, spawnargs: string[]) { - super(); - this.spawnfile = spawnfile; - this.spawnargs = spawnargs; - this.stdin = new Writable(); - this.stdout = new Readable(); - this.stderr = null; - this.channel = 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.eventMap = new Map(); - } - - stdin: Writable | null; - - stdout: Readable | null; - - stderr: Readable | null; - - eventMap: Map; - - 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, - sendHandle?: SendHandle, - options?: MessageOptions, - 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 { - // Implementation of the send method - // For example, you might want to emit a 'message' event - this.stdout?.push(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: '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; - - 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; - - 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; - - 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; - - 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; - - 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; - } - - 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; - - 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; - - 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; - - 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; - - 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; - - 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 []; - } - - 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 8ba7dd9a6f00..18212b2d1032 100644 --- a/src/test/testing/testController/pytest/pytestDiscoveryAdapter.unit.test.ts +++ b/src/test/testing/testController/pytest/pytestDiscoveryAdapter.unit.test.ts @@ -5,7 +5,6 @@ import * as assert from 'assert'; import { Uri } from 'vscode'; import * as typeMoq from 'typemoq'; import * as path from 'path'; -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'; @@ -13,11 +12,9 @@ import { IPythonExecutionFactory, IPythonExecutionService, SpawnOptions, - Output, } from '../../../../client/common/process/types'; +import { createDeferred, Deferred } from '../../../../client/common/utils/async'; import { EXTENSION_ROOT_DIR } from '../../../../client/constants'; -import { MockChildProcess } from '../../../mocks/mockChildProcess'; -import { Deferred, createDeferred } from '../../../../client/common/utils/async'; suite('pytest test discovery adapter', () => { let testServer: typeMoq.IMock; @@ -32,7 +29,6 @@ suite('pytest test discovery adapter', () => { let expectedPath: string; let uri: Uri; let expectedExtraVariables: Record; - let mockProc: MockChildProcess; setup(() => { const mockExtensionRootDir = typeMoq.Mock.ofType(); @@ -70,46 +66,32 @@ suite('pytest test discovery adapter', () => { }), } as unknown) as IConfigurationService; - // 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); - outputChannel = typeMoq.Mock.ofType(); - }); - test('Discovery should call exec with correct basic args', async () => { - // set up exec mock - deferred = createDeferred(); + // 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 + execService = typeMoq.Mock.ofType(); + deferred = createDeferred(); + execService + .setup((x) => x.exec(typeMoq.It.isAny(), typeMoq.It.isAny())) .returns(() => { deferred.resolve(); - return Promise.resolve(execService.object); + return Promise.resolve({ stdout: '{}' }); }); - + 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 () => { adapter = new PytestTestDiscoveryAdapter(testServer.object, configService, outputChannel.object); - adapter.discoverTests(uri, execFactory.object); - // add in await and trigger - await deferred.promise; - mockProc.trigger('close'); - - // verification + await adapter.discoverTests(uri, execFactory.object); const expectedArgs = ['-m', 'pytest', '-p', 'vscode_pytest', '--collect-only', '.']; + execService.verify( (x) => - x.execObservable( + x.exec( expectedArgs, typeMoq.It.is((options) => { assert.deepEqual(options.extraVariables, expectedExtraVariables); @@ -126,34 +108,16 @@ 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(() => { - deferred.resolve(); - return Promise.resolve(execService.object); - }); - adapter = new PytestTestDiscoveryAdapter(testServer.object, configServiceNew, outputChannel.object); - adapter.discoverTests(uri, execFactory.object); - // add in await and trigger - await deferred.promise; - mockProc.trigger('close'); - - // verification + await adapter.discoverTests(uri, execFactory.object); const expectedArgs = ['-m', 'pytest', '-p', 'vscode_pytest', '--collect-only', '.', 'abc', 'xyz']; execService.verify( (x) => - x.execObservable( + x.exec( expectedArgs, typeMoq.It.is((options) => { assert.deepEqual(options.extraVariables, expectedExtraVariables); diff --git a/src/test/testing/testController/pytest/pytestExecutionAdapter.unit.test.ts b/src/test/testing/testController/pytest/pytestExecutionAdapter.unit.test.ts index 43b763f56e6c..44116fd753b0 100644 --- a/src/test/testing/testController/pytest/pytestExecutionAdapter.unit.test.ts +++ b/src/test/testing/testController/pytest/pytestExecutionAdapter.unit.test.ts @@ -6,13 +6,11 @@ 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'; @@ -20,7 +18,6 @@ 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; @@ -32,8 +29,8 @@ suite('pytest test execution adapter', () => { let debugLauncher: typeMoq.IMock; (global as any).EXTENSION_ROOT_DIR = EXTENSION_ROOT_DIR; let myTestPath: string; - let mockProc: MockChildProcess; - let utilsStub: sinon.SinonStub; + let startTestIdServerStub: sinon.SinonStub; + setup(() => { testServer = typeMoq.Mock.ofType(); testServer.setup((t) => t.getPort()).returns(() => 12345); @@ -50,24 +47,8 @@ suite('pytest test execution adapter', () => { }), isTestExecution: () => false, } as unknown) as IConfigurationService; - - // 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'); + execService = typeMoq.Mock.ofType(); debugLauncher = typeMoq.Mock.ofType(); execFactory .setup((x) => x.createActivatedEnvironment(typeMoq.It.isAny())) @@ -85,6 +66,7 @@ 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); @@ -95,25 +77,10 @@ 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(() => { - deferred2.resolve(); - return Promise.resolve(execService.object); - }); - utilsStub.callsFake(() => { - 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.onRunDataReceived(typeMoq.It.isAny(), typeMoq.It.isAny())) + .setup((t) => t.onDiscoveryDataReceived(typeMoq.It.isAny(), typeMoq.It.isAny())) .returns(() => ({ dispose: () => { /* no-body */ @@ -121,38 +88,19 @@ 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']; - adapter.runTests(uri, testIds, false, testRun.object, execFactory.object); - // add in await and trigger - await deferred2.promise; - await deferred3.promise; - mockProc.trigger('close'); + const testIds = ['test1id', 'test2id']; + await adapter.runTests(uri, testIds, false, testRun.object, execFactory.object); - // assert - sinon.assert.calledWithExactly(utilsStub, testIds); + sinon.assert.calledWithExactly(startTestIdServerStub, 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(() => { - deferred2.resolve(); - return Promise.resolve(execService.object); - }); - utilsStub.callsFake(() => { - 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.onRunDataReceived(typeMoq.It.isAny(), typeMoq.It.isAny())) + .setup((t) => t.onDiscoveryDataReceived(typeMoq.It.isAny(), typeMoq.It.isAny())) .returns(() => ({ dispose: () => { /* no-body */ @@ -160,12 +108,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); - adapter.runTests(uri, [], false, testRun.object, execFactory.object); - - await deferred2.promise; - await deferred3.promise; - mockProc.trigger('close'); + await adapter.runTests(uri, [], false, testRun.object, execFactory.object); const pathToPythonFiles = path.join(EXTENSION_ROOT_DIR, 'pythonFiles'); const pathToPythonScript = path.join(pathToPythonFiles, 'vscode_pytest', 'run_pytest_script.py'); @@ -178,7 +123,7 @@ suite('pytest test execution adapter', () => { // execService.verify((x) => x.exec(expectedArgs, typeMoq.It.isAny()), typeMoq.Times.once()); execService.verify( (x) => - x.execObservable( + x.exec( expectedArgs, typeMoq.It.is((options) => { assert.equal(options.extraVariables?.PYTHONPATH, expectedExtraVariables.PYTHONPATH); @@ -194,21 +139,6 @@ 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(() => { - deferred2.resolve(); - return Promise.resolve(execService.object); - }); - utilsStub.callsFake(() => { - 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: () => ({ @@ -219,7 +149,7 @@ suite('pytest test execution adapter', () => { const uri = Uri.file(myTestPath); const uuid = 'uuid123'; testServer - .setup((t) => t.onRunDataReceived(typeMoq.It.isAny(), typeMoq.It.isAny())) + .setup((t) => t.onDiscoveryDataReceived(typeMoq.It.isAny(), typeMoq.It.isAny())) .returns(() => ({ dispose: () => { /* no-body */ @@ -227,12 +157,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); - adapter.runTests(uri, [], false, testRun.object, execFactory.object); - - await deferred2.promise; - await deferred3.promise; - mockProc.trigger('close'); + await adapter.runTests(uri, [], false, testRun.object, execFactory.object); const pathToPythonFiles = path.join(EXTENSION_ROOT_DIR, 'pythonFiles'); const pathToPythonScript = path.join(pathToPythonFiles, 'vscode_pytest', 'run_pytest_script.py'); @@ -245,7 +172,7 @@ suite('pytest test execution adapter', () => { execService.verify( (x) => - x.execObservable( + x.exec( expectedArgs, typeMoq.It.is((options) => { assert.equal(options.extraVariables?.PYTHONPATH, expectedExtraVariables.PYTHONPATH); @@ -261,17 +188,10 @@ suite('pytest test execution adapter', () => { ); }); test('Debug launched correctly for pytest', async () => { - const deferred3 = createDeferred(); - utilsStub.callsFake(() => { - 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.onRunDataReceived(typeMoq.It.isAny(), typeMoq.It.isAny())) + .setup((t) => t.onDiscoveryDataReceived(typeMoq.It.isAny(), typeMoq.It.isAny())) .returns(() => ({ dispose: () => { /* no-body */ @@ -279,9 +199,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( diff --git a/src/test/testing/testController/server.unit.test.ts b/src/test/testing/testController/server.unit.test.ts index 53c2b72e40f7..1131c26c6444 100644 --- a/src/test/testing/testController/server.unit.test.ts +++ b/src/test/testing/testController/server.unit.test.ts @@ -1,24 +1,15 @@ // 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 { Observable } from 'rxjs'; -import * as typeMoq from 'typemoq'; -import { - IPythonExecutionFactory, - IPythonExecutionService, - Output, - SpawnOptions, -} from '../../../client/common/process/types'; +import { IPythonExecutionFactory, IPythonExecutionService, SpawnOptions } from '../../../client/common/process/types'; import { PythonTestServer } from '../../../client/testing/testController/common/server'; import { ITestDebugLauncher } from '../../../client/testing/common/types'; -import { Deferred, createDeferred } from '../../../client/common/utils/async'; -import { MockChildProcess } from '../../mocks/mockChildProcess'; +import { createDeferred } from '../../../client/common/utils/async'; suite('Python Test Server', () => { const fakeUuid = 'fake-uuid'; @@ -27,12 +18,10 @@ 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; - let execService: typeMoq.IMock; - let deferred: Deferred; - let execFactory = typeMoq.Mock.ofType(); setup(() => { sandbox = sinon.createSandbox(); @@ -40,42 +29,27 @@ suite('Python Test Server', () => { v4Stub.returns(fakeUuid); stubExecutionService = ({ - execObservable: () => Promise.resolve({ stdout: '', stderr: '' }), + exec: (args: string[], spawnOptionsProvided: SpawnOptions) => { + execArgs = args; + spawnOptions = spawnOptionsProvided; + return Promise.resolve({ stdout: '', stderr: '' }); + }, } as unknown) as IPythonExecutionService; 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(() => { sandbox.restore(); + execArgs = []; server.dispose(); }); 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, @@ -85,31 +59,17 @@ suite('Python Test Server', () => { 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(() => { - deferred2.resolve(); - return Promise.resolve(execService.object); - }); - server = new PythonTestServer(execFactory.object, debugLauncher); + server = new PythonTestServer(stubExecutionFactory, debugLauncher); await server.serverReady(); - server.sendCommand(options, '56789'); - // add in await and trigger - await deferred2.promise; - mockProc.trigger('close'); - + await server.sendCommand(options, '56789'); const port = server.getPort(); - const expectedArgs = ['myscript', '--port', `${port}`, '--uuid', fakeUuid, '-foo', 'foo']; - execService.verify((x) => x.execObservable(expectedArgs, expectedSpawnOptions), typeMoq.Times.once()); + + assert.deepStrictEqual(execArgs, ['myscript', '--port', `${port}`, '--uuid', fakeUuid, '-foo', 'foo']); + assert.deepStrictEqual(spawnOptions, expectedSpawnOptions); }); test('sendCommand should write to an output channel if it is provided as an option', async () => { @@ -120,31 +80,17 @@ 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(() => { - deferred.resolve(); - return Promise.resolve(execService.object); - }); - server = new PythonTestServer(execFactory.object, debugLauncher); + server = new PythonTestServer(stubExecutionFactory, debugLauncher); await server.serverReady(); - server.sendCommand(options); - // add in await and trigger - await deferred.promise; - mockProc.trigger('close'); + await server.sendCommand(options); const port = server.getPort(); const expected = ['python', 'myscript', '--port', `${port}`, '--uuid', fakeUuid, '-foo', 'foo'].join(' '); @@ -153,12 +99,13 @@ 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[] } | undefined; + let eventData: { status: string; errors: string[] }; stubExecutionService = ({ - execObservable: () => { + exec: () => { throw new Error('Failed to execute'); }, } as unknown) as IPythonExecutionService; + const options = { command: { script: 'myscript', args: ['-foo', 'foo'] }, workspaceFolder: Uri.file('/foo/bar'), @@ -175,43 +122,30 @@ suite('Python Test Server', () => { await server.sendCommand(options); - assert.notEqual(eventData, undefined); - assert.deepStrictEqual(eventData?.status, 'error'); - assert.deepStrictEqual(eventData?.errors, ['Failed to execute']); + 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(); + const deferred = createDeferred(); + 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: () => { + + stubExecutionService = ({ + exec: async () => { client.connect(server.getPort()); - return { - proc: mockProc, - out: output, - dispose: () => { - /* no-body */ - }, - }; + return Promise.resolve({ stdout: '', stderr: '' }); }, } as unknown) as IPythonExecutionService; - const stubExecutionFactory2 = ({ - createActivatedEnvironment: () => Promise.resolve(stubExecutionService2), - } as unknown) as IPythonExecutionFactory; - - deferred = createDeferred(); - server = new PythonTestServer(stubExecutionFactory2, debugLauncher); + server = new PythonTestServer(stubExecutionFactory, debugLauncher); await server.serverReady(); server.onDataReceived(({ data }) => { eventData = data; @@ -227,17 +161,16 @@ suite('Python Test Server', () => { console.log('Socket connection error:', error); }); - server.sendCommand(options); - // add in await and trigger + await server.sendCommand(options); await deferred.promise; - mockProc.trigger('close'); - 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(); + const deferred = createDeferred(); + const options = { command: { script: 'myscript', args: ['-foo', 'foo'] }, workspaceFolder: Uri.file('/foo/bar'), @@ -245,28 +178,14 @@ suite('Python Test Server', () => { uuid: fakeUuid, }; - deferred = createDeferred(); - mockProc = new MockChildProcess('', ['']); - const output = new Observable>(() => { - /* no op */ - }); - const stubExecutionService2 = ({ - execObservable: () => { + stubExecutionService = ({ + exec: async () => { client.connect(server.getPort()); - return { - proc: mockProc, - out: output, - dispose: () => { - /* no-body */ - }, - }; + return Promise.resolve({ stdout: '', stderr: '' }); }, } as unknown) as IPythonExecutionService; - const stubExecutionFactory2 = ({ - createActivatedEnvironment: () => Promise.resolve(stubExecutionService2), - } as unknown) as IPythonExecutionFactory; - server = new PythonTestServer(stubExecutionFactory2, debugLauncher); + server = new PythonTestServer(stubExecutionFactory, debugLauncher); await server.serverReady(); server.onDataReceived(({ data }) => { eventData = data; @@ -282,7 +201,7 @@ suite('Python Test Server', () => { console.log('Socket connection error:', error); }); - server.sendCommand(options); + await server.sendCommand(options); await deferred.promise; assert.deepStrictEqual(eventData, ''); }); @@ -293,34 +212,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(); - mockProc = new MockChildProcess('', ['']); - const output = new Observable>(() => { - /* no op */ - }); - const stubExecutionService2 = ({ - execObservable: () => { + + stubExecutionService = ({ + exec: async () => { client.connect(server.getPort()); - return { - proc: mockProc, - out: output, - dispose: () => { - /* no-body */ - }, - }; + return Promise.resolve({ stdout: '', stderr: '' }); }, } as unknown) as IPythonExecutionService; - const stubExecutionFactory2 = ({ - createActivatedEnvironment: () => Promise.resolve(stubExecutionService2), - } as unknown) as IPythonExecutionFactory; - server = new PythonTestServer(stubExecutionFactory2, debugLauncher); + server = new PythonTestServer(stubExecutionFactory, debugLauncher); await server.serverReady(); server.onDataReceived(({ data }) => { eventData = data; @@ -336,7 +244,7 @@ suite('Python Test Server', () => { console.log('Socket connection error:', error); }); - server.sendCommand(options); + await server.sendCommand(options); await deferred.promise; assert.deepStrictEqual(eventData, ''); }); @@ -359,6 +267,7 @@ 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'] }, @@ -366,28 +275,15 @@ Request-uuid: UUID_HERE cwd: '/foo/bar', uuid: fakeUuid, }; - deferred = createDeferred(); - mockProc = new MockChildProcess('', ['']); - const output = new Observable>(() => { - /* no op */ - }); - const stubExecutionService2 = ({ - execObservable: () => { + + stubExecutionService = ({ + exec: async () => { client.connect(server.getPort()); - return { - proc: mockProc, - out: output, - dispose: () => { - /* no-body */ - }, - }; + return Promise.resolve({ stdout: '', stderr: '' }); }, } as unknown) as IPythonExecutionService; - const stubExecutionFactory2 = ({ - createActivatedEnvironment: () => Promise.resolve(stubExecutionService2), - } as unknown) as IPythonExecutionFactory; - server = new PythonTestServer(stubExecutionFactory2, debugLauncher); + server = new PythonTestServer(stubExecutionFactory, debugLauncher); await server.serverReady(); const uuid = server.createUUID(); payload = payload.replace('UUID_HERE', uuid); @@ -405,7 +301,7 @@ Request-uuid: UUID_HERE console.log('Socket connection error:', error); }); - server.sendCommand(options); + await server.sendCommand(options); await deferred.promise; assert.deepStrictEqual(eventData, expectedResult); }); @@ -414,29 +310,8 @@ 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(); - mockProc = new MockChildProcess('', ['']); - const output = new Observable>(() => { - /* no op */ - }); - const stubExecutionService2 = ({ - execObservable: () => { - client.connect(server.getPort()); - 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'), @@ -444,6 +319,14 @@ 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); await server.serverReady(); const uuid = server.createUUID(); server.onRunDataReceived(({ data }) => { @@ -466,8 +349,9 @@ Request-uuid: ${uuid} console.log('Socket connection error:', error); }); - server.sendCommand(options); + await 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 41cd1bbd7ef2..5a2e48130746 100644 --- a/src/test/testing/testController/workspaceTestAdapter.unit.test.ts +++ b/src/test/testing/testController/workspaceTestAdapter.unit.test.ts @@ -164,7 +164,8 @@ suite('Workspace test adapter', () => { const buildErrorNodeOptionsStub = sinon.stub(util, 'buildErrorNodeOptions').returns(errorTestItemOptions); const testProvider = 'unittest'; - await workspaceTestAdapter.discoverTests(testController); + const abc = await workspaceTestAdapter.discoverTests(testController); + console.log(abc); sinon.assert.calledWithMatch(createErrorTestItemStub, sinon.match.any, sinon.match.any); sinon.assert.calledWithMatch(buildErrorNodeOptionsStub, Uri.parse('foo'), sinon.match.any, testProvider);