From 2631f2147aa719a4d63babf50cd20544fd65892c Mon Sep 17 00:00:00 2001 From: Kai Cataldo Date: Thu, 12 Sep 2019 18:38:30 -0400 Subject: [PATCH] Add tests --- lib/cli.js | 9 +- lib/{info/index.js => shared/info.js} | 37 +++-- tests/lib/cli.js | 27 +++- tests/lib/shared/info.js | 211 ++++++++++++++++++++++++++ 4 files changed, 253 insertions(+), 31 deletions(-) rename lib/{info/index.js => shared/info.js} (74%) create mode 100644 tests/lib/shared/info.js diff --git a/lib/cli.js b/lib/cli.js index 292d7dd4dbc..c1b0d2223c5 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -20,8 +20,8 @@ const fs = require("fs"), mkdirp = require("mkdirp"), { CLIEngine } = require("./cli-engine"), options = require("./options"), - info = require("./info"), - log = require("./shared/logging"); + log = require("./shared/logging"), + info = require("./shared/info"); const debug = require("debug")("eslint:cli"); @@ -160,7 +160,6 @@ const cli = { } const files = currentOptions._; - const useStdin = typeof text === "string"; if (currentOptions.version) { @@ -184,17 +183,13 @@ const cli = { } const engine = new CLIEngine(translateOptions(currentOptions)); - const fileConfig = engine.getConfigForFile(currentOptions.printConfig); log.info(JSON.stringify(fileConfig, null, " ")); return 0; } else if (currentOptions.help || (!files.length && !useStdin)) { - log.info(options.generateHelp()); - } else { - debug(`Running on ${useStdin ? "text" : "files"}`); if (currentOptions.fix && currentOptions.fixDryRun) { diff --git a/lib/info/index.js b/lib/shared/info.js similarity index 74% rename from lib/info/index.js rename to lib/shared/info.js index a08345c8037..324f457d80b 100644 --- a/lib/info/index.js +++ b/lib/shared/info.js @@ -1,5 +1,5 @@ /** - * @fileoverview Log information for debugging purposes + * @fileoverview Utility to get information about the execution environment. * @author Kai Cataldo */ @@ -13,18 +13,19 @@ const path = require("path"); const spawn = require("cross-spawn"); const { isEmpty } = require("lodash"); const log = require("../shared/logging"); +const packageJson = require("../../package.json"); //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ /** - * Checks if a given path is in a given directory. + * Checks if a path is a child of a directory. * @param {string} parentPath - The parent path to check. * @param {string} childPath - The path to check. - * @returns {boolean} Whether or not the given path is in the given directory. + * @returns {boolean} Whether or not the given path is a child of a directory. */ -function isInDirectory(parentPath, childPath) { +function isChildOfDirectory(parentPath, childPath) { return !path.relative(parentPath, childPath).startsWith(".."); } @@ -62,9 +63,9 @@ function getBinVersion(bin) { const binArgs = ["--version"]; try { - return execCommand(bin, binArgs); + return normalizeVersionStr(execCommand(bin, binArgs)); } catch (e) { - log.error(`Error finding ${bin} version running the command ${bin} ${binArgs.join(" ")}`); + log.error(`Error finding ${bin} version running the command \`${bin} ${binArgs.join(" ")}\``); throw e; } } @@ -86,7 +87,11 @@ function getNpmPackageVersion(pkg, { global = false } = {}) { try { const parsedStdout = JSON.parse(execCommand("npm", npmLsArgs)); - if (isEmpty(parsedStdout)) { + /* + * Checking globally returns an empty JSON object, while local checks + * include the name and version of the local project. + */ + if (isEmpty(parsedStdout) || !(parsedStdout.dependencies && parsedStdout.dependencies.eslint)) { return "Not found"; } @@ -96,20 +101,20 @@ function getNpmPackageVersion(pkg, { global = false } = {}) { try { npmBinPath = execCommand("npm", npmBinArgs); } catch (e) { - log.error(`Error finding npm binary path when running command npm ${npmBinArgs.join(" ")}`); + log.error(`Error finding npm binary path when running command \`npm ${npmBinArgs.join(" ")}\``); throw e; } - const isGlobal = isInDirectory(npmBinPath, processBinPath); + const isGlobal = isChildOfDirectory(npmBinPath, processBinPath); let pkgVersion = parsedStdout.dependencies.eslint.version; if ((global && isGlobal) || (!global && !isGlobal)) { pkgVersion += " (Currently used)"; } - return pkgVersion; + return normalizeVersionStr(pkgVersion); } catch (e) { - log.error(`Error finding ${pkg} version running the command npm ${npmLsArgs.join(" ")}`); + log.error(`Error finding ${pkg} version running the command \`npm ${npmLsArgs.join(" ")}\``); throw e; } } @@ -122,10 +127,10 @@ function environment() { return [ "Environment Info:", "", - `Node version: ${normalizeVersionStr(getBinVersion("node"))}`, - `npm version: ${normalizeVersionStr(getBinVersion("npm"))}`, - `Local ESLint version: ${normalizeVersionStr(getNpmPackageVersion("eslint", { global: false }))}`, - `Global ESLint version: ${normalizeVersionStr(getNpmPackageVersion("eslint", { global: true }))}` + `Node version: ${getBinVersion("node")}`, + `npm version: ${getBinVersion("npm")}`, + `Local ESLint version: ${getNpmPackageVersion("eslint", { global: false })}`, + `Global ESLint version: ${getNpmPackageVersion("eslint", { global: true })}` ].join("\n"); } @@ -134,7 +139,7 @@ function environment() { * @returns {string} The version from the currently executing ESLint's package.json. */ function version() { - return `v${require("../../package.json").version}`; + return `v${packageJson.version}`; } //------------------------------------------------------------------------------ diff --git a/tests/lib/cli.js b/tests/lib/cli.js index baae75b53cb..c555b448bc1 100644 --- a/tests/lib/cli.js +++ b/tests/lib/cli.js @@ -29,14 +29,18 @@ const proxyquire = require("proxyquire").noCallThru().noPreserveCache(); //------------------------------------------------------------------------------ describe("cli", () => { - let fixtureDir; const log = { info: sinon.spy(), error: sinon.spy() }; + const info = { + environment: sinon.stub(), + version: sinon.stub() + }; const cli = proxyquire("../../lib/cli", { - "./shared/logging": log + "./shared/logging": log, + "./shared/info": info }); /** @@ -324,15 +328,27 @@ describe("cli", () => { describe("when executing with version flag", () => { it("should print out current version", () => { assert.strictEqual(cli.execute("-v"), 0); + assert.strictEqual(log.info.callCount, 1); + }); + }); + describe("when executing with info flag", () => { + it("should print out environment information", () => { + assert.strictEqual(cli.execute("--info"), 0); assert.strictEqual(log.info.callCount, 1); }); + + it("should print error message and return error code", () => { + info.environment.throws("There was an error!"); + + assert.strictEqual(cli.execute("--info"), 2); + assert.strictEqual(log.error.callCount, 1); + }); }); describe("when executing with help flag", () => { it("should print out help", () => { assert.strictEqual(cli.execute("-h"), 0); - assert.strictEqual(log.info.callCount, 1); }); }); @@ -349,7 +365,6 @@ describe("cli", () => { }); describe("when given a file in excluded files list", () => { - it("should not process the file", () => { const ignorePath = getFixturePath(".eslintignore"); const filePath = getFixturePath("passing.js"); @@ -398,7 +413,6 @@ describe("cli", () => { }); describe("when executing a file with a shebang", () => { - it("should execute without error", () => { const filePath = getFixturePath("shebang.js"); const exit = cli.execute(`--no-ignore ${filePath}`); @@ -408,7 +422,6 @@ describe("cli", () => { }); describe("when loading a custom rule", () => { - it("should return an error when rule isn't found", () => { const rulesPath = getFixturePath("rules", "wrong"); const configPath = getFixturePath("rules", "eslint.json"); @@ -551,7 +564,6 @@ describe("cli", () => { }); describe("when supplied with report output file path", () => { - afterEach(() => { sh.rm("-rf", "tests/output"); }); @@ -594,7 +606,6 @@ describe("cli", () => { }); describe("when supplied with a plugin", () => { - it("should pass plugins to CLIEngine", () => { const examplePluginName = "eslint-plugin-example"; diff --git a/tests/lib/shared/info.js b/tests/lib/shared/info.js new file mode 100644 index 00000000000..ca7b8bbe230 --- /dev/null +++ b/tests/lib/shared/info.js @@ -0,0 +1,211 @@ +/** + * @fileoverview Tests for info util + * @author Kai Cataldo + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const assert = require("chai").assert; +const sinon = require("sinon"); +const spawn = require("cross-spawn"); +const { unIndent } = require("../_utils"); +const info = require("../../../lib/shared/info"); +const log = require("../../../lib/shared/logging"); +const packageJson = require("../../../package.json"); + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +const LOCAL_ESLINT_BIN_PATH = "/Users/username/code/project/node_modules/eslint/bin/eslint.js"; +const GLOBAL_ESLINT_BIN_PATH = "/usr/local/bin/npm/node_modules/eslint/bin/eslint.js"; + +/** + * Sets up spawn.sync() stub calls to return values and throw errors in the order in which they are given. + * @param {Function} stub - The stub to set up. + * @param {Array} returnVals - Values to be returned by subsequent stub calls. + * @returns {Function} The set up stub. + */ +function setupSpawnSyncStubReturnVals(stub, returnVals) { + let stubChain = stub; + + for (const [i, val] of returnVals.entries()) { + const returnVal = val instanceof Error + ? { error: val } + : { stdout: val }; + + stubChain = stubChain.onCall(i).returns(returnVal); + } + + return stubChain; +} + +describe("info", () => { + describe("environment()", () => { + let spawnSyncStub; + let logErrorStub; + let originalProcessArgv; + let spawnSyncStubArgs; + + beforeEach(() => { + spawnSyncStub = sinon.stub(spawn, "sync"); + logErrorStub = sinon.stub(log, "error"); + originalProcessArgv = process.argv; + process.argv[1] = LOCAL_ESLINT_BIN_PATH; + spawnSyncStubArgs = [ + "v12.8.0", + "6.11.3", + unIndent` + { + "name": "project", + "version": "1.0.0", + "dependencies": { + "eslint": { + "version": "6.3.0" + } + } + } + `, + "/usr/local/bin/npm", + unIndent` + { + "dependencies": { + "eslint": { + "version": "5.16.0", + "from": "eslint", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.16.0.tgz" + } + } + } + `, + "/usr/local/bin/npm" + ]; + }); + + afterEach(() => { + spawnSyncStub.restore(); + logErrorStub.restore(); + process.argv = originalProcessArgv; + }); + + + it("should return a string containing environment information when running local installation", () => { + setupSpawnSyncStubReturnVals(spawnSyncStub, spawnSyncStubArgs); + + assert.strictEqual( + info.environment(), + unIndent` + Environment Info: + + Node version: v12.8.0 + npm version: v6.11.3 + Local ESLint version: v6.3.0 (Currently used) + Global ESLint version: v5.16.0 + ` + ); + }); + + it("should return a string containing environment information when running global installation", () => { + setupSpawnSyncStubReturnVals(spawnSyncStub, spawnSyncStubArgs); + process.argv[1] = GLOBAL_ESLINT_BIN_PATH; + + assert.strictEqual( + info.environment(), + unIndent` + Environment Info: + + Node version: v12.8.0 + npm version: v6.11.3 + Local ESLint version: v6.3.0 + Global ESLint version: v5.16.0 (Currently used) + ` + ); + }); + + it("should return a string containing environment information when not installed locally", () => { + spawnSyncStubArgs.splice(2, 2, unIndent` + { + "name": "project", + "version": "1.0.0" + } + `); + setupSpawnSyncStubReturnVals(spawnSyncStub, spawnSyncStubArgs); + process.argv[1] = GLOBAL_ESLINT_BIN_PATH; + + assert.strictEqual( + info.environment(), + unIndent` + Environment Info: + + Node version: v12.8.0 + npm version: v6.11.3 + Local ESLint version: Not found + Global ESLint version: v5.16.0 (Currently used) + ` + ); + }); + + it("should return a string containing environment information when not installed globally", () => { + spawnSyncStubArgs[4] = "{}"; + setupSpawnSyncStubReturnVals(spawnSyncStub, spawnSyncStubArgs); + + assert.strictEqual( + info.environment(), + unIndent` + Environment Info: + + Node version: v12.8.0 + npm version: v6.11.3 + Local ESLint version: v6.3.0 (Currently used) + Global ESLint version: Not found + ` + ); + }); + + it("log and throw an error when npm version can not be found", () => { + const expectedErr = new Error("npm can not be found"); + + spawnSyncStubArgs[1] = expectedErr; + setupSpawnSyncStubReturnVals(spawnSyncStub, spawnSyncStubArgs); + + assert.throws(info.environment, expectedErr); + assert.strictEqual(logErrorStub.args[0][0], "Error finding npm version running the command `npm --version`"); + }); + + it("log and throw an error when npm binary path can not be found", () => { + const expectedErr = new Error("npm can not be found"); + + spawnSyncStubArgs[3] = expectedErr; + setupSpawnSyncStubReturnVals(spawnSyncStub, spawnSyncStubArgs); + + assert.throws(info.environment, expectedErr); + assert.strictEqual(logErrorStub.args[0][0], "Error finding npm binary path when running command `npm bin -g`"); + }); + + it("log and throw an error when checking for local ESLint version when returned output of command is malformed", () => { + spawnSyncStubArgs[2] = "This is not JSON"; + setupSpawnSyncStubReturnVals(spawnSyncStub, spawnSyncStubArgs); + + assert.throws(info.environment, "Unexpected token T in JSON at position 0"); + assert.strictEqual(logErrorStub.args[0][0], "Error finding eslint version running the command `npm ls --depth=0 --json eslint`"); + }); + + it("log and throw an error when checking for global ESLint version when returned output of command is malformed", () => { + spawnSyncStubArgs[4] = "This is not JSON"; + setupSpawnSyncStubReturnVals(spawnSyncStub, spawnSyncStubArgs); + + assert.throws(info.environment, "Unexpected token T in JSON at position 0"); + assert.strictEqual(logErrorStub.args[0][0], "Error finding eslint version running the command `npm ls --depth=0 --json eslint -g`"); + }); + }); + + describe("version()", () => { + it("should return the version of the package defined in package.json", () => { + assert.strictEqual(info.version(), `v${packageJson.version}`); + }); + }); +});