Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions src/test/pythonFiles/debugging/test_pytest_processpool.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import os
import time
from concurrent.futures import ProcessPoolExecutor, wait as futures_wait


def sleep_briefly() -> None:
time.sleep(0.1)


def run_process_pool_tasks() -> None:
futures = []
with ProcessPoolExecutor(max_workers=4) as pool:
for _ in range(8):
futures.append(pool.submit(sleep_briefly))
completed_futures, pending_futures = futures_wait(futures)
assert not pending_futures
for fut in completed_futures:
fut.result()


def test_library_process_pool() -> None:
run_process_pool_tasks()
done_file = os.environ.get("DEBUG_DONE_FILE")
if done_file:
with open(done_file, "w", encoding="utf-8") as fp:
fp.write("done")
38 changes: 38 additions & 0 deletions src/test/unittest/adapter/adapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,14 @@ function resolveWSFile(wsRoot: string, ...filePath: string[]): string {
return path.join(wsRoot, ...filePath);
}

const SUBPROCESS_DEBUG_TIMEOUT_MS = 120_000;

suite('Debugger Integration', () => {
const file = resolveWSFile(WS_ROOT, 'pythonFiles', 'debugging', 'wait_for_file.py');
const processPoolTestFile = resolveWSFile(WS_ROOT, 'pythonFiles', 'debugging', 'test_pytest_processpool.py');
const doneFile = resolveWSFile(WS_ROOT, 'should-not-exist');
const outFile = resolveWSFile(WS_ROOT, 'output.txt');
const processPoolDoneFile = resolveWSFile(WS_ROOT, 'pytest-processpool-debug-done.txt');
const resource = vscode.Uri.file(file);
const defaultScriptArgs = [doneFile];
let workspaceRoot: vscode.WorkspaceFolder;
Expand Down Expand Up @@ -107,4 +111,38 @@ suite('Debugger Integration', () => {
});
}
});

suite('pytest multiprocess test debugging', () => {
test('process-pool test reaches code after worker joins in debug-test session', async function () {
// This path starts pytest under the debugger with subprocess attach enabled,
// so allow extra time for child session orchestration and process-pool teardown.
this.timeout(SUBPROCESS_DEBUG_TIMEOUT_MS);
fix.addFSCleanup(processPoolDoneFile);

const config = {
type: 'python',
name: 'debug pytest processpool',
request: 'launch',
module: 'pytest',
args: ['-s', processPoolTestFile, '-k', 'test_library_process_pool'],
console: 'integratedTerminal',
purpose: ['debug-test'],
subProcess: true,
cwd: WS_ROOT,
env: {
DEBUG_DONE_FILE: processPoolDoneFile,
},
};

const session = fix.resolveDebuggerWithConfig(config, workspaceRoot);
await session.start();
const result = await session.waitUntilDone();

expect(result.exitCode).to.equal(0, 'bad exit code');
expect(await fs.pathExists(processPoolDoneFile)).to.equal(
true,
'pytest test did not reach code after process pool join',
);
});
});
});
29 changes: 29 additions & 0 deletions src/test/unittest/hooks/childProcessAttachService.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,4 +235,33 @@ suite('Debug - Attach to Child Process', () => {
// The original data object must not be mutated.
expect(data).to.have.property('purpose').deep.equal(['debug-test']);
});
test('Child process attach keeps pytest launch fields while removing debug-test purpose', async () => {
const pytestArgs = ['-s', '/workspace/tests/test_multiproc.py'];
const data: AttachRequestArguments = {
request: 'attach',
type: debuggerTypeName,
name: 'Attach',
port: 1234,
subProcessId: 2,
purpose: ['debug-test'],
module: 'pytest',
args: pytestArgs,
console: 'integratedTerminal',
};
const session: any = {};

getWorkspaceFoldersStub.returns(undefined);
startDebuggingStub.resolves(true);

await attachService.attach(data, session);

sinon.assert.calledOnce(startDebuggingStub);
const [, secondArg] = startDebuggingStub.args[0];
expect(secondArg).to.include({
module: 'pytest',
console: 'integratedTerminal',
});
expect(secondArg).to.have.property('args').deep.equal(pytestArgs);
expect(secondArg).to.not.have.property('purpose');
});
});
10 changes: 10 additions & 0 deletions src/test/unittest/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,16 @@ class DebuggerSession {
}

export class DebuggerFixture extends PythonFixture {
public resolveDebuggerWithConfig(
config: vscode.DebugConfiguration,
wsRoot?: vscode.WorkspaceFolder,
): DebuggerSession {
const id = debuggers.track(config);
const session = new DebuggerSession(id, config, wsRoot);
debuggers.setDAPHandler(id, (src, msg) => session.handleDAPMessage(src, msg));
return session;
}

public resolveDebugger(
configName: string,
file: string,
Expand Down
Loading