From a5b2355af7e32b4c839f49774de7f51eec7e31cb Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Fri, 10 Jan 2025 12:20:22 -0800 Subject: [PATCH 01/13] working experience minus PATH --- debugpy | 2 + script_tester_configless.py | 20 +++++++ src/extension/configlessDebugInit.ts | 81 ++++++++++++++++++++++++++++ src/extension/extensionInit.ts | 3 ++ 4 files changed, 106 insertions(+) create mode 100755 debugpy create mode 100644 script_tester_configless.py create mode 100644 src/extension/configlessDebugInit.ts diff --git a/debugpy b/debugpy new file mode 100755 index 00000000..17479e67 --- /dev/null +++ b/debugpy @@ -0,0 +1,2 @@ +#! /bin/bash +python /Users/eleanorboyd/vscode-python-debugger/bundled/libs/debugpy --listen 0 --wait-for-client $@ \ No newline at end of file diff --git a/script_tester_configless.py b/script_tester_configless.py new file mode 100644 index 00000000..a4a5709d --- /dev/null +++ b/script_tester_configless.py @@ -0,0 +1,20 @@ +if __name__ == "__main__": + print("Hello, World!") + import sys + import os + import json + + print(os.environ["DEBUGPY_ADAPTER_ENDPOINTS"]) + file_path = "/Users/eleanorboyd/vscode-python-debugger/comms-file.txt" + if os.path.exists(file_path): + with open(file_path, "r") as file: + print("FILE CONTENTS: \n") + contents = file.read() + c_thing = json.loads(contents) + if c_thing.get("client"): + # print("CLIENT: ", c_thing["client"]) + if c_thing.get("client").get("port"): + print("Port: ", c_thing.get("client").get("port")) + # print(contents) + else: + print(f"{file_path} does not exist.") diff --git a/src/extension/configlessDebugInit.ts b/src/extension/configlessDebugInit.ts new file mode 100644 index 00000000..0ad5df1c --- /dev/null +++ b/src/extension/configlessDebugInit.ts @@ -0,0 +1,81 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import { IExtensionContext } from './common/types'; +import { DebugSessionOptions, debug, RelativePattern, workspace } from 'vscode'; + +const PATH_VARIABLE = 'PATH'; + +export async function registerConfiglessDebug(context: IExtensionContext): Promise { + const collection = context.environmentVariableCollection; + // constants + const debuggerAdapterEndpointFolderPath = path.join(context.extensionPath, 'src/extension/configlessCommunication'); + const debuggerAdapterEndpointPath = path.join(debuggerAdapterEndpointFolderPath, 'debuggerAdapterEndpoint.txt'); + + // Add env vars for DEBUGPY_ADAPTER_ENDPOINTS and PATH + collection.replace('DEBUGPY_ADAPTER_ENDPOINTS', debuggerAdapterEndpointPath); + // TODO: append for path + + // const pathVariableChange = path.delimiter + '/Users/eleanorboyd/vscode-python-debugger'; + // if (context.environmentVariableCollection.get(PATH_VARIABLE)) { + // context.environmentVariableCollection.delete(PATH_VARIABLE); + // } else if (context.environmentVariableCollection.get(PATH_VARIABLE)?.value !== pathVariableChange) { + // context.environmentVariableCollection.description = 'enable config-less debug'; + // context.environmentVariableCollection.delete(PATH_VARIABLE); + // context.environmentVariableCollection.append(PATH_VARIABLE, pathVariableChange); + // } + + // create file system watcher for the debuggerAdapterEndpointFolder for when the communication port is written + context.subscriptions.push( + workspace + .createFileSystemWatcher(new RelativePattern(debuggerAdapterEndpointFolderPath, '**/*')) + .onDidCreate((uri) => { + console.log(`File created: ${uri.fsPath}`); + const filePath = uri.fsPath; + fs.readFile(filePath, 'utf8', (err, data) => { + if (err) { + console.error(`Error reading file: ${err}`); + return; + } + try { + // parse the client port + const jsonData = JSON.parse(data); + const clientPort = jsonData.client?.port; + console.log(`Client port: ${clientPort}`); + + const options: DebugSessionOptions = { + noDebug: false, + }; + + // start debug session with the client port + debug + .startDebugging( + undefined, + { + type: 'python', + request: 'attach', + name: 'Attach to Python', + port: clientPort, + host: 'localhost', + }, + options, + ) + .then( + (started) => { + if (started) { + console.log('Debug session started successfully'); + } else { + console.error('Failed to start debug session'); + } + }, + (error) => { + console.error(`Error starting debug session: ${error}`); + }, + ); + } catch (parseErr) { + console.error(`Error parsing JSON: ${parseErr}`); + } + }); + JSON.parse; + }), + ); +} diff --git a/src/extension/extensionInit.ts b/src/extension/extensionInit.ts index 976e3405..5b9d0e33 100644 --- a/src/extension/extensionInit.ts +++ b/src/extension/extensionInit.ts @@ -53,6 +53,7 @@ import { IExtensionApi } from './apiTypes'; import { registerHexDebugVisualizationTreeProvider } from './debugger/visualizers/inlineHexDecoder'; import { PythonInlineValueProvider } from './debugger/inlineValue/pythonInlineValueProvider'; import { traceLog } from './common/log/logging'; +import { registerConfiglessDebug } from './configlessDebugInit'; export async function registerDebugger(context: IExtensionContext): Promise { const childProcessAttachService = new ChildProcessAttachService(); @@ -247,5 +248,7 @@ export async function registerDebugger(context: IExtensionContext): Promise Date: Mon, 13 Jan 2025 11:59:28 -0800 Subject: [PATCH 02/13] working with path insert --- bundled/scripts/noConfigScripts/debugpy | 4 + bundled/scripts/noConfigScripts/debugpy.bat | 3 + bundled/scripts/noConfigScripts/debugpy.fish | 2 + bundled/scripts/noConfigScripts/debugpy.ps1 | 2 + debugpy | 2 - src/extension/configlessDebugInit.ts | 81 ------------------- src/extension/extensionInit.ts | 2 +- src/extension/noConfigDebugInit.ts | 85 ++++++++++++++++++++ 8 files changed, 97 insertions(+), 84 deletions(-) create mode 100755 bundled/scripts/noConfigScripts/debugpy create mode 100644 bundled/scripts/noConfigScripts/debugpy.bat create mode 100644 bundled/scripts/noConfigScripts/debugpy.fish create mode 100644 bundled/scripts/noConfigScripts/debugpy.ps1 delete mode 100755 debugpy delete mode 100644 src/extension/configlessDebugInit.ts create mode 100644 src/extension/noConfigDebugInit.ts diff --git a/bundled/scripts/noConfigScripts/debugpy b/bundled/scripts/noConfigScripts/debugpy new file mode 100755 index 00000000..0c97dea8 --- /dev/null +++ b/bundled/scripts/noConfigScripts/debugpy @@ -0,0 +1,4 @@ +#! /bin/bash +# Bash script +python $BUNDLED_DEBUGPY_PATH --listen 0 --wait-for-client $@ +echo "Executed: python $BUNDLED_DEBUGPY_PATH --listen 0 --wait-for-client $@" \ No newline at end of file diff --git a/bundled/scripts/noConfigScripts/debugpy.bat b/bundled/scripts/noConfigScripts/debugpy.bat new file mode 100644 index 00000000..d6e7685a --- /dev/null +++ b/bundled/scripts/noConfigScripts/debugpy.bat @@ -0,0 +1,3 @@ +@echo off +:: Bat script +python %BUNDLED_DEBUGPY_PATH% --listen 0 --wait-for-client %* \ No newline at end of file diff --git a/bundled/scripts/noConfigScripts/debugpy.fish b/bundled/scripts/noConfigScripts/debugpy.fish new file mode 100644 index 00000000..7319d863 --- /dev/null +++ b/bundled/scripts/noConfigScripts/debugpy.fish @@ -0,0 +1,2 @@ +# Fish script +python $BUNDLED_DEBUGPY_PATH --listen 0 --wait-for-client $argv diff --git a/bundled/scripts/noConfigScripts/debugpy.ps1 b/bundled/scripts/noConfigScripts/debugpy.ps1 new file mode 100644 index 00000000..ebffa418 --- /dev/null +++ b/bundled/scripts/noConfigScripts/debugpy.ps1 @@ -0,0 +1,2 @@ +# PowerShell script +python $env:BUNDLED_DEBUGPY_PATH --listen 0 --wait-for-client $args diff --git a/debugpy b/debugpy deleted file mode 100755 index 17479e67..00000000 --- a/debugpy +++ /dev/null @@ -1,2 +0,0 @@ -#! /bin/bash -python /Users/eleanorboyd/vscode-python-debugger/bundled/libs/debugpy --listen 0 --wait-for-client $@ \ No newline at end of file diff --git a/src/extension/configlessDebugInit.ts b/src/extension/configlessDebugInit.ts deleted file mode 100644 index 0ad5df1c..00000000 --- a/src/extension/configlessDebugInit.ts +++ /dev/null @@ -1,81 +0,0 @@ -import * as fs from 'fs'; -import * as path from 'path'; -import { IExtensionContext } from './common/types'; -import { DebugSessionOptions, debug, RelativePattern, workspace } from 'vscode'; - -const PATH_VARIABLE = 'PATH'; - -export async function registerConfiglessDebug(context: IExtensionContext): Promise { - const collection = context.environmentVariableCollection; - // constants - const debuggerAdapterEndpointFolderPath = path.join(context.extensionPath, 'src/extension/configlessCommunication'); - const debuggerAdapterEndpointPath = path.join(debuggerAdapterEndpointFolderPath, 'debuggerAdapterEndpoint.txt'); - - // Add env vars for DEBUGPY_ADAPTER_ENDPOINTS and PATH - collection.replace('DEBUGPY_ADAPTER_ENDPOINTS', debuggerAdapterEndpointPath); - // TODO: append for path - - // const pathVariableChange = path.delimiter + '/Users/eleanorboyd/vscode-python-debugger'; - // if (context.environmentVariableCollection.get(PATH_VARIABLE)) { - // context.environmentVariableCollection.delete(PATH_VARIABLE); - // } else if (context.environmentVariableCollection.get(PATH_VARIABLE)?.value !== pathVariableChange) { - // context.environmentVariableCollection.description = 'enable config-less debug'; - // context.environmentVariableCollection.delete(PATH_VARIABLE); - // context.environmentVariableCollection.append(PATH_VARIABLE, pathVariableChange); - // } - - // create file system watcher for the debuggerAdapterEndpointFolder for when the communication port is written - context.subscriptions.push( - workspace - .createFileSystemWatcher(new RelativePattern(debuggerAdapterEndpointFolderPath, '**/*')) - .onDidCreate((uri) => { - console.log(`File created: ${uri.fsPath}`); - const filePath = uri.fsPath; - fs.readFile(filePath, 'utf8', (err, data) => { - if (err) { - console.error(`Error reading file: ${err}`); - return; - } - try { - // parse the client port - const jsonData = JSON.parse(data); - const clientPort = jsonData.client?.port; - console.log(`Client port: ${clientPort}`); - - const options: DebugSessionOptions = { - noDebug: false, - }; - - // start debug session with the client port - debug - .startDebugging( - undefined, - { - type: 'python', - request: 'attach', - name: 'Attach to Python', - port: clientPort, - host: 'localhost', - }, - options, - ) - .then( - (started) => { - if (started) { - console.log('Debug session started successfully'); - } else { - console.error('Failed to start debug session'); - } - }, - (error) => { - console.error(`Error starting debug session: ${error}`); - }, - ); - } catch (parseErr) { - console.error(`Error parsing JSON: ${parseErr}`); - } - }); - JSON.parse; - }), - ); -} diff --git a/src/extension/extensionInit.ts b/src/extension/extensionInit.ts index 5b9d0e33..4d18e221 100644 --- a/src/extension/extensionInit.ts +++ b/src/extension/extensionInit.ts @@ -53,7 +53,7 @@ import { IExtensionApi } from './apiTypes'; import { registerHexDebugVisualizationTreeProvider } from './debugger/visualizers/inlineHexDecoder'; import { PythonInlineValueProvider } from './debugger/inlineValue/pythonInlineValueProvider'; import { traceLog } from './common/log/logging'; -import { registerConfiglessDebug } from './configlessDebugInit'; +import { registerConfiglessDebug } from './noConfigDebugInit'; export async function registerDebugger(context: IExtensionContext): Promise { const childProcessAttachService = new ChildProcessAttachService(); diff --git a/src/extension/noConfigDebugInit.ts b/src/extension/noConfigDebugInit.ts new file mode 100644 index 00000000..6b63ab02 --- /dev/null +++ b/src/extension/noConfigDebugInit.ts @@ -0,0 +1,85 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import { IExtensionContext } from './common/types'; +import { DebugSessionOptions, debug, RelativePattern, workspace } from 'vscode'; + +/** + * Registers the configuration-less debugging setup for the extension. + * + * This function sets up environment variables and a file system watcher to + * facilitate debugging without requiring a pre-configured launch.json file. + * + * @param context - The extension context which provides access to the environment variable collection and subscriptions. + * + * Environment Variables: + * - `DEBUGPY_ADAPTER_ENDPOINTS`: Path to the file containing the debugger adapter endpoint. + * - `BUNDLED_DEBUGPY_PATH`: Path to the bundled debugpy library. + * - `PATH`: Appends the path to the noConfigScripts directory. + */ +export async function registerConfiglessDebug(context: IExtensionContext): Promise { + const collection = context.environmentVariableCollection; + + // Add env vars for DEBUGPY_ADAPTER_ENDPOINTS, BUNDLED_DEBUGPY_PATH, and PATH + const debugAdapterEndpointDir = path.join(context.extensionPath, 'noConfigDebugAdapterEndpoints'); + const debuggerAdapterEndpointPath = path.join(debugAdapterEndpointDir, 'debuggerAdapterEndpoint.txt'); + collection.replace('DEBUGPY_ADAPTER_ENDPOINTS', debuggerAdapterEndpointPath); + + const noConfigScriptsDir = path.join(context.extensionPath, 'bundled/scripts/noConfigScripts'); + collection.append('PATH', `:${noConfigScriptsDir}`); + + const bundledDebugPath = path.join(context.extensionPath, 'bundled/libs/debugpy'); + collection.replace('BUNDLED_DEBUGPY_PATH', bundledDebugPath); + + // create file system watcher for the debuggerAdapterEndpointFolder for when the communication port is written + context.subscriptions.push( + workspace.createFileSystemWatcher(new RelativePattern(debugAdapterEndpointDir, '**/*')).onDidCreate((uri) => { + console.log(`File created: ${uri.fsPath}`); + const filePath = uri.fsPath; + fs.readFile(filePath, 'utf8', (err, data) => { + if (err) { + console.error(`Error reading file: ${err}`); + return; + } + try { + // parse the client port + const jsonData = JSON.parse(data); + const clientPort = jsonData.client?.port; + console.log(`Client port: ${clientPort}`); + + const options: DebugSessionOptions = { + noDebug: false, + }; + + // start debug session with the client port + debug + .startDebugging( + undefined, + { + type: 'python', + request: 'attach', + name: 'Attach to Python', + port: clientPort, + host: 'localhost', + }, + options, + ) + .then( + (started) => { + if (started) { + console.log('Debug session started successfully'); + } else { + console.error('Failed to start debug session'); + } + }, + (error) => { + console.error(`Error starting debug session: ${error}`); + }, + ); + } catch (parseErr) { + console.error(`Error parsing JSON: ${parseErr}`); + } + }); + JSON.parse; + }), + ); +} From 0809f40ec78a1679d59b2d9736d8e02ff2a2a8d4 Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Tue, 14 Jan 2025 09:12:08 -0800 Subject: [PATCH 03/13] add test --- .vscode/launch.json | 7 +- src/extension/noConfigDebugInit.ts | 20 ++--- src/extension/utils.ts | 5 ++ .../unittest/noConfigDebugInit.unit.test.ts | 79 +++++++++++++++++++ 4 files changed, 100 insertions(+), 11 deletions(-) create mode 100644 src/extension/utils.ts create mode 100644 src/test/unittest/noConfigDebugInit.unit.test.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index 0b3fee7b..42fd2619 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -31,12 +31,15 @@ "args": [ "./out/test/**/*.unit.test.js", "--extensionDevelopmentPath=${workspaceFolder}", - "--extensionTestsPath=${workspaceFolder}/out/test/unittest/index" + "--extensionTestsPath=${workspaceFolder}/out/test/unittest/index", + //"--grep", + //"this test suite" ], "outFiles": [ "${workspaceFolder}/out/**/*.js", ], - "preLaunchTask": "tasks: watch-tests" + "preLaunchTask": "tasks: watch-tests", + "timeout": 600000 // Increase timeout to 10 minutes (600000 ms) }, ] } \ No newline at end of file diff --git a/src/extension/noConfigDebugInit.ts b/src/extension/noConfigDebugInit.ts index 6b63ab02..cb936504 100644 --- a/src/extension/noConfigDebugInit.ts +++ b/src/extension/noConfigDebugInit.ts @@ -1,7 +1,8 @@ import * as fs from 'fs'; import * as path from 'path'; import { IExtensionContext } from './common/types'; -import { DebugSessionOptions, debug, RelativePattern, workspace } from 'vscode'; +import { DebugSessionOptions, debug, RelativePattern } from 'vscode'; +import { createFileSystemWatcher } from './utils'; /** * Registers the configuration-less debugging setup for the extension. @@ -32,19 +33,19 @@ export async function registerConfiglessDebug(context: IExtensionContext): Promi // create file system watcher for the debuggerAdapterEndpointFolder for when the communication port is written context.subscriptions.push( - workspace.createFileSystemWatcher(new RelativePattern(debugAdapterEndpointDir, '**/*')).onDidCreate((uri) => { - console.log(`File created: ${uri.fsPath}`); + createFileSystemWatcher(new RelativePattern(debugAdapterEndpointDir, '**/*')).onDidCreate((uri) => { + // console.log(`File created: ${uri.fsPath}`); const filePath = uri.fsPath; fs.readFile(filePath, 'utf8', (err, data) => { if (err) { - console.error(`Error reading file: ${err}`); + // console.error(`Error reading file: ${err}`); return; } try { // parse the client port const jsonData = JSON.parse(data); const clientPort = jsonData.client?.port; - console.log(`Client port: ${clientPort}`); + //console.log(`Client port: ${clientPort}`); const options: DebugSessionOptions = { noDebug: false, @@ -66,17 +67,18 @@ export async function registerConfiglessDebug(context: IExtensionContext): Promi .then( (started) => { if (started) { - console.log('Debug session started successfully'); + //console.log('Debug session started successfully'); } else { - console.error('Failed to start debug session'); + //console.error('Failed to start debug session'); } }, (error) => { - console.error(`Error starting debug session: ${error}`); + //console.error(`Error starting debug session: ${error}`); + error; }, ); } catch (parseErr) { - console.error(`Error parsing JSON: ${parseErr}`); + //console.error(`Error parsing JSON: ${parseErr}`); } }); JSON.parse; diff --git a/src/extension/utils.ts b/src/extension/utils.ts new file mode 100644 index 00000000..4fbfd33c --- /dev/null +++ b/src/extension/utils.ts @@ -0,0 +1,5 @@ +import { workspace } from 'vscode'; + +export function createFileSystemWatcher(args: any) { + return workspace.createFileSystemWatcher(args); +} diff --git a/src/test/unittest/noConfigDebugInit.unit.test.ts b/src/test/unittest/noConfigDebugInit.unit.test.ts new file mode 100644 index 00000000..8fd52ef5 --- /dev/null +++ b/src/test/unittest/noConfigDebugInit.unit.test.ts @@ -0,0 +1,79 @@ +import * as path from 'path'; +import { IExtensionContext } from '../../extension/common/types'; +import { registerConfiglessDebug } from '../../extension/noConfigDebugInit'; +import * as TypeMoq from 'typemoq'; +import * as sinon from 'sinon'; +import { Uri } from 'vscode'; +import * as utils from '../../extension/utils'; +import { assert } from 'console'; +import * as fs from 'fs'; + +suite('registerConfiglessDebug', function () { + this.timeout(100000); // Increase timeout to 10 seconds + let replaceStub: sinon.SinonStub; + let appendStub: sinon.SinonStub; + let context: TypeMoq.IMock; + let createFileSystemWatcher: sinon.SinonStub; + + setup(() => { + context = TypeMoq.Mock.ofType(); + + context.setup((c) => c.storageUri).returns(() => Uri.parse('a')); + context.setup((c) => (c as any).extensionPath).returns(() => 'b'); + context.setup((c) => c.subscriptions).returns(() => []); + + createFileSystemWatcher = sinon.stub(utils, 'createFileSystemWatcher'); + createFileSystemWatcher.callsFake(() => { + return { + onDidCreate: (cb: (arg0: Uri) => void) => { + cb(Uri.parse('a')); + }, + }; + }); + sinon.stub(fs, 'readFile').callsFake( + (TypeMoq.It.isAny(), + TypeMoq.It.isAny(), + (err, data) => { + console.log(err, data); + }), + ); + }); + teardown(() => { + sinon.restore(); + }); + + test('should add environment variables for DEBUGPY_ADAPTER_ENDPOINTS, BUNDLED_DEBUGPY_PATH, and PATH', async () => { + const debugAdapterEndpointDir = path.join(context.object.extensionPath, 'noConfigDebugAdapterEndpoints'); + const debuggerAdapterEndpointPath = path.join(debugAdapterEndpointDir, 'debuggerAdapterEndpoint.txt'); + const noConfigScriptsDir = path.join(context.object.extensionPath, 'bundled/scripts/noConfigScripts'); + const bundledDebugPath = path.join(context.object.extensionPath, 'bundled/libs/debugpy'); + + const environmentVariableCollectionMock = TypeMoq.Mock.ofType(); + replaceStub = sinon.stub(); + appendStub = sinon.stub(); + environmentVariableCollectionMock + .setup((x) => x.replace(TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .callback((key, value) => { + if (key === 'DEBUGPY_ADAPTER_ENDPOINTS') { + assert(value === debuggerAdapterEndpointPath); + } else if (key === 'BUNDLED_DEBUGPY_PATH') { + assert(value === bundledDebugPath); + } + }) + .returns(replaceStub); + environmentVariableCollectionMock + .setup((x) => x.append(TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .callback((key, value) => { + if (key === 'PATH') { + assert(value === `:${noConfigScriptsDir}`); + } + }) + .returns(appendStub); + + context.setup((c) => c.environmentVariableCollection).returns(() => environmentVariableCollectionMock.object); + + await registerConfiglessDebug(context.object); + console.log('All done!'); + sinon.assert.calledTwice(replaceStub); + }); +}); From 1d36b7ad3b6b92a668303710e1c4cb062ba4abdf Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Tue, 14 Jan 2025 11:17:19 -0800 Subject: [PATCH 04/13] switch to trace logging, add final test --- src/extension/extensionInit.ts | 4 +- src/extension/noConfigDebugInit.ts | 66 ++++--- src/extension/utils.ts | 20 +- .../unittest/noConfigDebugInit.unit.test.ts | 181 ++++++++++++++---- 4 files changed, 194 insertions(+), 77 deletions(-) diff --git a/src/extension/extensionInit.ts b/src/extension/extensionInit.ts index 4d18e221..f1c8eb8d 100644 --- a/src/extension/extensionInit.ts +++ b/src/extension/extensionInit.ts @@ -53,7 +53,7 @@ import { IExtensionApi } from './apiTypes'; import { registerHexDebugVisualizationTreeProvider } from './debugger/visualizers/inlineHexDecoder'; import { PythonInlineValueProvider } from './debugger/inlineValue/pythonInlineValueProvider'; import { traceLog } from './common/log/logging'; -import { registerConfiglessDebug } from './noConfigDebugInit'; +import { registerNoConfigDebug } from './noConfigDebugInit'; export async function registerDebugger(context: IExtensionContext): Promise { const childProcessAttachService = new ChildProcessAttachService(); @@ -248,7 +248,7 @@ export async function registerDebugger(context: IExtensionContext): Promise { +export async function registerNoConfigDebug(context: IExtensionContext): Promise { const collection = context.environmentVariableCollection; // Add env vars for DEBUGPY_ADAPTER_ENDPOINTS, BUNDLED_DEBUGPY_PATH, and PATH @@ -34,51 +35,48 @@ export async function registerConfiglessDebug(context: IExtensionContext): Promi // create file system watcher for the debuggerAdapterEndpointFolder for when the communication port is written context.subscriptions.push( createFileSystemWatcher(new RelativePattern(debugAdapterEndpointDir, '**/*')).onDidCreate((uri) => { - // console.log(`File created: ${uri.fsPath}`); const filePath = uri.fsPath; - fs.readFile(filePath, 'utf8', (err, data) => { + fs.readFile(filePath, (err, data) => { + const dataParse = data.toString(); if (err) { - // console.error(`Error reading file: ${err}`); + traceError(`Error reading debuggerAdapterEndpoint.txt file: ${err}`); return; } try { // parse the client port - const jsonData = JSON.parse(data); + const jsonData = JSON.parse(dataParse); const clientPort = jsonData.client?.port; - //console.log(`Client port: ${clientPort}`); + traceVerbose(`Parsed client port: ${clientPort}`); const options: DebugSessionOptions = { noDebug: false, }; // start debug session with the client port - debug - .startDebugging( - undefined, - { - type: 'python', - request: 'attach', - name: 'Attach to Python', - port: clientPort, - host: 'localhost', - }, - options, - ) - .then( - (started) => { - if (started) { - //console.log('Debug session started successfully'); - } else { - //console.error('Failed to start debug session'); - } - }, - (error) => { - //console.error(`Error starting debug session: ${error}`); - error; - }, - ); + debugStartDebugging( + undefined, + { + type: 'python', + request: 'attach', + name: 'Attach to Python', + port: clientPort, + host: 'localhost', + }, + options, + ).then( + (started) => { + if (started) { + traceVerbose('Successfully started debug session'); + } else { + traceError('Error starting debug session, session not started.'); + } + }, + (error) => { + traceError(`Error starting debug session: ${error}`); + }, + ); } catch (parseErr) { - //console.error(`Error parsing JSON: ${parseErr}`); + traceError(`Error parsing JSON: ${parseErr}`); } }); JSON.parse; diff --git a/src/extension/utils.ts b/src/extension/utils.ts index 4fbfd33c..c74d1b49 100644 --- a/src/extension/utils.ts +++ b/src/extension/utils.ts @@ -1,5 +1,21 @@ -import { workspace } from 'vscode'; +import { + workspace, + debug, + WorkspaceFolder, + DebugConfiguration, + DebugSession, + DebugSessionOptions, + FileSystemWatcher, +} from 'vscode'; -export function createFileSystemWatcher(args: any) { +export function createFileSystemWatcher(args: any): FileSystemWatcher { return workspace.createFileSystemWatcher(args); } + +export function debugStartDebugging( + folder: WorkspaceFolder | undefined, + nameOrConfiguration: string | DebugConfiguration, + parentSessionOrOptions?: DebugSession | DebugSessionOptions, +): Thenable { + return debug.startDebugging(folder, nameOrConfiguration, parentSessionOrOptions); +} diff --git a/src/test/unittest/noConfigDebugInit.unit.test.ts b/src/test/unittest/noConfigDebugInit.unit.test.ts index 8fd52ef5..c90fc791 100644 --- a/src/test/unittest/noConfigDebugInit.unit.test.ts +++ b/src/test/unittest/noConfigDebugInit.unit.test.ts @@ -1,66 +1,68 @@ import * as path from 'path'; import { IExtensionContext } from '../../extension/common/types'; -import { registerConfiglessDebug } from '../../extension/noConfigDebugInit'; +import { registerNoConfigDebug as registerNoConfigDebug } from '../../extension/noConfigDebugInit'; import * as TypeMoq from 'typemoq'; import * as sinon from 'sinon'; -import { Uri } from 'vscode'; +import { DebugConfiguration, DebugSessionOptions, RelativePattern, Uri } from 'vscode'; import * as utils from '../../extension/utils'; import { assert } from 'console'; import * as fs from 'fs'; -suite('registerConfiglessDebug', function () { - this.timeout(100000); // Increase timeout to 10 seconds - let replaceStub: sinon.SinonStub; - let appendStub: sinon.SinonStub; +suite('setup for no-config debug scenario', function () { + let envVarCollectionReplaceStub: sinon.SinonStub; + let envVarCollectionAppendStub: sinon.SinonStub; let context: TypeMoq.IMock; - let createFileSystemWatcher: sinon.SinonStub; + let debugAdapterEndpointDir: string; + let debuggerAdapterEndpointPath: string; + let noConfigScriptsDir: string; + let bundledDebugPath: string; + let DEBUGPY_ADAPTER_ENDPOINTS = 'DEBUGPY_ADAPTER_ENDPOINTS'; + let BUNDLED_DEBUGPY_PATH = 'BUNDLED_DEBUGPY_PATH'; + const testDataDir = path.join(__dirname, 'testData'); + const testFilePath = path.join(testDataDir, 'debuggerAdapterEndpoint.txt'); + suiteSetup(() => { + // create file called testData/debuggerAdapterEndpoint.txt + if (!fs.existsSync(testDataDir)) { + fs.mkdirSync(testDataDir); + } + fs.writeFileSync(testFilePath, JSON.stringify({ client: { port: 5678 } })); + }); setup(() => { context = TypeMoq.Mock.ofType(); - context.setup((c) => c.storageUri).returns(() => Uri.parse('a')); - context.setup((c) => (c as any).extensionPath).returns(() => 'b'); + context.setup((c) => (c as any).extensionPath).returns(() => 'fake/extension/path'); context.setup((c) => c.subscriptions).returns(() => []); - - createFileSystemWatcher = sinon.stub(utils, 'createFileSystemWatcher'); - createFileSystemWatcher.callsFake(() => { - return { - onDidCreate: (cb: (arg0: Uri) => void) => { - cb(Uri.parse('a')); - }, - }; - }); - sinon.stub(fs, 'readFile').callsFake( - (TypeMoq.It.isAny(), - TypeMoq.It.isAny(), - (err, data) => { - console.log(err, data); - }), - ); + debugAdapterEndpointDir = path.join(context.object.extensionPath, 'noConfigDebugAdapterEndpoints'); + debuggerAdapterEndpointPath = path.join(debugAdapterEndpointDir, 'debuggerAdapterEndpoint.txt'); + noConfigScriptsDir = path.join(context.object.extensionPath, 'bundled/scripts/noConfigScripts'); + bundledDebugPath = path.join(context.object.extensionPath, 'bundled/libs/debugpy'); }); teardown(() => { sinon.restore(); }); + suiteTeardown(() => { + if (fs.existsSync(testDataDir)) { + fs.rmSync(testDataDir, { recursive: true, force: true }); + } + }); test('should add environment variables for DEBUGPY_ADAPTER_ENDPOINTS, BUNDLED_DEBUGPY_PATH, and PATH', async () => { - const debugAdapterEndpointDir = path.join(context.object.extensionPath, 'noConfigDebugAdapterEndpoints'); - const debuggerAdapterEndpointPath = path.join(debugAdapterEndpointDir, 'debuggerAdapterEndpoint.txt'); - const noConfigScriptsDir = path.join(context.object.extensionPath, 'bundled/scripts/noConfigScripts'); - const bundledDebugPath = path.join(context.object.extensionPath, 'bundled/libs/debugpy'); - const environmentVariableCollectionMock = TypeMoq.Mock.ofType(); - replaceStub = sinon.stub(); - appendStub = sinon.stub(); + envVarCollectionReplaceStub = sinon.stub(); + envVarCollectionAppendStub = sinon.stub(); + + // set up the environment variable collection mock including asserts for the key, value pairs environmentVariableCollectionMock .setup((x) => x.replace(TypeMoq.It.isAny(), TypeMoq.It.isAny())) .callback((key, value) => { - if (key === 'DEBUGPY_ADAPTER_ENDPOINTS') { + if (key === DEBUGPY_ADAPTER_ENDPOINTS) { assert(value === debuggerAdapterEndpointPath); - } else if (key === 'BUNDLED_DEBUGPY_PATH') { + } else if (key === BUNDLED_DEBUGPY_PATH) { assert(value === bundledDebugPath); } }) - .returns(replaceStub); + .returns(envVarCollectionReplaceStub); environmentVariableCollectionMock .setup((x) => x.append(TypeMoq.It.isAny(), TypeMoq.It.isAny())) .callback((key, value) => { @@ -68,12 +70,113 @@ suite('registerConfiglessDebug', function () { assert(value === `:${noConfigScriptsDir}`); } }) - .returns(appendStub); + .returns(envVarCollectionAppendStub); + + context.setup((c) => c.environmentVariableCollection).returns(() => environmentVariableCollectionMock.object); + + setupFileSystemWatchers(); + + // run init for no config debug + await registerNoConfigDebug(context.object); + + // assert that functions called right number of times + sinon.assert.calledTwice(envVarCollectionReplaceStub); + sinon.assert.calledOnce(envVarCollectionAppendStub); + }); + + test('should create file system watcher for debuggerAdapterEndpointFolder', async () => { + // Arrange + const environmentVariableCollectionMock = TypeMoq.Mock.ofType(); + context.setup((c) => c.environmentVariableCollection).returns(() => environmentVariableCollectionMock.object); + let createFileSystemWatcherFunct = setupFileSystemWatchers(); + // Act + await registerNoConfigDebug(context.object); + + // Assert + sinon.assert.calledOnce(createFileSystemWatcherFunct); + const expectedPattern = new RelativePattern(debugAdapterEndpointDir, '**/*'); + sinon.assert.calledWith(createFileSystemWatcherFunct, expectedPattern); + }); + + test('should start debug session with client port', async () => { + // Arrange + const environmentVariableCollectionMock = TypeMoq.Mock.ofType(); context.setup((c) => c.environmentVariableCollection).returns(() => environmentVariableCollectionMock.object); - await registerConfiglessDebug(context.object); - console.log('All done!'); - sinon.assert.calledTwice(replaceStub); + // mock file sys watcher to give back test file + let createFileSystemWatcherFunct: sinon.SinonStub; + createFileSystemWatcherFunct = sinon.stub(utils, 'createFileSystemWatcher'); + createFileSystemWatcherFunct.callsFake(() => { + return { + onDidCreate: (callback: (arg0: Uri) => void) => { + callback(Uri.parse(testFilePath)); + }, + }; + }); + + // create stub of fs.readFile function + sinon.stub(fs, 'readFile').callsFake((_path: any, callback: (arg0: null, arg1: Buffer) => void) => { + console.log('reading file'); + callback(null, Buffer.from(JSON.stringify({ client: { port: 5678 } }))); + }); + + const debugStub = sinon.stub(utils, 'debugStartDebugging').resolves(true); + + // Act + await registerNoConfigDebug(context.object); + + // Assert + sinon.assert.calledOnce(debugStub); + const expectedConfig: DebugConfiguration = { + type: 'python', + request: 'attach', + name: 'Attach to Python', + port: 5678, + host: 'localhost', + }; + const optionsExpected: DebugSessionOptions = { + noDebug: false, + }; + const actualConfig = debugStub.getCall(0).args[1]; + const actualOptions = debugStub.getCall(0).args[2]; + + if (JSON.stringify(actualConfig) !== JSON.stringify(expectedConfig)) { + console.log('Config diff:', { + expected: expectedConfig, + actual: actualConfig, + }); + } + + if (JSON.stringify(actualOptions) !== JSON.stringify(optionsExpected)) { + console.log('Options diff:', { + expected: optionsExpected, + actual: actualOptions, + }); + } + + sinon.assert.calledWith(debugStub, undefined, expectedConfig, optionsExpected); }); }); + +function setupFileSystemWatchers(): sinon.SinonStub { + // create stub of createFileSystemWatcher function that will return a fake watcher with a callback + let createFileSystemWatcherFunct: sinon.SinonStub; + createFileSystemWatcherFunct = sinon.stub(utils, 'createFileSystemWatcher'); + createFileSystemWatcherFunct.callsFake(() => { + return { + onDidCreate: (callback: (arg0: Uri) => void) => { + callback(Uri.parse('fake/debuggerAdapterEndpoint.txt')); + }, + }; + }); + // create stub of fs.readFile function + sinon.stub(fs, 'readFile').callsFake( + (TypeMoq.It.isAny(), + TypeMoq.It.isAny(), + (err, data) => { + console.log(err, data); + }), + ); + return createFileSystemWatcherFunct; +} From fa4c53d02f43e3d433a081427e4f23f96a2da907 Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Tue, 14 Jan 2025 11:35:05 -0800 Subject: [PATCH 05/13] make scripts executable --- bundled/scripts/noConfigScripts/debugpy.bat | 0 bundled/scripts/noConfigScripts/debugpy.fish | 0 bundled/scripts/noConfigScripts/debugpy.ps1 | 0 3 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 bundled/scripts/noConfigScripts/debugpy.bat mode change 100644 => 100755 bundled/scripts/noConfigScripts/debugpy.fish mode change 100644 => 100755 bundled/scripts/noConfigScripts/debugpy.ps1 diff --git a/bundled/scripts/noConfigScripts/debugpy.bat b/bundled/scripts/noConfigScripts/debugpy.bat old mode 100644 new mode 100755 diff --git a/bundled/scripts/noConfigScripts/debugpy.fish b/bundled/scripts/noConfigScripts/debugpy.fish old mode 100644 new mode 100755 diff --git a/bundled/scripts/noConfigScripts/debugpy.ps1 b/bundled/scripts/noConfigScripts/debugpy.ps1 old mode 100644 new mode 100755 From 58600b1c409c71fbe97a4e390094546f04bcb652 Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Tue, 14 Jan 2025 11:43:21 -0800 Subject: [PATCH 06/13] fix paths and path sep --- src/extension/noConfigDebugInit.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/extension/noConfigDebugInit.ts b/src/extension/noConfigDebugInit.ts index 141935ab..1dc11908 100644 --- a/src/extension/noConfigDebugInit.ts +++ b/src/extension/noConfigDebugInit.ts @@ -26,10 +26,11 @@ export async function registerNoConfigDebug(context: IExtensionContext): Promise const debuggerAdapterEndpointPath = path.join(debugAdapterEndpointDir, 'debuggerAdapterEndpoint.txt'); collection.replace('DEBUGPY_ADAPTER_ENDPOINTS', debuggerAdapterEndpointPath); - const noConfigScriptsDir = path.join(context.extensionPath, 'bundled/scripts/noConfigScripts'); - collection.append('PATH', `:${noConfigScriptsDir}`); + const noConfigScriptsDir = path.join(context.extensionPath, 'bundled', 'scripts', 'noConfigScripts'); + const pathSeparator = process.platform === 'win32' ? ';' : ':'; + collection.append('PATH', `${pathSeparator}${noConfigScriptsDir}`); - const bundledDebugPath = path.join(context.extensionPath, 'bundled/libs/debugpy'); + const bundledDebugPath = path.join(context.extensionPath, 'bundled', 'libs', 'debugpy'); collection.replace('BUNDLED_DEBUGPY_PATH', bundledDebugPath); // create file system watcher for the debuggerAdapterEndpointFolder for when the communication port is written From d0ff6c595a7c02ecbc748a575768a98f25dd0a34 Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Tue, 14 Jan 2025 13:36:31 -0800 Subject: [PATCH 07/13] add folder and fix debugpy path --- noConfigDebugAdapterEndpoints/info.txt | 5 +++++ src/test/unittest/noConfigDebugInit.unit.test.ts | 3 +++ 2 files changed, 8 insertions(+) create mode 100644 noConfigDebugAdapterEndpoints/info.txt diff --git a/noConfigDebugAdapterEndpoints/info.txt b/noConfigDebugAdapterEndpoints/info.txt new file mode 100644 index 00000000..8828afb2 --- /dev/null +++ b/noConfigDebugAdapterEndpoints/info.txt @@ -0,0 +1,5 @@ +Copyright (c) Microsoft Corporation. All rights reserved. +Licensed under the MIT License. + +This folder is necessary as it is the location where the debug adapter +will create the debuggerAdapterEndpoint.txt to communicate the endpoint. \ No newline at end of file diff --git a/src/test/unittest/noConfigDebugInit.unit.test.ts b/src/test/unittest/noConfigDebugInit.unit.test.ts index c90fc791..9ac6afbf 100644 --- a/src/test/unittest/noConfigDebugInit.unit.test.ts +++ b/src/test/unittest/noConfigDebugInit.unit.test.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + import * as path from 'path'; import { IExtensionContext } from '../../extension/common/types'; import { registerNoConfigDebug as registerNoConfigDebug } from '../../extension/noConfigDebugInit'; From d7a8695ebea931d189813726e19f981c2b437775 Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Tue, 14 Jan 2025 15:52:57 -0800 Subject: [PATCH 08/13] minor edits --- .vscode/launch.json | 8 +++----- script_tester_configless.py | 20 -------------------- 2 files changed, 3 insertions(+), 25 deletions(-) delete mode 100644 script_tester_configless.py diff --git a/.vscode/launch.json b/.vscode/launch.json index 42fd2619..6f278e49 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -32,14 +32,12 @@ "./out/test/**/*.unit.test.js", "--extensionDevelopmentPath=${workspaceFolder}", "--extensionTestsPath=${workspaceFolder}/out/test/unittest/index", - //"--grep", - //"this test suite" + //"--grep", "", + "--timeout=300000" ], "outFiles": [ "${workspaceFolder}/out/**/*.js", ], - "preLaunchTask": "tasks: watch-tests", - "timeout": 600000 // Increase timeout to 10 minutes (600000 ms) - }, + "preLaunchTask": "tasks: watch-tests" }, ] } \ No newline at end of file diff --git a/script_tester_configless.py b/script_tester_configless.py deleted file mode 100644 index a4a5709d..00000000 --- a/script_tester_configless.py +++ /dev/null @@ -1,20 +0,0 @@ -if __name__ == "__main__": - print("Hello, World!") - import sys - import os - import json - - print(os.environ["DEBUGPY_ADAPTER_ENDPOINTS"]) - file_path = "/Users/eleanorboyd/vscode-python-debugger/comms-file.txt" - if os.path.exists(file_path): - with open(file_path, "r") as file: - print("FILE CONTENTS: \n") - contents = file.read() - c_thing = json.loads(contents) - if c_thing.get("client"): - # print("CLIENT: ", c_thing["client"]) - if c_thing.get("client").get("port"): - print("Port: ", c_thing.get("client").get("port")) - # print(contents) - else: - print(f"{file_path} does not exist.") From 54dfc0767b3dc4976b02793f4910e9e0966a897a Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Wed, 15 Jan 2025 14:25:51 -0800 Subject: [PATCH 09/13] add mit license --- src/extension/noConfigDebugInit.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/extension/noConfigDebugInit.ts b/src/extension/noConfigDebugInit.ts index 1dc11908..10a62d1b 100644 --- a/src/extension/noConfigDebugInit.ts +++ b/src/extension/noConfigDebugInit.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + import * as fs from 'fs'; import * as path from 'path'; import { IExtensionContext } from './common/types'; From 51edcafd9690f18fc0ea99aefda5dd20ed1e1563 Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Thu, 16 Jan 2025 09:11:38 -0800 Subject: [PATCH 10/13] suggested fixes --- .vscode/launch.json | 70 +++++++++---------- src/extension/noConfigDebugInit.ts | 6 +- src/extension/utils.ts | 4 +- .../unittest/noConfigDebugInit.unit.test.ts | 6 +- 4 files changed, 42 insertions(+), 44 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 6f278e49..08fec254 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -3,41 +3,35 @@ // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 { - "version": "0.2.0", - "configurations": [ - { - "name": "Run Extension", - "type": "extensionHost", - "request": "launch", - "args": [ - "--extensionDevelopmentPath=${workspaceFolder}" - ], - "outFiles": [ - "${workspaceFolder}/dist/**/*.js", - "!${workspaceFolder}/**/node_modules**/*" - ], - "preLaunchTask": "npm: watch", - "presentation": { - "hidden": false, - "group": "", - "order": 2 - } - }, - { - "name": "Unit Tests", - "type": "extensionHost", - "request": "launch", - "runtimeExecutable": "${execPath}", - "args": [ - "./out/test/**/*.unit.test.js", - "--extensionDevelopmentPath=${workspaceFolder}", - "--extensionTestsPath=${workspaceFolder}/out/test/unittest/index", - //"--grep", "", - "--timeout=300000" - ], - "outFiles": [ - "${workspaceFolder}/out/**/*.js", - ], - "preLaunchTask": "tasks: watch-tests" }, - ] -} \ No newline at end of file + "version": "0.2.0", + "configurations": [ + { + "name": "Run Extension", + "type": "extensionHost", + "request": "launch", + "args": ["--extensionDevelopmentPath=${workspaceFolder}"], + "outFiles": ["${workspaceFolder}/dist/**/*.js", "!${workspaceFolder}/**/node_modules**/*"], + "preLaunchTask": "npm: watch", + "presentation": { + "hidden": false, + "group": "", + "order": 2 + } + }, + { + "name": "Unit Tests", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": [ + "./out/test/**/*.unit.test.js", + "--extensionDevelopmentPath=${workspaceFolder}", + "--extensionTestsPath=${workspaceFolder}/out/test/unittest/index", + //"--grep", "", + "--timeout=300000" + ], + "outFiles": ["${workspaceFolder}/out/**/*.js"], + "preLaunchTask": "tasks: watch-tests" + } + ] +} diff --git a/src/extension/noConfigDebugInit.ts b/src/extension/noConfigDebugInit.ts index 10a62d1b..95089c7c 100644 --- a/src/extension/noConfigDebugInit.ts +++ b/src/extension/noConfigDebugInit.ts @@ -63,8 +63,10 @@ export async function registerNoConfigDebug(context: IExtensionContext): Promise type: 'python', request: 'attach', name: 'Attach to Python', - port: clientPort, - host: 'localhost', + connect: { + port: clientPort, + host: 'localhost', + }, }, options, ).then( diff --git a/src/extension/utils.ts b/src/extension/utils.ts index c74d1b49..4e64b602 100644 --- a/src/extension/utils.ts +++ b/src/extension/utils.ts @@ -12,10 +12,10 @@ export function createFileSystemWatcher(args: any): FileSystemWatcher { return workspace.createFileSystemWatcher(args); } -export function debugStartDebugging( +export async function debugStartDebugging( folder: WorkspaceFolder | undefined, nameOrConfiguration: string | DebugConfiguration, parentSessionOrOptions?: DebugSession | DebugSessionOptions, -): Thenable { +): Promise { return debug.startDebugging(folder, nameOrConfiguration, parentSessionOrOptions); } diff --git a/src/test/unittest/noConfigDebugInit.unit.test.ts b/src/test/unittest/noConfigDebugInit.unit.test.ts index 9ac6afbf..3e397a58 100644 --- a/src/test/unittest/noConfigDebugInit.unit.test.ts +++ b/src/test/unittest/noConfigDebugInit.unit.test.ts @@ -135,8 +135,10 @@ suite('setup for no-config debug scenario', function () { type: 'python', request: 'attach', name: 'Attach to Python', - port: 5678, - host: 'localhost', + connect: { + port: 5678, + host: 'localhost', + }, }; const optionsExpected: DebugSessionOptions = { noDebug: false, From ea77f6c8a9137122af8506e503bea73ad4942068 Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Thu, 16 Jan 2025 09:16:34 -0800 Subject: [PATCH 11/13] additional fix --- src/extension/noConfigDebugInit.ts | 97 +++++++++++++++--------------- 1 file changed, 49 insertions(+), 48 deletions(-) diff --git a/src/extension/noConfigDebugInit.ts b/src/extension/noConfigDebugInit.ts index 95089c7c..dbb60675 100644 --- a/src/extension/noConfigDebugInit.ts +++ b/src/extension/noConfigDebugInit.ts @@ -37,55 +37,56 @@ export async function registerNoConfigDebug(context: IExtensionContext): Promise collection.replace('BUNDLED_DEBUGPY_PATH', bundledDebugPath); // create file system watcher for the debuggerAdapterEndpointFolder for when the communication port is written - context.subscriptions.push( - createFileSystemWatcher(new RelativePattern(debugAdapterEndpointDir, '**/*')).onDidCreate((uri) => { - const filePath = uri.fsPath; - fs.readFile(filePath, (err, data) => { - const dataParse = data.toString(); - if (err) { - traceError(`Error reading debuggerAdapterEndpoint.txt file: ${err}`); - return; - } - try { - // parse the client port - const jsonData = JSON.parse(dataParse); - const clientPort = jsonData.client?.port; - traceVerbose(`Parsed client port: ${clientPort}`); + const fileSystemWatcher = createFileSystemWatcher(new RelativePattern(debugAdapterEndpointDir, '**/*')); + const fileCreationEvent = fileSystemWatcher.onDidCreate(async (uri) => { + const filePath = uri.fsPath; + fs.readFile(filePath, (err, data) => { + const dataParse = data.toString(); + if (err) { + traceError(`Error reading debuggerAdapterEndpoint.txt file: ${err}`); + return; + } + try { + // parse the client port + const jsonData = JSON.parse(dataParse); + const clientPort = jsonData.client?.port; + traceVerbose(`Parsed client port: ${clientPort}`); - const options: DebugSessionOptions = { - noDebug: false, - }; + const options: DebugSessionOptions = { + noDebug: false, + }; - // start debug session with the client port - debugStartDebugging( - undefined, - { - type: 'python', - request: 'attach', - name: 'Attach to Python', - connect: { - port: clientPort, - host: 'localhost', - }, + // start debug session with the client port + debugStartDebugging( + undefined, + { + type: 'python', + request: 'attach', + name: 'Attach to Python', + connect: { + port: clientPort, + host: 'localhost', }, - options, - ).then( - (started) => { - if (started) { - traceVerbose('Successfully started debug session'); - } else { - traceError('Error starting debug session, session not started.'); - } - }, - (error) => { - traceError(`Error starting debug session: ${error}`); - }, - ); - } catch (parseErr) { - traceError(`Error parsing JSON: ${parseErr}`); - } - }); - JSON.parse; - }), - ); + }, + options, + ).then( + (started) => { + if (started) { + traceVerbose('Successfully started debug session'); + } else { + traceError('Error starting debug session, session not started.'); + } + }, + (error) => { + traceError(`Error starting debug session: ${error}`); + }, + ); + } catch (parseErr) { + traceError(`Error parsing JSON: ${parseErr}`); + } + }); + JSON.parse; + }); + context.subscriptions.push(fileCreationEvent); + context.subscriptions.push(fileSystemWatcher); } From 5c23abb8f8aaf9cba20678e18ef4f737747cb1b9 Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Fri, 17 Jan 2025 11:43:21 -0800 Subject: [PATCH 12/13] updates based on comments feedback --- bundled/scripts/noConfigScripts/debugpy | 1 - bundled/scripts/noConfigScripts/debugpy.bat | 2 +- src/extension/extensionInit.ts | 4 +- src/extension/noConfigDebugInit.ts | 52 +++++++++++++----- .../unittest/noConfigDebugInit.unit.test.ts | 53 +++++++++---------- 5 files changed, 68 insertions(+), 44 deletions(-) diff --git a/bundled/scripts/noConfigScripts/debugpy b/bundled/scripts/noConfigScripts/debugpy index 0c97dea8..7b440d2c 100755 --- a/bundled/scripts/noConfigScripts/debugpy +++ b/bundled/scripts/noConfigScripts/debugpy @@ -1,4 +1,3 @@ #! /bin/bash # Bash script python $BUNDLED_DEBUGPY_PATH --listen 0 --wait-for-client $@ -echo "Executed: python $BUNDLED_DEBUGPY_PATH --listen 0 --wait-for-client $@" \ No newline at end of file diff --git a/bundled/scripts/noConfigScripts/debugpy.bat b/bundled/scripts/noConfigScripts/debugpy.bat index d6e7685a..25e3bf5f 100755 --- a/bundled/scripts/noConfigScripts/debugpy.bat +++ b/bundled/scripts/noConfigScripts/debugpy.bat @@ -1,3 +1,3 @@ @echo off :: Bat script -python %BUNDLED_DEBUGPY_PATH% --listen 0 --wait-for-client %* \ No newline at end of file +python %BUNDLED_DEBUGPY_PATH% --listen 0 --wait-for-client %* diff --git a/src/extension/extensionInit.ts b/src/extension/extensionInit.ts index f1c8eb8d..c4f52b6c 100644 --- a/src/extension/extensionInit.ts +++ b/src/extension/extensionInit.ts @@ -248,7 +248,9 @@ export async function registerDebugger(context: IExtensionContext): Promise { - const collection = context.environmentVariableCollection; +export async function registerNoConfigDebug( + envVarCollection: GlobalEnvironmentVariableCollection, + extPath: string, +): Promise { + const collection = envVarCollection; + + // create a temp directory for the noConfigDebugAdapterEndpoints + // file path format: tempDir/noConfigDebugAdapterEndpoints-/debuggerAdapterEndpoint.txt + const randomSuffix = crypto.randomBytes(10).toString('hex'); + const tempDirName = `noConfigDebugAdapterEndpoints-${randomSuffix}`; + let tempDirPath = path.join(os.tmpdir(), tempDirName); + try { + traceLog('Attempting to use temp directory for noConfigDebugAdapterEndpoints, dir name:', tempDirName); + await fs.promises.mkdir(tempDirPath, { recursive: true }); + } catch (error) { + // Handle the error when accessing the temp directory + traceError('Error accessing temp directory:', error, ' Attempt to use extension root dir instead'); + // Make new temp directory in extension root dird + tempDirPath = path.join(extPath, '.temp'); + await fs.promises.mkdir(tempDirPath, { recursive: true }); + } + const tempFilePath = path.join(tempDirPath, 'debuggerAdapterEndpoint.txt'); // Add env vars for DEBUGPY_ADAPTER_ENDPOINTS, BUNDLED_DEBUGPY_PATH, and PATH - const debugAdapterEndpointDir = path.join(context.extensionPath, 'noConfigDebugAdapterEndpoints'); - const debuggerAdapterEndpointPath = path.join(debugAdapterEndpointDir, 'debuggerAdapterEndpoint.txt'); - collection.replace('DEBUGPY_ADAPTER_ENDPOINTS', debuggerAdapterEndpointPath); + collection.replace('DEBUGPY_ADAPTER_ENDPOINTS', tempFilePath); - const noConfigScriptsDir = path.join(context.extensionPath, 'bundled', 'scripts', 'noConfigScripts'); + const noConfigScriptsDir = path.join(extPath, 'bundled', 'scripts', 'noConfigScripts'); const pathSeparator = process.platform === 'win32' ? ';' : ':'; collection.append('PATH', `${pathSeparator}${noConfigScriptsDir}`); - const bundledDebugPath = path.join(context.extensionPath, 'bundled', 'libs', 'debugpy'); + const bundledDebugPath = path.join(extPath, 'bundled', 'libs', 'debugpy'); collection.replace('BUNDLED_DEBUGPY_PATH', bundledDebugPath); // create file system watcher for the debuggerAdapterEndpointFolder for when the communication port is written - const fileSystemWatcher = createFileSystemWatcher(new RelativePattern(debugAdapterEndpointDir, '**/*')); + const fileSystemWatcher = createFileSystemWatcher(new RelativePattern(tempDirPath, '**/*')); const fileCreationEvent = fileSystemWatcher.onDidCreate(async (uri) => { const filePath = uri.fsPath; fs.readFile(filePath, (err, data) => { @@ -87,6 +107,10 @@ export async function registerNoConfigDebug(context: IExtensionContext): Promise }); JSON.parse; }); - context.subscriptions.push(fileCreationEvent); - context.subscriptions.push(fileSystemWatcher); + return Promise.resolve( + new Disposable(() => { + fileSystemWatcher.dispose(); + fileCreationEvent.dispose(); + }), + ); } diff --git a/src/test/unittest/noConfigDebugInit.unit.test.ts b/src/test/unittest/noConfigDebugInit.unit.test.ts index 3e397a58..7ade02a7 100644 --- a/src/test/unittest/noConfigDebugInit.unit.test.ts +++ b/src/test/unittest/noConfigDebugInit.unit.test.ts @@ -10,45 +10,44 @@ import { DebugConfiguration, DebugSessionOptions, RelativePattern, Uri } from 'v import * as utils from '../../extension/utils'; import { assert } from 'console'; import * as fs from 'fs'; +import * as os from 'os'; +import * as crypto from 'crypto'; suite('setup for no-config debug scenario', function () { let envVarCollectionReplaceStub: sinon.SinonStub; let envVarCollectionAppendStub: sinon.SinonStub; let context: TypeMoq.IMock; - let debugAdapterEndpointDir: string; - let debuggerAdapterEndpointPath: string; let noConfigScriptsDir: string; let bundledDebugPath: string; let DEBUGPY_ADAPTER_ENDPOINTS = 'DEBUGPY_ADAPTER_ENDPOINTS'; let BUNDLED_DEBUGPY_PATH = 'BUNDLED_DEBUGPY_PATH'; + let tempDirPath: string; const testDataDir = path.join(__dirname, 'testData'); const testFilePath = path.join(testDataDir, 'debuggerAdapterEndpoint.txt'); - suiteSetup(() => { - // create file called testData/debuggerAdapterEndpoint.txt - if (!fs.existsSync(testDataDir)) { - fs.mkdirSync(testDataDir); - } - fs.writeFileSync(testFilePath, JSON.stringify({ client: { port: 5678 } })); - }); setup(() => { - context = TypeMoq.Mock.ofType(); - - context.setup((c) => (c as any).extensionPath).returns(() => 'fake/extension/path'); - context.setup((c) => c.subscriptions).returns(() => []); - debugAdapterEndpointDir = path.join(context.object.extensionPath, 'noConfigDebugAdapterEndpoints'); - debuggerAdapterEndpointPath = path.join(debugAdapterEndpointDir, 'debuggerAdapterEndpoint.txt'); - noConfigScriptsDir = path.join(context.object.extensionPath, 'bundled/scripts/noConfigScripts'); - bundledDebugPath = path.join(context.object.extensionPath, 'bundled/libs/debugpy'); + try { + context = TypeMoq.Mock.ofType(); + + const randomSuffix = '1234567899'; + const tempDirName = `noConfigDebugAdapterEndpoints-${randomSuffix}`; + tempDirPath = path.join(os.tmpdir(), tempDirName); + context.setup((c) => (c as any).extensionPath).returns(() => 'fake/extension/path'); + context.setup((c) => c.subscriptions).returns(() => []); + noConfigScriptsDir = path.join(context.object.extensionPath, 'bundled/scripts/noConfigScripts'); + bundledDebugPath = path.join(context.object.extensionPath, 'bundled/libs/debugpy'); + + // Stub crypto.randomBytes with proper typing + let randomBytesStub = sinon.stub(crypto, 'randomBytes'); + // Provide a valid Buffer object + randomBytesStub.callsFake((_size: number) => Buffer.from('1234567899', 'hex')); + } catch (error) { + console.error('Error in setup:', error); + } }); teardown(() => { sinon.restore(); }); - suiteTeardown(() => { - if (fs.existsSync(testDataDir)) { - fs.rmSync(testDataDir, { recursive: true, force: true }); - } - }); test('should add environment variables for DEBUGPY_ADAPTER_ENDPOINTS, BUNDLED_DEBUGPY_PATH, and PATH', async () => { const environmentVariableCollectionMock = TypeMoq.Mock.ofType(); @@ -60,7 +59,7 @@ suite('setup for no-config debug scenario', function () { .setup((x) => x.replace(TypeMoq.It.isAny(), TypeMoq.It.isAny())) .callback((key, value) => { if (key === DEBUGPY_ADAPTER_ENDPOINTS) { - assert(value === debuggerAdapterEndpointPath); + assert(value.includes('noConfigDebugAdapterEndpoints-1234567899')); } else if (key === BUNDLED_DEBUGPY_PATH) { assert(value === bundledDebugPath); } @@ -80,7 +79,7 @@ suite('setup for no-config debug scenario', function () { setupFileSystemWatchers(); // run init for no config debug - await registerNoConfigDebug(context.object); + await registerNoConfigDebug(context.object.environmentVariableCollection, context.object.extensionPath); // assert that functions called right number of times sinon.assert.calledTwice(envVarCollectionReplaceStub); @@ -94,11 +93,11 @@ suite('setup for no-config debug scenario', function () { let createFileSystemWatcherFunct = setupFileSystemWatchers(); // Act - await registerNoConfigDebug(context.object); + await registerNoConfigDebug(context.object.environmentVariableCollection, context.object.extensionPath); // Assert sinon.assert.calledOnce(createFileSystemWatcherFunct); - const expectedPattern = new RelativePattern(debugAdapterEndpointDir, '**/*'); + const expectedPattern = new RelativePattern(tempDirPath, '**/*'); sinon.assert.calledWith(createFileSystemWatcherFunct, expectedPattern); }); @@ -127,7 +126,7 @@ suite('setup for no-config debug scenario', function () { const debugStub = sinon.stub(utils, 'debugStartDebugging').resolves(true); // Act - await registerNoConfigDebug(context.object); + await registerNoConfigDebug(context.object.environmentVariableCollection, context.object.extensionPath); // Assert sinon.assert.calledOnce(debugStub); From 49fb265c42cd70af803d064cf717d4bd90f29f48 Mon Sep 17 00:00:00 2001 From: eleanorjboyd Date: Fri, 17 Jan 2025 11:49:10 -0800 Subject: [PATCH 13/13] remove initial endpoint design --- noConfigDebugAdapterEndpoints/info.txt | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 noConfigDebugAdapterEndpoints/info.txt diff --git a/noConfigDebugAdapterEndpoints/info.txt b/noConfigDebugAdapterEndpoints/info.txt deleted file mode 100644 index 8828afb2..00000000 --- a/noConfigDebugAdapterEndpoints/info.txt +++ /dev/null @@ -1,5 +0,0 @@ -Copyright (c) Microsoft Corporation. All rights reserved. -Licensed under the MIT License. - -This folder is necessary as it is the location where the debug adapter -will create the debuggerAdapterEndpoint.txt to communicate the endpoint. \ No newline at end of file