UNPKG

@stryker-mutator/core

Version:

The extendable JavaScript mutation testing framework

179 lines 8.95 kB
import fs from 'fs'; import { fileURLToPath } from 'url'; import path from 'path'; import { expect } from 'chai'; import log4js from 'log4js'; import { lastValueFrom, toArray } from 'rxjs'; import { LoggingServer, testInjector, factory, assertions } from '@stryker-mutator/test-helpers'; import { DryRunStatus } from '@stryker-mutator/api/test-runner'; import { createTestRunnerFactory } from '../../../src/test-runner/index.js'; import { sleep } from '../../helpers/test-utils.js'; import { coreTokens } from '../../../src/di/index.js'; import { IdGenerator } from '../../../src/child-proxy/id-generator.js'; import { additionalTestRunnersFileUrl, CounterTestRunner } from './additional-test-runners.js'; describe(`${createTestRunnerFactory.name} integration`, () => { let createSut; let sut; let loggingContext; let loggingServer; let alreadyDisposed; const pluginModulePaths = Object.freeze([additionalTestRunnersFileUrl]); function rmSync(fileName) { if (fs.existsSync(fileName)) { fs.unlinkSync(fileName); } } beforeEach(async () => { // Make sure there is a logging server listening loggingServer = new LoggingServer(); const port = await loggingServer.listen(); loggingContext = { port, level: "trace" /* LogLevel.Trace */ }; testInjector.options.someRegex = /someRegex/; testInjector.options.testRunner = 'karma'; testInjector.options.maxTestRunnerReuse = 0; alreadyDisposed = false; createSut = testInjector.injector .provideValue(coreTokens.sandbox, { workingDirectory: path.dirname(fileURLToPath(import.meta.url)) }) .provideValue(coreTokens.loggingContext, loggingContext) .provideValue(coreTokens.pluginModulePaths, pluginModulePaths) .provideClass(coreTokens.workerIdGenerator, IdGenerator) .injectFunction(createTestRunnerFactory); rmSync(CounterTestRunner.COUNTER_FILE); }); afterEach(async () => { var _a; if (!alreadyDisposed) { await ((_a = sut.dispose) === null || _a === void 0 ? void 0 : _a.call(sut)); } await loggingServer.dispose(); rmSync(CounterTestRunner.COUNTER_FILE); }); async function arrangeSut(name) { var _a; testInjector.options.testRunner = name; sut = createSut(); await ((_a = sut.init) === null || _a === void 0 ? void 0 : _a.call(sut)); } function actDryRun(timeout = 4000) { return sut.dryRun(factory.dryRunOptions({ timeout, coverageAnalysis: 'all' })); } function actMutantRun(options = factory.mutantRunOptions()) { return sut.mutantRun(options); } it('should pass along the coverage result from the test runner behind', async () => { await arrangeSut('coverage-reporting'); const result = await actDryRun(); assertions.expectCompleted(result); expect(result.mutantCoverage).deep.eq(factory.mutantCoverage({ static: { 1: 42 } })); }); it('should pass along the run result', async () => { await arrangeSut('direct-resolved'); const result = await actDryRun(); expect(result.status).eq(DryRunStatus.Complete); }); it('should try to report coverage from the global scope, even when the test runner behind does not', async () => { await arrangeSut('direct-resolved'); const result = await actDryRun(); assertions.expectCompleted(result); expect(result.mutantCoverage).eq('coverageObject'); }); it('should resolve in a timeout if the test runner never resolves', async () => { await arrangeSut('never-resolved'); const result = await actDryRun(1000); expect(result.status).eq(DryRunStatus.Timeout); }); it('should be able to recover from a timeout by creating a new child process', async () => { await arrangeSut('never-resolved'); await actDryRun(1000); // first timeout const result = await actDryRun(1000); expect(result.status).eq(DryRunStatus.Timeout); }); it('should convert any `Error` objects to string', async () => { await arrangeSut('errored'); const result = await actDryRun(1000); assertions.expectErrored(result); expect(result.errorMessage).includes('SyntaxError: This is invalid syntax!').and.includes('at ErroredTestRunner.dryRun'); }); it('should run only after initialization, even when it is slow', async () => { await arrangeSut('slow-init-dispose'); const result = await actDryRun(1000); assertions.expectCompleted(result); }); it('should be able to run twice in quick succession', async () => { await arrangeSut('direct-resolved'); const result = await actDryRun(); assertions.expectCompleted(result); }); it('should reject when `init` of test runner behind rejects', async () => { await expect(arrangeSut('reject-init')).rejectedWith('Init was rejected'); }); it('should still shutdown the child process, even when test runner dispose rejects', async () => { var _a; await arrangeSut('errored'); await ((_a = sut.dispose) === null || _a === void 0 ? void 0 : _a.call(sut)); }); it('should change the current working directory to the sandbox directory', async () => { await arrangeSut('verify-working-folder'); const result = await actDryRun(); assertions.expectCompleted(result); }); it('should be able to recover from an async crash', async () => { // time-bomb will crash after 500 ms await arrangeSut('time-bomb'); await sleep(550); const result = await actDryRun(); assertions.expectCompleted(result); }); it('should report if a crash happens twice', async () => { await arrangeSut('proximity-mine'); const result = await actDryRun(); assertions.expectErrored(result); expect(result.errorMessage).contains('Test runner crashed'); }); it('should handle asynchronously handled promise rejections from the underlying test runner', async () => { var _a; const logEvents = lastValueFrom(loggingServer.event$.pipe(toArray())); await arrangeSut('async-promise-rejection-handler'); await actDryRun(); await ((_a = sut.dispose) === null || _a === void 0 ? void 0 : _a.call(sut)); alreadyDisposed = true; await loggingServer.dispose(); const actualLogEvents = await logEvents; expect(actualLogEvents.find((logEvent) => log4js.levels.DEBUG.isEqualTo(logEvent.level) && logEvent.data.toString().includes('UnhandledPromiseRejectionWarning: Unhandled promise rejection'))).ok; }); it('should restart the worker after it has exceeded the maxTestRunnerReuse', async () => { testInjector.options.maxTestRunnerReuse = 3; await arrangeSut('counter'); await actMutantRun(); expect(fs.readFileSync(CounterTestRunner.COUNTER_FILE, 'utf8')).to.equal('1'); await actMutantRun(); expect(fs.readFileSync(CounterTestRunner.COUNTER_FILE, 'utf8')).to.equal('2'); await actMutantRun(); expect(fs.readFileSync(CounterTestRunner.COUNTER_FILE, 'utf8')).to.equal('3'); await actMutantRun(); expect(fs.readFileSync(CounterTestRunner.COUNTER_FILE, 'utf8')).to.equal('1'); }); describe('running static mutants', () => { beforeEach(async () => { await arrangeSut('static'); }); it('should not reload environment when reloadEnvironment = false', async () => { const result1 = await actMutantRun(factory.mutantRunOptions({ reloadEnvironment: false })); const result2 = await actMutantRun(factory.mutantRunOptions({ reloadEnvironment: false })); assertions.expectKilled(result1); // killed means it was the first run in the environment assertions.expectSurvived(result2); // survived means it was the second run in the environment }); it('should reload environment when reloadEnvironment is true', async () => { await actMutantRun(factory.mutantRunOptions({ reloadEnvironment: false })); const result = await actMutantRun(factory.mutantRunOptions({ activeMutant: factory.mutantTestCoverage({ id: '2' }), reloadEnvironment: true })); assertions.expectKilled(result); }); it('should reload environment when reloadEnvironment is true and dry run came before', async () => { await actDryRun(); const result = await actMutantRun(factory.mutantRunOptions({ activeMutant: factory.mutantTestCoverage({ id: '2' }), reloadEnvironment: true })); assertions.expectKilled(result); }); }); }); //# sourceMappingURL=create-test-runner-factory.it.spec.js.map