diff --git a/package-lock.json b/package-lock.json index 583d9e5e26de..8958168b021e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "python", - "version": "2024.22.1", + "version": "2024.22.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "python", - "version": "2024.22.1", + "version": "2024.22.2", "license": "MIT", "dependencies": { "@iarna/toml": "^2.2.5", diff --git a/package.json b/package.json index da5934031efc..7167ed9f990e 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "python", "displayName": "Python", "description": "Python language support with extension access points for IntelliSense (Pylance), Debugging (Python Debugger), linting, formatting, refactoring, unit tests, and more.", - "version": "2024.22.1", + "version": "2024.22.2", "featureFlags": { "usingNewInterpreterStorage": true }, diff --git a/python_files/unittestadapter/pvsc_utils.py b/python_files/unittestadapter/pvsc_utils.py index cba3a2d1f59d..34b8553600f1 100644 --- a/python_files/unittestadapter/pvsc_utils.py +++ b/python_files/unittestadapter/pvsc_utils.py @@ -329,7 +329,7 @@ def send_post_request( if __writer is None: try: - __writer = open(test_run_pipe, "w", encoding="utf-8", newline="\r\n") # noqa: SIM115, PTH123 + __writer = open(test_run_pipe, "wb") # noqa: SIM115, PTH123 except Exception as error: error_msg = f"Error attempting to connect to extension named pipe {test_run_pipe}[vscode-unittest]: {error}" print(error_msg, file=sys.stderr) @@ -343,9 +343,17 @@ def send_post_request( data = json.dumps(rpc) try: if __writer: - request = f"""content-length: {len(data)}\ncontent-type: application/json\n\n{data}""" - __writer.write(request) - __writer.flush() + request = ( + f"""content-length: {len(data)}\r\ncontent-type: application/json\r\n\r\n{data}""" + ) + size = 4096 + encoded = request.encode("utf-8") + bytes_written = 0 + while bytes_written < len(encoded): + print("writing more bytes!") + segment = encoded[bytes_written : bytes_written + size] + bytes_written += __writer.write(segment) + __writer.flush() else: print( f"Connection error[vscode-unittest], writer is None \n[vscode-unittest] data: \n{data} \n", diff --git a/python_files/vscode_pytest/__init__.py b/python_files/vscode_pytest/__init__.py index 59a1b75e9688..45f3cc18b031 100644 --- a/python_files/vscode_pytest/__init__.py +++ b/python_files/vscode_pytest/__init__.py @@ -919,7 +919,7 @@ def send_message( if __writer is None: try: - __writer = open(TEST_RUN_PIPE, "w", encoding="utf-8", newline="\r\n") # noqa: SIM115, PTH123 + __writer = open(TEST_RUN_PIPE, "wb") # noqa: SIM115, PTH123 except Exception as error: error_msg = f"Error attempting to connect to extension named pipe {TEST_RUN_PIPE}[vscode-pytest]: {error}" print(error_msg, file=sys.stderr) @@ -939,9 +939,16 @@ def send_message( data = json.dumps(rpc, cls=cls_encoder) try: if __writer: - request = f"""content-length: {len(data)}\ncontent-type: application/json\n\n{data}""" - __writer.write(request) - __writer.flush() + request = ( + f"""content-length: {len(data)}\r\ncontent-type: application/json\r\n\r\n{data}""" + ) + size = 4096 + encoded = request.encode("utf-8") + bytes_written = 0 + while bytes_written < len(encoded): + segment = encoded[bytes_written : bytes_written + size] + bytes_written += __writer.write(segment) + __writer.flush() else: print( f"Plugin error connection error[vscode-pytest], writer is None \n[vscode-pytest] data: \n{data} \n", diff --git a/src/client/testing/testController/common/utils.ts b/src/client/testing/testController/common/utils.ts index 6c1492c2a9b7..7e026105f920 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,10 +7,8 @@ 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 { IServiceContainer } from '../../../ioc/types'; import { DiscoveredTestItem, DiscoveredTestNode, @@ -22,6 +19,8 @@ import { import { Deferred, createDeferred } from '../../../common/utils/async'; import { createReaderPipe, generateRandomPipeName } from '../../../common/pipes/namedPipes'; import { EXTENSION_ROOT_DIR } from '../../../constants'; +import { IExperimentService } from '../../../common/types'; +import { EnableTestAdapterRewrite } from '../../../common/experiments/groups'; export function fixLogLines(content: string): string { const lines = content.split(/\r?\n/g); @@ -61,114 +60,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 { - /** - * Parses the provided raw data to extract JSON-RPC specific headers and remaining data. - * - * This function aims to extract specific JSON-RPC headers (like UUID, content length, - * and content type) from the provided raw string data. Headers are expected to be - * delimited by newlines and the format should be "key:value". The function stops parsing - * once it encounters an empty line, and the rest of the data after this line is treated - * as the remaining raw data. - * - * @param {string} rawData - The raw string containing headers and possibly other data. - * @returns {ParsedRPCHeadersAndData} An object containing the parsed headers as a map and the - * remaining raw data after the headers. - */ - const lines = rawData.split('\n'); - let remainingRawData = ''; - const headerMap = new Map(); - for (let i = 0; i < lines.length; i += 1) { - const line = lines[i]; - if (line === '') { - remainingRawData = lines.slice(i + 1).join('\n'); - break; - } - const [key, value] = line.split(':'); - if (value && value.trim()) { - if ([JSONRPC_UUID_HEADER, JSONRPC_CONTENT_LENGTH_HEADER, JSONRPC_CONTENT_TYPE_HEADER].includes(key)) { - headerMap.set(key.trim(), value.trim()); - } - } - } - - return { - headers: headerMap, - remainingRawData, - }; -} - -export function ExtractJsonRPCData(payloadLength: string | undefined, rawData: string): IJSONRPCData { - /** - * Extracts JSON-RPC content based on provided headers and raw data. - * - * This function uses the `Content-Length` header from the provided headers map - * to determine how much of the rawData string represents the actual JSON content. - * After extracting the expected content, it also returns any remaining data - * that comes after the extracted content as remaining raw data. - * - * @param {string | undefined} payloadLength - The value of the `Content-Length` header. - * @param {string} rawData - The raw string data from which the JSON content will be extracted. - * - * @returns {IJSONRPCContent} An object containing the extracted JSON content and any remaining raw data. - */ - const length = parseInt(payloadLength ?? '0', 10); - const data = rawData.slice(0, length); - const remainingRawData = rawData.slice(length); - return { - extractedJSON: data, - remainingRawData, - }; -} - -export function pythonTestAdapterRewriteEnabled(serviceContainer: IServiceContainer): boolean { - const experiment = serviceContainer.get(IExperimentService); - return experiment.inExperimentSync(EnableTestAdapterRewrite.experiment); -} - interface ExecutionResultMessage extends Message { params: ExecutionTestPayload; } @@ -203,6 +94,28 @@ export async function writeTestIdsFile(testIds: string[]): Promise { return tempFileName; } +export function ExtractJsonRPCData(payloadLength: string | undefined, rawData: string): IJSONRPCData { + /** + * Extracts JSON-RPC content based on provided headers and raw data. + * + * This function uses the `Content-Length` header from the provided headers map + * to determine how much of the rawData string represents the actual JSON content. + * After extracting the expected content, it also returns any remaining data + * that comes after the extracted content as remaining raw data. + * + * @param {string | undefined} payloadLength - The value of the `Content-Length` header. + * @param {string} rawData - The raw string data from which the JSON content will be extracted. + * + * @returns {IJSONRPCContent} An object containing the extracted JSON content and any remaining raw data. + */ + const length = parseInt(payloadLength ?? '0', 10); + const data = rawData.slice(0, length); + const remainingRawData = rawData.slice(length); + return { + extractedJSON: data, + remainingRawData, + }; +} export async function startRunResultNamedPipe( dataReceivedCallback: (payload: ExecutionTestPayload) => void, deferredTillServerClose: Deferred, @@ -224,7 +137,7 @@ export async function startRunResultNamedPipe( if (cancellationToken) { disposables.push( cancellationToken?.onCancellationRequested(() => { - console.log(`Test Result named pipe ${pipeName} cancelled`); + traceLog(`Test Result named pipe ${pipeName} cancelled`); disposable.dispose(); }), ); @@ -254,6 +167,47 @@ interface DiscoveryResultMessage extends Message { params: DiscoveredTestPayload; } +export function parseJsonRPCHeadersAndData(rawData: string): ParsedRPCHeadersAndData { + /** + * Parses the provided raw data to extract JSON-RPC specific headers and remaining data. + * + * This function aims to extract specific JSON-RPC headers (like UUID, content length, + * and content type) from the provided raw string data. Headers are expected to be + * delimited by newlines and the format should be "key:value". The function stops parsing + * once it encounters an empty line, and the rest of the data after this line is treated + * as the remaining raw data. + * + * @param {string} rawData - The raw string containing headers and possibly other data. + * @returns {ParsedRPCHeadersAndData} An object containing the parsed headers as a map and the + * remaining raw data after the headers. + */ + const lines = rawData.split('\n'); + let remainingRawData = ''; + const headerMap = new Map(); + for (let i = 0; i < lines.length; i += 1) { + const line = lines[i]; + if (line === '') { + remainingRawData = lines.slice(i + 1).join('\n'); + break; + } + const [key, value] = line.split(':'); + if (value && value.trim()) { + if ([JSONRPC_UUID_HEADER, JSONRPC_CONTENT_LENGTH_HEADER, JSONRPC_CONTENT_TYPE_HEADER].includes(key)) { + headerMap.set(key.trim(), value.trim()); + } + } + } + + return { + headers: headerMap, + remainingRawData, + }; +} +export function pythonTestAdapterRewriteEnabled(serviceContainer: IServiceContainer): boolean { + const experiment = serviceContainer.get(IExperimentService); + return experiment.inExperimentSync(EnableTestAdapterRewrite.experiment); +} + export async function startDiscoveryNamedPipe( callback: (payload: DiscoveredTestPayload) => void, cancellationToken?: CancellationToken, @@ -297,63 +251,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/client/testing/testController/pytest/pytestExecutionAdapter.ts b/src/client/testing/testController/pytest/pytestExecutionAdapter.ts index bc5ac7dfae9f..7eb03bd86a26 100644 --- a/src/client/testing/testController/pytest/pytestExecutionAdapter.ts +++ b/src/client/testing/testController/pytest/pytestExecutionAdapter.ts @@ -239,9 +239,6 @@ export class PytestTestExecutionAdapter implements ITestExecutionAdapter { runInstance, ); } - // this doesn't work, it instead directs us to the noop one which is defined first - // potentially this is due to the server already being close, if this is the case? - console.log('right before serverDispose'); } // deferredTillEOT is resolved when all data sent on stdout and stderr is received, close event is only called when this occurs diff --git a/src/test/testing/common/testingAdapter.test.ts b/src/test/testing/common/testingAdapter.test.ts index 24a34f8645ed..ec19ce00f13f 100644 --- a/src/test/testing/common/testingAdapter.test.ts +++ b/src/test/testing/common/testingAdapter.test.ts @@ -1066,7 +1066,6 @@ suite('End to End Tests: test adapters', () => { let callCount = 0; let failureOccurred = false; let failureMsg = ''; - console.log('EFB: beginning function'); resultResolver._resolveExecution = async (data, _token?) => { // do the following asserts for each time resolveExecution is called, should be called once per test. console.log(`pytest execution adapter seg fault error handling \n ${JSON.stringify(data)}`); diff --git a/src/testTestingRootWkspc/largeWorkspace/test_parameterized_subtest.py b/src/testTestingRootWkspc/largeWorkspace/test_parameterized_subtest.py index a76856ebb929..40c5de531f7c 100644 --- a/src/testTestingRootWkspc/largeWorkspace/test_parameterized_subtest.py +++ b/src/testTestingRootWkspc/largeWorkspace/test_parameterized_subtest.py @@ -14,3 +14,106 @@ def test_even(self): for i in range(0, 2000): with self.subTest(i=i): self.assertEqual(i % 2, 0) + + +# The repeated tests below are to test the unittest communication as it hits it maximum limit of bytes. + + +class NumberedTests1(unittest.TestCase): + def test_abc(self): + self.assertEqual(1 % 2, 0) + + +class NumberedTests2(unittest.TestCase): + def test_abc(self): + self.assertEqual(1 % 2, 0) + + +class NumberedTests3(unittest.TestCase): + def test_abc(self): + self.assertEqual(1 % 2, 0) + + +class NumberedTests4(unittest.TestCase): + def test_abc(self): + self.assertEqual(1 % 2, 0) + + +class NumberedTests5(unittest.TestCase): + def test_abc(self): + self.assertEqual(1 % 2, 0) + + +class NumberedTests6(unittest.TestCase): + def test_abc(self): + self.assertEqual(1 % 2, 0) + + +class NumberedTests7(unittest.TestCase): + def test_abc(self): + self.assertEqual(1 % 2, 0) + + +class NumberedTests8(unittest.TestCase): + def test_abc(self): + self.assertEqual(1 % 2, 0) + + +class NumberedTests9(unittest.TestCase): + def test_abc(self): + self.assertEqual(1 % 2, 0) + + +class NumberedTests10(unittest.TestCase): + def test_abc(self): + self.assertEqual(1 % 2, 0) + + +class NumberedTests11(unittest.TestCase): + def test_abc(self): + self.assertEqual(1 % 2, 0) + + +class NumberedTests12(unittest.TestCase): + def test_abc(self): + self.assertEqual(1 % 2, 0) + + +class NumberedTests13(unittest.TestCase): + def test_abc(self): + self.assertEqual(1 % 2, 0) + + +class NumberedTests14(unittest.TestCase): + def test_abc(self): + self.assertEqual(1 % 2, 0) + + +class NumberedTests15(unittest.TestCase): + def test_abc(self): + self.assertEqual(1 % 2, 0) + + +class NumberedTests16(unittest.TestCase): + def test_abc(self): + self.assertEqual(1 % 2, 0) + + +class NumberedTests17(unittest.TestCase): + def test_abc(self): + self.assertEqual(1 % 2, 0) + + +class NumberedTests18(unittest.TestCase): + def test_abc(self): + self.assertEqual(1 % 2, 0) + + +class NumberedTests19(unittest.TestCase): + def test_abc(self): + self.assertEqual(1 % 2, 0) + + +class NumberedTests20(unittest.TestCase): + def test_abc(self): + self.assertEqual(1 % 2, 0)