From 483d1db8a8a779220bbf62f03b78f96618d05aa6 Mon Sep 17 00:00:00 2001 From: meowjesty <43983236+meowjesty@users.noreply.github.com> Date: Fri, 6 Oct 2023 05:34:59 -0300 Subject: [PATCH] Change verify config action to pass --ide, changing some target related errors to warnings. (#71) * Change verify config action to pass --ide, changing some target related errors to warnings. * wrap entry in try/catch * log stdout * move entrypoint to a function so we dont have a huge try/catch * changelog * formatting * log catch * revert --path * lint --- changelog.d/72.fixed.md | 1 + src/api.ts | 10 +- src/config.ts | 5 +- src/debugger.ts | 216 ++++++++++++++++++++------------------ src/tests/e2e.ts | 224 ++++++++++++++++++++-------------------- 5 files changed, 236 insertions(+), 220 deletions(-) create mode 100644 changelog.d/72.fixed.md diff --git a/changelog.d/72.fixed.md b/changelog.d/72.fixed.md new file mode 100644 index 00000000..e19162da --- /dev/null +++ b/changelog.d/72.fixed.md @@ -0,0 +1 @@ +Uses the `verify-config --ide` flag now to signal mirrord we're in an IDE context. Changes `isTargetSet` to check for both `null` and `undefined`. diff --git a/src/api.ts b/src/api.ts index 6480d8fd..d95a8912 100644 --- a/src/api.ts +++ b/src/api.ts @@ -164,6 +164,9 @@ export class MirrordAPI { child.stdout.on("data", (data) => stdoutData += data.toString()); child.stderr.on("data", (data) => stderrData += data.toString()); + child.stdout.on('end', () => console.log(`${stdoutData}`)); + child.stderr.on('end', () => console.log(`${stderrData}`)); + child.on("error", (err) => { console.error(err); reject(`process failed: ${err.message}`); @@ -197,7 +200,7 @@ export class MirrordAPI { }); } - /** + /** * Spawn the mirrord cli with the given arguments. * Used for reading/interacting while process still runs. */ @@ -248,9 +251,8 @@ export class MirrordAPI { * `VerifiedConfig`. */ async verifyConfig(configPath: vscode.Uri | null): Promise { - const args = ['verify-config']; if (configPath) { - args.push(configPath.path); + const args = ['verify-config', '--ide', `${configPath.path}`]; const stdout = await this.exec(args); const verifiedConfig: VerifiedConfig = JSON.parse(stdout); @@ -396,7 +398,7 @@ class MirrordWarningHandler { } } -/** +/** * Updates the global feedback counter. * After each `FEEDBACK_COUNTER_REVIEW_AFTER` mirrord runs, displays a message asking the user to leave a review in the marketplace. */ diff --git a/src/config.ts b/src/config.ts index 81fe03e6..f0e91923 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,8 +1,5 @@ import * as vscode from 'vscode'; -import YAML from 'yaml'; -import TOML from 'toml'; import { NotificationBuilder } from './notification'; -import { mirrordFailure } from './api'; /** * Default mirrord configuration. @@ -69,7 +66,7 @@ export function isTargetSet(verifiedConfig: VerifiedConfig): boolean { switch (verifiedConfig.type) { case 'Success': verifiedConfig.warnings.forEach((warn) => new NotificationBuilder().withMessage(warn).warning()); - return verifiedConfig.config.path !== undefined; + return verifiedConfig.config.path !== undefined && verifiedConfig.config.path !== null; case 'Fail': verifiedConfig.errors.forEach((fail) => new NotificationBuilder().withMessage(fail).error()); throw new Error('mirrord verify-config detected an invalid configuration!'); diff --git a/src/debugger.ts b/src/debugger.ts index 63219b17..f2d31277 100644 --- a/src/debugger.ts +++ b/src/debugger.ts @@ -85,137 +85,153 @@ function setLastActiveMirrordPath(path: string) { } /** -* We implement the `resolveDebugConfiguration` that comes with vscode variables resolved already. +* Entrypoint for the vscode extension, called from `resolveDebugConfigurationWithSubstitutedVariables`. */ -export class ConfigurationProvider implements vscode.DebugConfigurationProvider { - async resolveDebugConfigurationWithSubstitutedVariables( - folder: vscode.WorkspaceFolder | undefined, - config: vscode.DebugConfiguration, - token: vscode.CancellationToken): Promise { - - if (!globalContext.workspaceState.get('enabled')) { - return config; - } +async function main( + folder: vscode.WorkspaceFolder | undefined, + config: vscode.DebugConfiguration, + _token: vscode.CancellationToken): Promise { + if (!globalContext.workspaceState.get('enabled')) { + return config; + } - // For some reason resolveDebugConfiguration runs twice for Node projects. __parentId is populated. - if (config.__parentId || config.env?.["__MIRRORD_EXT_INJECTED"] === 'true') { - return config; - } + // For some reason resolveDebugConfiguration runs twice for Node projects. __parentId is populated. + if (config.__parentId || config.env?.["__MIRRORD_EXT_INJECTED"] === 'true') { + return config; + } - updateTelemetries(); + updateTelemetries(); - //TODO: add progress bar maybe ? - let cliPath; + //TODO: add progress bar maybe ? + let cliPath; - try { - cliPath = await getMirrordBinary(); - } catch (err) { - // Get last active, that should work? - cliPath = await getLastActiveMirrordPath(); - - // Well try any mirrord we can try :\ + try { + cliPath = await getMirrordBinary(); + } catch (err) { + // Get last active, that should work? + cliPath = await getLastActiveMirrordPath(); + + // Well try any mirrord we can try :\ + if (!cliPath) { + cliPath = await getLocalMirrordBinary(); if (!cliPath) { - cliPath = await getLocalMirrordBinary(); - if (!cliPath) { - mirrordFailure(`Couldn't download mirrord binaries or find local one in path ${err}.`); - return null; - } + mirrordFailure(`Couldn't download mirrord binaries or find local one in path ${err}.`); + return null; } } - setLastActiveMirrordPath(cliPath); + } + setLastActiveMirrordPath(cliPath); - let mirrordApi = new MirrordAPI(cliPath); + let mirrordApi = new MirrordAPI(cliPath); - config.env ||= {}; - let target = null; + config.env ||= {}; + let target = null; - let configPath = await MirrordConfigManager.getInstance().resolveMirrordConfig(folder, config); - const verifiedConfig = await mirrordApi.verifyConfig(configPath); + let configPath = await MirrordConfigManager.getInstance().resolveMirrordConfig(folder, config); + const verifiedConfig = await mirrordApi.verifyConfig(configPath); - // If target wasn't specified in the config file (or there's no config file), let user choose pod from dropdown - if (!configPath || (verifiedConfig && !isTargetSet(verifiedConfig))) { - let targets; - try { - targets = await mirrordApi.listTargets(configPath?.path); - } catch (err) { - mirrordFailure(`mirrord failed to list targets: ${err}`); - return null; - } - if (targets.length === 0) { - new NotificationBuilder() - .withMessage( - "No mirrord target available in the configured namespace. " + - "You can run targetless, or set a different target namespace or kubeconfig in the mirrord configuration file.", - ) - .info(); - } - - let selected = false; + // If target wasn't specified in the config file (or there's no config file), let user choose pod from dropdown + if (!configPath || (verifiedConfig && !isTargetSet(verifiedConfig))) { + let targets; + try { + targets = await mirrordApi.listTargets(configPath?.path); + } catch (err) { + mirrordFailure(`mirrord failed to list targets: ${err}`); + return null; + } + if (targets.length === 0) { + new NotificationBuilder() + .withMessage( + "No mirrord target available in the configured namespace. " + + "You can run targetless, or set a different target namespace or kubeconfig in the mirrord configuration file.", + ) + .info(); + } - while (!selected) { - let targetPick = await vscode.window.showQuickPick(targets.quickPickItems(), { - placeHolder: 'Select a target path to mirror' - }); + let selected = false; - if (targetPick) { - if (targetPick.type === 'page') { - targets.switchPage(targetPick); + while (!selected) { + let targetPick = await vscode.window.showQuickPick(targets.quickPickItems(), { + placeHolder: 'Select a target path to mirror' + }); - continue; - } + if (targetPick) { + if (targetPick.type === 'page') { + targets.switchPage(targetPick); - if (targetPick.type !== 'targetless') { - target = targetPick.value; - } + continue; + } - globalContext.globalState.update(LAST_TARGET_KEY, target); - globalContext.workspaceState.update(LAST_TARGET_KEY, target); + if (targetPick.type !== 'targetless') { + target = targetPick.value; } - selected = true; + globalContext.globalState.update(LAST_TARGET_KEY, target); + globalContext.workspaceState.update(LAST_TARGET_KEY, target); } - if (!target) { - new NotificationBuilder() - .withMessage("mirrord running targetless") - .withDisableAction("promptTargetless") - .info(); - } + selected = true; } - if (config.type === "go") { - config.env["MIRRORD_SKIP_PROCESSES"] = "dlv;debugserver;compile;go;asm;cgo;link;git;gcc;as;ld;collect2;cc1"; - } else if (config.type === "python") { - config.env["MIRRORD_DETECT_DEBUGGER_PORT"] = "debugpy"; - } else if (config.type === "java") { - config.env["MIRRORD_DETECT_DEBUGGER_PORT"] = "javaagent"; + if (!target) { + new NotificationBuilder() + .withMessage("mirrord running targetless") + .withDisableAction("promptTargetless") + .info(); } + } - // Add a fixed range of ports that VS Code uses for debugging. - // TODO: find a way to use MIRRORD_DETECT_DEBUGGER_PORT for other debuggers. - config.env["MIRRORD_IGNORE_DEBUGGER_PORTS"] = "45000-65535"; + if (config.type === "go") { + config.env["MIRRORD_SKIP_PROCESSES"] = "dlv;debugserver;compile;go;asm;cgo;link;git;gcc;as;ld;collect2;cc1"; + } else if (config.type === "python") { + config.env["MIRRORD_DETECT_DEBUGGER_PORT"] = "debugpy"; + } else if (config.type === "java") { + config.env["MIRRORD_DETECT_DEBUGGER_PORT"] = "javaagent"; + } - let isMac = platform() === "darwin"; + // Add a fixed range of ports that VS Code uses for debugging. + // TODO: find a way to use MIRRORD_DETECT_DEBUGGER_PORT for other debuggers. + config.env["MIRRORD_IGNORE_DEBUGGER_PORTS"] = "45000-65535"; - let [executableFieldName, executable] = isMac ? getFieldAndExecutable(config) : [null, null]; + let isMac = platform() === "darwin"; - let executionInfo; - try { - executionInfo = await mirrordApi.binaryExecute(target, configPath?.path || null, executable); - } catch (err) { - mirrordFailure(`mirrord preparation failed: ${err}`); - return null; - } + let [executableFieldName, executable] = isMac ? getFieldAndExecutable(config) : [null, null]; - if (isMac) { - changeConfigForSip(config, executableFieldName as string, executionInfo); - } + let executionInfo; + try { + executionInfo = await mirrordApi.binaryExecute(target, configPath?.path || null, executable); + } catch (err) { + mirrordFailure(`mirrord preparation failed: ${err}`); + return null; + } - let env = executionInfo?.env; - config.env = Object.assign({}, config.env, Object.fromEntries(env)); + if (isMac) { + changeConfigForSip(config, executableFieldName as string, executionInfo); + } - config.env["__MIRRORD_EXT_INJECTED"] = 'true'; + let env = executionInfo?.env; + config.env = Object.assign({}, config.env, Object.fromEntries(env)); - return config; + config.env["__MIRRORD_EXT_INJECTED"] = 'true'; + + return config; +} + +/** +* We implement the `resolveDebugConfiguration` that comes with vscode variables resolved already. +*/ +export class ConfigurationProvider implements vscode.DebugConfigurationProvider { + async resolveDebugConfigurationWithSubstitutedVariables( + folder: vscode.WorkspaceFolder | undefined, + config: vscode.DebugConfiguration, + _token: vscode.CancellationToken): Promise { + try { + return await main(folder, config, _token); + } catch (fail) { + console.error(`Something went wrong in the extension: ${fail}`); + new NotificationBuilder() + .withMessage(`Something went wrong: ${fail}`) + .error(); + } } } diff --git a/src/tests/e2e.ts b/src/tests/e2e.ts index b958265c..18c1bf41 100644 --- a/src/tests/e2e.ts +++ b/src/tests/e2e.ts @@ -15,127 +15,127 @@ import get from "axios"; const kubeService = process.env.KUBE_SERVICE; const podToSelect = process.env.POD_TO_SELECT; -describe("mirrord sample flow test", function () { - - this.timeout(1000000); // --> mocha tests timeout - this.bail(true); // --> stop tests on first failure - - let browser: VSBrowser; - - const testWorkspace = join(__dirname, '../../test-workspace'); - const fileName = "app_flask.py"; - const mirrordConfigPath = join(testWorkspace, '.mirrord/mirrord.json'); - const defaultTimeout = 10000; - - before(async function () { - console.log("podToSelect: " + podToSelect); - console.log("kubeService: " + kubeService); - - expect(podToSelect).to.not.be.undefined; - expect(kubeService).to.not.be.undefined; - - browser = VSBrowser.instance; - // need to bring the flask app in open editors - await browser.openResources(testWorkspace, join(testWorkspace, fileName)); - }); - - it("enable mirrord", async function () { - const statusBar = new StatusBar(); - await browser.driver.wait(async () => { - for (let button of await statusBar.getItems()) { - try { - if ((await button.getText()).startsWith('mirrord')) { - await button.click(); - - return true; - } - } catch (e) { } - } - }, defaultTimeout, "mirrord `enable` button not found -- timed out"); - - await browser.driver.wait(async () => { - for (let button of await statusBar.getItems()) { - try { - if ((await button.getText()).startsWith('mirrord')) { - return true; - } - } catch (e) { } - } - }, defaultTimeout, "mirrord `disable` button not found -- timed out"); - }); - - it("select pod from quickpick", async function () { - await setBreakPoint(fileName, browser, defaultTimeout); - await startDebugging(); - - const inputBox = await InputBox.create(defaultTimeout * 2); - // assertion that podToSelect is not undefined is done in "before" block - await browser.driver.wait(async () => { - if (!await inputBox.isDisplayed()) { - return false; - } - - for (const pick of await inputBox.getQuickPicks()) { - let label = await pick.getLabel(); - - if (label === podToSelect) { - return true; - } - - if (label === "Show Pods") { - await pick.select(); - } - } - - return false; - }, defaultTimeout * 2, "quickPick not found -- timed out"); - - await inputBox.selectQuickPick(podToSelect!); - }); - - it("wait for breakpoint to be hit", async function () { - const debugToolbar = await DebugToolbar.create(2 * defaultTimeout); - // waiting for breakpoint and sending traffic to pod are run in parallel - // however, traffic is sent after 10 seconds that we are sure the IDE is listening - // for breakpoints - await browser.driver.wait(async () => { - return await debugToolbar.isDisplayed(); - }, 2 * defaultTimeout, "debug toolbar not found -- timed out"); - - sendTrafficToPod(debugToolbar); - debugToolbar.waitForBreakPoint(); - }); +describe("mirrord sample flow test", function() { + + this.timeout(1000000); // --> mocha tests timeout + this.bail(true); // --> stop tests on first failure + + let browser: VSBrowser; + + const testWorkspace = join(__dirname, '../../test-workspace'); + const fileName = "app_flask.py"; + const mirrordConfigPath = join(testWorkspace, '.mirrord/mirrord.json'); + const defaultTimeout = 10000; + + before(async function() { + console.log("podToSelect: " + podToSelect); + console.log("kubeService: " + kubeService); + + expect(podToSelect).to.not.be.undefined; + expect(kubeService).to.not.be.undefined; + + browser = VSBrowser.instance; + // need to bring the flask app in open editors + await browser.openResources(testWorkspace, join(testWorkspace, fileName)); + }); + + it("enable mirrord", async function() { + const statusBar = new StatusBar(); + await browser.driver.wait(async () => { + for (let button of await statusBar.getItems()) { + try { + if ((await button.getText()).startsWith('mirrord')) { + await button.click(); + + return true; + } + } catch (e) { console.error(`Something went wrong ${e}`); } + } + }, defaultTimeout, "mirrord `enable` button not found -- timed out"); + + await browser.driver.wait(async () => { + for (let button of await statusBar.getItems()) { + try { + if ((await button.getText()).startsWith('mirrord')) { + return true; + } + } catch (e) { console.error(`Something went wrong ${e}`); } + } + }, defaultTimeout, "mirrord `disable` button not found -- timed out"); + }); + + it("select pod from quickpick", async function() { + await setBreakPoint(fileName, browser, defaultTimeout); + await startDebugging(); + + const inputBox = await InputBox.create(defaultTimeout * 2); + // assertion that podToSelect is not undefined is done in "before" block + await browser.driver.wait(async () => { + if (!await inputBox.isDisplayed()) { + return false; + } + + for (const pick of await inputBox.getQuickPicks()) { + let label = await pick.getLabel(); + + if (label === podToSelect) { + return true; + } + + if (label === "Show Pods") { + await pick.select(); + } + } + + return false; + }, defaultTimeout * 2, "quickPick not found -- timed out"); + + await inputBox.selectQuickPick(podToSelect!); + }); + + it("wait for breakpoint to be hit", async function() { + const debugToolbar = await DebugToolbar.create(2 * defaultTimeout); + // waiting for breakpoint and sending traffic to pod are run in parallel + // however, traffic is sent after 10 seconds that we are sure the IDE is listening + // for breakpoints + await browser.driver.wait(async () => { + return await debugToolbar.isDisplayed(); + }, 2 * defaultTimeout, "debug toolbar not found -- timed out"); + + sendTrafficToPod(debugToolbar); + debugToolbar.waitForBreakPoint(); + }); }); async function sendTrafficToPod(debugToolbar: DebugToolbar) { - const response = await get(kubeService!!); - expect(response.status).to.equal(200); - expect(response.data).to.equal("OK - GET: Request completed\n"); + const response = await get(kubeService!!); + expect(response.status).to.equal(200); + expect(response.data).to.equal("OK - GET: Request completed\n"); } // opens and sets a breakpoint in the given file async function setBreakPoint(fileName: string, browser: VSBrowser, timeout: number, breakPoint: number = 9) { - const editorView = new EditorView(); - await editorView.openEditor(fileName); - const currentTab = await editorView.getActiveTab(); - expect(currentTab).to.not.be.undefined; - await browser.driver.wait(async () => { - const tabTitle = await currentTab?.getTitle(); - if (tabTitle !== undefined) { - return tabTitle === fileName; - } - }, timeout, "editor tab title not found -- timed out"); - - const textEditor = new TextEditor(); - await textEditor.toggleBreakpoint(breakPoint); + const editorView = new EditorView(); + await editorView.openEditor(fileName); + const currentTab = await editorView.getActiveTab(); + expect(currentTab).to.not.be.undefined; + await browser.driver.wait(async () => { + const tabTitle = await currentTab?.getTitle(); + if (tabTitle !== undefined) { + return tabTitle === fileName; + } + }, timeout, "editor tab title not found -- timed out"); + + const textEditor = new TextEditor(); + await textEditor.toggleBreakpoint(breakPoint); } // starts debugging the current file with the provided configuration // debugging starts from the "Run and Debug" button in the activity bar async function startDebugging(configurationFile: string = "Python: Current File") { - const activityBar = await new ActivityBar().getViewControl("Run and Debug"); - expect(activityBar).to.not.be.undefined; - const debugView = await activityBar?.openView() as DebugView; - await debugView.selectLaunchConfiguration(configurationFile); - debugView.start(); -} \ No newline at end of file + const activityBar = await new ActivityBar().getViewControl("Run and Debug"); + expect(activityBar).to.not.be.undefined; + const debugView = await activityBar?.openView() as DebugView; + await debugView.selectLaunchConfiguration(configurationFile); + debugView.start(); +}