Skip to content

Commit

Permalink
Split test-shell.ts to separate concerns
Browse files Browse the repository at this point in the history
  • Loading branch information
kraenhansen committed Sep 26, 2024
1 parent 8f3b45e commit 0601808
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 101 deletions.
2 changes: 1 addition & 1 deletion packages/e2e-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"url": "git://github.com/mongodb-js/mongosh.git"
},
"scripts": {
"test": "mocha -r ts-node/register -r \"../../scripts/import-expansions.js\" -r \"./test/test-shell.ts\" --timeout 15000 --colors \"./test/*.spec.ts\"",
"test": "mocha -r ts-node/register -r \"../../scripts/import-expansions.js\" -r \"./test/test-shell-context.ts\" --timeout 15000 --colors \"./test/*.spec.ts\"",
"test-ci": "node ../../scripts/run-if-package-requested.js npm test",
"test-coverage": "nyc --no-clean --cwd ../.. --reporter=none npm run test",
"test-ci-coverage": "nyc --no-clean --cwd ../.. --reporter=none npm run test-ci",
Expand Down
3 changes: 2 additions & 1 deletion packages/e2e-tests/test/e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import type { Db } from 'mongodb';
import { MongoClient } from 'mongodb';

import { eventually } from '../../../testing/eventually';
import { ensureTestShellAfterHook, TestShell } from './test-shell';
import { TestShell } from './test-shell';
import { ensureTestShellAfterHook } from './test-shell-context';
import {
skipIfServerVersion,
startSharedTestServer,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ensureTestShellAfterHook, TestShell } from './test-shell';
import { TestShell } from './test-shell';
import { ensureTestShellAfterHook } from './test-shell-context';

describe('TestShell', function () {
describe('TestShell context', function () {
context('hooks and tests', function () {
before(async function () {
const shell = this.startTestShell({ args: ['--nodb'] });
Expand Down
96 changes: 96 additions & 0 deletions packages/e2e-tests/test/test-shell-context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import assert from 'assert';
import Mocha from 'mocha';
import { TestShell, type TestShellOptions } from './test-shell';

declare module 'mocha' {
interface Context {
/**
* Starts a test shell and registers a hook to kill it after the test
*/
startTestShell(options?: TestShellOptions): TestShell;
}
}

const TEST_SHELLS_AFTER_ALL = Symbol('test-shells-after-all');
const TEST_SHELLS_AFTER_EACH = Symbol('test-shells-after-each');

type AfterAllInjectedSuite = {
[TEST_SHELLS_AFTER_ALL]: Set<TestShell>;
};

type AfterEachInjectedSuite = {
[TEST_SHELLS_AFTER_EACH]: Set<TestShell>;
};

/**
* Registers an after (all or each) hook to kill test shells started during the hooks or tests.
* You don't have to call this from tests, but you can if you want to register an after (each) hook
* which runs after the shells have been killed.
*/
export function ensureTestShellAfterHook(
hookName: 'afterEach',
suite: Mocha.Suite
): asserts suite is AfterEachInjectedSuite & Mocha.Suite;
export function ensureTestShellAfterHook(
hookName: 'afterAll',
suite: Mocha.Suite
): asserts suite is AfterAllInjectedSuite & Mocha.Suite;
export function ensureTestShellAfterHook(
hookName: 'afterEach' | 'afterAll',
suite: Partial<AfterAllInjectedSuite & AfterEachInjectedSuite> & Mocha.Suite
): void {
const symbol =
hookName === 'afterAll' ? TEST_SHELLS_AFTER_ALL : TEST_SHELLS_AFTER_EACH;
if (!suite[symbol]) {
// Store the set of shells to kill afterwards
const shells = new Set<TestShell>();
suite[symbol] = shells;
suite[hookName](async () => {
const shellsToKill = [...shells];
shells.clear();
await Promise.all(
shellsToKill.map((shell) => {
// TODO: Consider if it's okay to kill those that are already killed?
shell.kill();
return shell.waitForExit();
})
);
});
}
}

Mocha.Context.prototype.startTestShell = function (
this: Mocha.Context,
options: TestShellOptions
) {
const { test: runnable } = this;
assert(runnable, 'Expected a runnable / test');
const { parent } = runnable;
assert(parent, 'Expected runnable to have a parent');
// Start the shell
const shell = TestShell.start(options);
// Register a hook to kill the shell
if (runnable instanceof Mocha.Hook) {
if (
runnable.originalTitle === '"before each" hook' ||
runnable.originalTitle === '"after each" hook'
) {
ensureTestShellAfterHook('afterEach', parent);
parent[TEST_SHELLS_AFTER_EACH].add(shell);
} else if (
runnable.originalTitle === '"before all" hook' ||
runnable.originalTitle === '"after all" hook'
) {
ensureTestShellAfterHook('afterAll', parent);
parent[TEST_SHELLS_AFTER_ALL].add(shell);
} else {
throw new Error(`Unexpected ${runnable.originalTitle || runnable.title}`);
}
} else if (runnable instanceof Mocha.Test) {
ensureTestShellAfterHook('afterEach', parent);
parent[TEST_SHELLS_AFTER_EACH].add(shell);
} else {
throw new Error('Unexpected Runnable: Expected a Hook or a Test');
}
return shell;
};
99 changes: 2 additions & 97 deletions packages/e2e-tests/test/test-shell.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import Mocha from 'mocha';
import assert from 'assert';
import type {
ChildProcess,
Expand Down Expand Up @@ -49,7 +48,8 @@ export class TestShell {
private static _openShells: Set<TestShell> = new Set();

/**
* @deprecated Use the {@link Mocha.Context.startTestShell} hook instead
* Starts a test shell.
* @private Use the {@link Mocha.Context.startTestShell} hook instead
*/
static start(options: TestShellOptions = { args: [] }): TestShell {
let shellProcess: ChildProcessWithoutNullStreams;
Expand Down Expand Up @@ -335,98 +335,3 @@ export class TestShell {
return match.groups!.logId;
}
}

// Context extension to manage TestShell lifetime

declare module 'mocha' {
interface Context {
/**
* Starts a test shell and registers a hook to kill it after the test
*/
startTestShell(options?: TestShellOptions): TestShell;
}
}

const TEST_SHELLS_AFTER_ALL = Symbol('test-shells-after-all');
const TEST_SHELLS_AFTER_EACH = Symbol('test-shells-after-each');

type AfterAllInjectedSuite = {
[TEST_SHELLS_AFTER_ALL]: Set<TestShell>;
};

type AfterEachInjectedSuite = {
[TEST_SHELLS_AFTER_EACH]: Set<TestShell>;
};

/**
* Registers an after (all or each) hook to kill test shells started during the hooks or tests.
* You don't have to call this from tests, but you can if you want to register an after (each) hook
* which runs after the shells have been killed.
*/
export function ensureTestShellAfterHook(
hookName: 'afterEach',
suite: Mocha.Suite
): asserts suite is AfterEachInjectedSuite & Mocha.Suite;
export function ensureTestShellAfterHook(
hookName: 'afterAll',
suite: Mocha.Suite
): asserts suite is AfterAllInjectedSuite & Mocha.Suite;
export function ensureTestShellAfterHook(
hookName: 'afterEach' | 'afterAll',
suite: Partial<AfterAllInjectedSuite & AfterEachInjectedSuite> & Mocha.Suite
): void {
const symbol =
hookName === 'afterAll' ? TEST_SHELLS_AFTER_ALL : TEST_SHELLS_AFTER_EACH;
if (!suite[symbol]) {
// Store the set of shells to kill afterwards
const shells = new Set<TestShell>();
suite[symbol] = shells;
suite[hookName](async () => {
const shellsToKill = [...shells];
shells.clear();
await Promise.all(
shellsToKill.map((shell) => {
// TODO: Consider if it's okay to kill those that are already killed?
shell.kill();
return shell.waitForExit();
})
);
});
}
}

Mocha.Context.prototype.startTestShell = function (
this: Mocha.Context,
options: TestShellOptions
) {
const { test: runnable } = this;
assert(runnable, 'Expected a runnable / test');
const { parent } = runnable;
assert(parent, 'Expected runnable to have a parent');
// Start the shell
const shell = TestShell.start(options);
// Register a hook to kill the shell
if (runnable instanceof Mocha.Hook) {
if (
runnable.originalTitle === '"before each" hook' ||
runnable.originalTitle === '"after each" hook'
) {
ensureTestShellAfterHook('afterEach', parent);
parent[TEST_SHELLS_AFTER_EACH].add(shell);
} else if (
runnable.originalTitle === '"before all" hook' ||
runnable.originalTitle === '"after all" hook'
) {
ensureTestShellAfterHook('afterAll', parent);
parent[TEST_SHELLS_AFTER_ALL].add(shell);
} else {
throw new Error(`Unexpected ${runnable.originalTitle || runnable.title}`);
}
} else if (runnable instanceof Mocha.Test) {
ensureTestShellAfterHook('afterEach', parent);
parent[TEST_SHELLS_AFTER_EACH].add(shell);
} else {
throw new Error('Unexpected Runnable: Expected a Hook or a Test');
}
return shell;
};

0 comments on commit 0601808

Please sign in to comment.