From 849254283aaa06c031380b75282996419ab52588 Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Mon, 9 Dec 2024 13:28:49 -0800 Subject: [PATCH] v2.2 --- src/client/testing/common/debugLauncher.ts | 39 ++-- .../testController/common/discoveryHelper.ts | 43 ----- .../common/externalDependencies.ts | 20 -- .../testController/common/resultsHelper.ts | 174 ------------------ .../testing/testController/common/utils.ts | 108 ----------- .../testing/common/debugLauncher.unit.test.ts | 4 - 6 files changed, 15 insertions(+), 373 deletions(-) delete mode 100644 src/client/testing/testController/common/discoveryHelper.ts delete mode 100644 src/client/testing/testController/common/externalDependencies.ts delete mode 100644 src/client/testing/testController/common/resultsHelper.ts diff --git a/src/client/testing/common/debugLauncher.ts b/src/client/testing/common/debugLauncher.ts index 1954072b17b0..c28535b30644 100644 --- a/src/client/testing/common/debugLauncher.ts +++ b/src/client/testing/common/debugLauncher.ts @@ -16,7 +16,6 @@ import { getConfigurationsForWorkspace } from '../../debugger/extension/configur import { getWorkspaceFolder, getWorkspaceFolders } from '../../common/vscodeApis/workspaceApis'; import { showErrorMessage } from '../../common/vscodeApis/windowApis'; import { createDeferred } from '../../common/utils/async'; -import { pythonTestAdapterRewriteEnabled } from '../testController/common/utils'; import { addPathToPythonpath } from './helpers'; @injectable() @@ -199,11 +198,10 @@ export class DebugLauncher implements ITestDebugLauncher { workspaceFolder: WorkspaceFolder, options: LaunchOptions, ): Promise { - const pythonTestAdapterRewriteExperiment = pythonTestAdapterRewriteEnabled(this.serviceContainer); const configArgs = debugConfig as LaunchRequestArguments; const testArgs = options.testProvider === 'unittest' ? options.args.filter((item) => item !== '--debug') : options.args; - const script = DebugLauncher.getTestLauncherScript(options.testProvider, pythonTestAdapterRewriteExperiment); + const script = DebugLauncher.getTestLauncherScript(options.testProvider); const args = script(testArgs); const [program] = args; configArgs.program = program; @@ -229,19 +227,18 @@ export class DebugLauncher implements ITestDebugLauncher { } launchArgs.request = 'launch'; - if (pythonTestAdapterRewriteExperiment) { - if (options.pytestPort && options.runTestIdsPort) { - launchArgs.env = { - ...launchArgs.env, - TEST_RUN_PIPE: options.pytestPort, - RUN_TEST_IDS_PIPE: options.runTestIdsPort, - }; - } else { - throw Error( - `Missing value for debug setup, both port and uuid need to be defined. port: "${options.pytestPort}" uuid: "${options.pytestUUID}"`, - ); - } + if (options.pytestPort && options.runTestIdsPort) { + launchArgs.env = { + ...launchArgs.env, + TEST_RUN_PIPE: options.pytestPort, + RUN_TEST_IDS_PIPE: options.runTestIdsPort, + }; + } else { + throw Error( + `Missing value for debug setup, both port and uuid need to be defined. port: "${options.pytestPort}" uuid: "${options.pytestUUID}"`, + ); } + const pluginPath = path.join(EXTENSION_ROOT_DIR, 'python_files'); // check if PYTHONPATH is already set in the environment variables if (launchArgs.env) { @@ -263,19 +260,13 @@ export class DebugLauncher implements ITestDebugLauncher { return launchArgs; } - private static getTestLauncherScript(testProvider: TestProvider, pythonTestAdapterRewriteExperiment?: boolean) { + private static getTestLauncherScript(testProvider: TestProvider) { switch (testProvider) { case 'unittest': { - if (pythonTestAdapterRewriteExperiment) { - return internalScripts.execution_py_testlauncher; // this is the new way to run unittest execution, debugger - } - return internalScripts.visualstudio_py_testlauncher; // old way unittest execution, debugger + return internalScripts.execution_py_testlauncher; // this is the new way to run unittest execution, debugger } case 'pytest': { - if (pythonTestAdapterRewriteExperiment) { - return internalScripts.pytestlauncher; // this is the new way to run pytest execution, debugger - } - return internalScripts.testlauncher; // old way pytest execution, debugger + return internalScripts.pytestlauncher; // this is the new way to run pytest execution, debugger } default: { throw new Error(`Unknown test provider '${testProvider}'`); diff --git a/src/client/testing/testController/common/discoveryHelper.ts b/src/client/testing/testController/common/discoveryHelper.ts deleted file mode 100644 index 4f3209496c18..000000000000 --- a/src/client/testing/testController/common/discoveryHelper.ts +++ /dev/null @@ -1,43 +0,0 @@ -// // Copyright (c) Microsoft Corporation. All rights reserved. -// // Licensed under the MIT License. - -// import { inject, injectable } from 'inversify'; -// import { -// ExecutionFactoryCreateWithEnvironmentOptions, -// IPythonExecutionFactory, -// SpawnOptions, -// } from '../../../common/process/types'; -// import { TestDiscoveryOptions } from '../../common/types'; -// import { ITestDiscoveryHelper, RawDiscoveredTests } from './types'; - -// @injectable() -// export class TestDiscoveryHelper implements ITestDiscoveryHelper { -// constructor(@inject(IPythonExecutionFactory) private readonly pythonExecFactory: IPythonExecutionFactory) {} - -// public async runTestDiscovery(options: TestDiscoveryOptions): Promise { -// const creationOptions: ExecutionFactoryCreateWithEnvironmentOptions = { -// allowEnvironmentFetchExceptions: false, -// resource: options.workspaceFolder, -// }; -// const execService = await this.pythonExecFactory.createActivatedEnvironment(creationOptions); - -// const spawnOptions: SpawnOptions = { -// token: options.token, -// cwd: options.cwd, -// throwOnStdErr: true, -// }; - -// if (options.outChannel) { -// options.outChannel.appendLine(`python ${options.args.join(' ')}`); -// } - -// const proc = await execService.exec(options.args, spawnOptions); -// try { -// return JSON.parse(proc.stdout); -// } catch (ex) { -// const error = ex as SyntaxError; -// error.message = proc.stdout; -// throw ex; // re-throw -// } -// } -// } diff --git a/src/client/testing/testController/common/externalDependencies.ts b/src/client/testing/testController/common/externalDependencies.ts deleted file mode 100644 index db7bc9448d27..000000000000 --- a/src/client/testing/testController/common/externalDependencies.ts +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import * as tmp from 'tmp'; -import { TemporaryFile } from '../../../common/platform/types'; - -export function createTemporaryFile(ext = '.tmp'): Promise { - return new Promise((resolve, reject) => { - tmp.file({ postfix: ext }, (err, filename, _fd, cleanUp): void => { - if (err) { - reject(err); - } else { - resolve({ - filePath: filename, - dispose: cleanUp, - }); - } - }); - }); -} diff --git a/src/client/testing/testController/common/resultsHelper.ts b/src/client/testing/testController/common/resultsHelper.ts deleted file mode 100644 index 6474c726e09c..000000000000 --- a/src/client/testing/testController/common/resultsHelper.ts +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { Location, TestItem, TestMessage, TestRun } from 'vscode'; -import * as fsapi from '../../../common/platform/fs-paths'; -import { getRunIdFromRawData, getTestCaseNodes } from './testItemUtilities'; -import { TestData } from './types'; -import { fixLogLines } from './utils'; - -type TestSuiteResult = { - $: { - errors: string; - failures: string; - name: string; - skips: string; - skip: string; - tests: string; - time: string; - }; - testcase: TestCaseResult[]; -}; -type TestCaseResult = { - $: { - classname: string; - file: string; - line: string; - name: string; - time: string; - }; - failure: { - _: string; - $: { message: string; type: string }; - }[]; - error: { - _: string; - $: { message: string; type: string }; - }[]; - skipped: { - _: string; - $: { message: string; type: string }; - }[]; -}; - -async function parseXML(data: string): Promise { - const xml2js = await import('xml2js'); - - return new Promise((resolve, reject) => { - xml2js.parseString(data, (error: Error, result: unknown) => { - if (error) { - return reject(error); - } - return resolve(result); - }); - }); -} - -function getJunitResults(parserResult: unknown): TestSuiteResult | undefined { - // This is the newer JUnit XML format (e.g. pytest 5.1 and later). - const fullResults = parserResult as { testsuites: { testsuite: TestSuiteResult[] } }; - if (!fullResults.testsuites) { - return (parserResult as { testsuite: TestSuiteResult }).testsuite; - } - - const junitSuites = fullResults.testsuites.testsuite; - if (!Array.isArray(junitSuites)) { - throw Error('bad JUnit XML data'); - } - if (junitSuites.length === 0) { - return undefined; - } - if (junitSuites.length > 1) { - throw Error('got multiple XML results'); - } - return junitSuites[0]; -} - -export async function updateResultFromJunitXml( - outputXmlFile: string, - testNode: TestItem, - runInstance: TestRun, - idToRawData: Map, -): Promise { - const data = await fsapi.readFile(outputXmlFile); - const parserResult = await parseXML(data.toString('utf8')); - const junitSuite = getJunitResults(parserResult); - const testCaseNodes = getTestCaseNodes(testNode); - - if (junitSuite && junitSuite.testcase.length > 0 && testCaseNodes.length > 0) { - let failures = 0; - let skipped = 0; - let errors = 0; - let passed = 0; - - testCaseNodes.forEach((node) => { - const rawTestCaseNode = idToRawData.get(node.id); - if (!rawTestCaseNode) { - return; - } - - const result = junitSuite.testcase.find((t) => { - const idResult = getRunIdFromRawData(`${t.$.classname}::${t.$.name}`); - const idNode = rawTestCaseNode.runId; - return idResult === idNode || idNode.endsWith(idResult); - }); - if (result) { - if (result.error) { - errors += 1; - const error = result.error[0]; - const text = `${rawTestCaseNode.rawId} Failed with Error: [${error.$.type}]${error.$.message}\r\n${error._}\r\n\r\n`; - const message = new TestMessage(text); - - if (node.uri && node.range) { - message.location = new Location(node.uri, node.range); - } - - runInstance.errored(node, message); - runInstance.appendOutput(fixLogLines(text)); - } else if (result.failure) { - failures += 1; - const failure = result.failure[0]; - const text = `${rawTestCaseNode.rawId} Failed: [${failure.$.type}]${failure.$.message}\r\n${failure._}\r\n`; - const message = new TestMessage(text); - - if (node.uri && node.range) { - message.location = new Location(node.uri, node.range); - } - - runInstance.failed(node, message); - runInstance.appendOutput(fixLogLines(text)); - } else if (result.skipped) { - const skip = result.skipped[0]; - let text = ''; - if (skip.$.type === 'pytest.xfail') { - passed += 1; - // pytest.xfail ==> expected failure via @unittest.expectedFailure - text = `${rawTestCaseNode.rawId} Passed: [${skip.$.type}]${skip.$.message}\r\n`; - runInstance.passed(node); - } else { - skipped += 1; - text = `${rawTestCaseNode.rawId} Skipped: [${skip.$.type}]${skip.$.message}\r\n`; - runInstance.skipped(node); - } - runInstance.appendOutput(fixLogLines(text)); - } else { - passed += 1; - const text = `${rawTestCaseNode.rawId} Passed\r\n`; - runInstance.passed(node); - runInstance.appendOutput(fixLogLines(text)); - } - } else { - const text = `Test result not found for: ${rawTestCaseNode.rawId}\r\n`; - runInstance.appendOutput(fixLogLines(text)); - const message = new TestMessage(text); - - if (node.uri && node.range) { - message.location = new Location(node.uri, node.range); - } - runInstance.errored(node, message); - } - }); - - runInstance.appendOutput(`Total number of tests expected to run: ${testCaseNodes.length}\r\n`); - runInstance.appendOutput(`Total number of tests run: ${passed + failures + errors + skipped}\r\n`); - runInstance.appendOutput(`Total number of tests passed: ${passed}\r\n`); - runInstance.appendOutput(`Total number of tests failed: ${failures}\r\n`); - runInstance.appendOutput(`Total number of tests failed with errors: ${errors}\r\n`); - runInstance.appendOutput(`Total number of tests skipped: ${skipped}\r\n`); - runInstance.appendOutput( - `Total number of tests with no result data: ${ - testCaseNodes.length - passed - failures - errors - skipped - }\r\n`, - ); - } -} diff --git a/src/client/testing/testController/common/utils.ts b/src/client/testing/testController/common/utils.ts index 6c1492c2a9b7..496c23a5c9aa 100644 --- a/src/client/testing/testController/common/utils.ts +++ b/src/client/testing/testController/common/utils.ts @@ -1,6 +1,5 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import * as net from 'net'; import * as path from 'path'; import * as fs from 'fs'; import * as os from 'os'; @@ -8,9 +7,6 @@ import * as crypto from 'crypto'; import { CancellationToken, Position, TestController, TestItem, Uri, Range, Disposable } from 'vscode'; import { Message } from 'vscode-jsonrpc'; import { traceError, traceInfo, traceLog, traceVerbose } from '../../../logging'; -import { EnableTestAdapterRewrite } from '../../../common/experiments/groups'; -import { IExperimentService } from '../../../common/types'; -import { IServiceContainer } from '../../../ioc/types'; import { DebugTestTag, ErrorTestItemOptions, RunTestTag } from './testItemUtilities'; import { DiscoveredTestItem, @@ -61,48 +57,6 @@ export function createTestingDeferred(): Deferred { return createDeferred(); } -export function extractJsonPayload(rawData: string, uuids: Array): ExtractOutput { - /** - * Extracts JSON-RPC payload from the provided raw data. - * @param {string} rawData - The raw string data from which the JSON payload will be extracted. - * @param {Array} uuids - The list of UUIDs that are active. - * @returns {string} The remaining raw data after the JSON payload is extracted. - */ - - const rpcHeaders: ParsedRPCHeadersAndData = parseJsonRPCHeadersAndData(rawData); - - // verify the RPC has a UUID and that it is recognized - let uuid = rpcHeaders.headers.get(JSONRPC_UUID_HEADER); - uuid = checkUuid(uuid, uuids); - - const payloadLength = rpcHeaders.headers.get('Content-Length'); - - // separate out the data within context length of the given payload from the remaining data in the buffer - const rpcContent: IJSONRPCData = ExtractJsonRPCData(payloadLength, rpcHeaders.remainingRawData); - const cleanedJsonData = rpcContent.extractedJSON; - const { remainingRawData } = rpcContent; - - // if the given payload has the complete json, process it otherwise wait for the rest in the buffer - if (cleanedJsonData.length === Number(payloadLength)) { - // call to process this data - // remove this data from the buffer - return { uuid, cleanedJsonData, remainingRawData }; - } - // wait for the remaining - return { uuid: undefined, cleanedJsonData: undefined, remainingRawData: rawData }; -} - -export function checkUuid(uuid: string | undefined, uuids: Array): string | undefined { - if (!uuid) { - // no UUID found, this could occurred if the payload is full yet so send back without erroring - return undefined; - } - if (!uuids.includes(uuid)) { - // no UUID found, this could occurred if the payload is full yet so send back without erroring - throw new Error('On data received: Error occurred because the payload UUID is not recognized'); - } - return uuid; -} export function parseJsonRPCHeadersAndData(rawData: string): ParsedRPCHeadersAndData { /** @@ -164,11 +118,6 @@ export function ExtractJsonRPCData(payloadLength: string | undefined, rawData: s }; } -export function pythonTestAdapterRewriteEnabled(serviceContainer: IServiceContainer): boolean { - const experiment = serviceContainer.get(IExperimentService); - return experiment.inExperimentSync(EnableTestAdapterRewrite.experiment); -} - interface ExecutionResultMessage extends Message { params: ExecutionTestPayload; } @@ -297,63 +246,6 @@ export async function startDiscoveryNamedPipe( return pipeName; } -export async function startTestIdServer(testIds: string[]): Promise { - const startServer = (): Promise => - new Promise((resolve, reject) => { - const server = net.createServer((socket: net.Socket) => { - // Convert the test_ids array to JSON - const testData = JSON.stringify(testIds); - - // Create the headers - const headers = [`Content-Length: ${Buffer.byteLength(testData)}`, 'Content-Type: application/json']; - - // Create the payload by concatenating the headers and the test data - const payload = `${headers.join('\r\n')}\r\n\r\n${testData}`; - - // Send the payload to the socket - socket.write(payload); - - // Handle socket events - socket.on('data', (data) => { - traceLog('Received data:', data.toString()); - }); - - socket.on('end', () => { - traceLog('Client disconnected'); - }); - }); - - server.listen(0, () => { - const { port } = server.address() as net.AddressInfo; - traceLog(`Server listening on port ${port}`); - resolve(port); - }); - - server.on('error', (error: Error) => { - reject(error); - }); - }); - - // Start the server and wait until it is listening - let returnPort = 0; - try { - await startServer() - .then((assignedPort) => { - traceVerbose(`Server started for pytest test ids server and listening on port ${assignedPort}`); - returnPort = assignedPort; - }) - .catch((error) => { - traceError('Error starting server for pytest test ids server:', error); - return 0; - }) - .finally(() => returnPort); - return returnPort; - } catch { - traceError('Error starting server for pytest test ids server, cannot get port.'); - return returnPort; - } -} - export function buildErrorNodeOptions(uri: Uri, message: string, testType: string): ErrorTestItemOptions { const labelText = testType === 'pytest' ? 'pytest Discovery Error' : 'Unittest Discovery Error'; return { diff --git a/src/test/testing/common/debugLauncher.unit.test.ts b/src/test/testing/common/debugLauncher.unit.test.ts index cb4b582639ea..045dd7d8fd27 100644 --- a/src/test/testing/common/debugLauncher.unit.test.ts +++ b/src/test/testing/common/debugLauncher.unit.test.ts @@ -29,7 +29,6 @@ import { ITestingSettings } from '../../../client/testing/configuration/types'; import { TestProvider } from '../../../client/testing/types'; import { isOs, OSType } from '../../common'; import { IEnvironmentActivationService } from '../../../client/interpreter/activation/types'; -import * as util from '../../../client/testing/testController/common/utils'; import { createDeferred } from '../../../client/common/utils/async'; use(chaiAsPromised.default); @@ -47,7 +46,6 @@ suite('Unit Tests - Debug Launcher', () => { let getWorkspaceFoldersStub: sinon.SinonStub; let pathExistsStub: sinon.SinonStub; let readFileStub: sinon.SinonStub; - let pythonTestAdapterRewriteEnabledStub: sinon.SinonStub; const envVars = { FOO: 'BAR' }; setup(async () => { @@ -68,8 +66,6 @@ suite('Unit Tests - Debug Launcher', () => { getWorkspaceFoldersStub = sinon.stub(workspaceApis, 'getWorkspaceFolders'); pathExistsStub = sinon.stub(fs, 'pathExists'); readFileStub = sinon.stub(fs, 'readFile'); - pythonTestAdapterRewriteEnabledStub = sinon.stub(util, 'pythonTestAdapterRewriteEnabled'); - pythonTestAdapterRewriteEnabledStub.returns(false); const appShell = TypeMoq.Mock.ofType(undefined, TypeMoq.MockBehavior.Strict); appShell.setup((a) => a.showErrorMessage(TypeMoq.It.isAny())).returns(() => Promise.resolve(undefined));