From cdd8772a00f0bed5fd6fa024ae8a0062f3a2f852 Mon Sep 17 00:00:00 2001 From: DudaGod Date: Wed, 11 Oct 2023 19:11:54 +0300 Subject: [PATCH] fix: ability to load local files through cli option "require" --- src/cli/index.js | 13 +++++--- src/utils/fs.ts | 10 ++++++ src/utils/module.ts | 8 +++++ src/worker/hermione-facade.js | 13 ++++++-- test/src/cli/index.js | 10 +++--- test/src/utils/fs.ts | 33 +++++++++++++++++++ test/src/utils/module.ts | 52 ++++++++++++++++++++++++++++++ test/src/worker/hermione-facade.js | 13 ++++---- 8 files changed, 134 insertions(+), 18 deletions(-) create mode 100644 src/utils/fs.ts create mode 100644 src/utils/module.ts create mode 100644 test/src/utils/fs.ts create mode 100644 test/src/utils/module.ts diff --git a/src/cli/index.js b/src/cli/index.js index c7fe1c0a9..be15e3b79 100644 --- a/src/cli/index.js +++ b/src/cli/index.js @@ -8,6 +8,7 @@ const info = require("./info"); const { Hermione } = require("../hermione"); const pkg = require("../../package.json"); const logger = require("../utils/logger"); +const { requireModule } = require("../utils/module"); let hermione; @@ -58,7 +59,7 @@ exports.run = () => { .arguments("[paths...]") .action(async paths => { try { - handleRequires(program.require); + await handleRequires(program.require); const isTestsSuccess = await hermione.run(paths, { reporters: program.reporter || defaults.reporters, @@ -99,10 +100,6 @@ function preparseOption(program, option) { return configFileParser[option]; } -function handleRequires(requires = []) { - requires.forEach(module => require(module)); -} - function compileGrep(grep) { try { return new RegExp(`(${grep})|(${escapeRe(grep)})`); @@ -111,3 +108,9 @@ function compileGrep(grep) { return new RegExp(escapeRe(grep)); } } + +async function handleRequires(requires = []) { + for (const modulePath of requires) { + await requireModule(modulePath); + } +} diff --git a/src/utils/fs.ts b/src/utils/fs.ts new file mode 100644 index 000000000..79d678b6a --- /dev/null +++ b/src/utils/fs.ts @@ -0,0 +1,10 @@ +import fs from "fs"; + +export const exists = async (path: string): Promise => { + try { + await fs.promises.access(path); + return true; + } catch (_) { + return false; + } +}; diff --git a/src/utils/module.ts b/src/utils/module.ts new file mode 100644 index 000000000..a32ac837b --- /dev/null +++ b/src/utils/module.ts @@ -0,0 +1,8 @@ +import path from "path"; +import { exists } from "./fs"; + +export const requireModule = async (modulePath: string): Promise => { + const isModuleLocal = await exists(modulePath); + + return require(isModuleLocal ? path.resolve(modulePath) : modulePath); +}; diff --git a/src/worker/hermione-facade.js b/src/worker/hermione-facade.js index de0fdef39..2a48a6a30 100644 --- a/src/worker/hermione-facade.js +++ b/src/worker/hermione-facade.js @@ -6,6 +6,7 @@ const Promise = require("bluebird"); const debug = require("debug")(`hermione:worker:${process.pid}`); const ipc = require("../utils/ipc"); const { MASTER_INIT, MASTER_SYNC_CONFIG, WORKER_INIT, WORKER_SYNC_CONFIG } = require("../constants/process-messages"); +const { requireModule } = require("../utils/module"); module.exports = class HermioneFacade { static create() { @@ -45,15 +46,21 @@ module.exports = class HermioneFacade { ipc.on(MASTER_INIT, ({ configPath, runtimeConfig } = {}) => { try { + const promise = Promise.resolve(); + if (runtimeConfig.requireModules) { - runtimeConfig.requireModules.forEach(module => require(module)); + runtimeConfig.requireModules.forEach(modulePath => { + promise.then(requireModule(modulePath)); + }); } RuntimeConfig.getInstance().extend(runtimeConfig); const hermione = Hermione.create(configPath); - debug("worker initialized"); - resolve(hermione); + promise.then(() => { + debug("worker initialized"); + resolve(hermione); + }); } catch (e) { debug("worker initialization failed"); reject(e); diff --git a/test/src/cli/index.js b/test/src/cli/index.js index 3fe9c6cc1..541099e26 100644 --- a/test/src/cli/index.js +++ b/test/src/cli/index.js @@ -52,14 +52,14 @@ describe("cli", () => { }); it('should require modules specified in "require" option', async () => { - const fooRequire = sandbox.stub().returns({}); + const requireModule = sandbox.stub(); const stubHermioneCli = proxyquire("src/cli", { - foo: (() => fooRequire())(), + "../utils/module": { requireModule }, }); await run_("--require foo", stubHermioneCli); - assert.calledOnce(fooRequire); + assert.calledOnceWith(requireModule, "foo"); }); it("should create Hermione without config by default", async () => { @@ -155,7 +155,9 @@ describe("cli", () => { }); it("should use require modules from cli", async () => { - const stubHermioneCli = proxyquire("src/cli", { foo: {} }); + const stubHermioneCli = proxyquire("src/cli", { + "../utils/module": { requireModule: sandbox.stub() }, + }); await run_("--require foo", stubHermioneCli); assert.calledWithMatch(Hermione.prototype.run, any, { requireModules: ["foo"] }); diff --git a/test/src/utils/fs.ts b/test/src/utils/fs.ts new file mode 100644 index 000000000..2bab7f4a4 --- /dev/null +++ b/test/src/utils/fs.ts @@ -0,0 +1,33 @@ +import fs from "fs"; +import sinon, { SinonStub } from "sinon"; +import { exists } from "../../../src/utils/fs"; + +describe("utils/fs", () => { + const sandbox = sinon.createSandbox(); + + beforeEach(() => { + sandbox.stub(fs.promises, "access"); + }); + + afterEach(() => sandbox.restore()); + + describe("exists", () => { + it("should return 'true' if file exists", async () => { + const path = "./some-path.js"; + (fs.promises.access as SinonStub).withArgs(path).resolves(); + + const isExists = await exists(path); + + assert.isTrue(isExists); + }); + + it("should return 'false' if file doesn't exists", async () => { + const path = "./some-path.js"; + (fs.promises.access as SinonStub).withArgs(path).rejects(new Error("o.O")); + + const isExists = await exists(path); + + assert.isFalse(isExists); + }); + }); +}); diff --git a/test/src/utils/module.ts b/test/src/utils/module.ts new file mode 100644 index 000000000..5c2a001d4 --- /dev/null +++ b/test/src/utils/module.ts @@ -0,0 +1,52 @@ +import path from "path"; +import sinon, { SinonStub } from "sinon"; +import proxyquire from "proxyquire"; +import { requireModule as realRequireModule } from "../../../src/utils/module"; + +describe("utils/module", () => { + let isNodeModuleRequired: SinonStub; + let isLocalModuleRequired: SinonStub; + let exists: SinonStub; + let requireModule: typeof realRequireModule; + + const sandbox = sinon.createSandbox(); + const nodeModulePath = "foo-module"; + const relativeLocalModulePath = "./bar-module"; + const absoluteLocalModulePath = path.resolve(relativeLocalModulePath); + + beforeEach(() => { + isNodeModuleRequired = sinon.stub(); + isLocalModuleRequired = sinon.stub(); + exists = sinon.stub(); + + ({ requireModule } = proxyquire.noCallThru().load("../../../src/utils/module", { + "./fs": { exists }, + [nodeModulePath]: isNodeModuleRequired, + [absoluteLocalModulePath]: isLocalModuleRequired, + })); + }); + + afterEach(() => sandbox.restore()); + + describe("requireModule", () => { + it("should require module from node-modules", async () => { + exists.withArgs(nodeModulePath).resolves(false); + const module = await requireModule(nodeModulePath); + + module(); + + assert.calledOnce(isNodeModuleRequired); + assert.notCalled(isLocalModuleRequired); + }); + + it("should require module from node-modules", async () => { + exists.withArgs(relativeLocalModulePath).resolves(true); + const module = await requireModule(relativeLocalModulePath); + + module(); + + assert.calledOnce(isLocalModuleRequired); + assert.notCalled(isNodeModuleRequired); + }); + }); +}); diff --git a/test/src/worker/hermione-facade.js b/test/src/worker/hermione-facade.js index 7ab14954c..f018bb4b4 100644 --- a/test/src/worker/hermione-facade.js +++ b/test/src/worker/hermione-facade.js @@ -1,5 +1,6 @@ "use strict"; +const proxyquire = require("proxyquire"); const { AsyncEmitter } = require("src/events/async-emitter"); const { Hermione } = require("src/worker/hermione"); const { makeConfigStub } = require("../../utils"); @@ -46,18 +47,18 @@ describe("worker/hermione-facade", () => { }); it("should require passed modules", async () => { - const hermioneFacadeModule = module.children.find(({ filename }) => - /\/hermione-facade\.js$/.test(filename), - ); - sandbox.stub(hermioneFacadeModule, "require"); + const requireModule = sinon.stub(); + const HermioneFacadeModule = proxyquire("src/worker/hermione-facade", { + "../utils/module": { requireModule }, + }); ipc.on.withArgs(MASTER_INIT).yieldsAsync({ runtimeConfig: { requireModules: ["foo"] }, }); - await hermioneFacade.init(); + await HermioneFacadeModule.create().init(); - assert.calledOnceWith(hermioneFacadeModule.require, "foo"); + assert.calledOnceWith(requireModule, "foo"); }); });