diff --git a/src/runner/index.ts b/src/runner/index.ts index 94e36e6a8..10053d304 100644 --- a/src/runner/index.ts +++ b/src/runner/index.ts @@ -18,13 +18,14 @@ import PromiseGroup from "./promise-group"; import { TestCollection } from "../test-collection"; import * as logger from "../utils/logger"; import { Config } from "../config"; -import type { runTest } from "../worker"; +import type { runTest, cancel } from "../worker"; import type { Stats as RunnerStats } from "../stats"; import EventEmitter from "events"; import { Test } from "../types"; interface WorkerMethods { runTest: typeof runTest; + cancel: typeof cancel; } export interface Workers extends EventEmitter, WorkerMethods {} @@ -73,7 +74,7 @@ export class MainRunner extends Runner { } this.workersRegistry.init(); - this.workers = this.workersRegistry.register(require.resolve("../worker"), ["runTest"]) as Workers; + this.workers = this.workersRegistry.register(require.resolve("../worker"), ["runTest", "cancel"]) as Workers; this.browserPool = pool.create(this.config, this); } @@ -173,6 +174,8 @@ export class MainRunner extends Runner { this.browserPool?.cancel(); this.activeBrowserRunners.forEach(runner => runner.cancel()); + + this.workers?.cancel(); } registerWorkers>(workerFilepath: string, exportedMethods: T): RegisterWorkers { diff --git a/src/worker/index.js b/src/worker/index.js index c459d020f..b49d34249 100644 --- a/src/worker/index.js +++ b/src/worker/index.js @@ -8,3 +8,7 @@ testplaneFacade.init(); exports.runTest = (fullTitle, options) => { return testplaneFacade.runTest(fullTitle, options); }; + +exports.cancel = () => { + return testplaneFacade.cancel(); +}; diff --git a/src/worker/testplane-facade.ts b/src/worker/testplane-facade.ts index 535bcf11c..15b902698 100644 --- a/src/worker/testplane-facade.ts +++ b/src/worker/testplane-facade.ts @@ -31,6 +31,10 @@ module.exports = class TestplaneFacade { return this.promise; } + cancel(): void { + RuntimeConfig.getInstance().replServer?.close(); + } + syncConfig(): Promise { this.syncConfig = (): Promise => this.promise; diff --git a/test/src/runner/index.js b/test/src/runner/index.js index 1fdf44248..1e85473fe 100644 --- a/test/src/runner/index.js +++ b/test/src/runner/index.js @@ -23,6 +23,7 @@ describe("NodejsEnvRunner", () => { const mkWorkers_ = () => { return { runTest: sandbox.stub().resolves(), + cancel: sandbox.stub().resolves(), }; }; @@ -694,5 +695,16 @@ describe("NodejsEnvRunner", () => { assert.notCalled(BrowserRunner.prototype.run); assert.notCalled(BrowserRunner.prototype.cancel); }); + + it("should cancel all executing workers", async () => { + const workers = mkWorkers_(); + WorkersRegistry.prototype.register.withArgs(sinon.match.string, ["runTest", "cancel"]).returns(workers); + const runner = new Runner(makeConfigStub()); + + runner.init(); + runner.cancel(); + + assert.calledOnceWithExactly(workers.cancel); + }); }); }); diff --git a/test/src/worker/index.js b/test/src/worker/index.js index 652bca25c..9b8c584cc 100644 --- a/test/src/worker/index.js +++ b/test/src/worker/index.js @@ -43,4 +43,29 @@ describe("worker", () => { return assert.isRejected(runTest("fullTitle", { some: "opts" }), /foo/); }); }); + + describe("cancel", () => { + let cancel; + + beforeEach(() => { + TestplaneFacade.prototype.cancel.returns(); + + const worker = require("src/worker"); + cancel = worker.cancel; + }); + + it("should delegate cancel call to testplane facade", () => { + TestplaneFacade.prototype.cancel.returns(); + + cancel(); + + assert.calledOnceWithExactly(TestplaneFacade.prototype.cancel); + }); + + it("should throws on testplane facade cancel fail", () => { + TestplaneFacade.prototype.cancel.throws(new Error("o.O")); + + assert.throws(() => cancel(), Error, "o.O"); + }); + }); }); diff --git a/test/src/worker/testplane-facade.js b/test/src/worker/testplane-facade.js index 772b3fc43..dcdee9cdd 100644 --- a/test/src/worker/testplane-facade.js +++ b/test/src/worker/testplane-facade.js @@ -3,6 +3,7 @@ const proxyquire = require("proxyquire"); const { AsyncEmitter } = require("src/events/async-emitter"); const { Testplane } = require("src/worker/testplane"); +const RuntimeConfig = require("src/config/runtime-config"); const { makeConfigStub } = require("../../utils"); const ipc = require("src/utils/ipc"); const TestplaneFacade = require("src/worker/testplane-facade"); @@ -79,4 +80,23 @@ describe("worker/testplane-facade", () => { assert.callOrder(TestplaneFacade.prototype.syncConfig, testplane.runTest); }); }); + + describe("cancel", () => { + beforeEach(() => { + sandbox.stub(RuntimeConfig, "getInstance").returns({}); + }); + + it("should not throw if repl server is not exists in runtime config", () => { + assert.doesNotThrow(() => testplaneFacade.cancel()); + }); + + it("should close repl server if it exists in runtime config", () => { + const replServer = { close: sandbox.stub() }; + RuntimeConfig.getInstance.returns({ replServer }); + + testplaneFacade.cancel(); + + assert.calledOnceWithExactly(replServer.close); + }); + }); });