diff --git a/package.json b/package.json index 8ac5083221..d42fd0c301 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ ], "scripts": { "build": "gulp updateLicense && lerna run build && npm run lint && npm run checkTestsCompile && npm run circularDependencyCheck", - "build:exe": "cd zowex && cargo build && cargo test", + "build:exe": "cd zowex && cargo build && cargo clippy && cargo test", "clean": "lerna run --parallel clean", "clean:exe": "cd zowex && cargo clean", "installWithBuild": "npm install && npm run build", @@ -28,6 +28,7 @@ "test:system": "cross-env FORCE_COLOR=1 jest \".*__tests__.*\\**\\.system\\.(spec|test)\\.ts\\$\" --coverage false", "test:unit": "cross-env FORCE_COLOR=1 jest \".*__tests__.*\\**\\.unit\\.(spec|test)\\.ts\\$\" --coverage", "watch": "lerna run --parallel watch", + "watch:exe": "cd zowex && cargo install cargo-watch && cargo watch -x build -x clippy -x test", "watch:test": "jest --watch", "doc:clean": "rimraf docs/CLIReadme.md", "doc:generate": "npm run doc:clean && gulp doc", diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 626594343a..c8db75b005 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -4,6 +4,9 @@ All notable changes to the Zowe CLI package will be documented in this file. ## Recent Changes +- BugFix: Fixed 'daemon disable' command to kill any running zowe daemon on Linux and Mac. [#1270](https://github.com/zowe/zowe-cli/issues/1270) +- BugFix: Fixed stdin data being corrupted when daemon server processes CLI command containing double-byte characters. +- Enhancement: Added a user message within 'daemon enable' and disable to open a new terminal when needed. - **BREAKING** Enhancement: Make the `user` field on SSH profiles secure. [#682](https://github.com/zowe/zowe-cli/issues/682) ## `7.0.0-next.202201121428` diff --git a/packages/cli/__tests__/daemon/__integration__/disable/cli.daemon.disable.integration.test.ts b/packages/cli/__tests__/daemon/__integration__/disable/cli.daemon.disable.integration.test.ts index 608e530f9b..bf8ab3a307 100644 --- a/packages/cli/__tests__/daemon/__integration__/disable/cli.daemon.disable.integration.test.ts +++ b/packages/cli/__tests__/daemon/__integration__/disable/cli.daemon.disable.integration.test.ts @@ -26,6 +26,8 @@ let testEnvironment: ITestEnvironment; describe("daemon disable", () => { const rimrafSync = require("rimraf").sync; const fakeExeContent = "This is not a real executable"; + const zoweCmdRegEx = "zowe.*[/|\\\\]cli[/|\\\\]lib[/|\\\\]main.js.* --daemon" + "|" + + "[/|\\\\]bin[/|\\\\]zowe.* --daemon"; let exePath: string; let pathToBin: string; @@ -116,6 +118,11 @@ describe("daemon disable", () => { const response = runCliScript(__dirname + "/__scripts__/daemon_disable.sh", testEnvironment); const stdoutStr = response.stdout.toString(); expect(stdoutStr).toContain("Zowe CLI daemon mode is disabled."); + if (ProcessUtils.getBasicSystemInfo().platform === "win32") { + expect(stdoutStr).not.toContain("close this terminal and open a new terminal"); + } else { + expect(stdoutStr).toContain("close this terminal and open a new terminal"); + } expect(response.status).toBe(0); } }); @@ -143,9 +150,8 @@ describe("daemon disable", () => { const procArray = await findProc('name', 'node', true); // match and kill any running Zowe daemon - const zoweCmdRegEx = "@zowe[/|\\\\]cli[/|\\\\]lib[/|\\\\]main.js"; for (const nextProc of procArray) { - if (nextProc.cmd.match(zoweCmdRegEx) && nextProc.cmd.includes("--daemon")) { + if (nextProc.cmd.match(zoweCmdRegEx)) { process.kill(nextProc.pid, "SIGINT"); } } @@ -173,12 +179,14 @@ describe("daemon disable", () => { // match any running Zowe daemon const findProc = require('find-process'); const procArray = await findProc('name', 'node', true); - const zoweCmdRegEx = "@zowe[/|\\\\]cli[/|\\\\]lib[/|\\\\]main.js"; + let weFoundDaemon = false; for (const nextProc of procArray) { - if (nextProc.cmd.match(zoweCmdRegEx) && nextProc.cmd.includes("--daemon")) { - expect("we should not find a daemon").toContain("so the disable command failed."); + if (nextProc.cmd.match(zoweCmdRegEx)) { + weFoundDaemon = true; + break; } } + expect(weFoundDaemon).toBe(false); } }); }); diff --git a/packages/cli/__tests__/daemon/__integration__/enable/cli.daemon.enable.integration.test.ts b/packages/cli/__tests__/daemon/__integration__/enable/cli.daemon.enable.integration.test.ts index 23e634b438..75bdceaf72 100644 --- a/packages/cli/__tests__/daemon/__integration__/enable/cli.daemon.enable.integration.test.ts +++ b/packages/cli/__tests__/daemon/__integration__/enable/cli.daemon.enable.integration.test.ts @@ -164,7 +164,7 @@ describe("daemon enable", () => { if (willRunNodeJsZowe()) { const response = runCliScript(__dirname + "/__scripts__/daemon_enable.sh", testEnvironment); const stdoutStr = response.stdout.toString(); - expect(stdoutStr).toContain("Zowe CLI daemon mode enabled"); + expect(stdoutStr).toContain("Zowe CLI daemon mode is enabled"); expect(stdoutStr).toContain("Zowe CLI native executable version ="); expect(IO.existsSync(exePath)).toBe(true); expect(response.status).toBe(0); @@ -176,7 +176,7 @@ describe("daemon enable", () => { fs.mkdirSync(pathToBin, 0o755); const response = runCliScript(__dirname + "/__scripts__/daemon_enable.sh", testEnvironment); const stdoutStr = response.stdout.toString(); - expect(stdoutStr).toContain("Zowe CLI daemon mode enabled"); + expect(stdoutStr).toContain("Zowe CLI daemon mode is enabled"); expect(stdoutStr).toContain("Zowe CLI native executable version ="); expect(IO.existsSync(exePath)).toBe(true); expect(response.status).toBe(0); @@ -191,7 +191,7 @@ describe("daemon enable", () => { const response = runCliScript(__dirname + "/__scripts__/daemon_enable.sh", testEnvironment); const stdoutStr = response.stdout.toString(); - expect(stdoutStr).toContain("Zowe CLI daemon mode enabled"); + expect(stdoutStr).toContain("Zowe CLI daemon mode is enabled"); expect(IO.existsSync(exePath)).toBe(true); // our test tgz file is more than 10 bytes larger than this fake tgz @@ -205,10 +205,11 @@ describe("daemon enable", () => { if (willRunNodeJsZowe()) { const response = runCliScript(__dirname + "/__scripts__/daemon_enable.sh", testEnvironment); const stdoutStr = response.stdout.toString(); - expect(stdoutStr).toContain("Zowe CLI daemon mode enabled"); + expect(stdoutStr).toContain("Zowe CLI daemon mode is enabled"); expect(stdoutStr).toContain("Zowe CLI native executable version ="); - expect(stdoutStr).toContain(`Add '${pathToBin}' to your PATH`); + expect(stdoutStr).toContain(`Manually add '${pathToBin}' to your PATH`); expect(stdoutStr).toContain("Otherwise, you will continue to run the classic Zowe CLI interpreter"); + expect(stdoutStr).toContain("close this terminal and open a new terminal"); expect(IO.existsSync(exePath)).toBe(true); expect(response.status).toBe(0); } @@ -222,9 +223,14 @@ describe("daemon enable", () => { testEnvironment.env["PATH"] = pathOrig; const stdoutStr = response.stdout.toString(); - expect(stdoutStr).toContain("Zowe CLI daemon mode enabled"); + expect(stdoutStr).toContain("Zowe CLI daemon mode is enabled"); expect(stdoutStr).toContain("Zowe CLI native executable version ="); expect(stdoutStr).not.toContain(`Add '${pathToBin}' to your PATH`); + if (ProcessUtils.getBasicSystemInfo().platform === "win32") { + expect(stdoutStr).not.toContain("close this terminal and open a new terminal"); + } else { + expect(stdoutStr).toContain("close this terminal and open a new terminal"); + } expect(IO.existsSync(exePath)).toBe(true); expect(response.status).toBe(0); } @@ -237,7 +243,7 @@ describe("daemon enable", () => { delete testEnvironment.env.ZOWE_USE_DAEMON; const stdoutStr = response.stdout.toString(); - expect(stdoutStr).toContain("Zowe CLI daemon mode enabled"); + expect(stdoutStr).toContain("Zowe CLI daemon mode is enabled"); expect(stdoutStr).toContain("Zowe CLI native executable version ="); expect(stdoutStr).toContain("Your ZOWE_USE_DAEMON environment variable is set to 'no'"); expect(stdoutStr).toContain("You must remove it, or set it to 'yes' to use daemon mode"); @@ -251,7 +257,7 @@ describe("daemon enable", () => { delete testEnvironment.env.ZOWE_USE_DAEMON; const response = runCliScript(__dirname + "/__scripts__/daemon_enable.sh", testEnvironment); const stdoutStr = response.stdout.toString(); - expect(stdoutStr).toContain("Zowe CLI daemon mode enabled"); + expect(stdoutStr).toContain("Zowe CLI daemon mode is enabled"); expect(testEnvironment.env["ZOWE_USE_DAEMON"]).toBeFalsy(); expect(stdoutStr).not.toContain("Your ZOWE_USE_DAEMON environment variable is set to"); expect(IO.existsSync(exePath)).toBe(true); @@ -266,7 +272,7 @@ describe("daemon enable", () => { delete testEnvironment.env.ZOWE_USE_DAEMON; const stdoutStr = response.stdout.toString(); - expect(stdoutStr).toContain("Zowe CLI daemon mode enabled"); + expect(stdoutStr).toContain("Zowe CLI daemon mode is enabled"); expect(stdoutStr).not.toContain("Your ZOWE_USE_DAEMON environment variable is set to"); expect(IO.existsSync(exePath)).toBe(true); expect(response.status).toBe(0); diff --git a/packages/cli/__tests__/daemon/__unit__/DaemonClient.unit.test.ts b/packages/cli/__tests__/daemon/__unit__/DaemonClient.unit.test.ts index 339a5012ed..c15ffa90da 100644 --- a/packages/cli/__tests__/daemon/__unit__/DaemonClient.unit.test.ts +++ b/packages/cli/__tests__/daemon/__unit__/DaemonClient.unit.test.ts @@ -50,9 +50,9 @@ describe("DaemonClient tests", () => { const daemonClient = new DaemonClient(client as any, server, "fake"); const daemonResponse: IDaemonResponse = { - argv: ["feed", "dog"], + argv: ["feed", "🐶"], cwd: "fake", - env: {}, + env: { FORCE_COLOR: "0" }, stdinLength: 0, stdin: null, user: Buffer.from("fake").toString('base64') @@ -61,9 +61,10 @@ describe("DaemonClient tests", () => { daemonClient.run(); // force `data` call and verify input is from instantiation of DaemonClient // and is what is passed to mocked Imperative.parse via snapshot - (daemonClient as any).data(JSON.stringify(daemonResponse)); + const stringData = JSON.stringify(daemonResponse); + (daemonClient as any).data(Buffer.from(stringData)); - expect(log).toHaveBeenLastCalledWith('daemon input command: feed dog'); + expect(log).toHaveBeenLastCalledWith('daemon input command: feed 🐶'); expect(log).toHaveBeenCalledTimes(2); expect(parse).toHaveBeenCalled(); }); @@ -99,9 +100,9 @@ describe("DaemonClient tests", () => { }; const daemonClient = new DaemonClient(client as any, server, "fake"); - const stdinData = String.fromCharCode(...Array(256).keys()); + const stdinData = String.fromCharCode(...Array(1024).keys()); const daemonResponse: IDaemonResponse = { - argv: ["feed", "cat"], + argv: ["feed", "🐱"], cwd: "fake", env: {}, stdinLength: stdinData.length, @@ -114,11 +115,11 @@ describe("DaemonClient tests", () => { // force `data` call and verify input is from instantiation of DaemonClient // and is what is passed to mocked Imperative.parse via snapshot const stringData = JSON.stringify(daemonResponse) + "\f" + stdinData; - await (daemonClient as any).data(stringData); + await (daemonClient as any).data(Buffer.from(stringData)); - expect(log).toHaveBeenLastCalledWith('daemon input command: feed cat'); + expect(log).toHaveBeenLastCalledWith('daemon input command: feed 🐱'); expect(log).toHaveBeenCalledTimes(2); - expect(writeToStdinSpy).toHaveBeenCalledWith(stdinData, stdinData.length); + expect(writeToStdinSpy).toHaveBeenCalledWith(Buffer.from(stdinData), stdinData.length); expect(parse).toHaveBeenCalled(); }); @@ -157,7 +158,7 @@ describe("DaemonClient tests", () => { daemonClient.run(); // force `data` call and verify input is from instantiation of DaemonClient // and is what is passed to mocked Imperative.parse via snapshot - (daemonClient as any).data("not json"); + (daemonClient as any).data(Buffer.from("not json")); expect(log).toHaveBeenCalledTimes(3); expect(log).toHaveBeenLastCalledWith("First 1024 bytes of daemon request:\n", "not json"); @@ -200,7 +201,8 @@ describe("DaemonClient tests", () => { // force `data` call and verify input is from instantiation of DaemonClient // and is what is passed to mocked Imperative.parse via snapshot const promptResponse = { stdin: "some answer", user: Buffer.from("fake").toString('base64') }; - (daemonClient as any).data(JSON.stringify(promptResponse)); + const stringData = JSON.stringify(promptResponse); + (daemonClient as any).data(Buffer.from(stringData)); expect(log).toHaveBeenCalledTimes(1); expect(parse).not.toHaveBeenCalled(); @@ -246,7 +248,8 @@ describe("DaemonClient tests", () => { daemonClient.run(); // force `data` call and verify write method is called with termination message const shutdownResponse = { stdin: DaemonClient.CTRL_C_CHAR, user: Buffer.from("fake").toString('base64') }; - (daemonClient as any).data(JSON.stringify(shutdownResponse)); + const stringData = JSON.stringify(shutdownResponse); + (daemonClient as any).data(Buffer.from(stringData)); expect(log).toHaveBeenCalledTimes(2); expect(shutdownSpy).toHaveBeenCalledTimes(1); @@ -339,7 +342,7 @@ describe("DaemonClient tests", () => { const daemonClient = new DaemonClient(client as any, server, "fake"); const daemonResponse: IDaemonResponse = { - argv: ["feed", "dog"], + argv: ["feed", "🐶"], cwd: "fake", env: {}, stdinLength: 0, @@ -350,7 +353,8 @@ describe("DaemonClient tests", () => { daemonClient.run(); // force `data` call and verify input is from instantiation of DaemonClient // and is what is passed to mocked Imperative.parse via snapshot - (daemonClient as any).data(JSON.stringify(daemonResponse)); + const stringData = JSON.stringify(daemonResponse); + (daemonClient as any).data(Buffer.from(stringData)); expect(log).toHaveBeenCalledTimes(2); expect(log).toHaveBeenLastCalledWith("The user 'ekaf' attempted to connect."); @@ -390,7 +394,7 @@ describe("DaemonClient tests", () => { const daemonClient = new DaemonClient(client as any, server, "fake"); const daemonResponse: IDaemonResponse = { - argv: ["feed", "dog"], + argv: ["feed", "🐶"], cwd: "fake", env: {}, stdinLength: 0, @@ -400,7 +404,8 @@ describe("DaemonClient tests", () => { daemonClient.run(); // force `data` call and verify input is from instantiation of DaemonClient // and is what is passed to mocked Imperative.parse via snapshot - (daemonClient as any).data(JSON.stringify(daemonResponse)); + const stringData = JSON.stringify(daemonResponse); + (daemonClient as any).data(Buffer.from(stringData)); expect(log).toHaveBeenCalledTimes(2); expect(log).toHaveBeenLastCalledWith("A connection was attempted without a valid user."); @@ -440,7 +445,7 @@ describe("DaemonClient tests", () => { const daemonClient = new DaemonClient(client as any, server, "fake"); const daemonResponse: IDaemonResponse = { - argv: ["feed", "dog"], + argv: ["feed", "🐶"], cwd: "fake", env: {}, stdinLength: 0, @@ -451,7 +456,8 @@ describe("DaemonClient tests", () => { daemonClient.run(); // force `data` call and verify input is from instantiation of DaemonClient // and is what is passed to mocked Imperative.parse via snapshot - (daemonClient as any).data(JSON.stringify(daemonResponse)); + const stringData = JSON.stringify(daemonResponse); + (daemonClient as any).data(Buffer.from(stringData)); expect(log).toHaveBeenCalledTimes(2); expect(log).toHaveBeenLastCalledWith("The user 'zF�' attempted to connect."); diff --git a/packages/cli/__tests__/daemon/__unit__/__snapshots__/DaemonClient.unit.test.ts.snap b/packages/cli/__tests__/daemon/__unit__/__snapshots__/DaemonClient.unit.test.ts.snap index c21c47ad1e..138d50a6d2 100644 --- a/packages/cli/__tests__/daemon/__unit__/__snapshots__/DaemonClient.unit.test.ts.snap +++ b/packages/cli/__tests__/daemon/__unit__/__snapshots__/DaemonClient.unit.test.ts.snap @@ -3,7 +3,7 @@ exports[`DaemonClient tests should process data when received 1`] = ` Array [ "feed", - "dog", + "🐶", ] `; @@ -12,10 +12,12 @@ Object { "daemonResponse": Object { "argv": Array [ "feed", - "dog", + "🐶", ], "cwd": "fake", - "env": Object {}, + "env": Object { + "FORCE_COLOR": "0", + }, "stdin": null, "stdinLength": 0, "user": "ZmFrZQ==", @@ -60,7 +62,7 @@ Object { exports[`DaemonClient tests should process response with binary stdin data when received 1`] = ` Array [ "feed", - "cat", + "🐱", ] `; @@ -69,12 +71,12 @@ Object { "daemonResponse": Object { "argv": Array [ "feed", - "cat", + "🐱", ], "cwd": "fake", "env": Object {}, "stdin": null, - "stdinLength": 256, + "stdinLength": 1024, "user": "ZmFrZQ==", }, "stream": Object { diff --git a/packages/cli/__tests__/daemon/__unit__/disable/Disable.handler.unit.test.ts b/packages/cli/__tests__/daemon/__unit__/disable/Disable.handler.unit.test.ts index 2ea26bf97d..0e82996c2c 100644 --- a/packages/cli/__tests__/daemon/__unit__/disable/Disable.handler.unit.test.ts +++ b/packages/cli/__tests__/daemon/__unit__/disable/Disable.handler.unit.test.ts @@ -84,6 +84,60 @@ describe("Disable daemon handler", () => { expect(logMessage).toContain("Zowe CLI daemon mode is disabled."); }); + it("should tell you to open new terminal on Linux", async () => { + const getBasicSystemInfoOrig = ProcessUtils.getBasicSystemInfo; + ProcessUtils.getBasicSystemInfo = jest.fn(() => { + return { + "arch": "ArchNotNeeded", + "platform": "linux" + }; + }); + + // spy on our handler's private disableDaemon() function + disableDaemonSpy = jest.spyOn(DisableDaemonHandler.prototype as any, "disableDaemon"); + disableDaemonSpy.mockImplementation(() => {return;}); + + let error; + try { + // Invoke the handler with a full set of mocked arguments and response functions + await disableHandler.process(cmdParms); + } catch (e) { + error = e; + } + + expect(error).toBeUndefined(); + expect(disableDaemonSpy).toHaveBeenCalledTimes(1); + expect(logMessage).toContain("Zowe CLI daemon mode is disabled."); + expect(logMessage).toContain("close this terminal and open a new terminal"); + }); + + it("should NOT tell you to open new terminal on Windows", async () => { + const getBasicSystemInfoOrig = ProcessUtils.getBasicSystemInfo; + ProcessUtils.getBasicSystemInfo = jest.fn(() => { + return { + "arch": "ArchNotNeeded", + "platform": "win32" + }; + }); + + // spy on our handler's private disableDaemon() function + disableDaemonSpy = jest.spyOn(DisableDaemonHandler.prototype as any, "disableDaemon"); + disableDaemonSpy.mockImplementation(() => {return;}); + + let error; + try { + // Invoke the handler with a full set of mocked arguments and response functions + await disableHandler.process(cmdParms); + } catch (e) { + error = e; + } + + expect(error).toBeUndefined(); + expect(disableDaemonSpy).toHaveBeenCalledTimes(1); + expect(logMessage).toContain("Zowe CLI daemon mode is disabled."); + expect(logMessage).not.toContain("close this terminal and open a new terminal"); + }); + it("should fail when the disableDaemon function fails", async () => { const badStuffMsg = "Some bad exception happened"; diff --git a/packages/cli/__tests__/daemon/__unit__/enable/Enable.handler.unit.test.ts b/packages/cli/__tests__/daemon/__unit__/enable/Enable.handler.unit.test.ts index f28f29807a..eba2dccd75 100644 --- a/packages/cli/__tests__/daemon/__unit__/enable/Enable.handler.unit.test.ts +++ b/packages/cli/__tests__/daemon/__unit__/enable/Enable.handler.unit.test.ts @@ -13,6 +13,7 @@ jest.mock("child_process"); // using child_process from the __mocks__ directory import { ImperativeConfig, ImperativeError, IO, ProcessUtils, ISystemInfo } from "@zowe/imperative"; +import { IDaemonEnableQuestions } from "../../../../src/daemon/doc/IDaemonEnableQuestions"; import EnableDaemonHandler from "../../../../src/daemon/enable/Enable.handler"; import * as fs from "fs"; @@ -129,7 +130,7 @@ describe("Handler for daemon enable", () => { expect(error).toBeUndefined(); expect(enableDaemonSpy).toHaveBeenCalledTimes(1); - expect(logMessage).toContain("Zowe CLI daemon mode enabled."); + expect(logMessage).toContain("Zowe CLI daemon mode is enabled."); expect(logMessage).toContain(allOkMsg); }); @@ -172,6 +173,11 @@ describe("Handler for daemon enable", () => { }); const zoweBinDirMock = cliHomeDirMock + nodeJsPath.sep + "bin"; + const noAskNoAddPath: IDaemonEnableQuestions = { + canAskUser: false, + addBinToPathVal: "n" + }; + it("should fail on an unsupported platform", async () => { const getBasicSystemInfoOrig = ProcessUtils.getBasicSystemInfo; ProcessUtils.getBasicSystemInfo = jest.fn(() => { @@ -183,7 +189,7 @@ describe("Handler for daemon enable", () => { let error; try { - await enableHandler.enableDaemon(); + await enableHandler.enableDaemon(noAskNoAddPath); } catch (e) { error = e; } @@ -200,7 +206,7 @@ describe("Handler for daemon enable", () => { let error; try { - await enableHandler.enableDaemon(); + await enableHandler.enableDaemon(noAskNoAddPath); } catch (e) { error = e; } @@ -222,7 +228,7 @@ describe("Handler for daemon enable", () => { let error; try { - await enableHandler.enableDaemon(); + await enableHandler.enableDaemon(noAskNoAddPath); } catch (e) { error = e; } @@ -247,7 +253,7 @@ describe("Handler for daemon enable", () => { let error; try { - await enableHandler.enableDaemon(); + await enableHandler.enableDaemon(noAskNoAddPath); } catch (e) { error = e; } @@ -280,7 +286,7 @@ describe("Handler for daemon enable", () => { let error; let userInfoMsg: string; try { - userInfoMsg = await enableHandler.enableDaemon(); + userInfoMsg = await enableHandler.enableDaemon(noAskNoAddPath); } catch (e) { error = e; } @@ -322,7 +328,7 @@ describe("Handler for daemon enable", () => { let error; let userInfoMsg: string; try { - userInfoMsg = await enableHandler.enableDaemon(); + userInfoMsg = await enableHandler.enableDaemon(noAskNoAddPath); } catch (e) { error = e; } @@ -361,18 +367,121 @@ describe("Handler for daemon enable", () => { let error; let userInfoMsg: string; try { - userInfoMsg = await enableHandler.enableDaemon(); + userInfoMsg = await enableHandler.enableDaemon(noAskNoAddPath); + } catch (e) { + error = e; + } + + expect(error).toBeUndefined(); + expect(unzipTgzSpy).toHaveBeenCalledTimes(1); + expect(userInfoMsg).toContain(`Manually add '${zoweBinDirMock}' to your PATH.`); + expect(userInfoMsg).toContain("close this terminal and open a new terminal"); + + IO.existsSync = existsSyncOrig; + IO.isDir = isDirOrig; + IO.createDirSync = createDirSyncOrig; + process.env.PATH = pathOrig; + }); + + it("should tell you to open new terminal on Linux even when PATH is set", async () => { + // Mock the IO functions to simulate stuff is working + const existsSyncOrig = IO.existsSync; + IO.existsSync = jest.fn(() => { + return true; + }); + + const isDirOrig = IO.isDir; + IO.isDir = jest.fn(() => { + return true; + }); + + const createDirSyncOrig = IO.createDirSync; + IO.createDirSync = jest.fn(); + + const getBasicSystemInfoOrig = ProcessUtils.getBasicSystemInfo; + ProcessUtils.getBasicSystemInfo = jest.fn(() => { + return { + "arch": "ArchNotNeeded", + "platform": "linux" + }; + }); + + const pathOrig = process.env.PATH; + process.env.PATH = "stuff/in/path:" + + nodeJsPath.normalize(ImperativeConfig.instance.cliHome + "/bin") + + ":more/stuff/in/path"; + + // spy on our handler's private enableDaemon() function + unzipTgzSpy = jest.spyOn(EnableDaemonHandler.prototype as any, "unzipTgz"); + unzipTgzSpy.mockImplementation((tgzFile: string, toDir: string, fileToExtract: string) => {return;}); + + let error; + let userInfoMsg: string; + try { + userInfoMsg = await enableHandler.enableDaemon(noAskNoAddPath); } catch (e) { error = e; } expect(error).toBeUndefined(); expect(unzipTgzSpy).toHaveBeenCalledTimes(1); - expect(userInfoMsg).toContain(`Add '${zoweBinDirMock}' to your PATH.`); + expect(userInfoMsg).toContain("close this terminal and open a new terminal"); IO.existsSync = existsSyncOrig; IO.isDir = isDirOrig; IO.createDirSync = createDirSyncOrig; + ProcessUtils.getBasicSystemInfo = getBasicSystemInfoOrig; + process.env.PATH = pathOrig; + }); + + it("should NOT tell you to open new terminal on Windows when PATH is set", async () => { + // Mock the IO functions to simulate stuff is working + const existsSyncOrig = IO.existsSync; + IO.existsSync = jest.fn(() => { + return true; + }); + + const isDirOrig = IO.isDir; + IO.isDir = jest.fn(() => { + return true; + }); + + const createDirSyncOrig = IO.createDirSync; + IO.createDirSync = jest.fn(); + + const getBasicSystemInfoOrig = ProcessUtils.getBasicSystemInfo; + ProcessUtils.getBasicSystemInfo = jest.fn(() => { + return { + "arch": "ArchNotNeeded", + "platform": "win32" + }; + }); + + const pathOrig = process.env.PATH; + process.env.PATH = "stuff/in/path:" + + nodeJsPath.normalize(ImperativeConfig.instance.cliHome + "/bin") + + ":more/stuff/in/path"; + + // spy on our handler's private enableDaemon() function + unzipTgzSpy = jest.spyOn(EnableDaemonHandler.prototype as any, "unzipTgz"); + unzipTgzSpy.mockImplementation((tgzFile: string, toDir: string, fileToExtract: string) => {return;}); + + let error; + let userInfoMsg: string; + try { + userInfoMsg = await enableHandler.enableDaemon(noAskNoAddPath); + } catch (e) { + error = e; + } + + expect(error).toBeUndefined(); + expect(unzipTgzSpy).toHaveBeenCalledTimes(1); + expect(userInfoMsg).not.toContain("close this terminal and open a new terminal"); + + IO.existsSync = existsSyncOrig; + IO.isDir = isDirOrig; + IO.createDirSync = createDirSyncOrig; + ProcessUtils.getBasicSystemInfo = getBasicSystemInfoOrig; process.env.PATH = pathOrig; }); @@ -398,7 +507,7 @@ describe("Handler for daemon enable", () => { let error; let userInfoMsg: string; try { - userInfoMsg = await enableHandler.enableDaemon(); + userInfoMsg = await enableHandler.enableDaemon(noAskNoAddPath); } catch (e) { error = e; } @@ -439,7 +548,7 @@ describe("Handler for daemon enable", () => { let error; let userInfoMsg: string; try { - userInfoMsg = await enableHandler.enableDaemon(); + userInfoMsg = await enableHandler.enableDaemon(noAskNoAddPath); } catch (e) { error = e; } diff --git a/packages/cli/src/daemon/DaemonClient.ts b/packages/cli/src/daemon/DaemonClient.ts index c89ae87882..0b450410bb 100644 --- a/packages/cli/src/daemon/DaemonClient.ts +++ b/packages/cli/src/daemon/DaemonClient.ts @@ -143,13 +143,12 @@ export class DaemonClient { if (this.pendingStdinLength > 0) return; // Split JSON body and binary data from multipart response - const stringData = data.toString(); - const jsonEndIdx = stringData.indexOf("}" + DaemonRequest.EOW_DELIMITER); + const jsonEndIdx = data.indexOf("}" + DaemonRequest.EOW_DELIMITER); let jsonData: IDaemonResponse; let stdinData: Buffer; try { - jsonData = JSON.parse(jsonEndIdx !== -1 ? stringData.slice(0, jsonEndIdx + 1) : stringData); + jsonData = JSON.parse((jsonEndIdx !== -1 ? data.slice(0, jsonEndIdx + 1) : data).toString()); stdinData = jsonEndIdx !== -1 ? data.slice(jsonEndIdx + 2) : undefined; } catch (error) { Imperative.api.appLogger.logError(new ImperativeError({ @@ -157,7 +156,7 @@ export class DaemonClient { causeErrors: error })); // eslint-disable-next-line @typescript-eslint/no-magic-numbers - Imperative.api.appLogger.trace("First 1024 bytes of daemon request:\n", stringData.slice(0, 1024)); + Imperative.api.appLogger.trace("First 1024 bytes of daemon request:\n", data.slice(0, 1024).toString()); const responsePayload: string = DaemonRequest.create({ stderr: "Failed to parse data received from daemon client:\n" + error.stack, exitCode: 1 diff --git a/packages/cli/src/daemon/disable/Disable.handler.ts b/packages/cli/src/daemon/disable/Disable.handler.ts index 8d19cdcde7..626e6f0b67 100644 --- a/packages/cli/src/daemon/disable/Disable.handler.ts +++ b/packages/cli/src/daemon/disable/Disable.handler.ts @@ -41,6 +41,10 @@ export default class DisableDaemonHandler implements ICommandHandler { } cmdParams.response.console.log("Zowe CLI daemon mode is disabled."); + if (ProcessUtils.getBasicSystemInfo().platform != "win32") { + cmdParams.response.console.log("To run further Zowe commands, close this terminal and open a new terminal."); + } + cmdParams.response.data.setExitCode(0); } @@ -106,14 +110,20 @@ export default class DisableDaemonHandler implements ICommandHandler { }); } - /* Paths in proc list on Windows sometimes have forward slash - * and sometimes backslash, so allow either. + /* Paths in proc list on Windows sometimes have forward slash and + * sometimes backslash, so allow either. This RegEx is designed to + * match patterns like these: + * + * node /home/someUser/.nvm/versions/node/v17.1.0/bin/zowe --daemon + * node /home/someUser/GitHub/zowe-cli-next/packages/cli/lib/main.js --daemon + * "C:\Program Files\nodejs\node.exe" "C:\Program Files\nodejs\node_modules\@zowe\cli\lib\main.js" --daemon */ - const zoweCmdRegEx = "@zowe[/|\\\\]cli[/|\\\\]lib[/|\\\\]main.js"; + const zoweCmdRegEx = "zowe.*[/|\\\\]cli[/|\\\\]lib[/|\\\\]main.js.* --daemon" + "|" + + "[/|\\\\]bin[/|\\\\]zowe.* --daemon"; // match and kill any running Zowe daemon for (const nextProc of procArray) { - if (nextProc.cmd.match(zoweCmdRegEx) && nextProc.cmd.includes("--daemon")) { + if (nextProc.cmd.match(zoweCmdRegEx)) { process.kill(nextProc.pid, "SIGINT"); } } diff --git a/packages/cli/src/daemon/doc/IDaemonEnableQuestions.ts b/packages/cli/src/daemon/doc/IDaemonEnableQuestions.ts new file mode 100644 index 0000000000..96eb9b1ece --- /dev/null +++ b/packages/cli/src/daemon/doc/IDaemonEnableQuestions.ts @@ -0,0 +1,23 @@ +/* +* This program and the accompanying materials are made available under the terms of the +* Eclipse Public License v2.0 which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Copyright Contributors to the Zowe Project. +* +*/ + +/** + * Specifies wwhether questions can be asked of the user, and if not, + * what value should be used for a question when we do not ask. + */ +export interface IDaemonEnableQuestions { + canAskUser: boolean; // can we ask the user questions? + + /* Answer for the "can we add .zowe/bin to PATH" question. + * The value is only used when we cannot ask the user. + */ + addBinToPathVal: string; +} diff --git a/packages/cli/src/daemon/enable/Enable.handler.ts b/packages/cli/src/daemon/enable/Enable.handler.ts index 2e71b9fc30..3435852858 100644 --- a/packages/cli/src/daemon/enable/Enable.handler.ts +++ b/packages/cli/src/daemon/enable/Enable.handler.ts @@ -19,6 +19,8 @@ import { IO, ISystemInfo, ProcessUtils } from "@zowe/imperative"; +import { IDaemonEnableQuestions } from "../doc/IDaemonEnableQuestions"; + /** * Handler to enable daemon mode. * @export @@ -26,6 +28,8 @@ import { * @implements {ICommandHandler} */ export default class EnableDaemonHandler implements ICommandHandler { + private static openNewTerminalMsg = "To run further Zowe commands, close this terminal and open a new terminal."; + /** * Process the enable daemon command and populates the response * object as needed. @@ -37,14 +41,22 @@ export default class EnableDaemonHandler implements ICommandHandler { public async process(cmdParams: IHandlerParameters): Promise { let userMsg: string; try { - userMsg = await this.enableDaemon(); + const userQuestions: IDaemonEnableQuestions = { + /* TODO: Use this code block when we are ready to automatically add zowe/bin to the PATH + canAskUser: true, + addBinToPathVal: "y" + */ + canAskUser: false, + addBinToPathVal: "n" + }; + userMsg = await this.enableDaemon(userQuestions); } catch(impErr) { cmdParams.response.console.log("Failed to enable Zowe CLI daemon mode.\n" + (impErr as ImperativeError).message); cmdParams.response.data.setExitCode(1); return; } - cmdParams.response.console.log("Zowe CLI daemon mode enabled.\n" + userMsg); + cmdParams.response.console.log("Zowe CLI daemon mode is enabled.\n" + userMsg); cmdParams.response.data.setExitCode(0); } @@ -54,10 +66,12 @@ export default class EnableDaemonHandler implements ICommandHandler { * * @throws {ImperativeError} * + * @param {boolean} canAskQuestions Can we interactively ask the user questions? + * * @returns {string} An informational message to display to the user after * successful completion of the operation. */ - private async enableDaemon(): Promise { + private async enableDaemon(userQuestions: IDaemonEnableQuestions): Promise { // determine our current OS const sysInfo: ISystemInfo = ProcessUtils.getBasicSystemInfo(); @@ -93,29 +107,29 @@ export default class EnableDaemonHandler implements ICommandHandler { } // form the path to the bin directory in ZOWE_CLI_HOME - const zoweHomeBin = nodeJsPath.normalize(ImperativeConfig.instance.cliHome + "/bin"); + const pathToZoweBin = nodeJsPath.normalize(ImperativeConfig.instance.cliHome + "/bin"); // Does the ZOWE_CLI_HOME bin directory exist? - if (IO.existsSync(zoweHomeBin)) { - if (!IO.isDir(zoweHomeBin)) { + if (IO.existsSync(pathToZoweBin)) { + if (!IO.isDir(pathToZoweBin)) { throw new ImperativeError({ - msg: `The existing file '${zoweHomeBin}' must be a directory.` + msg: `The existing file '${pathToZoweBin}' must be a directory.` }); } } else { // create the directory try { - IO.createDirSync(zoweHomeBin); + IO.createDirSync(pathToZoweBin); } catch(err) { throw new ImperativeError({ - msg: `Unable to create directory '${zoweHomeBin}'.\nReason: ${err}` + msg: `Unable to create directory '${pathToZoweBin}'.\nReason: ${err}` }); } } // extract executable from the tar file into the bin directory - await this.unzipTgz(preBldTgz, zoweHomeBin, ImperativeConfig.instance.rootCommandName); + await this.unzipTgz(preBldTgz, pathToZoweBin, ImperativeConfig.instance.rootCommandName); /* Even though we await the unzip above, the OS still considers the exe file in-use * for a while. We will get the following error message when trying to run the exe. @@ -127,30 +141,28 @@ export default class EnableDaemonHandler implements ICommandHandler { // display the version of the executable let userInfoMsg: string = "Zowe CLI native executable version = "; - const zoweExePath = nodeJsPath.resolve(zoweHomeBin, exeFileName); - const pipe: StdioOptions = ["pipe", "pipe", process.stderr]; + const zoweExePath = nodeJsPath.resolve(pathToZoweBin, exeFileName); + const ioOpts: StdioOptions = ["pipe", "pipe", "pipe"]; try { const spawnResult = spawnSync(zoweExePath, ["--version-exe"], { - stdio: pipe, + stdio: ioOpts, shell: false }); if (spawnResult.stdout) { // remove any newlines from the version number userInfoMsg += spawnResult.stdout.toString().replace(/\r?\n|\r/g, ""); } else { - userInfoMsg += "Failed to get version number"; + userInfoMsg += "Failed to get version number\n"; + if (spawnResult.stderr) { + userInfoMsg += spawnResult.stderr.toString(); + } } } catch (err) { userInfoMsg += err.message; } - // if ZOWE_CLI_HOME/bin is not on our PATH, add an instruction to add it - if (process.env?.PATH?.length > 0) { - if (!process.env.PATH.includes(zoweHomeBin)) { - userInfoMsg += `\n\nAdd '${zoweHomeBin}' to your PATH.` + - "\nOtherwise, you will continue to run the classic Zowe CLI interpreter."; - } - } + // add our bin directory to the PATH if is it is not already there + userInfoMsg += await this.addZoweBinToPath(pathToZoweBin, userQuestions); // if ZOWE_USE_DAEMON is set, and turned off, add a warning message if (process.env?.ZOWE_USE_DAEMON?.length > 0) { @@ -207,4 +219,120 @@ export default class EnableDaemonHandler implements ICommandHandler { }); }); } + + /** + * Add our .zowe/bin directory to the user's PATH. + * + * @param pathToZoweBin The absolute path to our .zowe/bin drectory. + * + * @param {IDaemonEnableQuestions} userQuestions Questions for user (if permitted) + * + * @returns {string} An informational message to display to the user after + * successful completion of the operation. + */ + private async addZoweBinToPath(pathToZoweBin: string, userQuestions: IDaemonEnableQuestions): Promise { + let userInfoMsg: string = ""; + const osPlatform: string = ProcessUtils.getBasicSystemInfo().platform; + + if (process.env.PATH?.includes(pathToZoweBin)) { + // bash & zsh command-path caching forces us to require a new terminal + if ( osPlatform !== "win32") { + userInfoMsg += "\n\n" + EnableDaemonHandler.openNewTerminalMsg; + } + } else { + // ZOWE_CLI_HOME/bin is not on our PATH, we want to add it + let answer: string = null; + if (userQuestions.canAskUser) { + // alter PATH question by OS + let pathQuestion = "May we add the Zowe bin directory to your\nPATH in your "; + if ( osPlatform === "win32") { + pathQuestion += "permanent user environment"; + } else { + pathQuestion += ".profile file"; + } + pathQuestion += " [y or n] ? "; + // ask user for permission to update PATH + answer = await CliUtils.readPrompt(pathQuestion); + } else { + // don't ask, just use default + answer = userQuestions.addBinToPathVal; + } + + if (answer !== null && answer === "y" || answer === "Y") { + // user wants us to do it for him/her + if ( osPlatform === "win32") { + userInfoMsg += await this.addZoweBinOnWindows(pathToZoweBin); + } else { + userInfoMsg += this.addZoweBinOnPosix(pathToZoweBin); + } + } else { + userInfoMsg += `\n\nManually add '${pathToZoweBin}' to your PATH.` + + "\nOtherwise, you will continue to run the classic Zowe CLI interpreter."; + } + + // when zowe/bin not already on path, user needs a new terminal + userInfoMsg += "\n\n" + EnableDaemonHandler.openNewTerminalMsg; + } // end zowe/bin not in path + + return userInfoMsg; + } + + /** + * Add our .zowe/bin directory to the front of the user's PATH on Windows. + * + * @param pathToZoweBin The absolute path to our .zowe/bin drectory. + * + * @returns {string} An informational message to display to the user after + * successful completion of the operation. + */ + private async addZoweBinOnWindows(pathToZoweBin: string): Promise { + let userInfoMsg: string = ""; + try { + /* TODO: + * - Detect if zowe/bin is in system or user PATH env variable + * - For user PATH : reg query "HKCU\Environment" + * - For system PATH: reg query "???" + * - Add zowe/bin to the front of either user or system PATH + * - confirm that we do not exceed max path (1024 for user) + * - For system PATH + * - get user name + * - prompt for password + * - Use setx to set the new PATH value + */ + + const ioOptions: StdioOptions = ["pipe", "pipe", "pipe"]; + const spawnResult = spawnSync("setx", + ["zowe_set_env_test", pathToZoweBin + ";" + process.env.PATH], + { + stdio: ioOptions, + shell: false + } + ); + if (spawnResult.stdout) { + userInfoMsg += spawnResult.stdout.toString(); + } + if (spawnResult.stderr) { + userInfoMsg += spawnResult.stderr.toString(); + } + } catch (err) { + userInfoMsg += "Failed to run setx. Reason = " + err.message; + } + + return userInfoMsg; + } + + /** + * Add our .zowe/bin directory to the front of the user's PATH on Linux and MAC. + * Do that by adding a line at the end of the user's .profile file. + * + * @param pathToZoweBin The absolute path to our .zowe/bin drectory. + * + * @returns {string} An informational message to display to the user after + * successful completion of the operation. + */ + private addZoweBinOnPosix(pathToZoweBin: string): string { + // Todo: Implement addZoweBinOnPosix + const userInfoMsg: string = ""; + return userInfoMsg; + } } diff --git a/zowex/Cargo.lock b/zowex/Cargo.lock index 5fc4d97a94..a8299007d9 100644 --- a/zowex/Cargo.lock +++ b/zowex/Cargo.lock @@ -414,7 +414,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "zowe" -version = "0.6.0" +version = "0.6.1" dependencies = [ "atty", "base64", diff --git a/zowex/Cargo.toml b/zowex/Cargo.toml index fed479d0d0..3ad49256f9 100644 --- a/zowex/Cargo.toml +++ b/zowex/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "zowe" -version = "0.6.0" +version = "0.6.1" authors = ["Zowe Project"] edition = "2018" license = "EPL-2.0" diff --git a/zowex/src/main.rs b/zowex/src/main.rs index 1afd093beb..d5ecdde542 100644 --- a/zowex/src/main.rs +++ b/zowex/src/main.rs @@ -11,7 +11,7 @@ use std::collections::HashMap; use std::env; -use std::ffi::OsString; + use std::io; use std::io::prelude::*; use std::io::BufReader; @@ -94,7 +94,7 @@ struct DaemonResponse { // >> } | Measure-Object -Property TotalSeconds -Average // 3.6393932 and 0.76156812 zowe average over 10 run sample = 2.87782508 sec faster on windows -fn main() -> std::io::Result<()> { +fn main() -> io::Result<()> { // turn args into vector let mut _args: Vec = env::args().collect(); let cmd_result: Result; @@ -102,11 +102,9 @@ fn main() -> std::io::Result<()> { _args.drain(..1); // remove first (exe name) // Do we only need to display our version? - if _args.len() >= 1 { - if _args[0] == "--version-exe" { - println!("{}", env!("CARGO_PKG_VERSION")); - std::process::exit(EXIT_CODE_SUCCESS); - } + if !_args.is_empty() && _args[0] == "--version-exe" { + println!("{}", env!("CARGO_PKG_VERSION")); + std::process::exit(EXIT_CODE_SUCCESS); } // daemon commands that overwrite our executable cannot be run by our executable @@ -161,7 +159,7 @@ fn main() -> std::io::Result<()> { * The user-supplied command line arguments to the zowe command. * Each argument is in its own vector element. */ -fn exit_when_alt_cmd_needed(cmd_line_args: &Vec) { +fn exit_when_alt_cmd_needed(cmd_line_args: &[String]) { // commands other than daemon commands can be run by our exe if cmd_line_args.len() < 2 { return; @@ -192,7 +190,7 @@ fn exit_when_alt_cmd_needed(cmd_line_args: &Vec) { // We use COMSPEC so that the command will work for CMD and PowerShell match env::var("COMSPEC") { Ok(comspec_val) => { - if comspec_val.len() > 0 { + if !comspec_val.is_empty() { if comspec_val.contains(' ') { zowe_cmd_to_show.push('"'); zowe_cmd_to_show.push_str(&comspec_val); @@ -229,7 +227,7 @@ fn exit_when_alt_cmd_needed(cmd_line_args: &Vec) { * @returns * A String containing all of the command line arguments. */ -fn arg_vec_to_string(arg_vec: &Vec) -> String { +fn arg_vec_to_string(arg_vec: &[String]) -> String { let mut arg_string = String::new(); let mut arg_count = 1; for next_arg in arg_vec.iter() { @@ -241,7 +239,7 @@ fn arg_vec_to_string(arg_vec: &Vec) -> String { * enclosed in double quotes when it is placed into a single argument * string. */ - if next_arg.contains(' ') || next_arg.len() == 0 { + if next_arg.contains(' ') || next_arg.is_empty() { arg_string.push('"'); arg_string.push_str(next_arg); arg_string.push('"'); @@ -249,10 +247,10 @@ fn arg_vec_to_string(arg_vec: &Vec) -> String { arg_string.push_str(next_arg); } - arg_count = arg_count + 1; + arg_count += 1; } - return arg_string; + arg_string } fn get_zowe_env() -> HashMap { @@ -261,7 +259,7 @@ fn get_zowe_env() -> HashMap { ).collect() } -fn run_daemon_command(args: &mut Vec) -> std::io::Result<()> { +fn run_daemon_command(args: &mut Vec) -> io::Result<()> { let cwd = env::current_dir()?; let mut stdin = Vec::new(); if !atty::is(Stream::Stdin) { @@ -286,14 +284,14 @@ fn run_daemon_command(args: &mut Vec) -> std::io::Result<()> { let port_string = get_port_string(); let mut stream = establish_connection(daemon_host, port_string)?; - Ok(talk(&_resp, &mut stream)?) + talk(&_resp, &mut stream) } /** * Attempt to make a TCP connection to the daemon. * Iterate to enable a slow system to start the daemon. */ -fn establish_connection(host: String, port: String) -> std::io::Result { +fn establish_connection(host: String, port: String) -> io::Result { const THREE_SEC_DELAY: u64 = 3; const THREE_MIN_OF_RETRIES: i32 = 60; const RETRY_TO_SHOW_DIAG: i32 = 5; @@ -314,23 +312,21 @@ fn establish_connection(host: String, port: String) -> std::io::Result std::io::Result 0 { println!("{} ({} of {})", retry_msg, conn_retries, THREE_MIN_OF_RETRIES); } - conn_retries = conn_retries + 1; + conn_retries += 1; }; Ok(stream) @@ -374,7 +370,7 @@ fn talk(message: &[u8], stream: &mut TcpStream) -> io::Result<()> { /* * Send the command line arguments to the daemon and await responses. */ - stream.write(message).unwrap(); // write it + stream.write_all(message).unwrap(); // write it let mut stream_clone = stream.try_clone().expect("clone failed"); let mut reader = BufReader::new(&*stream); @@ -411,72 +407,54 @@ fn talk(message: &[u8], stream: &mut TcpStream) -> io::Result<()> { } }; - match p.stdout { - Some(s) => { - print!("{}", s); - io::stdout().flush().unwrap(); - } - None => (), // do nothing + if let Some(s) = p.stdout { + print!("{}", s); + io::stdout().flush().unwrap(); } - match p.stderr { - Some(s) => { - eprint!("{}", s); - io::stderr().flush().unwrap(); - } - None => (), // do nothing + if let Some(s) = p.stderr { + eprint!("{}", s); + io::stderr().flush().unwrap(); } - match p.prompt { - Some(s) => { - print!("{}", s); - io::stdout().flush().unwrap(); - let mut reply = String::new(); - io::stdin().read_line(&mut reply).unwrap(); - let response: DaemonResponse = DaemonResponse { - argv: None, - cwd: None, - env: None, - stdinLength: None, - stdin: Some(reply), - user: Some(encode(username())), - }; - let v = serde_json::to_string(&response)?; - - stream_clone.write(v.as_bytes()).unwrap(); - } - None => (), // do nothing + if let Some(s) = p.prompt { + print!("{}", s); + io::stdout().flush().unwrap(); + let mut reply = String::new(); + io::stdin().read_line(&mut reply).unwrap(); + let response: DaemonResponse = DaemonResponse { + argv: None, + cwd: None, + env: None, + stdinLength: None, + stdin: Some(reply), + user: Some(encode(username())), + }; + let v = serde_json::to_string(&response)?; + + stream_clone.write_all(v.as_bytes()).unwrap(); } - match p.securePrompt { - Some(s) => { - print!("{}", s); - io::stdout().flush().unwrap(); - let reply; - reply = read_password().unwrap(); - let response: DaemonResponse = DaemonResponse { - argv: None, - cwd: None, - env: None, - stdinLength: None, - stdin: Some(reply), - user: Some(encode(username())), - }; - let v = serde_json::to_string(&response)?; - stream_clone.write(v.as_bytes()).unwrap(); - } - None => (), // do nothing + if let Some(s) = p.securePrompt { + print!("{}", s); + io::stdout().flush().unwrap(); + let reply; + reply = read_password().unwrap(); + let response: DaemonResponse = DaemonResponse { + argv: None, + cwd: None, + env: None, + stdinLength: None, + stdin: Some(reply), + user: Some(encode(username())), + }; + let v = serde_json::to_string(&response)?; + stream_clone.write_all(v.as_bytes()).unwrap(); } - exit_code = match p.exitCode { - Some(s) => s, - None => 0, // do nothing - }; + exit_code = p.exitCode.unwrap_or(0); - _progress = match p.progress { - Some(s) => s, - None => false, // do nothing - }; + _progress = p.progress.unwrap_or(false); } else { // end of reading break; @@ -510,8 +488,8 @@ fn get_port_string() -> String { Ok(val) => _port = val.parse::().unwrap(), Err(_e) => _port = DEFAULT_PORT, } - let port_string = _port.to_string(); - return port_string; + + _port.to_string() } // Get the file path to the command that runs the NodeJS version of Zowe @@ -541,8 +519,8 @@ fn get_nodejs_zowe_path() -> String { let path_ext = env::var_os("PATHEXT"); for njs_zowe_path_buf in PathSearcher::new( zowe_cmd, - path.as_ref().map(OsString::as_os_str), - path_ext.as_ref().map(OsString::as_os_str), + path.as_deref(), + path_ext.as_deref(), ) { njs_zowe_path = njs_zowe_path_buf.to_string_lossy().to_string(); if njs_zowe_path.to_lowercase().eq(&my_exe_path.to_lowercase()) { @@ -560,7 +538,7 @@ fn get_nodejs_zowe_path() -> String { std::process::exit(EXIT_CODE_NO_NODEJS_ZOWE_ON_PATH); } - return njs_zowe_path; + njs_zowe_path } /** @@ -592,12 +570,12 @@ fn is_daemon_running() -> DaemonProcInfo { }; } } - return DaemonProcInfo { + DaemonProcInfo { is_running: false, name: "no name".to_string(), pid: "no pid".to_string(), cmd: "no cmd".to_string(), - }; + } } /** @@ -618,7 +596,7 @@ fn user_wants_daemon() -> bool { { return false; } - return true; + true } /** @@ -655,7 +633,7 @@ fn run_nodejs_command(cmd_line_args: &mut Vec) -> Result { } }; - return Ok(exit_code); + Ok(exit_code) } /** @@ -726,9 +704,9 @@ fn start_daemon(njs_zowe_path: &str) -> String { // return the command that we run (for display purposes) let mut cmd_to_show: String = njs_zowe_path.to_owned(); - cmd_to_show.push_str(" "); + cmd_to_show.push(' '); cmd_to_show.push_str(daemon_arg); - return cmd_to_show; + cmd_to_show } // @@ -741,11 +719,6 @@ mod tests { // Note this useful idiom: importing names from outer (for mod tests) scope. use super::*; - #[test] - fn test_parse_headers_get_total_length() { - assert_eq!(8, 8); - } - #[test] fn test_get_port_string() { // expect default port with no env @@ -756,5 +729,18 @@ mod tests { env::set_var("ZOWE_DAEMON", "777"); let port_string = get_port_string(); assert_eq!("777", port_string); + env::remove_var("ZOWE_DAEMON"); + } + + #[test] + fn test_get_zowe_env() { + let env = get_zowe_env(); + assert_eq!(env.get("ZOWE_EDITOR"), None); + + env::set_var("ZOWE_EDITOR", "nano"); + let env = get_zowe_env(); + assert_eq!(env.get("ZOWE_EDITOR"), Some(&"nano".to_owned())); + + env::remove_var("ZOWE_EDITOR"); } }