From af602621e7dbc912acf16d4dafa20870bcbb8d4c Mon Sep 17 00:00:00 2001 From: Eleanor Boyd Date: Tue, 7 Jan 2025 11:04:07 -0800 Subject: [PATCH] fix for testing capacity (#24695) fixes https://github.com/microsoft/vscode-python/issues/24656 this change: restores the reverted code which switches to using fifo fixes the bug for large workspaces with fifo for mac --- noxfile.py | 1 + python_files/testing_tools/socket_manager.py | 56 ++--- python_files/tests/pytestadapter/helpers.py | 33 ++- python_files/unittestadapter/pvsc_utils.py | 19 +- python_files/vscode_pytest/__init__.py | 50 ++-- python_files/vscode_pytest/_common.py | 2 + src/client/common/pipes/namedPipes.ts | 231 +++++++++++++----- .../testing/testController/common/utils.ts | 142 +++++------ .../pytest/pytestDiscoveryAdapter.ts | 9 +- .../pytest/pytestExecutionAdapter.ts | 25 +- .../unittest/testDiscoveryAdapter.ts | 4 +- .../unittest/testExecutionAdapter.ts | 20 +- .../testing/common/testingAdapter.test.ts | 86 +------ .../pytestDiscoveryAdapter.unit.test.ts | 9 +- .../pytestExecutionAdapter.unit.test.ts | 9 +- .../testCancellationRunAdapters.unit.test.ts | 31 ++- .../testDiscoveryAdapter.unit.test.ts | 9 +- .../testExecutionAdapter.unit.test.ts | 9 +- .../errorWorkspace/test_seg_fault.py | 3 +- .../test_parameterized_subtest.py | 103 ++++++++ 20 files changed, 478 insertions(+), 373 deletions(-) create mode 100644 python_files/vscode_pytest/_common.py diff --git a/noxfile.py b/noxfile.py index 60e22d461074..3991ee8c025a 100644 --- a/noxfile.py +++ b/noxfile.py @@ -53,6 +53,7 @@ def install_python_libs(session: nox.Session): ) session.install("packaging") + session.install("debugpy") # Download get-pip script session.run( diff --git a/python_files/testing_tools/socket_manager.py b/python_files/testing_tools/socket_manager.py index 347453a6ca1a..f143ac111cdb 100644 --- a/python_files/testing_tools/socket_manager.py +++ b/python_files/testing_tools/socket_manager.py @@ -20,39 +20,24 @@ def __exit__(self, *_): self.close() def connect(self): - if sys.platform == "win32": - self._writer = open(self.name, "w", encoding="utf-8") # noqa: SIM115, PTH123 - # reader created in read method - else: - self._socket = _SOCKET(socket.AF_UNIX, socket.SOCK_STREAM) - self._socket.connect(self.name) + self._writer = open(self.name, "w", encoding="utf-8") # noqa: SIM115, PTH123 + # reader created in read method return self def close(self): - if sys.platform == "win32": - self._writer.close() - else: - # add exception catch - self._socket.close() + self._writer.close() + if hasattr(self, "_reader"): + self._reader.close() def write(self, data: str): - if sys.platform == "win32": - try: - # for windows, is should only use \n\n - request = ( - f"""content-length: {len(data)}\ncontent-type: application/json\n\n{data}""" - ) - self._writer.write(request) - self._writer.flush() - except Exception as e: - print("error attempting to write to pipe", e) - raise (e) - else: - # must include the carriage-return defined (as \r\n) for unix systems - request = ( - f"""content-length: {len(data)}\r\ncontent-type: application/json\r\n\r\n{data}""" - ) - self._socket.send(request.encode("utf-8")) + try: + # for windows, is should only use \n\n + request = f"""content-length: {len(data)}\ncontent-type: application/json\n\n{data}""" + self._writer.write(request) + self._writer.flush() + except Exception as e: + print("error attempting to write to pipe", e) + raise (e) def read(self, bufsize=1024) -> str: """Read data from the socket. @@ -63,17 +48,10 @@ def read(self, bufsize=1024) -> str: Returns: data (str): Data received from the socket. """ - if sys.platform == "win32": - # returns a string automatically from read - if not hasattr(self, "_reader"): - self._reader = open(self.name, encoding="utf-8") # noqa: SIM115, PTH123 - return self._reader.read(bufsize) - else: - # receive bytes and convert to string - while True: - part: bytes = self._socket.recv(bufsize) - data: str = part.decode("utf-8") - return data + # returns a string automatically from read + if not hasattr(self, "_reader"): + self._reader = open(self.name, encoding="utf-8") # noqa: SIM115, PTH123 + return self._reader.read(bufsize) class SocketManager: diff --git a/python_files/tests/pytestadapter/helpers.py b/python_files/tests/pytestadapter/helpers.py index 7972eedd0919..7a75e6248844 100644 --- a/python_files/tests/pytestadapter/helpers.py +++ b/python_files/tests/pytestadapter/helpers.py @@ -128,6 +128,22 @@ def parse_rpc_message(data: str) -> Tuple[Dict[str, str], str]: print("json decode error") +def _listen_on_fifo(pipe_name: str, result: List[str], completed: threading.Event): + # Open the FIFO for reading + fifo_path = pathlib.Path(pipe_name) + with fifo_path.open() as fifo: + print("Waiting for data...") + while True: + if completed.is_set(): + break # Exit loop if completed event is set + data = fifo.read() # This will block until data is available + if len(data) == 0: + # If data is empty, assume EOF + break + print(f"Received: {data}") + result.append(data) + + def _listen_on_pipe_new(listener, result: List[str], completed: threading.Event): """Listen on the named pipe or Unix domain socket for JSON data from the server. @@ -307,14 +323,19 @@ def runner_with_cwd_env( # if additional environment variables are passed, add them to the environment if env_add: env.update(env_add) - server = UnixPipeServer(pipe_name) - server.start() + # server = UnixPipeServer(pipe_name) + # server.start() + ################# + # Create the FIFO (named pipe) if it doesn't exist + # if not pathlib.Path.exists(pipe_name): + os.mkfifo(pipe_name) + ################# completed = threading.Event() result = [] # result is a string array to store the data during threading t1: threading.Thread = threading.Thread( - target=_listen_on_pipe_new, args=(server, result, completed) + target=_listen_on_fifo, args=(pipe_name, result, completed) ) t1.start() @@ -364,14 +385,14 @@ def generate_random_pipe_name(prefix=""): # For Windows, named pipes have a specific naming convention. if sys.platform == "win32": - return f"\\\\.\\pipe\\{prefix}-{random_suffix}-sock" + return f"\\\\.\\pipe\\{prefix}-{random_suffix}" # For Unix-like systems, use either the XDG_RUNTIME_DIR or a temporary directory. xdg_runtime_dir = os.getenv("XDG_RUNTIME_DIR") if xdg_runtime_dir: - return os.path.join(xdg_runtime_dir, f"{prefix}-{random_suffix}.sock") # noqa: PTH118 + return os.path.join(xdg_runtime_dir, f"{prefix}-{random_suffix}") # noqa: PTH118 else: - return os.path.join(tempfile.gettempdir(), f"{prefix}-{random_suffix}.sock") # noqa: PTH118 + return os.path.join(tempfile.gettempdir(), f"{prefix}-{random_suffix}") # noqa: PTH118 class UnixPipeServer: diff --git a/python_files/unittestadapter/pvsc_utils.py b/python_files/unittestadapter/pvsc_utils.py index 09e61ff40518..34b8553600f1 100644 --- a/python_files/unittestadapter/pvsc_utils.py +++ b/python_files/unittestadapter/pvsc_utils.py @@ -18,8 +18,6 @@ from typing_extensions import NotRequired # noqa: E402 -from testing_tools import socket_manager # noqa: E402 - # Types @@ -331,10 +329,10 @@ def send_post_request( if __writer is None: try: - __writer = socket_manager.PipeManager(test_run_pipe) - __writer.connect() + __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) __writer = None raise VSCodeUnittestError(error_msg) from error @@ -343,10 +341,19 @@ def send_post_request( "params": payload, } data = json.dumps(rpc) - try: if __writer: - __writer.write(data) + 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 682ed0af90e2..45f3cc18b031 100644 --- a/python_files/vscode_pytest/__init__.py +++ b/python_files/vscode_pytest/__init__.py @@ -21,11 +21,6 @@ import pytest -script_dir = pathlib.Path(__file__).parent.parent -sys.path.append(os.fspath(script_dir)) -sys.path.append(os.fspath(script_dir / "lib" / "python")) -from testing_tools import socket_manager # noqa: E402 - if TYPE_CHECKING: from pluggy import Result @@ -171,7 +166,7 @@ def pytest_exception_interact(node, call, report): collected_test = TestRunResultDict() collected_test[node_id] = item_result cwd = pathlib.Path.cwd() - execution_post( + send_execution_message( os.fsdecode(cwd), "success", collected_test if collected_test else None, @@ -295,7 +290,7 @@ def pytest_report_teststatus(report, config): # noqa: ARG001 ) collected_test = TestRunResultDict() collected_test[absolute_node_id] = item_result - execution_post( + send_execution_message( os.fsdecode(cwd), "success", collected_test if collected_test else None, @@ -329,7 +324,7 @@ def pytest_runtest_protocol(item, nextitem): # noqa: ARG001 ) collected_test = TestRunResultDict() collected_test[absolute_node_id] = item_result - execution_post( + send_execution_message( os.fsdecode(cwd), "success", collected_test if collected_test else None, @@ -405,7 +400,7 @@ def pytest_sessionfinish(session, exitstatus): "children": [], "id_": "", } - post_response(os.fsdecode(cwd), error_node) + send_discovery_message(os.fsdecode(cwd), error_node) try: session_node: TestNode | None = build_test_tree(session) if not session_node: @@ -413,7 +408,7 @@ def pytest_sessionfinish(session, exitstatus): "Something went wrong following pytest finish, \ no session node was created" ) - post_response(os.fsdecode(cwd), session_node) + send_discovery_message(os.fsdecode(cwd), session_node) except Exception as e: ERRORS.append( f"Error Occurred, traceback: {(traceback.format_exc() if e.__traceback__ else '')}" @@ -425,7 +420,7 @@ def pytest_sessionfinish(session, exitstatus): "children": [], "id_": "", } - post_response(os.fsdecode(cwd), error_node) + send_discovery_message(os.fsdecode(cwd), error_node) else: if exitstatus == 0 or exitstatus == 1: exitstatus_bool = "success" @@ -435,7 +430,7 @@ def pytest_sessionfinish(session, exitstatus): ) exitstatus_bool = "error" - execution_post( + send_execution_message( os.fsdecode(cwd), exitstatus_bool, None, @@ -485,7 +480,7 @@ def pytest_sessionfinish(session, exitstatus): result=file_coverage_map, error=None, ) - send_post_request(payload) + send_message(payload) def build_test_tree(session: pytest.Session) -> TestNode: @@ -853,8 +848,10 @@ def get_node_path(node: Any) -> pathlib.Path: atexit.register(lambda: __writer.close() if __writer else None) -def execution_post(cwd: str, status: Literal["success", "error"], tests: TestRunResultDict | None): - """Sends a POST request with execution payload details. +def send_execution_message( + cwd: str, status: Literal["success", "error"], tests: TestRunResultDict | None +): + """Sends message execution payload details. Args: cwd (str): Current working directory. @@ -866,10 +863,10 @@ def execution_post(cwd: str, status: Literal["success", "error"], tests: TestRun ) if ERRORS: payload["error"] = ERRORS - send_post_request(payload) + send_message(payload) -def post_response(cwd: str, session_node: TestNode) -> None: +def send_discovery_message(cwd: str, session_node: TestNode) -> None: """ Sends a POST request with test session details in payload. @@ -885,7 +882,7 @@ def post_response(cwd: str, session_node: TestNode) -> None: } if ERRORS is not None: payload["error"] = ERRORS - send_post_request(payload, cls_encoder=PathEncoder) + send_message(payload, cls_encoder=PathEncoder) class PathEncoder(json.JSONEncoder): @@ -897,7 +894,7 @@ def default(self, o): return super().default(o) -def send_post_request( +def send_message( payload: ExecutionPayloadDict | DiscoveryPayloadDict | CoveragePayloadDict, cls_encoder=None, ): @@ -922,8 +919,7 @@ def send_post_request( if __writer is None: try: - __writer = socket_manager.PipeManager(TEST_RUN_PIPE) - __writer.connect() + __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) @@ -941,10 +937,18 @@ def send_post_request( "params": payload, } data = json.dumps(rpc, cls=cls_encoder) - try: if __writer: - __writer.write(data) + 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/python_files/vscode_pytest/_common.py b/python_files/vscode_pytest/_common.py new file mode 100644 index 000000000000..9f835f555b6e --- /dev/null +++ b/python_files/vscode_pytest/_common.py @@ -0,0 +1,2 @@ +# def send_post_request(): +# return diff --git a/src/client/common/pipes/namedPipes.ts b/src/client/common/pipes/namedPipes.ts index c6010d491822..8cccd4cdcfed 100644 --- a/src/client/common/pipes/namedPipes.ts +++ b/src/client/common/pipes/namedPipes.ts @@ -1,67 +1,18 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +import * as cp from 'child_process'; import * as crypto from 'crypto'; +import * as fs from 'fs-extra'; import * as net from 'net'; import * as os from 'os'; import * as path from 'path'; import * as rpc from 'vscode-jsonrpc/node'; +import { CancellationError, CancellationToken, Disposable } from 'vscode'; import { traceVerbose } from '../../logging'; - -export interface ConnectedServerObj { - serverOnClosePromise(): Promise; -} - -export function createNamedPipeServer( - pipeName: string, - onConnectionCallback: (value: [rpc.MessageReader, rpc.MessageWriter]) => void, -): Promise { - traceVerbose(`Creating named pipe server on ${pipeName}`); - - let connectionCount = 0; - return new Promise((resolve, reject) => { - // create a server, resolves and returns server on listen - const server = net.createServer((socket) => { - // this lambda function is called whenever a client connects to the server - connectionCount += 1; - traceVerbose('new client is connected to the socket, connectionCount: ', connectionCount, pipeName); - socket.on('close', () => { - // close event is emitted by client to the server - connectionCount -= 1; - traceVerbose('client emitted close event, connectionCount: ', connectionCount); - if (connectionCount <= 0) { - // if all clients are closed, close the server - traceVerbose('connection count is <= 0, closing the server: ', pipeName); - server.close(); - } - }); - - // upon connection create a reader and writer and pass it to the callback - onConnectionCallback([ - new rpc.SocketMessageReader(socket, 'utf-8'), - new rpc.SocketMessageWriter(socket, 'utf-8'), - ]); - }); - const closedServerPromise = new Promise((resolveOnServerClose) => { - // get executed on connection close and resolves - // implementation of the promise is the arrow function - server.on('close', resolveOnServerClose); - }); - server.on('error', reject); - - server.listen(pipeName, () => { - // this function is called when the server is listening - server.removeListener('error', reject); - const connectedServer = { - // when onClosed event is called, so is closed function - // goes backwards up the chain, when resolve2 is called, so is onClosed that means server.onClosed() on the other end can work - // event C - serverOnClosePromise: () => closedServerPromise, - }; - resolve(connectedServer); - }); - }); -} +import { isWindows } from '../utils/platform'; +import { createDeferred } from '../utils/async'; +import { noop } from '../utils/misc'; const { XDG_RUNTIME_DIR } = process.env; export function generateRandomPipeName(prefix: string): string { @@ -72,20 +23,178 @@ export function generateRandomPipeName(prefix: string): string { } if (process.platform === 'win32') { - return `\\\\.\\pipe\\${prefix}-${randomSuffix}-sock`; + return `\\\\.\\pipe\\${prefix}-${randomSuffix}`; } let result; if (XDG_RUNTIME_DIR) { - result = path.join(XDG_RUNTIME_DIR, `${prefix}-${randomSuffix}.sock`); + result = path.join(XDG_RUNTIME_DIR, `${prefix}-${randomSuffix}`); } else { - result = path.join(os.tmpdir(), `${prefix}-${randomSuffix}.sock`); + result = path.join(os.tmpdir(), `${prefix}-${randomSuffix}`); } return result; } -export function namedPipeClient(name: string): [rpc.MessageReader, rpc.MessageWriter] { - const socket = net.connect(name); - return [new rpc.SocketMessageReader(socket, 'utf-8'), new rpc.SocketMessageWriter(socket, 'utf-8')]; +async function mkfifo(fifoPath: string): Promise { + return new Promise((resolve, reject) => { + const proc = cp.spawn('mkfifo', [fifoPath]); + proc.on('error', (err) => { + reject(err); + }); + proc.on('exit', (code) => { + if (code === 0) { + resolve(); + } + }); + }); +} + +export async function createWriterPipe(pipeName: string, token?: CancellationToken): Promise { + // windows implementation of FIFO using named pipes + if (isWindows()) { + const deferred = createDeferred(); + const server = net.createServer((socket) => { + traceVerbose(`Pipe connected: ${pipeName}`); + server.close(); + deferred.resolve(new rpc.SocketMessageWriter(socket, 'utf-8')); + }); + + server.on('error', deferred.reject); + server.listen(pipeName); + if (token) { + token.onCancellationRequested(() => { + if (server.listening) { + server.close(); + } + deferred.reject(new CancellationError()); + }); + } + return deferred.promise; + } + // linux implementation of FIFO + await mkfifo(pipeName); + try { + await fs.chmod(pipeName, 0o666); + } catch { + // Intentionally ignored + } + const writer = fs.createWriteStream(pipeName, { + encoding: 'utf-8', + }); + return new rpc.StreamMessageWriter(writer, 'utf-8'); +} + +class CombinedReader implements rpc.MessageReader { + private _onError = new rpc.Emitter(); + + private _onClose = new rpc.Emitter(); + + private _onPartialMessage = new rpc.Emitter(); + + // eslint-disable-next-line @typescript-eslint/no-empty-function + private _callback: rpc.DataCallback = () => {}; + + private _disposables: rpc.Disposable[] = []; + + private _readers: rpc.MessageReader[] = []; + + constructor() { + this._disposables.push(this._onClose, this._onError, this._onPartialMessage); + } + + onError: rpc.Event = this._onError.event; + + onClose: rpc.Event = this._onClose.event; + + onPartialMessage: rpc.Event = this._onPartialMessage.event; + + listen(callback: rpc.DataCallback): rpc.Disposable { + this._callback = callback; + // eslint-disable-next-line no-return-assign, @typescript-eslint/no-empty-function + return new Disposable(() => (this._callback = () => {})); + } + + add(reader: rpc.MessageReader): void { + this._readers.push(reader); + reader.listen((msg) => { + this._callback(msg as rpc.NotificationMessage); + }); + this._disposables.push(reader); + reader.onClose(() => { + this.remove(reader); + if (this._readers.length === 0) { + this._onClose.fire(); + } + }); + reader.onError((e) => { + this.remove(reader); + this._onError.fire(e); + }); + } + + remove(reader: rpc.MessageReader): void { + const found = this._readers.find((r) => r === reader); + if (found) { + this._readers = this._readers.filter((r) => r !== reader); + reader.dispose(); + } + } + + dispose(): void { + this._readers.forEach((r) => r.dispose()); + this._readers = []; + this._disposables.forEach((disposable) => disposable.dispose()); + this._disposables = []; + } +} + +export async function createReaderPipe(pipeName: string, token?: CancellationToken): Promise { + if (isWindows()) { + // windows implementation of FIFO using named pipes + const deferred = createDeferred(); + const combined = new CombinedReader(); + + let refs = 0; + const server = net.createServer((socket) => { + traceVerbose(`Pipe connected: ${pipeName}`); + refs += 1; + + socket.on('close', () => { + refs -= 1; + if (refs <= 0) { + server.close(); + } + }); + combined.add(new rpc.SocketMessageReader(socket, 'utf-8')); + }); + server.on('error', deferred.reject); + server.listen(pipeName); + if (token) { + token.onCancellationRequested(() => { + if (server.listening) { + server.close(); + } + deferred.reject(new CancellationError()); + }); + } + deferred.resolve(combined); + return deferred.promise; + } + // mac/linux implementation of FIFO + await mkfifo(pipeName); + try { + await fs.chmod(pipeName, 0o666); + } catch { + // Intentionally ignored + } + const fd = await fs.open(pipeName, fs.constants.O_RDONLY | fs.constants.O_NONBLOCK); + const socket = new net.Socket({ fd }); + const reader = new rpc.SocketMessageReader(socket, 'utf-8'); + socket.on('close', () => { + fs.close(fd).catch(noop); + reader.dispose(); + }); + + return reader; } diff --git a/src/client/testing/testController/common/utils.ts b/src/client/testing/testController/common/utils.ts index 67884029959e..7e026105f920 100644 --- a/src/client/testing/testController/common/utils.ts +++ b/src/client/testing/testController/common/utils.ts @@ -17,7 +17,7 @@ import { ITestResultResolver, } from './types'; import { Deferred, createDeferred } from '../../../common/utils/async'; -import { createNamedPipeServer, generateRandomPipeName } from '../../../common/pipes/namedPipes'; +import { createReaderPipe, generateRandomPipeName } from '../../../common/pipes/namedPipes'; import { EXTENSION_ROOT_DIR } from '../../../constants'; import { IExperimentService } from '../../../common/types'; import { EnableTestAdapterRewrite } from '../../../common/experiments/groups'; @@ -60,28 +60,6 @@ export function createTestingDeferred(): Deferred { return createDeferred(); } -export async function startTestIdsNamedPipe(testIds: string[]): Promise { - const pipeName: string = generateRandomPipeName('python-test-ids'); - // uses callback so the on connect action occurs after the pipe is created - await createNamedPipeServer(pipeName, ([_reader, writer]) => { - traceVerbose('Test Ids named pipe connected'); - // const num = await - const msg = { - jsonrpc: '2.0', - params: testIds, - } as Message; - writer - .write(msg) - .then(() => { - writer.end(); - }) - .catch((ex) => { - traceError('Failed to write test ids to named pipe', ex); - }); - }); - return pipeName; -} - interface ExecutionResultMessage extends Message { params: ExecutionTestPayload; } @@ -142,47 +120,47 @@ export async function startRunResultNamedPipe( dataReceivedCallback: (payload: ExecutionTestPayload) => void, deferredTillServerClose: Deferred, cancellationToken?: CancellationToken, -): Promise<{ name: string } & Disposable> { +): Promise { traceVerbose('Starting Test Result named pipe'); const pipeName: string = generateRandomPipeName('python-test-results'); - let disposeOfServer: () => void = () => { + + const reader = await createReaderPipe(pipeName, cancellationToken); + traceVerbose(`Test Results named pipe ${pipeName} connected`); + let disposables: Disposable[] = []; + const disposable = new Disposable(() => { + traceVerbose(`Test Results named pipe ${pipeName} disposed`); + disposables.forEach((d) => d.dispose()); + disposables = []; deferredTillServerClose.resolve(); - /* noop */ - }; - const server = await createNamedPipeServer(pipeName, ([reader, _writer]) => { - // this lambda function is: onConnectionCallback - // this is called once per client connecting to the server - traceVerbose(`Test Result named pipe ${pipeName} connected`); - let perConnectionDisposables: (Disposable | undefined)[] = [reader]; - - // create a function to dispose of the server - disposeOfServer = () => { - // dispose of all data listeners and cancelation listeners - perConnectionDisposables.forEach((d) => d?.dispose()); - perConnectionDisposables = []; - deferredTillServerClose.resolve(); - }; - perConnectionDisposables.push( - // per connection, add a listener for the cancellation token and the data + }); + + if (cancellationToken) { + disposables.push( cancellationToken?.onCancellationRequested(() => { - console.log(`Test Result named pipe ${pipeName} cancelled`); - // if cancel is called on one connection, dispose of all connections - disposeOfServer(); - }), - reader.listen((data: Message) => { - traceVerbose(`Test Result named pipe ${pipeName} received data`); - dataReceivedCallback((data as ExecutionResultMessage).params as ExecutionTestPayload); + traceLog(`Test Result named pipe ${pipeName} cancelled`); + disposable.dispose(); }), ); - server.serverOnClosePromise().then(() => { + } + disposables.push( + reader, + reader.listen((data: Message) => { + traceVerbose(`Test Result named pipe ${pipeName} received data`); + // if EOT, call decrement connection count (callback) + dataReceivedCallback((data as ExecutionResultMessage).params as ExecutionTestPayload); + }), + reader.onClose(() => { // this is called once the server close, once per run instance traceVerbose(`Test Result named pipe ${pipeName} closed. Disposing of listener/s.`); // dispose of all data listeners and cancelation listeners - disposeOfServer(); - }); - }); + disposable.dispose(); + }), + reader.onError((error) => { + traceError(`Test Results named pipe ${pipeName} error:`, error); + }), + ); - return { name: pipeName, dispose: disposeOfServer }; + return pipeName; } interface DiscoveryResultMessage extends Message { @@ -233,36 +211,44 @@ export function pythonTestAdapterRewriteEnabled(serviceContainer: IServiceContai export async function startDiscoveryNamedPipe( callback: (payload: DiscoveredTestPayload) => void, cancellationToken?: CancellationToken, -): Promise<{ name: string } & Disposable> { +): Promise { traceVerbose('Starting Test Discovery named pipe'); + // const pipeName: string = '/Users/eleanorboyd/testingFiles/inc_dec_example/temp33.txt'; const pipeName: string = generateRandomPipeName('python-test-discovery'); - let dispose: () => void = () => { - /* noop */ - }; - await createNamedPipeServer(pipeName, ([reader, _writer]) => { - traceVerbose(`Test Discovery named pipe ${pipeName} connected`); - let disposables: (Disposable | undefined)[] = [reader]; - dispose = () => { - traceVerbose(`Test Discovery named pipe ${pipeName} disposed`); - disposables.forEach((d) => d?.dispose()); - disposables = []; - }; + const reader = await createReaderPipe(pipeName, cancellationToken); + + traceVerbose(`Test Discovery named pipe ${pipeName} connected`); + let disposables: Disposable[] = []; + const disposable = new Disposable(() => { + traceVerbose(`Test Discovery named pipe ${pipeName} disposed`); + disposables.forEach((d) => d.dispose()); + disposables = []; + }); + + if (cancellationToken) { disposables.push( - cancellationToken?.onCancellationRequested(() => { + cancellationToken.onCancellationRequested(() => { traceVerbose(`Test Discovery named pipe ${pipeName} cancelled`); - dispose(); - }), - reader.listen((data: Message) => { - traceVerbose(`Test Discovery named pipe ${pipeName} received data`); - callback((data as DiscoveryResultMessage).params as DiscoveredTestPayload); - }), - reader.onClose(() => { - traceVerbose(`Test Discovery named pipe ${pipeName} closed`); - dispose(); + disposable.dispose(); }), ); - }); - return { name: pipeName, dispose }; + } + + disposables.push( + reader, + reader.listen((data: Message) => { + traceVerbose(`Test Discovery named pipe ${pipeName} received data`); + callback((data as DiscoveryResultMessage).params as DiscoveredTestPayload); + }), + reader.onClose(() => { + traceVerbose(`Test Discovery named pipe ${pipeName} closed`); + disposable.dispose(); + }), + reader.onError((error) => { + traceError(`Test Discovery named pipe ${pipeName} error:`, error); + }), + ); + return pipeName; } export function buildErrorNodeOptions(uri: Uri, message: string, testType: string): ErrorTestItemOptions { diff --git a/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts b/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts index b9565971f0da..837d2bd8f6c0 100644 --- a/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts +++ b/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts @@ -41,15 +41,12 @@ export class PytestTestDiscoveryAdapter implements ITestDiscoveryAdapter { executionFactory?: IPythonExecutionFactory, interpreter?: PythonEnvironment, ): Promise { - const { name, dispose } = await startDiscoveryNamedPipe((data: DiscoveredTestPayload) => { + const name = await startDiscoveryNamedPipe((data: DiscoveredTestPayload) => { this.resultResolver?.resolveDiscovery(data); }); - try { - await this.runPytestDiscovery(uri, name, executionFactory, interpreter); - } finally { - dispose(); - } + await this.runPytestDiscovery(uri, name, executionFactory, interpreter); + // this is only a placeholder to handle function overloading until rewrite is finished const discoveryPayload: DiscoveredTestPayload = { cwd: uri.fsPath, status: 'success' }; return discoveryPayload; diff --git a/src/client/testing/testController/pytest/pytestExecutionAdapter.ts b/src/client/testing/testController/pytest/pytestExecutionAdapter.ts index bb80ca10ed1a..7eb03bd86a26 100644 --- a/src/client/testing/testController/pytest/pytestExecutionAdapter.ts +++ b/src/client/testing/testController/pytest/pytestExecutionAdapter.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { DebugSessionOptions, TestRun, TestRunProfileKind, Uri } from 'vscode'; +import { CancellationTokenSource, DebugSessionOptions, TestRun, TestRunProfileKind, Uri } from 'vscode'; import * as path from 'path'; import { ChildProcess } from 'child_process'; import { IConfigurationService, ITestOutputChannel } from '../../../common/types'; @@ -48,16 +48,16 @@ export class PytestTestExecutionAdapter implements ITestExecutionAdapter { traceError(`No run instance found, cannot resolve execution, for workspace ${uri.fsPath}.`); } }; - const { name, dispose: serverDispose } = await utils.startRunResultNamedPipe( + const cSource = new CancellationTokenSource(); + runInstance?.token.onCancellationRequested(() => cSource.cancel()); + + const name = await utils.startRunResultNamedPipe( dataReceivedCallback, // callback to handle data received deferredTillServerClose, // deferred to resolve when server closes - runInstance?.token, // token to cancel + cSource.token, // token to cancel ); runInstance?.token.onCancellationRequested(() => { traceInfo(`Test run cancelled, resolving 'TillServerClose' deferred for ${uri.fsPath}.`); - // if canceled, stop listening for results - serverDispose(); // this will resolve deferredTillServerClose - const executionPayload: ExecutionTestPayload = { cwd: uri.fsPath, status: 'success', @@ -71,7 +71,7 @@ export class PytestTestExecutionAdapter implements ITestExecutionAdapter { uri, testIds, name, - serverDispose, + cSource, runInstance, profileKind, executionFactory, @@ -96,7 +96,7 @@ export class PytestTestExecutionAdapter implements ITestExecutionAdapter { uri: Uri, testIds: string[], resultNamedPipeName: string, - serverDispose: () => void, + serverCancel: CancellationTokenSource, runInstance?: TestRun, profileKind?: TestRunProfileKind, executionFactory?: IPythonExecutionFactory, @@ -174,7 +174,7 @@ export class PytestTestExecutionAdapter implements ITestExecutionAdapter { await debugLauncher!.launchDebugger( launchOptions, () => { - serverDispose(); // this will resolve the deferredTillAllServerClose + serverCancel.cancel(); }, sessionOptions, ); @@ -195,6 +195,7 @@ export class PytestTestExecutionAdapter implements ITestExecutionAdapter { resultProc?.kill(); } else { deferredTillExecClose.resolve(); + serverCancel.cancel(); } }); @@ -238,12 +239,12 @@ 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? - serverDispose(); // this will resolve deferredTillServerClose } + + // deferredTillEOT is resolved when all data sent on stdout and stderr is received, close event is only called when this occurs // due to the sync reading of the output. deferredTillExecClose.resolve(); + serverCancel.cancel(); }); await deferredTillExecClose.promise; } diff --git a/src/client/testing/testController/unittest/testDiscoveryAdapter.ts b/src/client/testing/testController/unittest/testDiscoveryAdapter.ts index b2047f96a01f..ba52d1ffd57b 100644 --- a/src/client/testing/testController/unittest/testDiscoveryAdapter.ts +++ b/src/client/testing/testController/unittest/testDiscoveryAdapter.ts @@ -44,7 +44,7 @@ export class UnittestTestDiscoveryAdapter implements ITestDiscoveryAdapter { const { unittestArgs } = settings.testing; const cwd = settings.testing.cwd && settings.testing.cwd.length > 0 ? settings.testing.cwd : uri.fsPath; - const { name, dispose } = await startDiscoveryNamedPipe((data: DiscoveredTestPayload) => { + const name = await startDiscoveryNamedPipe((data: DiscoveredTestPayload) => { this.resultResolver?.resolveDiscovery(data); }); @@ -66,7 +66,7 @@ export class UnittestTestDiscoveryAdapter implements ITestDiscoveryAdapter { try { await this.runDiscovery(uri, options, name, cwd, executionFactory); } finally { - dispose(); + // none } // placeholder until after the rewrite is adopted // TODO: remove after adoption. diff --git a/src/client/testing/testController/unittest/testExecutionAdapter.ts b/src/client/testing/testController/unittest/testExecutionAdapter.ts index e0d153d0764c..3254e9570fd9 100644 --- a/src/client/testing/testController/unittest/testExecutionAdapter.ts +++ b/src/client/testing/testController/unittest/testExecutionAdapter.ts @@ -2,7 +2,7 @@ // Licensed under the MIT License. import * as path from 'path'; -import { DebugSessionOptions, TestRun, TestRunProfileKind, Uri } from 'vscode'; +import { CancellationTokenSource, DebugSessionOptions, TestRun, TestRunProfileKind, Uri } from 'vscode'; import { ChildProcess } from 'child_process'; import { IConfigurationService, ITestOutputChannel } from '../../../common/types'; import { Deferred, createDeferred } from '../../../common/utils/async'; @@ -58,23 +58,24 @@ export class UnittestTestExecutionAdapter implements ITestExecutionAdapter { traceError(`No run instance found, cannot resolve execution, for workspace ${uri.fsPath}.`); } }; - const { name: resultNamedPipeName, dispose: serverDispose } = await utils.startRunResultNamedPipe( + const cSource = new CancellationTokenSource(); + runInstance?.token.onCancellationRequested(() => cSource.cancel()); + const name = await utils.startRunResultNamedPipe( dataReceivedCallback, // callback to handle data received deferredTillServerClose, // deferred to resolve when server closes - runInstance?.token, // token to cancel + cSource.token, // token to cancel ); runInstance?.token.onCancellationRequested(() => { console.log(`Test run cancelled, resolving 'till TillAllServerClose' deferred for ${uri.fsPath}.`); // if canceled, stop listening for results deferredTillServerClose.resolve(); - serverDispose(); }); try { await this.runTestsNew( uri, testIds, - resultNamedPipeName, - serverDispose, + name, + cSource, runInstance, profileKind, executionFactory, @@ -97,7 +98,7 @@ export class UnittestTestExecutionAdapter implements ITestExecutionAdapter { uri: Uri, testIds: string[], resultNamedPipeName: string, - serverDispose: () => void, + serverCancel: CancellationTokenSource, runInstance?: TestRun, profileKind?: TestRunProfileKind, executionFactory?: IPythonExecutionFactory, @@ -177,7 +178,7 @@ export class UnittestTestExecutionAdapter implements ITestExecutionAdapter { await debugLauncher.launchDebugger( launchOptions, () => { - serverDispose(); // this will resolve the deferredTillAllServerClose + serverCancel.cancel(); }, sessionOptions, ); @@ -196,6 +197,7 @@ export class UnittestTestExecutionAdapter implements ITestExecutionAdapter { resultProc?.kill(); } else { deferredTillExecClose?.resolve(); + serverCancel.cancel(); } }); @@ -232,9 +234,9 @@ export class UnittestTestExecutionAdapter implements ITestExecutionAdapter { runInstance, ); } - serverDispose(); } deferredTillExecClose.resolve(); + serverCancel.cancel(); }); await deferredTillExecClose.promise; } diff --git a/src/test/testing/common/testingAdapter.test.ts b/src/test/testing/common/testingAdapter.test.ts index 8a1891962429..ec19ce00f13f 100644 --- a/src/test/testing/common/testingAdapter.test.ts +++ b/src/test/testing/common/testingAdapter.test.ts @@ -81,8 +81,6 @@ suite('End to End Tests: test adapters', () => { 'coverageWorkspace', ); suiteSetup(async () => { - serviceContainer = (await initialize()).serviceContainer; - // create symlink for specific symlink test const target = rootPathSmallWorkspace; const dest = rootPathDiscoverySymlink; @@ -107,6 +105,7 @@ suite('End to End Tests: test adapters', () => { }); setup(async () => { + serviceContainer = (await initialize()).serviceContainer; getPixiStub = sinon.stub(pixi, 'getPixi'); getPixiStub.resolves(undefined); @@ -678,7 +677,7 @@ suite('End to End Tests: test adapters', () => { }); test('pytest execution adapter small workspace with correct output', async () => { // result resolver and saved data for assertions - resultResolver = new PythonResultResolver(testController, unittestProvider, workspaceUri); + resultResolver = new PythonResultResolver(testController, pytestProvider, workspaceUri); let callCount = 0; let failureOccurred = false; let failureMsg = ''; @@ -875,7 +874,7 @@ suite('End to End Tests: test adapters', () => { }); test('pytest execution adapter large workspace', async () => { // result resolver and saved data for assertions - resultResolver = new PythonResultResolver(testController, unittestProvider, workspaceUri); + resultResolver = new PythonResultResolver(testController, pytestProvider, workspaceUri); let callCount = 0; let failureOccurred = false; let failureMsg = ''; @@ -1062,83 +1061,6 @@ suite('End to End Tests: test adapters', () => { assert.strictEqual(failureOccurred, false, failureMsg); }); }); - test('unittest execution adapter seg fault error handling', async () => { - resultResolver = new PythonResultResolver(testController, unittestProvider, workspaceUri); - let callCount = 0; - let failureOccurred = false; - let failureMsg = ''; - resultResolver._resolveExecution = async (data, _token?) => { - // do the following asserts for each time resolveExecution is called, should be called once per test. - callCount = callCount + 1; - traceLog(`unittest execution adapter seg fault error handling \n ${JSON.stringify(data)}`); - try { - if (data.status === 'error') { - if (data.error === undefined) { - // Dereference a NULL pointer - const indexOfTest = JSON.stringify(data).search('Dereference a NULL pointer'); - if (indexOfTest === -1) { - failureOccurred = true; - failureMsg = 'Expected test to have a null pointer'; - } - } else if (data.error.length === 0) { - failureOccurred = true; - failureMsg = "Expected errors in 'error' field"; - } - } else { - const indexOfTest = JSON.stringify(data.result).search('error'); - if (indexOfTest === -1) { - failureOccurred = true; - failureMsg = - 'If payload status is not error then the individual tests should be marked as errors. This should occur on windows machines.'; - } - } - if (data.result === undefined) { - failureOccurred = true; - failureMsg = 'Expected results to be present'; - } - // make sure the testID is found in the results - const indexOfTest = JSON.stringify(data).search('test_seg_fault.TestSegmentationFault.test_segfault'); - if (indexOfTest === -1) { - failureOccurred = true; - failureMsg = 'Expected testId to be present'; - } - } catch (err) { - failureMsg = err ? (err as Error).toString() : ''; - failureOccurred = true; - } - return Promise.resolve(); - }; - - const testId = `test_seg_fault.TestSegmentationFault.test_segfault`; - const testIds: string[] = [testId]; - - // set workspace to test workspace folder - workspaceUri = Uri.parse(rootPathErrorWorkspace); - configService.getSettings(workspaceUri).testing.unittestArgs = ['-s', '.', '-p', '*test*.py']; - - // run pytest execution - const executionAdapter = new UnittestTestExecutionAdapter( - configService, - testOutputChannel.object, - resultResolver, - envVarsService, - ); - const testRun = typeMoq.Mock.ofType(); - testRun - .setup((t) => t.token) - .returns( - () => - ({ - onCancellationRequested: () => undefined, - } as any), - ); - await executionAdapter - .runTests(workspaceUri, testIds, TestRunProfileKind.Run, testRun.object, pythonExecFactory) - .finally(() => { - assert.strictEqual(callCount, 1, 'Expected _resolveExecution to be called once'); - assert.strictEqual(failureOccurred, false, failureMsg); - }); - }); test('pytest execution adapter seg fault error handling', async () => { resultResolver = new PythonResultResolver(testController, pytestProvider, workspaceUri); let callCount = 0; @@ -1169,7 +1091,7 @@ suite('End to End Tests: test adapters', () => { failureMsg = err ? (err as Error).toString() : ''; failureOccurred = true; } - // return Promise.resolve(); + return Promise.resolve(); }; const testId = `${rootPathErrorWorkspace}/test_seg_fault.py::TestSegmentationFault::test_segfault`; diff --git a/src/test/testing/testController/pytest/pytestDiscoveryAdapter.unit.test.ts b/src/test/testing/testController/pytest/pytestDiscoveryAdapter.unit.test.ts index ddd44835be48..9670f52108a5 100644 --- a/src/test/testing/testController/pytest/pytestDiscoveryAdapter.unit.test.ts +++ b/src/test/testing/testController/pytest/pytestDiscoveryAdapter.unit.test.ts @@ -40,14 +40,7 @@ suite('pytest test discovery adapter', () => { mockExtensionRootDir.setup((m) => m.toString()).returns(() => '/mocked/extension/root/dir'); utilsStartDiscoveryNamedPipeStub = sinon.stub(util, 'startDiscoveryNamedPipe'); - utilsStartDiscoveryNamedPipeStub.callsFake(() => - Promise.resolve({ - name: 'discoveryResultPipe-mockName', - dispose: () => { - /* no-op */ - }, - }), - ); + utilsStartDiscoveryNamedPipeStub.callsFake(() => Promise.resolve('discoveryResultPipe-mockName')); // constants expectedPath = path.join('/', 'my', 'test', 'path'); diff --git a/src/test/testing/testController/pytest/pytestExecutionAdapter.unit.test.ts b/src/test/testing/testController/pytest/pytestExecutionAdapter.unit.test.ts index 8a618a629a47..2eb615dbd1a2 100644 --- a/src/test/testing/testController/pytest/pytestExecutionAdapter.unit.test.ts +++ b/src/test/testing/testController/pytest/pytestExecutionAdapter.unit.test.ts @@ -83,14 +83,7 @@ suite('pytest test execution adapter', () => { myTestPath = path.join('/', 'my', 'test', 'path', '/'); utilsStartRunResultNamedPipeStub = sinon.stub(util, 'startRunResultNamedPipe'); - utilsStartRunResultNamedPipeStub.callsFake(() => - Promise.resolve({ - name: 'runResultPipe-mockName', - dispose: () => { - /* no-op */ - }, - }), - ); + utilsStartRunResultNamedPipeStub.callsFake(() => Promise.resolve('runResultPipe-mockName')); }); teardown(() => { sinon.restore(); diff --git a/src/test/testing/testController/testCancellationRunAdapters.unit.test.ts b/src/test/testing/testController/testCancellationRunAdapters.unit.test.ts index d963361e890f..af4903a1515b 100644 --- a/src/test/testing/testController/testCancellationRunAdapters.unit.test.ts +++ b/src/test/testing/testController/testCancellationRunAdapters.unit.test.ts @@ -66,6 +66,9 @@ suite('Execution Flow Run Adapters', () => { const { token } = cancellationToken; testRunMock.setup((t) => t.token).returns(() => token); + // run result pipe mocking and the related server close dispose + let deferredTillServerCloseTester: Deferred | undefined; + // // mock exec service and exec factory execServiceStub .setup((x) => x.execObservable(typeMoq.It.isAny(), typeMoq.It.isAny())) @@ -92,11 +95,13 @@ suite('Execution Flow Run Adapters', () => { return Promise.resolve('named-pipe'); }); - // run result pipe mocking and the related server close dispose - let deferredTillServerCloseTester: Deferred | undefined; - utilsStartRunResultNamedPipe.callsFake((_callback, deferredTillServerClose, _token) => { + utilsStartRunResultNamedPipe.callsFake((_callback, deferredTillServerClose, token) => { deferredTillServerCloseTester = deferredTillServerClose; - return Promise.resolve({ name: 'named-pipes-socket-name', dispose: serverDisposeStub }); + token?.onCancellationRequested(() => { + deferredTillServerCloseTester?.resolve(); + }); + + return Promise.resolve('named-pipes-socket-name'); }); serverDisposeStub.callsFake(() => { console.log('server disposed'); @@ -122,9 +127,6 @@ suite('Execution Flow Run Adapters', () => { ); // wait for server to start to keep test from failing await deferredStartTestIdsNamedPipe.promise; - - // assert the server dispose function was called correctly - sinon.assert.calledOnce(serverDisposeStub); }); test(`Adapter ${adapter}: token called mid-debug resolves correctly`, async () => { // mock test run and cancelation token @@ -133,6 +135,9 @@ suite('Execution Flow Run Adapters', () => { const { token } = cancellationToken; testRunMock.setup((t) => t.token).returns(() => token); + // run result pipe mocking and the related server close dispose + let deferredTillServerCloseTester: Deferred | undefined; + // // mock exec service and exec factory execServiceStub .setup((x) => x.execObservable(typeMoq.It.isAny(), typeMoq.It.isAny())) @@ -159,14 +164,12 @@ suite('Execution Flow Run Adapters', () => { return Promise.resolve('named-pipe'); }); - // run result pipe mocking and the related server close dispose - let deferredTillServerCloseTester: Deferred | undefined; utilsStartRunResultNamedPipe.callsFake((_callback, deferredTillServerClose, _token) => { deferredTillServerCloseTester = deferredTillServerClose; - return Promise.resolve({ - name: 'named-pipes-socket-name', - dispose: serverDisposeStub, + token?.onCancellationRequested(() => { + deferredTillServerCloseTester?.resolve(); }); + return Promise.resolve('named-pipes-socket-name'); }); serverDisposeStub.callsFake(() => { console.log('server disposed'); @@ -205,10 +208,6 @@ suite('Execution Flow Run Adapters', () => { ); // wait for server to start to keep test from failing await deferredStartTestIdsNamedPipe.promise; - - // TODO: fix the server disposal so it is called once not twice, - // currently not a problem but would be useful to improve clarity - sinon.assert.called(serverDisposeStub); }); }); }); diff --git a/src/test/testing/testController/unittest/testDiscoveryAdapter.unit.test.ts b/src/test/testing/testController/unittest/testDiscoveryAdapter.unit.test.ts index e0442197467f..e6d1cbc29293 100644 --- a/src/test/testing/testController/unittest/testDiscoveryAdapter.unit.test.ts +++ b/src/test/testing/testController/unittest/testDiscoveryAdapter.unit.test.ts @@ -77,14 +77,7 @@ suite('Unittest test discovery adapter', () => { }; utilsStartDiscoveryNamedPipeStub = sinon.stub(util, 'startDiscoveryNamedPipe'); - utilsStartDiscoveryNamedPipeStub.callsFake(() => - Promise.resolve({ - name: 'discoveryResultPipe-mockName', - dispose: () => { - /* no-op */ - }, - }), - ); + utilsStartDiscoveryNamedPipeStub.callsFake(() => Promise.resolve('discoveryResultPipe-mockName')); }); teardown(() => { sinon.restore(); diff --git a/src/test/testing/testController/unittest/testExecutionAdapter.unit.test.ts b/src/test/testing/testController/unittest/testExecutionAdapter.unit.test.ts index 5135a51055ea..8f2afcc51cd1 100644 --- a/src/test/testing/testController/unittest/testExecutionAdapter.unit.test.ts +++ b/src/test/testing/testController/unittest/testExecutionAdapter.unit.test.ts @@ -83,14 +83,7 @@ suite('Unittest test execution adapter', () => { myTestPath = path.join('/', 'my', 'test', 'path', '/'); utilsStartRunResultNamedPipeStub = sinon.stub(util, 'startRunResultNamedPipe'); - utilsStartRunResultNamedPipeStub.callsFake(() => - Promise.resolve({ - name: 'runResultPipe-mockName', - dispose: () => { - /* no-op */ - }, - }), - ); + utilsStartRunResultNamedPipeStub.callsFake(() => Promise.resolve('runResultPipe-mockName')); }); teardown(() => { sinon.restore(); diff --git a/src/testTestingRootWkspc/errorWorkspace/test_seg_fault.py b/src/testTestingRootWkspc/errorWorkspace/test_seg_fault.py index bad7ff8fcbbd..80be80f023c2 100644 --- a/src/testTestingRootWkspc/errorWorkspace/test_seg_fault.py +++ b/src/testTestingRootWkspc/errorWorkspace/test_seg_fault.py @@ -7,11 +7,12 @@ class TestSegmentationFault(unittest.TestCase): def cause_segfault(self): + print("Causing a segmentation fault") ctypes.string_at(0) # Dereference a NULL pointer def test_segfault(self): - assert True self.cause_segfault() + assert True if __name__ == "__main__": 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)