From e07e9d2bc011482f67745001b68002c6e6b796b6 Mon Sep 17 00:00:00 2001 From: meowjesty <43983236+meowjesty@users.noreply.github.com> Date: Thu, 19 Oct 2023 10:29:48 -0300 Subject: [PATCH] Pass config env vars to mirrord verify-config. (#74) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Pass config env vars to mirrord verify-config. * changelog * pass config.env to binaryExecute * just pass config.env to binaryExewcute and spawn * missing semicolon * better docs * remove manual resolving config path // stop setting target to null * elseif * mini docs * format * fix order for env vars Co-authored-by: Michał Smolarek <34063647+Razz4780@users.noreply.github.com> --------- Co-authored-by: Michał Smolarek <34063647+Razz4780@users.noreply.github.com> --- changelog.d/70.fixed.md | 1 + src/api.ts | 72 ++++--- src/config.ts | 65 +++---- src/debugger.ts | 402 ++++++++++++++++++++-------------------- src/tests/e2e.ts | 212 ++++++++++----------- 5 files changed, 380 insertions(+), 372 deletions(-) create mode 100644 changelog.d/70.fixed.md diff --git a/changelog.d/70.fixed.md b/changelog.d/70.fixed.md new file mode 100644 index 00000000..f78c67db --- /dev/null +++ b/changelog.d/70.fixed.md @@ -0,0 +1 @@ +Passes the `launch.json` `env` section to `mirrord verify-config`, resolving config options that were set as env vars. diff --git a/src/api.ts b/src/api.ts index d95a8912..da6498e9 100644 --- a/src/api.ts +++ b/src/api.ts @@ -4,7 +4,8 @@ import { globalContext } from './extension'; import { tickWaitlistCounter } from './waitlist'; import { NotificationBuilder } from './notification'; import { MirrordStatus } from './status'; -import { VerifiedConfig } from './config'; +import { EnvVars, VerifiedConfig } from './config'; +import { PathLike } from 'fs'; /** * Key to access the feedback counter (see `tickFeedbackCounter`) from the global user config. @@ -133,6 +134,30 @@ export class MirrordExecution { } +/** +* Sets up the args that are going to be passed to the mirrord cli. +*/ +const makeMirrordArgs = (target: string | null, configFilePath: PathLike | null, userExecutable: PathLike | null): readonly string[] => { + let args = ["ext"]; + + if (target) { + console.log(`target ${target}`); + args.push("-t", target); + } + + if (configFilePath) { + console.log(`configFilePath ${configFilePath.toString()}`); + args.push("-f", configFilePath.toString()); + } + + if (userExecutable) { + console.log(`userExecutable ${userExecutable.toString()}`); + args.push("-e", userExecutable.toString()); + } + + return args; +}; + /** * API to interact with the mirrord CLI, runs in the "ext" mode. */ @@ -144,18 +169,19 @@ export class MirrordAPI { } // Return environment for the spawned mirrord cli processes. - private static getEnv(): NodeJS.ProcessEnv { + private static getEnv(configEnv: EnvVars): NodeJS.ProcessEnv { // clone env vars and add MIRRORD_PROGRESS_MODE return { + ...process.env, + ...configEnv, // eslint-disable-next-line @typescript-eslint/naming-convention "MIRRORD_PROGRESS_MODE": "json", - ...process.env, }; } // Execute the mirrord cli with the given arguments, return stdout. - private async exec(args: string[]): Promise { - const child = this.spawn(args); + private async exec(args: string[], configEnv: EnvVars): Promise { + const child = this.spawnCliWithArgsAndEnv(args, configEnv); return await new Promise((resolve, reject) => { let stdoutData = ""; @@ -204,15 +230,15 @@ export class MirrordAPI { * Spawn the mirrord cli with the given arguments. * Used for reading/interacting while process still runs. */ - private spawn(args: string[]): ChildProcessWithoutNullStreams { - return spawn(this.cliPath, args, { env: MirrordAPI.getEnv() }); + private spawnCliWithArgsAndEnv(args: readonly string[], configEnv: EnvVars): ChildProcessWithoutNullStreams { + return spawn(this.cliPath, args, { env: MirrordAPI.getEnv(configEnv) }); } /** * Runs mirrord --version and returns the version string. */ async getBinaryVersion(): Promise { - const stdout = await this.exec(["--version"]); + const stdout = await this.exec(["--version"], {}); // parse mirrord x.y.z return stdout.split(" ")[1].trim(); } @@ -227,7 +253,7 @@ export class MirrordAPI { args.push('-f', configPath); } - const stdout = await this.exec(args); + const stdout = await this.exec(args, {}); const targets: string[] = JSON.parse(stdout); targets.sort(); @@ -250,10 +276,10 @@ export class MirrordAPI { * Executes the `mirrord verify-config {configPath}` command, parsing its output into a * `VerifiedConfig`. */ - async verifyConfig(configPath: vscode.Uri | null): Promise { + async verifyConfig(configPath: vscode.Uri | null, configEnv: EnvVars): Promise { if (configPath) { const args = ['verify-config', '--ide', `${configPath.path}`]; - const stdout = await this.exec(args); + const stdout = await this.exec(args, configEnv); const verifiedConfig: VerifiedConfig = JSON.parse(stdout); return verifiedConfig; @@ -262,10 +288,13 @@ export class MirrordAPI { } } - // Run the extension execute sequence - // Creating agent and gathering execution runtime (env vars to set) - // Has 60 seconds timeout - async binaryExecute(target: string | null, configFile: string | null, executable: string | null): Promise { + /** + * Runs the extension execute sequence, creating agent and gathering execution runtime while also + * setting env vars, both from system, and from `launch.json` (`configEnv`). + * + * Has 60 seconds timeout + */ + async binaryExecute(target: string | null, configFile: string | null, executable: string | null, configEnv: EnvVars): Promise { tickWaitlistCounter(!!target?.startsWith('deployment/')); tickFeedbackCounter(); @@ -280,18 +309,9 @@ export class MirrordAPI { reject("timeout"); }, 120 * 1000); - const args = ["ext"]; - if (target) { - args.push("-t", target); - } - if (configFile) { - args.push("-f", configFile); - } - if (executable) { - args.push("-e", executable); - } + const args = makeMirrordArgs(target, configFile, executable); - const child = this.spawn(args); + const child = this.spawnCliWithArgsAndEnv(args, configEnv); let stderrData = ""; child.stderr.on("data", (data) => stderrData += data.toString()); diff --git a/src/config.ts b/src/config.ts index f0e91923..b76ae3c2 100644 --- a/src/config.ts +++ b/src/config.ts @@ -16,9 +16,11 @@ const DEFAULT_CONFIG = `{ } `; +export type EnvVars = { [key: string]: string }; + interface LaunchConfig { name: string, - env?: { [key: string]: string }; + env?: EnvVars; } /** @@ -284,20 +286,6 @@ export class MirrordConfigManager { vscode.window.showTextDocument(doc); } - /** - * Resolves config file path specified in the launch config. - * @param folder workspace folder of the launch config - * @param path taken from the `MIRRORD_CONFIG_FILE` environment variable in launch config - * @returns config file Uri - */ - private static processPathFromLaunchConfig(folder: vscode.WorkspaceFolder | undefined, path: string): vscode.Uri { - if (folder) { - path = path.replace(/\$\{workspaceFolder\}/g, folder.uri.fsPath); - } - - return vscode.Uri.file(path); - } - /** * Used when preparing mirrord environment for the process. * @param folder optional origin of the launch config @@ -306,20 +294,17 @@ export class MirrordConfigManager { */ public async resolveMirrordConfig(folder: vscode.WorkspaceFolder | undefined, config: vscode.DebugConfiguration): Promise { if (this.active) { + // User has selected a config (via active config button). new NotificationBuilder() .withMessage("using active mirrord configuration") .withOpenFileAction(this.active) .withDisableAction("promptUsingActiveConfig") .info(); return this.active; - } - - let launchConfig = config.env?.["MIRRORD_CONFIG_FILE"]; - if (typeof launchConfig === "string") { - return MirrordConfigManager.processPathFromLaunchConfig(folder, launchConfig); - } - - if (folder) { + } else if (config.env?.["MIRRORD_CONFIG_FILE"]) { + // Get the config path from the env var. + return vscode.Uri.parse(`file://${config.env?.["MIRRORD_CONFIG_FILE"]}`, true); + } else if (folder) { let predefinedConfig = await MirrordConfigManager.getDefaultConfig(folder); if (predefinedConfig) { new NotificationBuilder() @@ -328,24 +313,26 @@ export class MirrordConfigManager { .withDisableAction("promptUsingDefaultConfig") .warning(); return predefinedConfig; + } else { + return null; + } + } else { + folder = vscode.workspace.workspaceFolders?.[0]; + if (!folder) { + throw new Error("mirrord requires an open folder in the workspace"); } - } - - folder = vscode.workspace.workspaceFolders?.[0]; - if (!folder) { - throw new Error("mirrord requires an open folder in the workspace"); - } - let predefinedConfig = await MirrordConfigManager.getDefaultConfig(folder); - if (predefinedConfig) { - new NotificationBuilder() - .withMessage(`using a default mirrord config from folder ${folder.name}`) - .withOpenFileAction(predefinedConfig) - .withDisableAction("promptUsingDefaultConfig") - .warning(); - return predefinedConfig; + let predefinedConfig = await MirrordConfigManager.getDefaultConfig(folder); + if (predefinedConfig) { + new NotificationBuilder() + .withMessage(`using a default mirrord config from folder ${folder.name} `) + .withOpenFileAction(predefinedConfig) + .withDisableAction("promptUsingDefaultConfig") + .warning(); + return predefinedConfig; + } else { + return null; + } } - - return null; } } diff --git a/src/debugger.ts b/src/debugger.ts index 2d624882..4b8c3ad7 100644 --- a/src/debugger.ts +++ b/src/debugger.ts @@ -14,231 +14,231 @@ const DYLD_ENV_VAR_NAME = "DYLD_INSERT_LIBRARIES"; /// Also returning the executable because in some configuration types there is some extra logic to /// be done for retrieving the executable out of its field (see the `node-terminal` case). function getFieldAndExecutable(config: vscode.DebugConfiguration): [keyof vscode.DebugConfiguration, string | null] { - switch (config.type) { - case "pwa-node": - case "node": { - // https://code.visualstudio.com/docs/nodejs/nodejs-debugging#_launch-configuration-attributes - return ["runtimeExecutable", config["runtimeExecutable"]]; - } - case "node-terminal": { - // Command could contain multiple commands like "command1 arg1; command2 arg2", so we execute that command - // in a shell, to which we inject the layer. In order to inject the layer to the shell, we have to patch it - // for SIP, so we pass the shell to the mirrod CLI. - return ["command", vscode.env.shell]; - } - case "python": { - if ("python" in config) { - return ["python", config["python"]]; - } - // Official documentation states the relevant field name is "python" (https://code.visualstudio.com/docs/python/debugging#_python), - // but when debugging we see the field is called "pythonPath". - return ["pythonPath", config["pythonPath"]]; - } - default: { - return ["program", config["program"]]; - } - } + switch (config.type) { + case "pwa-node": + case "node": { + // https://code.visualstudio.com/docs/nodejs/nodejs-debugging#_launch-configuration-attributes + return ["runtimeExecutable", config["runtimeExecutable"]]; + } + case "node-terminal": { + // Command could contain multiple commands like "command1 arg1; command2 arg2", so we execute that command + // in a shell, to which we inject the layer. In order to inject the layer to the shell, we have to patch it + // for SIP, so we pass the shell to the mirrod CLI. + return ["command", vscode.env.shell]; + } + case "python": { + if ("python" in config) { + return ["python", config["python"]]; + } + // Official documentation states the relevant field name is "python" (https://code.visualstudio.com/docs/python/debugging#_python), + // but when debugging we see the field is called "pythonPath". + return ["pythonPath", config["pythonPath"]]; + } + default: { + return ["program", config["program"]]; + } + } } /// Edit the launch configuration in order to sidestep SIP on macOS, and allow the layer to be /// loaded into the process. This includes replacing the executable with the path to a patched /// executable if the original executable is SIP protected, and some other special workarounds. function changeConfigForSip(config: vscode.DebugConfiguration, executableFieldName: string, executionInfo: MirrordExecution) { - if (config.type === "node-terminal") { - const command = config[executableFieldName]; - if (command === null) { - return; - } - - // The command could have single quotes, and we are putting the whole command in single quotes in the changed command. - // So we replace each `'` with `'\''` (closes the string, concats an escaped single quote, opens the string) - const escapedCommand = command.replaceAll("'", "'\\''"); - const sh = executionInfo.patchedPath ?? vscode.env.shell; - - const libraryPath = executionInfo.env.get(DYLD_ENV_VAR_NAME); - - // Run the command in a SIP-patched shell, that way everything that runs in the original command will be SIP-patched - // on runtime. - config[executableFieldName] = `echo '${escapedCommand}' | ${DYLD_ENV_VAR_NAME}=${libraryPath} ${sh} -is`; - } else if (executionInfo.patchedPath !== null) { - config[executableFieldName] = executionInfo.patchedPath!; - } + if (config.type === "node-terminal") { + const command = config[executableFieldName]; + if (command === null) { + return; + } + + // The command could have single quotes, and we are putting the whole command in single quotes in the changed command. + // So we replace each `'` with `'\''` (closes the string, concats an escaped single quote, opens the string) + const escapedCommand = command.replaceAll("'", "'\\''"); + const sh = executionInfo.patchedPath ?? vscode.env.shell; + + const libraryPath = executionInfo.env.get(DYLD_ENV_VAR_NAME); + + // Run the command in a SIP-patched shell, that way everything that runs in the original command will be SIP-patched + // on runtime. + config[executableFieldName] = `echo '${escapedCommand}' | ${DYLD_ENV_VAR_NAME}=${libraryPath} ${sh} -is`; + } else if (executionInfo.patchedPath !== null) { + config[executableFieldName] = executionInfo.patchedPath!; + } } async function getLastActiveMirrordPath(): Promise { - const binaryPath = globalContext.globalState.get('binaryPath', null); - if (!binaryPath) { - return null; - } - try { - await vscode.workspace.fs.stat(binaryPath); - return binaryPath; - } catch (e) { - return null; - } + const binaryPath = globalContext.globalState.get('binaryPath', null); + if (!binaryPath) { + return null; + } + try { + await vscode.workspace.fs.stat(binaryPath); + return binaryPath; + } catch (e) { + return null; + } } function setLastActiveMirrordPath(path: string) { - globalContext.globalState.update('binaryPath', path); + globalContext.globalState.update('binaryPath', path); } /** * Entrypoint for the vscode extension, called from `resolveDebugConfigurationWithSubstitutedVariables`. */ async function main( - folder: vscode.WorkspaceFolder | undefined, - config: vscode.DebugConfiguration, - _token: vscode.CancellationToken): Promise { - if (!globalContext.workspaceState.get('enabled')) { - return config; - } - - if (config.request === "attach") { - new NotificationBuilder() - .withMessage("mirrord cannot be used with `attach` launch configurations") - .error(); - return null; - } - - // For some reason resolveDebugConfiguration runs twice for Node projects. __parentId is populated. - if (config.__parentId || config.env?.["__MIRRORD_EXT_INJECTED"] === 'true') { - return config; - } - - updateTelemetries(); - - //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 :\ - if (!cliPath) { - cliPath = await getLocalMirrordBinary(); - if (!cliPath) { - mirrordFailure(`Couldn't download mirrord binaries or find local one in path ${err}.`); - return null; - } - } - } - setLastActiveMirrordPath(cliPath); - - let mirrordApi = new MirrordAPI(cliPath); - - config.env ||= {}; - let target = null; - - 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; - - while (!selected) { - let targetPick = await vscode.window.showQuickPick(targets.quickPickItems(), { - placeHolder: 'Select a target path to mirror' - }); - - if (targetPick) { - if (targetPick.type === 'page') { - targets.switchPage(targetPick); - - continue; - } - - if (targetPick.type !== 'targetless') { - target = targetPick.value; - } - - globalContext.globalState.update(LAST_TARGET_KEY, target); - globalContext.workspaceState.update(LAST_TARGET_KEY, target); - } - - selected = true; - } - - if (!target) { - new NotificationBuilder() - .withMessage("mirrord running targetless") - .withDisableAction("promptTargetless") - .info(); - } - } - - 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"; - } - - // 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 isMac = platform() === "darwin"; - - let [executableFieldName, executable] = isMac ? getFieldAndExecutable(config) : [null, null]; - - let executionInfo; - try { - executionInfo = await mirrordApi.binaryExecute(target, configPath?.path || null, executable); - } catch (err) { - mirrordFailure(`mirrord preparation failed: ${err}`); - return null; - } - - if (isMac) { - changeConfigForSip(config, executableFieldName as string, executionInfo); - } - - let env = executionInfo?.env; - config.env = Object.assign({}, config.env, Object.fromEntries(env)); - - config.env["__MIRRORD_EXT_INJECTED"] = 'true'; - - return config; + folder: vscode.WorkspaceFolder | undefined, + config: vscode.DebugConfiguration, + _token: vscode.CancellationToken): Promise { + if (!globalContext.workspaceState.get('enabled')) { + return config; + } + + if (config.request === "attach") { + new NotificationBuilder() + .withMessage("mirrord cannot be used with `attach` launch configurations") + .error(); + return null; + } + + // For some reason resolveDebugConfiguration runs twice for Node projects. __parentId is populated. + if (config.__parentId || config.env?.["__MIRRORD_EXT_INJECTED"] === 'true') { + return config; + } + + updateTelemetries(); + + //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 :\ + if (!cliPath) { + cliPath = await getLocalMirrordBinary(); + if (!cliPath) { + mirrordFailure(`Couldn't download mirrord binaries or find local one in path ${err}.`); + return null; + } + } + } + setLastActiveMirrordPath(cliPath); + + let mirrordApi = new MirrordAPI(cliPath); + + config.env ||= {}; + let target = null; + + let configPath = await MirrordConfigManager.getInstance().resolveMirrordConfig(folder, config); + const verifiedConfig = await mirrordApi.verifyConfig(configPath, config.env); + + // 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; + + while (!selected) { + let targetPick = await vscode.window.showQuickPick(targets.quickPickItems(), { + placeHolder: 'Select a target path to mirror' + }); + + if (targetPick) { + if (targetPick.type === 'page') { + targets.switchPage(targetPick); + + continue; + } + + if (targetPick.type !== 'targetless') { + target = targetPick.value; + } + + globalContext.globalState.update(LAST_TARGET_KEY, target); + globalContext.workspaceState.update(LAST_TARGET_KEY, target); + } + + selected = true; + } + + if (!target) { + new NotificationBuilder() + .withMessage("mirrord running targetless") + .withDisableAction("promptTargetless") + .info(); + } + } + + 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"; + } + + // 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 isMac = platform() === "darwin"; + + let [executableFieldName, executable] = isMac ? getFieldAndExecutable(config) : [null, null]; + + let executionInfo; + try { + executionInfo = await mirrordApi.binaryExecute(target, configPath?.path || null, executable, config.env); + } catch (err) { + mirrordFailure(`mirrord preparation failed: ${err}`); + return null; + } + + if (isMac) { + changeConfigForSip(config, executableFieldName as string, executionInfo); + } + + let env = executionInfo?.env; + config.env = Object.assign({}, config.env, Object.fromEntries(env)); + + 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(); - } - } + 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 f11e879a..ffd7d5a2 100644 --- a/src/tests/e2e.ts +++ b/src/tests/e2e.ts @@ -15,122 +15,122 @@ const podToSelect = process.env.POD_TO_SELECT; * - Send traffic to the pod * - Tests successfully exit if "GET: Request completed" is found in the terminal */ -describe("mirrord sample flow test", function () { +describe("mirrord sample flow test", function() { - this.timeout("6 minutes"); // --> mocha tests timeout - this.bail(true); // --> stop tests on first failure + this.timeout("6 minutes"); // --> mocha tests timeout + this.bail(true); // --> stop tests on first failure - let browser: VSBrowser; + let browser: VSBrowser; - const testWorkspace = join(__dirname, '../../test-workspace'); - const fileName = "app_flask.py"; - const defaultTimeout = 10000; // = 10 seconds + const testWorkspace = join(__dirname, '../../test-workspace'); + const fileName = "app_flask.py"; + const defaultTimeout = 10000; // = 10 seconds - before(async function () { - console.log("podToSelect: " + podToSelect); - console.log("kubeService: " + kubeService); + before(async function() { + console.log("podToSelect: " + podToSelect); + console.log("kubeService: " + kubeService); - expect(podToSelect).to.not.be.undefined; - expect(kubeService).to.not.be.undefined; + expect(podToSelect).to.not.be.undefined; + expect(kubeService).to.not.be.undefined; - browser = VSBrowser.instance; - - await browser.openResources(testWorkspace, join(testWorkspace, fileName)); - await browser.waitForWorkbench(); + browser = VSBrowser.instance; - const ew = new EditorView(); - try { - await ew.closeEditor('Welcome'); - } catch (error) { - console.log("Welcome page is not displayed" + error); - // continue - Welcome page is not displayed - } - await ew.openEditor('app_flask.py'); - }); + await browser.openResources(testWorkspace, join(testWorkspace, fileName)); + await browser.waitForWorkbench(); - it("enable mirrord button", async function () { - const statusBar = new StatusBar(); - - await browser.driver.wait(async () => { - return await statusBar.isDisplayed(); - }); - - // vscode refreshes the status bar on load and there is no deterministic way but to retry to click on - // the mirrord button after an interval - await browser.driver.wait(async () => { - let retries = 0; - while (retries < 3) { - try { - for (let button of await statusBar.getItems()) { - if ((await button.getText()).startsWith('mirrord')) { - await button.click(); - return true; - } - } - } catch (e) { - if (e instanceof Error && e.name === 'StaleElementReferenceError') { - await new Promise(resolve => setTimeout(resolve, 1000)); - retries++; - } else { - throw e; - } - } - } - throw new Error('Failed to click the button after multiple attempts'); - }, defaultTimeout, "mirrord `enable` button not found -- timed out"); - }); + const ew = new EditorView(); + try { + await ew.closeEditor('Welcome'); + } catch (error) { + console.log("Welcome page is not displayed" + error); + // continue - Welcome page is not displayed + } + await ew.openEditor('app_flask.py'); + }); - it("select pod from quickpick", async function () { - 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; - } + it("enable mirrord button", async function() { + const statusBar = new StatusBar(); + + await browser.driver.wait(async () => { + return await statusBar.isDisplayed(); + }); - for (const pick of await inputBox.getQuickPicks()) { - let label = await pick.getLabel(); - - if (label === podToSelect) { - return true; - } - // to pick up the podToSelect, we need to select the "Show Pods" - // from quickpick as pods are not displayed first - if (label === "Show Pods") { - await pick.select(); - } + // vscode refreshes the status bar on load and there is no deterministic way but to retry to click on + // the mirrord button after an interval + await browser.driver.wait(async () => { + let retries = 0; + while (retries < 3) { + try { + for (let button of await statusBar.getItems()) { + if ((await button.getText()).startsWith('mirrord')) { + await button.click(); + return true; } + } + } catch (e) { + if (e instanceof Error && e.name === 'StaleElementReferenceError') { + await new Promise(resolve => setTimeout(resolve, 1000)); + retries++; + } else { + throw e; + } + } + } + throw new Error('Failed to click the button after multiple attempts'); + }, defaultTimeout, "mirrord `enable` button not found -- timed out"); + }); + + it("select pod from quickpick", async function() { + 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; + } + // to pick up the podToSelect, we need to select the "Show Pods" + // from quickpick as pods are not displayed first + if (label === "Show Pods") { + await pick.select(); + } + } - return false; - }, defaultTimeout * 2, "quickPick not found -- timed out"); + return false; + }, defaultTimeout * 2, "quickPick not found -- timed out"); - await inputBox.selectQuickPick(podToSelect!); - }); + await inputBox.selectQuickPick(podToSelect!); + }); - it("wait for process to write to terminal", async function () { - const debugToolbar = await DebugToolbar.create(2 * defaultTimeout); - const panel = new BottomBarPanel(); - await browser.driver.wait(async () => { - return await debugToolbar.isDisplayed(); - }, 2 * defaultTimeout, "debug toolbar not found -- timed out"); + it("wait for process to write to terminal", async function() { + const debugToolbar = await DebugToolbar.create(2 * defaultTimeout); + const panel = new BottomBarPanel(); + await browser.driver.wait(async () => { + return await debugToolbar.isDisplayed(); + }, 2 * defaultTimeout, "debug toolbar not found -- timed out"); - let terminal = await panel.openTerminalView(); + let terminal = await panel.openTerminalView(); - await browser.driver.wait(async () => { - const text = await terminal.getText(); - return await terminal.isDisplayed() && text.includes("Press CTRL+C to quit"); - }, 2 * defaultTimeout, "terminal text not found -- timed out"); + await browser.driver.wait(async () => { + const text = await terminal.getText(); + return await terminal.isDisplayed() && text.includes("Press CTRL+C to quit"); + }, 2 * defaultTimeout, "terminal text not found -- timed out"); - await sendTrafficToPod(); + await sendTrafficToPod(); - await browser.driver.wait(async () => { - const text = await terminal.getText(); - return text.includes("GET: Request completed"); - }, defaultTimeout, "terminal text not found -- timed out"); + await browser.driver.wait(async () => { + const text = await terminal.getText(); + return text.includes("GET: Request completed"); + }, defaultTimeout, "terminal text not found -- timed out"); - }); + }); }); @@ -138,19 +138,19 @@ describe("mirrord sample flow test", function () { * sends a GET request to the pod's nodePort */ async function sendTrafficToPod() { - 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"); } /** * starts debugging the current file with the provided configuration - * debugging starts from the "Run and Debug" button in the activity bar + * 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); - await 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); + await debugView.start(); +}