From c2f6b82cde18772103853357ca4fb72cc099dbf6 Mon Sep 17 00:00:00 2001 From: Nico Jansen Date: Tue, 17 Jul 2018 08:59:58 +0200 Subject: [PATCH] feat(logging): Allow log to a file (#954) This feature adds the ability to log to a "stryker.log" file using a separate log level. You can configure it like this: ```js // stryker.conf.js module.exports = function(config) { config.set({ fileLogLevel: 'trace' // defaults to 'off' }); } ``` **Note:** The console logger can still be configured as expected using `logLevel: 'info'`. This also adds logging to the stryker api. Plugin creators can now opt into stryker logging using the new logging api: ```ts import { getLogger, Logger } from 'stryker-api/logging'; const logger: Logger = getLogger('myLoggerCategory'); ``` They no longer have to 'role their own config'. Stryker will take care of the logging appearing in the expected places (console and file). Fixes #748 --- .gitignore | 1 + .../test/jasmine-jasmine/package.json | 2 +- .../test/jasmine-jasmine/stryker.conf.js | 6 +- .../test/jasmine-jasmine/verify/verify.ts | 8 + package.json | 1 - packages/stryker-api/core.ts | 3 +- packages/stryker-api/logging.ts | 4 + packages/stryker-api/src/config/Config.ts | 5 +- packages/stryker-api/src/core/LogLevel.ts | 12 ++ .../stryker-api/src/core/StrykerOptions.ts | 43 +++-- packages/stryker-api/src/logging/Logger.ts | 15 ++ .../stryker-api/src/logging/LoggerFactory.ts | 30 +++ .../src/logging/LoggerFactoryMethod.ts | 12 ++ packages/stryker-api/src/logging/getLogger.ts | 6 + .../install-module/install-module.ts | 18 +- .../test/unit/core/LogLevelSpec.ts | 19 ++ .../testResources/module/useCore.ts | 4 +- .../stryker-babel-transpiler/package.json | 3 +- .../src/BabelConfigReader.ts | 3 +- .../src/BabelTranspiler.ts | 2 - .../test/unit/BabelConfigReaderSpec.ts | 4 +- packages/stryker-html-reporter/package.json | 3 +- .../stryker-html-reporter/src/HtmlReporter.ts | 12 +- .../helpers/{log4jsMock.ts => loggingMock.ts} | 4 +- .../test/integration/MathReportSpec.ts | 2 +- packages/stryker-jasmine-runner/package.json | 2 +- packages/stryker-jasmine/package.json | 5 +- .../stryker-javascript-mutator/package.json | 3 +- .../src/JavaScriptMutator.ts | 3 +- .../test/helpers/LogMock.ts | 2 +- .../test/helpers/initSinon.ts | 4 +- packages/stryker-jest-runner/package.json | 4 +- .../stryker-jest-runner/src/JestTestRunner.ts | 7 +- .../JestPromiseTestAdapter.ts | 2 +- .../JestTestAdapterFactory.ts | 2 +- .../test/unit/JestTestRunnerSpec.ts | 4 +- .../JestPromiseTestAdapterSpec.ts | 4 +- packages/stryker-karma-runner/package.json | 4 +- .../src/KarmaTestRunner.ts | 6 +- .../src/starters/stryker-karma.conf.ts | 2 +- .../test/integration/KarmaTestRunner.it.ts | 6 +- .../test/unit/KarmaTestRunnerSpec.ts | 4 +- .../unit/starters/stryker-karma.confSpec.ts | 6 +- packages/stryker-mocha-framework/package.json | 6 +- packages/stryker-mocha-runner/package.json | 3 +- .../src/MochaConfigEditor.ts | 2 - .../src/MochaOptionsLoader.ts | 2 +- .../src/MochaTestRunner.ts | 3 +- .../src/StrykerMochaReporter.ts | 15 +- .../test/helpers/mockHelpers.ts | 4 +- .../test/unit/MochaOptionsLoaderSpec.ts | 6 +- .../test/unit/MochaTestRunnerSpec.ts | 6 +- .../package.json | 3 +- packages/stryker-typescript/package.json | 3 +- .../src/TypescriptConfigEditor.ts | 3 +- .../src/TypescriptTranspiler.ts | 2 - .../transpiler/TranspilingLanguageService.ts | 2 +- .../test/helpers/producers.ts | 4 +- .../test/integration/allowJS.it.ts | 6 - .../test/integration/ownDogFoodSpec.ts | 6 - .../test/integration/sampleSpec.ts | 6 - .../test/integration/useHeaderFile.ts | 6 - .../test/unit/TypescriptConfigEditorSpec.ts | 4 +- .../test/unit/TypescriptTranspilerSpec.ts | 8 - .../stryker-webpack-transpiler/package.json | 3 +- .../src/WebpackTranspiler.ts | 2 - .../src/compiler/ConfigLoader.ts | 2 +- .../test/helpers/sinonInit.ts | 4 +- .../test/unit/WebpackTranspilerSpec.ts | 8 - packages/stryker/.vscode/launch.json | 2 +- packages/stryker/README.md | 11 +- packages/stryker/package.json | 10 +- packages/stryker/src/MutantTestMatcher.ts | 2 +- packages/stryker/src/PluginLoader.ts | 2 +- packages/stryker/src/ReporterOrchestrator.ts | 2 +- packages/stryker/src/Sandbox.ts | 29 ++- packages/stryker/src/SandboxPool.ts | 7 +- packages/stryker/src/ScoreResultCalculator.ts | 2 +- packages/stryker/src/Stryker.ts | 24 +-- packages/stryker/src/StrykerCli.ts | 24 +-- .../stryker/src/TestFrameworkOrchestrator.ts | 2 +- .../src/child-proxy/ChildProcessProxy.ts | 27 ++- .../child-proxy/ChildProcessProxyWorker.ts | 16 +- .../src/child-proxy/messageProtocol.ts | 16 +- .../stryker/src/{ => config}/ConfigReader.ts | 24 ++- .../src/{ => config}/ConfigValidator.ts | 16 +- packages/stryker/src/initializer/NpmClient.ts | 2 +- .../src/initializer/StrykerConfigWriter.ts | 2 +- .../src/initializer/StrykerInitializer.ts | 2 +- .../stryker/src/input/InputFileCollection.ts | 2 +- .../stryker/src/input/InputFileResolver.ts | 2 +- .../isolated-runner/IsolatedRunnerOptions.ts | 7 - .../IsolatedTestRunnerAdapter.ts | 15 +- .../IsolatedTestRunnerAdapterWorker.ts | 23 ++- .../src/isolated-runner/MessageProtocol.ts | 8 +- .../ResilientTestRunnerFactory.ts | 7 +- .../isolated-runner/TestRunnerDecorator.ts | 2 - .../src/isolated-runner/TimeoutDecorator.ts | 2 +- .../stryker/src/logging/LogConfigurator.ts | 143 ++++++++++++++ .../src/logging/LoggingClientContext.ts | 13 ++ packages/stryker/src/logging/MultiAppender.ts | 28 +++ packages/stryker/src/logging/logUtils.ts | 15 ++ packages/stryker/src/mutators/ES5Mutator.ts | 2 +- .../src/process/InitialTestExecutor.ts | 7 +- .../src/process/MutationTestExecutor.ts | 8 +- .../src/reporters/BroadcastReporter.ts | 2 +- .../src/reporters/ClearTextReporter.ts | 2 +- .../src/reporters/DashboardReporter.ts | 2 +- .../src/reporters/EventRecorderReporter.ts | 2 +- .../DashboardReporterClient.ts | 2 +- .../src/transpiler/MutantTranspiler.ts | 5 +- .../stryker/src/transpiler/SourceMapper.ts | 2 +- .../stryker/src/utils/StrykerTempFolder.ts | 2 +- packages/stryker/src/utils/TempFolder.ts | 2 +- packages/stryker/src/utils/netUtils.ts | 6 + packages/stryker/src/utils/objectUtils.ts | 10 +- .../stryker/test/helpers/LoggingServer.ts | 47 +++++ .../helpers/{log4jsMock.ts => logMock.ts} | 6 +- packages/stryker/test/helpers/producers.ts | 4 +- .../child-proxy/ChildProcessProxy.it.ts | 46 ++++- .../test/integration/child-proxy/Echo.ts | 12 ++ .../config-reader/ConfigReaderSpec.ts | 40 ++-- .../isolated-runner/AdditionalTestRunners.ts | 45 +++-- .../ResilientTestRunnerFactory.it.ts | 161 ++++++++++++++++ .../ResilientTestRunnerFactorySpec.ts | 178 ------------------ .../source-mapper/SourceMapperIT.ts | 3 +- .../test/integration/utils/fileUtilsSpec.ts | 4 +- .../test/unit/MutantTestMatcherSpec.ts | 4 +- .../stryker/test/unit/PluginLoaderSpec.ts | 4 +- packages/stryker/test/unit/SandboxPoolSpec.ts | 9 +- packages/stryker/test/unit/SandboxSpec.ts | 69 +++---- .../test/unit/ScoreResultCalculatorSpec.ts | 4 +- packages/stryker/test/unit/StrykerSpec.ts | 45 ++++- .../unit/TestFrameworkOrchestratorSpec.ts | 4 +- .../unit/child-proxy/ChildProcessProxySpec.ts | 16 +- .../child-proxy/ChildProcessWorkerSpec.ts | 14 +- .../unit/{ => config}/ConfigValidatorSpec.ts | 75 +++----- .../initializer/StrykerInitializerSpec.ts | 4 +- .../test/unit/input/InputFileResolverSpec.ts | 8 +- .../IsolatedTestRunnerAdapterSpec.ts | 10 +- .../test/unit/logging/LogConfiguratorSpec.ts | 103 ++++++++++ .../test/unit/logging/MultiAppenderSpec.ts | 40 ++++ .../unit/process/InitialTestExecutorSpec.ts | 16 +- .../unit/process/MutationTestExecutorSpec.ts | 13 +- .../unit/reporters/BroadcastReporterSpec.ts | 4 +- .../unit/reporters/DashboardReporterSpec.ts | 4 +- .../reporters/EventRecorderReporterSpec.ts | 2 +- .../DashboardReporterClientSpec.ts | 4 +- .../unit/transpiler/MutantTranspilerSpec.ts | 25 ++- 149 files changed, 1269 insertions(+), 708 deletions(-) create mode 100644 packages/stryker-api/logging.ts create mode 100644 packages/stryker-api/src/core/LogLevel.ts create mode 100644 packages/stryker-api/src/logging/Logger.ts create mode 100644 packages/stryker-api/src/logging/LoggerFactory.ts create mode 100644 packages/stryker-api/src/logging/LoggerFactoryMethod.ts create mode 100644 packages/stryker-api/src/logging/getLogger.ts create mode 100644 packages/stryker-api/test/unit/core/LogLevelSpec.ts rename packages/stryker-html-reporter/test/helpers/{log4jsMock.ts => loggingMock.ts} (85%) rename packages/stryker/src/{ => config}/ConfigReader.ts (71%) rename packages/stryker/src/{ => config}/ConfigValidator.ts (88%) delete mode 100644 packages/stryker/src/isolated-runner/IsolatedRunnerOptions.ts create mode 100644 packages/stryker/src/logging/LogConfigurator.ts create mode 100644 packages/stryker/src/logging/LoggingClientContext.ts create mode 100644 packages/stryker/src/logging/MultiAppender.ts create mode 100644 packages/stryker/src/logging/logUtils.ts create mode 100644 packages/stryker/src/utils/netUtils.ts create mode 100644 packages/stryker/test/helpers/LoggingServer.ts rename packages/stryker/test/helpers/{log4jsMock.ts => logMock.ts} (52%) create mode 100644 packages/stryker/test/integration/isolated-runner/ResilientTestRunnerFactory.it.ts delete mode 100644 packages/stryker/test/integration/isolated-runner/ResilientTestRunnerFactorySpec.ts rename packages/stryker/test/unit/{ => config}/ConfigValidatorSpec.ts (83%) create mode 100644 packages/stryker/test/unit/logging/LogConfiguratorSpec.ts create mode 100644 packages/stryker/test/unit/logging/MultiAppenderSpec.ts diff --git a/.gitignore b/.gitignore index ef9d3a69dc..42c0746f6f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ node_modules lerna-debug.log npm-debug.log +stryker.log coverage reports diff --git a/integrationTest/test/jasmine-jasmine/package.json b/integrationTest/test/jasmine-jasmine/package.json index 646cb2e295..80274018cb 100644 --- a/integrationTest/test/jasmine-jasmine/package.json +++ b/integrationTest/test/jasmine-jasmine/package.json @@ -5,7 +5,7 @@ "description": "A module to perform an integration test", "main": "index.js", "scripts": { - "pretest": "rimraf \"reports\" \"verify/*.map\" && tsc -p .", + "pretest": "rimraf \"reports\" \"verify/*.map\" \"stryker.log\"", "test": "stryker run", "posttest": "mocha --require ts-node/register verify/*.ts" }, diff --git a/integrationTest/test/jasmine-jasmine/stryker.conf.js b/integrationTest/test/jasmine-jasmine/stryker.conf.js index f51e1e58d9..0e6e278843 100644 --- a/integrationTest/test/jasmine-jasmine/stryker.conf.js +++ b/integrationTest/test/jasmine-jasmine/stryker.conf.js @@ -1,3 +1,4 @@ +const { LogLevel } = require('stryker-api/core'); module.exports = function (config) { config.set({ mutate: ['lib/**/*.js'], @@ -6,7 +7,8 @@ module.exports = function (config) { testFramework: 'jasmine', testRunner: 'jasmine', reporter: ['clear-text', 'event-recorder'], - maxConcurrentTestRunners: 2, - jasmineConfigFile: 'spec/support/jasmine.json' + maxConcurrentTestRunners: 1, + jasmineConfigFile: 'spec/support/jasmine.json', + fileLogLevel: LogLevel.Debug }); }; diff --git a/integrationTest/test/jasmine-jasmine/verify/verify.ts b/integrationTest/test/jasmine-jasmine/verify/verify.ts index dd7878a7d8..32acb9838d 100644 --- a/integrationTest/test/jasmine-jasmine/verify/verify.ts +++ b/integrationTest/test/jasmine-jasmine/verify/verify.ts @@ -14,4 +14,12 @@ describe('After running stryker with test runner jasmine, test framework jasmine expect(scoreResult.noCoverage).eq(1); expect(scoreResult.mutationScore).greaterThan(85).and.lessThan(86); }); + + it('should write to a log file', async () => { + const strykerLog = await fs.readFile('./stryker.log', 'utf8'); + expect(strykerLog).contains('INFO InputFileResolver Found 2 of 10 file(s) to be mutated'); + expect(strykerLog).matches(/Stryker Done in \d+ seconds/); + // TODO, we now have an error because of a memory leak: https://github.com/jasmine/jasmine-npm/issues/134 + // expect(strykerLog).not.contains('ERROR'); + }); }); \ No newline at end of file diff --git a/package.json b/package.json index bd17307cd3..9f9c6c3165 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,6 @@ "@types/istanbul": "^0.4.29", "@types/karma": "^1.7.4", "@types/lodash": "^4.14.110", - "@types/log4js": "0.0.32", "@types/mkdirp": "^0.5.2", "@types/mocha": "^2.2.44", "@types/mz": "0.0.32", diff --git a/packages/stryker-api/core.ts b/packages/stryker-api/core.ts index 8db5735a93..cb8fbcab1f 100644 --- a/packages/stryker-api/core.ts +++ b/packages/stryker-api/core.ts @@ -5,4 +5,5 @@ export { default as Position } from './src/core/Position'; export { default as Location } from './src/core/Location'; export { default as Range } from './src/core/Range'; export { default as MutatorDescriptor } from './src/core/MutatorDescriptor'; -export { default as MutationScoreThresholds } from './src/core/MutationScoreThresholds'; \ No newline at end of file +export { default as MutationScoreThresholds } from './src/core/MutationScoreThresholds'; +export { default as LogLevel } from './src/core/LogLevel'; \ No newline at end of file diff --git a/packages/stryker-api/logging.ts b/packages/stryker-api/logging.ts new file mode 100644 index 0000000000..f7ea628c1b --- /dev/null +++ b/packages/stryker-api/logging.ts @@ -0,0 +1,4 @@ +export { default as LoggerFactory } from './src/logging/LoggerFactory'; +export { default as Logger } from './src/logging/Logger'; +export { default as LoggerFactoryMethod } from './src/logging/LoggerFactoryMethod'; +export { default as getLogger } from './src/logging/getLogger'; \ No newline at end of file diff --git a/packages/stryker-api/src/config/Config.ts b/packages/stryker-api/src/config/Config.ts index 2e46ffefad..cc59e1beb3 100644 --- a/packages/stryker-api/src/config/Config.ts +++ b/packages/stryker-api/src/config/Config.ts @@ -1,4 +1,4 @@ -import { StrykerOptions, MutatorDescriptor, MutationScoreThresholds } from '../../core'; +import { LogLevel, StrykerOptions, MutatorDescriptor, MutationScoreThresholds } from '../../core'; export default class Config implements StrykerOptions { @@ -7,7 +7,8 @@ export default class Config implements StrykerOptions { files: string[]; mutate: string[]; - logLevel = 'info'; + logLevel: LogLevel = LogLevel.Information; + fileLogLevel: LogLevel = LogLevel.Off; timeoutMs = 5000; timeoutFactor = 1.5; plugins: string[] = ['stryker-*']; diff --git a/packages/stryker-api/src/core/LogLevel.ts b/packages/stryker-api/src/core/LogLevel.ts new file mode 100644 index 0000000000..310a53e242 --- /dev/null +++ b/packages/stryker-api/src/core/LogLevel.ts @@ -0,0 +1,12 @@ + +enum LogLevel { + Off = 'off', + Fatal = 'fatal', + Error = 'error', + Warning = 'warn', + Information = 'info', + Debug = 'debug', + Trace = 'trace' +} + +export default LogLevel; \ No newline at end of file diff --git a/packages/stryker-api/src/core/StrykerOptions.ts b/packages/stryker-api/src/core/StrykerOptions.ts index 9f3dc91e09..b9fad23c28 100644 --- a/packages/stryker-api/src/core/StrykerOptions.ts +++ b/packages/stryker-api/src/core/StrykerOptions.ts @@ -1,30 +1,31 @@ import MutationScoreThresholds from './MutationScoreThresholds'; import MutatorDescriptor from './MutatorDescriptor'; +import LogLevel from './LogLevel'; interface StrykerOptions { - // this ensures that custom config for for example 'karma' can be added under the 'karma' key + // this ensures that plugins can load custom config. [customConfig: string]: any; /** - * The files array determines which files are in scope for mutation testing. - * These include library files, test files and files to mutate, but should NOT include test framework files (for example jasmine). - * Each element can be either a string or an object with 2 properties - * * `string`: A globbing expression used for selecting the files needed to run the tests. - * * { pattern: 'pattern', included: true, mutated: false, transpiled: true }: - * * The `pattern` property is mandatory and contains the globbing expression used for selecting the files - * * The `included` property is optional and determines whether or not this file should be loaded initially by the test-runner (default: true) - * * The `mutated` property is optional and determines whether or not this file should be targeted for mutations (default: false) - * * The `transpiled` property is optional and determines whether or not this file should be transpiled by a transpiler (see `transpilers` config option) (default: true) - * - * @example - * files: ['test/helpers/**\/*.js', 'test/unit/**\/*.js', { pattern: 'src/**\/*.js', included: false }], + * A list of globbing expression used for selecting the files that should be mutated. */ - files?: string[]; + mutate?: string[]; /** - * A list of globbing expression used for selecting the files that should be mutated. + * With `files` you can choose which files should be included in your test runner sandbox. + * This is normally not needed as it defaults to all files not ignored by git. + * Try it out yourself with this command: `git ls-files --others --exclude-standard --cached --exclude .stryker-tmp`. + * + * If you do need to override `files` (for example: when your project does not live in a git repository), + * you can override the files here. + * + * When using the command line, the list can only contain a comma separated list of globbing expressions. + * When using the config file you can provide an array with `string`s + * + * You can *ignore* files by adding an exclamation mark (`!`) at the start of an expression. + * */ - mutate?: string[]; + files?: string[]; /** * Specify the maximum number of concurrent test runners. Useful if you don't want to use @@ -104,9 +105,15 @@ interface StrykerOptions { reporter?: string | string[]; /** - * The log4js log level. Possible values: fatal, error, warn, info, debug, trace, all and off. Default is "info" + * The log level for logging to a file. If defined, stryker will output a log file called "stryker.log". + * Default: "off" + */ + fileLogLevel?: LogLevel; + + /** + * The log level for logging to the console. Default: "info". */ - logLevel?: string; + logLevel?: LogLevel; /** * Indicates whether or not to symlink the node_modules folder inside the sandbox folder(s). diff --git a/packages/stryker-api/src/logging/Logger.ts b/packages/stryker-api/src/logging/Logger.ts new file mode 100644 index 0000000000..029def6ebc --- /dev/null +++ b/packages/stryker-api/src/logging/Logger.ts @@ -0,0 +1,15 @@ +export default interface Logger { + isTraceEnabled(): boolean; + isDebugEnabled(): boolean; + isInfoEnabled(): boolean; + isWarnEnabled(): boolean; + isErrorEnabled(): boolean; + isFatalEnabled(): boolean; + + trace(message: string, ...args: any[]): void; + debug(message: string, ...args: any[]): void; + info(message: string, ...args: any[]): void; + warn(message: string, ...args: any[]): void; + error(message: string, ...args: any[]): void; + fatal(message: string, ...args: any[]): void; +} \ No newline at end of file diff --git a/packages/stryker-api/src/logging/LoggerFactory.ts b/packages/stryker-api/src/logging/LoggerFactory.ts new file mode 100644 index 0000000000..0f3fc55966 --- /dev/null +++ b/packages/stryker-api/src/logging/LoggerFactory.ts @@ -0,0 +1,30 @@ +import LoggerFactoryMethod from './LoggerFactoryMethod'; +import Logger from './Logger'; + +const noopLogger: Logger = { + isTraceEnabled(): boolean { return false; }, + isDebugEnabled(): boolean { return false; }, + isInfoEnabled(): boolean { return false; }, + isWarnEnabled(): boolean { return false; }, + isErrorEnabled(): boolean { return false; }, + isFatalEnabled(): boolean { return false; }, + trace(): void { }, + debug(): void { }, + info(): void { }, + warn(): void { }, + error(): void { }, + fatal(): void { } +}; + +let logImplementation: LoggerFactoryMethod = () => noopLogger; + +export default class LoggerFactory { + + static setLogImplementation(implementation: LoggerFactoryMethod) { + logImplementation = implementation; + } + + static getLogger: LoggerFactoryMethod = (categoryName?: string) => { + return logImplementation(categoryName); + } +} \ No newline at end of file diff --git a/packages/stryker-api/src/logging/LoggerFactoryMethod.ts b/packages/stryker-api/src/logging/LoggerFactoryMethod.ts new file mode 100644 index 0000000000..7aaf3e47d4 --- /dev/null +++ b/packages/stryker-api/src/logging/LoggerFactoryMethod.ts @@ -0,0 +1,12 @@ +import Logger from './Logger'; + +/** + * Represents a factory to get loggers by category name. + * This interface is used to describe the shape of a logger factory method. + * + * @param {String} [categoryName] name of category to log to. + * @returns {Logger} instance of logger for the category + */ +export default interface LoggerFactoryMethod { + (categoryName?: string): Logger; +} \ No newline at end of file diff --git a/packages/stryker-api/src/logging/getLogger.ts b/packages/stryker-api/src/logging/getLogger.ts new file mode 100644 index 0000000000..fbd602e2aa --- /dev/null +++ b/packages/stryker-api/src/logging/getLogger.ts @@ -0,0 +1,6 @@ +import LoggerFactory from './LoggerFactory'; +import LoggerFactoryMethod from './LoggerFactoryMethod'; + +const getLogger: LoggerFactoryMethod = LoggerFactory.getLogger; + +export default getLogger; \ No newline at end of file diff --git a/packages/stryker-api/test/integration/install-module/install-module.ts b/packages/stryker-api/test/integration/install-module/install-module.ts index 155d56115c..22f3745597 100644 --- a/packages/stryker-api/test/integration/install-module/install-module.ts +++ b/packages/stryker-api/test/integration/install-module/install-module.ts @@ -8,10 +8,20 @@ describe('we have a module using stryker', function () { const modulePath = path.resolve(__dirname, '../../../testResources/module'); - const execInModule = (command: string) => { - console.log(`Exec '${command}' in ${modulePath}`); - return exec(command, { cwd: modulePath }); - }; + function execInModule (command: string): Promise<[string, string]> { + return new Promise((res, rej) => { + console.log(`Exec '${command}' in ${modulePath}`); + exec(command, { cwd: modulePath }, (error, stdout, stderr) => { + if (error) { + console.log(stdout); + console.error(stderr); + rej(error); + } else { + res([stdout, stderr]); + } + }); + }); + } describe('after installing Stryker', () => { diff --git a/packages/stryker-api/test/unit/core/LogLevelSpec.ts b/packages/stryker-api/test/unit/core/LogLevelSpec.ts new file mode 100644 index 0000000000..8afcd3e3fe --- /dev/null +++ b/packages/stryker-api/test/unit/core/LogLevelSpec.ts @@ -0,0 +1,19 @@ +import { expect } from 'chai'; +import LogLevel from '../../../src/core/LogLevel'; + +describe('LogLevel', () => { + + function arrangeActAssertLogLevel(actual: LogLevel, expected: string) { + it(`should provide "${expected}" for log level "${actual}"`, () => { + expect(actual).eq(expected); + }); + } + + arrangeActAssertLogLevel(LogLevel.Off, 'off'); + arrangeActAssertLogLevel(LogLevel.Fatal, 'fatal'); + arrangeActAssertLogLevel(LogLevel.Error, 'error'); + arrangeActAssertLogLevel(LogLevel.Warning, 'warn'); + arrangeActAssertLogLevel(LogLevel.Information, 'info'); + arrangeActAssertLogLevel(LogLevel.Debug, 'debug'); + arrangeActAssertLogLevel(LogLevel.Trace, 'trace'); +}); \ No newline at end of file diff --git a/packages/stryker-api/testResources/module/useCore.ts b/packages/stryker-api/testResources/module/useCore.ts index 816f14eea1..af6c3ea136 100644 --- a/packages/stryker-api/testResources/module/useCore.ts +++ b/packages/stryker-api/testResources/module/useCore.ts @@ -1,4 +1,4 @@ -import { StrykerOptions, Factory, File, Position, Location, Range } from 'stryker-api/core'; +import { StrykerOptions, Factory, File, Position, Location, Range, LogLevel } from 'stryker-api/core'; const options: StrykerOptions = {}; const optionsAllArgs: StrykerOptions = { @@ -8,7 +8,7 @@ const optionsAllArgs: StrykerOptions = { testFramework: 'string', testRunner: 'string', reporter: 'string', - logLevel: 'string', + logLevel: LogLevel.Fatal, timeoutMs: 1, timeoutFactor: 2, plugins: ['string'], diff --git a/packages/stryker-babel-transpiler/package.json b/packages/stryker-babel-transpiler/package.json index 2818dfc7ae..4199bb8631 100644 --- a/packages/stryker-babel-transpiler/package.json +++ b/packages/stryker-babel-transpiler/package.json @@ -45,7 +45,6 @@ "license": "Apache-2.0", "dependencies": { "babel-core": "6.26.3", - "log4js": "~1.1.1", "minimatch": "~3.0.4" }, "devDependencies": { @@ -60,7 +59,7 @@ }, "peerDependencies": { "babel-core": "^6.26.0", - "stryker-api": ">=0.15.0 <0.18.0" + "stryker-api": "^0.18.0" }, "initStrykerConfig": { "babelrcFile": ".babelrc", diff --git a/packages/stryker-babel-transpiler/src/BabelConfigReader.ts b/packages/stryker-babel-transpiler/src/BabelConfigReader.ts index a102bbc3ef..2ac3035f29 100644 --- a/packages/stryker-babel-transpiler/src/BabelConfigReader.ts +++ b/packages/stryker-babel-transpiler/src/BabelConfigReader.ts @@ -2,13 +2,12 @@ import * as fs from 'fs'; import * as path from 'path'; import { Config } from 'stryker-api/config'; import { CONFIG_KEY_FILE, CONFIG_KEY_OPTIONS } from './helpers/keys'; -import { getLogger, setGlobalLogLevel } from 'log4js'; +import { getLogger } from 'stryker-api/logging'; export default class BabelConfigReader { private readonly log = getLogger(BabelConfigReader.name); public readConfig(config: Config): babel.TransformOptions { - setGlobalLogLevel(config.logLevel); const babelConfig: babel.TransformOptions = config[CONFIG_KEY_OPTIONS] || this.getConfigFile(config) || {}; this.log.debug(`babel config is: ${JSON.stringify(babelConfig, null, 2)}`); return babelConfig; diff --git a/packages/stryker-babel-transpiler/src/BabelTranspiler.ts b/packages/stryker-babel-transpiler/src/BabelTranspiler.ts index fd411f142a..79e958d82a 100644 --- a/packages/stryker-babel-transpiler/src/BabelTranspiler.ts +++ b/packages/stryker-babel-transpiler/src/BabelTranspiler.ts @@ -5,7 +5,6 @@ import * as path from 'path'; import BabelConfigReader from './BabelConfigReader'; import { CONFIG_KEY_FILE } from './helpers/keys'; import { toJSFileName } from './helpers/helpers'; -import { setGlobalLogLevel } from 'log4js'; const KNOWN_EXTENSIONS = Object.freeze([ '.es6', @@ -21,7 +20,6 @@ class BabelTranspiler implements Transpiler { private projectRoot: string; public constructor(options: TranspilerOptions) { - setGlobalLogLevel(options.config.logLevel); this.babelOptions = new BabelConfigReader().readConfig(options.config); this.projectRoot = this.determineProjectRoot(options); if (options.produceSourceMaps) { diff --git a/packages/stryker-babel-transpiler/test/unit/BabelConfigReaderSpec.ts b/packages/stryker-babel-transpiler/test/unit/BabelConfigReaderSpec.ts index 6b92e557ef..5e2e009b5a 100644 --- a/packages/stryker-babel-transpiler/test/unit/BabelConfigReaderSpec.ts +++ b/packages/stryker-babel-transpiler/test/unit/BabelConfigReaderSpec.ts @@ -2,7 +2,7 @@ import BabelConfigReader from '../../src/BabelConfigReader'; import { Config } from 'stryker-api/config'; import { expect } from 'chai'; import * as fs from 'fs'; -import * as log4js from 'log4js'; +import * as logging from 'stryker-api/logging'; import * as sinon from 'sinon'; import * as path from 'path'; @@ -24,7 +24,7 @@ describe('BabelConfigReader', () => { debug: sandbox.stub() }; - sandbox.stub(log4js, 'getLogger').returns(logStub); + sandbox.stub(logging, 'getLogger').returns(logStub); }); afterEach(() => { diff --git a/packages/stryker-html-reporter/package.json b/packages/stryker-html-reporter/package.json index c39110c862..287784930a 100644 --- a/packages/stryker-html-reporter/package.json +++ b/packages/stryker-html-reporter/package.json @@ -38,14 +38,13 @@ "dependencies": { "file-url": "~2.0.0", "lodash": "~4.17.10", - "log4js": "~1.1.1", "mkdirp": "~0.5.1", "mz": "~2.7.0", "rimraf": "~2.6.1", "typed-html": "~0.6.0" }, "peerDependencies": { - "stryker-api": ">=0.15.0 <0.18.0" + "stryker-api": "^0.18.0" }, "devDependencies": { "@types/file-url": "~2.0.0", diff --git a/packages/stryker-html-reporter/src/HtmlReporter.ts b/packages/stryker-html-reporter/src/HtmlReporter.ts index 78e9808a64..1f7444f689 100644 --- a/packages/stryker-html-reporter/src/HtmlReporter.ts +++ b/packages/stryker-html-reporter/src/HtmlReporter.ts @@ -1,4 +1,4 @@ -import * as log4js from 'log4js'; +import { getLogger } from 'stryker-api/logging'; import fileUrl = require('file-url'); import * as path from 'path'; import { Config } from 'stryker-api/config'; @@ -7,12 +7,11 @@ import * as util from './util'; import * as templates from './templates'; import Breadcrumb from './Breadcrumb'; -const log = log4js.getLogger('HtmlReporter'); const DEFAULT_BASE_FOLDER = path.normalize('reports/mutation/html'); export const RESOURCES_DIR_NAME = 'strykerResources'; export default class HtmlReporter implements Reporter { - + private log = getLogger(HtmlReporter.name); private _baseDir: string; private mainPromise: Promise; private mutantResults: MutantResult[]; @@ -20,7 +19,6 @@ export default class HtmlReporter implements Reporter { private scoreResult: ScoreResult; constructor(private options: Config) { - log4js.setGlobalLogLevel(options.logLevel || 'info'); } onAllSourceFilesRead(files: SourceFile[]) { @@ -44,7 +42,7 @@ export default class HtmlReporter implements Reporter { return this.cleanBaseFolder() .then(() => this.writeCommonResources()) .then(() => this.writeReportDirectory()) - .then(location => log.info(`Your report can be found at: ${fileUrl(location)}`)); + .then(location => this.log.info(`Your report can be found at: ${fileUrl(location)}`)); } private writeCommonResources() { @@ -107,9 +105,9 @@ export default class HtmlReporter implements Reporter { if (!this._baseDir) { if (this.options['htmlReporter'] && this.options['htmlReporter']['baseDir']) { this._baseDir = this.options['htmlReporter']['baseDir']; - log.debug(`Using configured output folder ${this._baseDir}`); + this.log.debug(`Using configured output folder ${this._baseDir}`); } else { - log.debug(`No base folder configuration found (using configuration: htmlReporter: { baseDir: 'output/folder' }), using default ${DEFAULT_BASE_FOLDER}`); + this.log.debug(`No base folder configuration found (using configuration: htmlReporter: { baseDir: 'output/folder' }), using default ${DEFAULT_BASE_FOLDER}`); this._baseDir = DEFAULT_BASE_FOLDER; } } diff --git a/packages/stryker-html-reporter/test/helpers/log4jsMock.ts b/packages/stryker-html-reporter/test/helpers/loggingMock.ts similarity index 85% rename from packages/stryker-html-reporter/test/helpers/log4jsMock.ts rename to packages/stryker-html-reporter/test/helpers/loggingMock.ts index dabc9f146a..1c084bcbdf 100644 --- a/packages/stryker-html-reporter/test/helpers/log4jsMock.ts +++ b/packages/stryker-html-reporter/test/helpers/loggingMock.ts @@ -1,4 +1,4 @@ -import * as log4js from 'log4js'; +import * as logging from 'stryker-api/logging'; import * as sinon from 'sinon'; let logger = { @@ -16,7 +16,7 @@ let logger = { fatal: sinon.stub() }; -sinon.stub(log4js, 'getLogger').returns(logger); +sinon.stub(logging, 'getLogger').returns(logger); beforeEach(() => { logger.trace.reset(); diff --git a/packages/stryker-html-reporter/test/integration/MathReportSpec.ts b/packages/stryker-html-reporter/test/integration/MathReportSpec.ts index 328d40ffdf..640accd84e 100644 --- a/packages/stryker-html-reporter/test/integration/MathReportSpec.ts +++ b/packages/stryker-html-reporter/test/integration/MathReportSpec.ts @@ -2,7 +2,7 @@ import * as fs from 'fs'; import * as path from 'path'; import { expect } from 'chai'; import { Config } from 'stryker-api/config'; -import logger from '../helpers/log4jsMock'; +import logger from '../helpers/loggingMock'; import EventPlayer from '../helpers/EventPlayer'; import fileUrl = require('file-url'); import HtmlReporter from '../../src/HtmlReporter'; diff --git a/packages/stryker-jasmine-runner/package.json b/packages/stryker-jasmine-runner/package.json index 81f7aa0856..0b00d1c721 100644 --- a/packages/stryker-jasmine-runner/package.json +++ b/packages/stryker-jasmine-runner/package.json @@ -38,7 +38,7 @@ "homepage": "https://github.com/stryker-mutator/stryker/tree/master/packages/stryker-jasmine-runner#readme", "peerDependencies": { "jasmine": ">=2", - "stryker-api": ">=0.15.0 <0.18.0" + "stryker-api": "^0.18.0" }, "devDependencies": { "stryker-api": "~0.17.3", diff --git a/packages/stryker-jasmine/package.json b/packages/stryker-jasmine/package.json index cc529b2019..d8bc1e54a2 100644 --- a/packages/stryker-jasmine/package.json +++ b/packages/stryker-jasmine/package.json @@ -31,10 +31,11 @@ "homepage": "https://github.com/stryker-mutator/stryker/tree/master/packages/stryker-jasmine#readme", "peerDependencies": { "jasmine-core": ">=2", - "stryker-api": ">=0.15.0 <0.18.0" + "stryker-api": "^0.18.0" }, "devDependencies": { "stryker-api": "~0.17.3" }, - "contributors": [] + "contributors": [], + "dependencies": {} } diff --git a/packages/stryker-javascript-mutator/package.json b/packages/stryker-javascript-mutator/package.json index d910899053..46f460187d 100644 --- a/packages/stryker-javascript-mutator/package.json +++ b/packages/stryker-javascript-mutator/package.json @@ -41,7 +41,6 @@ "babel-generator": "~6.26.0", "babylon": "~6.18.0", "lodash": "~4.17.4", - "log4js": "~1.1.1", "tslib": "~1.9.3" }, "devDependencies": { @@ -51,7 +50,7 @@ "stryker-mutator-specification": "~0.3.1" }, "peerDependencies": { - "stryker-api": ">=0.15.0 <0.18.0" + "stryker-api": "^0.18.0" }, "initStrykerConfig": { "mutate": [ diff --git a/packages/stryker-javascript-mutator/src/JavaScriptMutator.ts b/packages/stryker-javascript-mutator/src/JavaScriptMutator.ts index 05133f2caa..0ea67694fd 100644 --- a/packages/stryker-javascript-mutator/src/JavaScriptMutator.ts +++ b/packages/stryker-javascript-mutator/src/JavaScriptMutator.ts @@ -1,5 +1,5 @@ import * as babel from 'babel-core'; -import { getLogger, setGlobalLogLevel } from 'log4js'; +import { getLogger } from 'stryker-api/logging'; import { Mutator, Mutant } from 'stryker-api/mutant'; import { File } from 'stryker-api/core'; import { Config } from 'stryker-api/config'; @@ -16,7 +16,6 @@ export default class JavaScriptMutator implements Mutator { private log = getLogger(JavaScriptMutator.name); constructor(config: Config, private mutators: NodeMutator[] = defaultMutators()) { - setGlobalLogLevel(config.logLevel); } public mutate(inputFiles: File[]): Mutant[] { diff --git a/packages/stryker-javascript-mutator/test/helpers/LogMock.ts b/packages/stryker-javascript-mutator/test/helpers/LogMock.ts index 2b4ba4c148..ee5962d251 100644 --- a/packages/stryker-javascript-mutator/test/helpers/LogMock.ts +++ b/packages/stryker-javascript-mutator/test/helpers/LogMock.ts @@ -1,4 +1,4 @@ -import { Logger } from 'log4js'; +import { Logger } from 'stryker-api/logging'; import { SinonStub } from 'sinon'; export default class LogMock implements Mock { diff --git a/packages/stryker-javascript-mutator/test/helpers/initSinon.ts b/packages/stryker-javascript-mutator/test/helpers/initSinon.ts index 750d76a76c..825f513e46 100644 --- a/packages/stryker-javascript-mutator/test/helpers/initSinon.ts +++ b/packages/stryker-javascript-mutator/test/helpers/initSinon.ts @@ -1,11 +1,11 @@ import * as sinon from 'sinon'; -import * as log4js from 'log4js'; +import * as logging from 'stryker-api/logging'; import LogMock from './LogMock'; beforeEach(() => { global.sandbox = sinon.createSandbox(); global.logMock = new LogMock(); - global.sandbox.stub(log4js, 'getLogger').returns(global.logMock); + global.sandbox.stub(logging, 'getLogger').returns(global.logMock); }); afterEach(() => { diff --git a/packages/stryker-jest-runner/package.json b/packages/stryker-jest-runner/package.json index 259118c247..d6638b9dcd 100644 --- a/packages/stryker-jest-runner/package.json +++ b/packages/stryker-jest-runner/package.json @@ -39,7 +39,6 @@ }, "homepage": "https://github.com/stryker-mutator/stryker/tree/master/packages/stryker-jest-runner#readme", "devDependencies": { - "@types/log4js": "0.0.32", "@types/semver": "~5.5.0", "jest": "~22.0.0", "react": "~16.4.1", @@ -49,11 +48,10 @@ "stryker-api": "~0.17.3" }, "peerDependencies": { - "stryker-api": ">=0.13.0 <0.18.0", + "stryker-api": "^0.18.0", "jest": "^20.0.0" }, "dependencies": { - "log4js": "~1.1.1", "semver": "~5.5.0" }, "initStrykerConfig": { diff --git a/packages/stryker-jest-runner/src/JestTestRunner.ts b/packages/stryker-jest-runner/src/JestTestRunner.ts index 2c747b03e5..1aa17775f4 100644 --- a/packages/stryker-jest-runner/src/JestTestRunner.ts +++ b/packages/stryker-jest-runner/src/JestTestRunner.ts @@ -1,17 +1,14 @@ -import { getLogger } from 'log4js'; +import { getLogger } from 'stryker-api/logging'; import { RunnerOptions, RunResult, TestRunner, RunStatus, TestResult, TestStatus } from 'stryker-api/test_runner'; -import { EventEmitter } from 'events'; import * as jest from 'jest'; import JestTestAdapterFactory from './jestTestAdapters/JestTestAdapterFactory'; -export default class JestTestRunner extends EventEmitter implements TestRunner { +export default class JestTestRunner implements TestRunner { private log = getLogger(JestTestRunner.name); private jestConfig: jest.Configuration; private processEnvRef: NodeJS.ProcessEnv; public constructor(options: RunnerOptions, processEnvRef?: NodeJS.ProcessEnv) { - super(); - // Make sure process can be mocked by tests by passing it in the constructor this.processEnvRef = processEnvRef || /* istanbul ignore next */ process.env; diff --git a/packages/stryker-jest-runner/src/jestTestAdapters/JestPromiseTestAdapter.ts b/packages/stryker-jest-runner/src/jestTestAdapters/JestPromiseTestAdapter.ts index 665e547d76..d2004f9098 100644 --- a/packages/stryker-jest-runner/src/jestTestAdapters/JestPromiseTestAdapter.ts +++ b/packages/stryker-jest-runner/src/jestTestAdapters/JestPromiseTestAdapter.ts @@ -1,5 +1,5 @@ import JestTestAdapter from './JestTestAdapter'; -import { getLogger } from 'log4js'; +import { getLogger } from 'stryker-api/logging'; import { Configuration, runCLI, RunResult } from 'jest'; export default class JestPromiseTestAdapter implements JestTestAdapter { diff --git a/packages/stryker-jest-runner/src/jestTestAdapters/JestTestAdapterFactory.ts b/packages/stryker-jest-runner/src/jestTestAdapters/JestTestAdapterFactory.ts index 39839d0258..b1905b18e3 100644 --- a/packages/stryker-jest-runner/src/jestTestAdapters/JestTestAdapterFactory.ts +++ b/packages/stryker-jest-runner/src/jestTestAdapters/JestTestAdapterFactory.ts @@ -1,4 +1,4 @@ -import { getLogger } from 'log4js'; +import { getLogger } from 'stryker-api/logging'; import JestTestAdapter from './JestTestAdapter'; import JestPromiseAdapter from './JestPromiseTestAdapter'; diff --git a/packages/stryker-jest-runner/test/unit/JestTestRunnerSpec.ts b/packages/stryker-jest-runner/test/unit/JestTestRunnerSpec.ts index 66d481b1e8..2bc0c187a8 100644 --- a/packages/stryker-jest-runner/test/unit/JestTestRunnerSpec.ts +++ b/packages/stryker-jest-runner/test/unit/JestTestRunnerSpec.ts @@ -5,7 +5,7 @@ import * as fakeResults from '../helpers/testResultProducer'; import * as sinon from 'sinon'; import { assert, expect } from 'chai'; import { RunStatus, TestStatus } from 'stryker-api/test_runner'; -import * as log4js from 'log4js'; +import * as logging from 'stryker-api/logging'; describe('JestTestRunner', () => { const basePath = '/path/to/project/root'; @@ -26,7 +26,7 @@ describe('JestTestRunner', () => { runJestStub.resolves({ results: { testResults: [] } }); debugLoggerStub = sandbox.stub(); - sandbox.stub(log4js, 'getLogger').returns({ debug: debugLoggerStub }); + sandbox.stub(logging, 'getLogger').returns({ debug: debugLoggerStub }); strykerOptions = new Config; strykerOptions.set({ jest: { config: { property: 'value' } }, basePath }); diff --git a/packages/stryker-jest-runner/test/unit/jestTestAdapters/JestPromiseTestAdapterSpec.ts b/packages/stryker-jest-runner/test/unit/jestTestAdapters/JestPromiseTestAdapterSpec.ts index b15b6aa34e..3ab9f9afad 100644 --- a/packages/stryker-jest-runner/test/unit/jestTestAdapters/JestPromiseTestAdapterSpec.ts +++ b/packages/stryker-jest-runner/test/unit/jestTestAdapters/JestPromiseTestAdapterSpec.ts @@ -1,7 +1,7 @@ import JestPromiseTestAdapter from '../../../src/jestTestAdapters/JestPromiseTestAdapter'; import * as sinon from 'sinon'; import { expect, assert } from 'chai'; -import * as log4js from 'log4js'; +import * as logging from 'stryker-api/logging'; import * as jest from 'jest'; describe('JestPromiseTestAdapter', () => { @@ -24,7 +24,7 @@ describe('JestPromiseTestAdapter', () => { })); traceLoggerStub = sinon.stub(); - sandbox.stub(log4js, 'getLogger').returns({ trace: traceLoggerStub }); + sandbox.stub(logging, 'getLogger').returns({ trace: traceLoggerStub }); jestPromiseTestAdapter = new JestPromiseTestAdapter(); }); diff --git a/packages/stryker-karma-runner/package.json b/packages/stryker-karma-runner/package.json index 87efcb6131..959449b682 100644 --- a/packages/stryker-karma-runner/package.json +++ b/packages/stryker-karma-runner/package.json @@ -31,9 +31,10 @@ }, "homepage": "https://github.com/stryker-mutator/stryker/tree/master/packages/stryker-karma-runner#readme", "peerDependencies": { - "stryker-api": ">=0.15.0 <0.18.0" + "stryker-api": "^0.18.0" }, "devDependencies": { + "@types/express": "~4.11.1", "@types/semver": "~5.5.0", "jasmine-core": "~2.4.1", "karma": "~2.0.4", @@ -43,7 +44,6 @@ "stryker-jasmine": "~0.8.7" }, "dependencies": { - "log4js": "~1.1.1", "semver": "~5.5.0", "tslib": "~1.9.3" }, diff --git a/packages/stryker-karma-runner/src/KarmaTestRunner.ts b/packages/stryker-karma-runner/src/KarmaTestRunner.ts index aee188f618..40cbc5e452 100644 --- a/packages/stryker-karma-runner/src/KarmaTestRunner.ts +++ b/packages/stryker-karma-runner/src/KarmaTestRunner.ts @@ -1,9 +1,8 @@ -import * as log4js from 'log4js'; import { TestRunner, TestResult, RunStatus, RunResult, RunnerOptions, CoverageCollection, CoveragePerTestResult } from 'stryker-api/test_runner'; +import { getLogger } from 'stryker-api/logging'; import * as karma from 'karma'; import StrykerKarmaSetup, { DEPRECATED_KARMA_CONFIG, DEPRECATED_KARMA_CONFIG_FILE, KARMA_CONFIG_KEY } from './StrykerKarmaSetup'; import TestHooksMiddleware from './TestHooksMiddleware'; -import { setGlobalLogLevel } from 'log4js'; import StrykerReporter from './StrykerReporter'; import strykerKarmaConf = require('./starters/stryker-karma.conf'); import ProjectStarter from './starters/ProjectStarter'; @@ -13,7 +12,7 @@ export interface ConfigOptions extends karma.ConfigOptions { } export default class KarmaTestRunner implements TestRunner { - private log = log4js.getLogger(KarmaTestRunner.name); + private log = getLogger(KarmaTestRunner.name); private currentTestResults: TestResult[]; private currentErrorMessages: string[]; private currentCoverageReport?: CoverageCollection | CoveragePerTestResult; @@ -24,7 +23,6 @@ export default class KarmaTestRunner implements TestRunner { constructor(private options: RunnerOptions) { const setup = this.loadSetup(options); this.starter = new ProjectStarter(setup.project); - setGlobalLogLevel(options.strykerOptions.logLevel || 'info'); this.setGlobals(setup, options.port); this.cleanRun(); this.listenToRunComplete(); diff --git a/packages/stryker-karma-runner/src/starters/stryker-karma.conf.ts b/packages/stryker-karma-runner/src/starters/stryker-karma.conf.ts index 0d6637a104..88a8258fa7 100644 --- a/packages/stryker-karma-runner/src/starters/stryker-karma.conf.ts +++ b/packages/stryker-karma-runner/src/starters/stryker-karma.conf.ts @@ -3,7 +3,7 @@ import * as path from 'path'; import { requireModule } from '../utils'; import TestHooksMiddleware, { TEST_HOOKS_FILE_NAME } from '../TestHooksMiddleware'; import StrykerReporter from '../StrykerReporter'; -import { getLogger, Logger } from 'log4js'; +import { getLogger, Logger } from 'stryker-api/logging'; function setDefaultOptions(config: Config) { config.set({ diff --git a/packages/stryker-karma-runner/test/integration/KarmaTestRunner.it.ts b/packages/stryker-karma-runner/test/integration/KarmaTestRunner.it.ts index c8a5c5e05c..6d2087487e 100644 --- a/packages/stryker-karma-runner/test/integration/KarmaTestRunner.it.ts +++ b/packages/stryker-karma-runner/test/integration/KarmaTestRunner.it.ts @@ -36,7 +36,6 @@ describe('KarmaTestRunner', function () { testRunnerOptions = { port: 9877, strykerOptions: { - logLevel: 'trace', karma: { config: { files: [ @@ -88,10 +87,9 @@ describe('KarmaTestRunner', function () { describe('when some tests fail', () => { before(() => { - const testRunnerOptions = { + const testRunnerOptions: RunnerOptions = { port: 9878, strykerOptions: { - logLevel: 'trace', karma: { config: { files: [ @@ -101,7 +99,7 @@ describe('KarmaTestRunner', function () { ] } } - }, + }, fileNames: [] }; sut = new KarmaTestRunner(testRunnerOptions); diff --git a/packages/stryker-karma-runner/test/unit/KarmaTestRunnerSpec.ts b/packages/stryker-karma-runner/test/unit/KarmaTestRunnerSpec.ts index e9c5498912..909c1a0c7e 100644 --- a/packages/stryker-karma-runner/test/unit/KarmaTestRunnerSpec.ts +++ b/packages/stryker-karma-runner/test/unit/KarmaTestRunnerSpec.ts @@ -1,6 +1,6 @@ import { EventEmitter } from 'events'; import { expect } from 'chai'; -import * as log4js from 'log4js'; +import * as logging from 'stryker-api/logging'; import * as karma from 'karma'; import strykerKarmaConf = require('../../src/starters/stryker-karma.conf'); import ProjectStarter, * as projectStarterModule from '../../src/starters/ProjectStarter'; @@ -28,7 +28,7 @@ describe('KarmaTestRunner', () => { projectStarterMock = sandbox.createStubInstance(ProjectStarter); logMock = new LoggerStub(); sandbox.stub(projectStarterModule, 'default').returns(projectStarterMock); - sandbox.stub(log4js, 'getLogger').returns(logMock); + sandbox.stub(logging, 'getLogger').returns(logMock); sandbox.stub(StrykerReporter, 'instance').value(reporterMock); setGlobalsStub = sandbox.stub(strykerKarmaConf, 'setGlobals'); karmaRunStub = sandbox.stub(karma.runner, 'run'); diff --git a/packages/stryker-karma-runner/test/unit/starters/stryker-karma.confSpec.ts b/packages/stryker-karma-runner/test/unit/starters/stryker-karma.confSpec.ts index 5d6a65551b..bd3a995bc0 100644 --- a/packages/stryker-karma-runner/test/unit/starters/stryker-karma.confSpec.ts +++ b/packages/stryker-karma-runner/test/unit/starters/stryker-karma.confSpec.ts @@ -1,5 +1,5 @@ import * as path from 'path'; -import * as log4js from 'log4js'; +import * as logging from 'stryker-api/logging'; import sut = require('../../../src/starters/stryker-karma.conf'); import { Config, ConfigOptions } from 'karma'; import { expect } from 'chai'; @@ -17,7 +17,7 @@ describe('stryker-karma.conf.js', () => { beforeEach(() => { config = new KarmaConfigMock(); logMock = new LoggerStub(); - sandbox.stub(log4js, 'getLogger').returns(logMock); + sandbox.stub(logging, 'getLogger').returns(logMock); requireModuleStub = sandbox.stub(utils, 'requireModule'); }); @@ -27,7 +27,7 @@ describe('stryker-karma.conf.js', () => { it('should create the correct logger', () => { sut(config); - expect(log4js.getLogger).calledWith('stryker-karma.conf.js'); + expect(logging.getLogger).calledWith('stryker-karma.conf.js'); }); it('should set default options', () => { diff --git a/packages/stryker-mocha-framework/package.json b/packages/stryker-mocha-framework/package.json index d0620ec099..21ad1d4366 100644 --- a/packages/stryker-mocha-framework/package.json +++ b/packages/stryker-mocha-framework/package.json @@ -36,15 +36,13 @@ ], "homepage": "https://github.com/stryker-mutator/stryker/tree/master/packages/stryker-mocha-framework#readme", "license": "Apache-2.0", - "dependencies": { - "log4js": "~1.1.1" - }, + "dependencies": { }, "devDependencies": { "stryker-api": "~0.17.3", "tslib": "~1.9.3" }, "peerDependencies": { "mocha": ">= 2.3.3 < 6", - "stryker-api": ">=0.15.0 <0.18.0" + "stryker-api": "^0.18.0" } } diff --git a/packages/stryker-mocha-runner/package.json b/packages/stryker-mocha-runner/package.json index a0c4a4dc03..91e68f1549 100644 --- a/packages/stryker-mocha-runner/package.json +++ b/packages/stryker-mocha-runner/package.json @@ -35,7 +35,6 @@ }, "homepage": "https://github.com/stryker-mutator/stryker/tree/master/packages/stryker-mocha-runner#readme", "dependencies": { - "log4js": "~1.1.1", "multimatch": "~2.1.0", "tslib": "~1.9.3" }, @@ -46,6 +45,6 @@ }, "peerDependencies": { "mocha": ">= 2.3.3 < 6", - "stryker-api": ">=0.15.0 <0.18.0" + "stryker-api": "^0.18.0" } } diff --git a/packages/stryker-mocha-runner/src/MochaConfigEditor.ts b/packages/stryker-mocha-runner/src/MochaConfigEditor.ts index ce09a6f54e..a5f9c7ece6 100644 --- a/packages/stryker-mocha-runner/src/MochaConfigEditor.ts +++ b/packages/stryker-mocha-runner/src/MochaConfigEditor.ts @@ -1,11 +1,9 @@ import { ConfigEditor, Config } from 'stryker-api/config'; import { mochaOptionsKey } from './MochaRunnerOptions'; import MochaOptionsLoader from './MochaOptionsLoader'; -import { setGlobalLogLevel } from 'log4js'; export default class MochaConfigEditor implements ConfigEditor { edit(config: Config): void { - setGlobalLogLevel(config.logLevel || 'info'); config[mochaOptionsKey] = new MochaOptionsLoader().load(config); } } \ No newline at end of file diff --git a/packages/stryker-mocha-runner/src/MochaOptionsLoader.ts b/packages/stryker-mocha-runner/src/MochaOptionsLoader.ts index 0e9097e515..78e2f82598 100644 --- a/packages/stryker-mocha-runner/src/MochaOptionsLoader.ts +++ b/packages/stryker-mocha-runner/src/MochaOptionsLoader.ts @@ -1,7 +1,7 @@ import * as path from 'path'; import * as fs from 'fs'; import { StrykerOptions } from 'stryker-api/core'; -import { getLogger } from 'log4js'; +import { getLogger } from 'stryker-api/logging'; import MochaRunnerOptions, { mochaOptionsKey } from './MochaRunnerOptions'; export default class MochaOptionsLoader { diff --git a/packages/stryker-mocha-runner/src/MochaTestRunner.ts b/packages/stryker-mocha-runner/src/MochaTestRunner.ts index 4ac8dd3c65..371ab13ec6 100644 --- a/packages/stryker-mocha-runner/src/MochaTestRunner.ts +++ b/packages/stryker-mocha-runner/src/MochaTestRunner.ts @@ -1,4 +1,4 @@ -import { getLogger, setGlobalLogLevel } from 'log4js'; +import { getLogger } from 'stryker-api/logging'; import * as path from 'path'; import { TestRunner, RunResult, RunStatus, RunnerOptions } from 'stryker-api/test_runner'; import LibWrapper from './LibWrapper'; @@ -16,7 +16,6 @@ export default class MochaTestRunner implements TestRunner { private mochaRunnerOptions: MochaRunnerOptions; constructor(runnerOptions: RunnerOptions) { - setGlobalLogLevel(runnerOptions.strykerOptions.logLevel || 'info'); this.mochaRunnerOptions = runnerOptions.strykerOptions[mochaOptionsKey]; this.allFileNames = runnerOptions.fileNames; this.additionalRequires(); diff --git a/packages/stryker-mocha-runner/src/StrykerMochaReporter.ts b/packages/stryker-mocha-runner/src/StrykerMochaReporter.ts index c7fa9e54cf..b80d524570 100644 --- a/packages/stryker-mocha-runner/src/StrykerMochaReporter.ts +++ b/packages/stryker-mocha-runner/src/StrykerMochaReporter.ts @@ -1,11 +1,10 @@ import { RunResult, RunStatus, TestStatus } from 'stryker-api/test_runner'; -import * as log4js from 'log4js'; +import { getLogger } from 'stryker-api/logging'; import Timer from './Timer'; -const log = log4js.getLogger('StrykerMochaReporter'); - export default class StrykerMochaReporter { - + + private readonly log = getLogger(StrykerMochaReporter.name); public runResult: RunResult; private timer = new Timer(); private passedCount = 0; @@ -26,7 +25,7 @@ export default class StrykerMochaReporter { tests: [], errorMessages: [] }; - log.debug('Starting Mocha test run'); + this.log.debug('Starting Mocha test run'); }); this.runner.on('pass', (test: any) => { @@ -37,7 +36,7 @@ export default class StrykerMochaReporter { }); this.passedCount++; this.timer.reset(); - log.debug(`Test passed: ${test.fullTitle()}`); + this.log.debug(`Test passed: ${test.fullTitle()}`); }); this.runner.on('fail', (test: any, err: any) => { @@ -51,12 +50,12 @@ export default class StrykerMochaReporter { this.runResult.errorMessages = []; } this.runResult.errorMessages.push(err.message); - log.debug(`Test failed: ${test.fullTitle()}. Error: ${err.message}`); + this.log.debug(`Test failed: ${test.fullTitle()}. Error: ${err.message}`); }); this.runner.on('end', () => { this.runResult.status = RunStatus.Complete; - log.debug(`Mocha test run completed: ${this.passedCount}/${this.runResult.tests.length} passed`); + this.log.debug(`Mocha test run completed: ${this.passedCount}/${this.runResult.tests.length} passed`); }); } } \ No newline at end of file diff --git a/packages/stryker-mocha-runner/test/helpers/mockHelpers.ts b/packages/stryker-mocha-runner/test/helpers/mockHelpers.ts index 6ef8b41ce5..0018ccc696 100644 --- a/packages/stryker-mocha-runner/test/helpers/mockHelpers.ts +++ b/packages/stryker-mocha-runner/test/helpers/mockHelpers.ts @@ -1,5 +1,5 @@ import * as sinon from 'sinon'; -import { Logger } from 'log4js'; +import { Logger } from 'stryker-api/logging'; import { RunnerOptions } from 'stryker-api/test_runner'; export type Mock = { @@ -13,8 +13,6 @@ export function mock(constructorFn: { new(...args: any[]): T; }): Mock { export function logger(): Mock { return { - setLevel: sinon.stub(), - isLevelEnabled: sinon.stub(), trace: sinon.stub(), isTraceEnabled: sinon.stub(), debug: sinon.stub(), diff --git a/packages/stryker-mocha-runner/test/unit/MochaOptionsLoaderSpec.ts b/packages/stryker-mocha-runner/test/unit/MochaOptionsLoaderSpec.ts index 503a6c83f1..854df633a9 100644 --- a/packages/stryker-mocha-runner/test/unit/MochaOptionsLoaderSpec.ts +++ b/packages/stryker-mocha-runner/test/unit/MochaOptionsLoaderSpec.ts @@ -3,7 +3,7 @@ import * as fs from 'fs'; import { Config } from 'stryker-api/config'; import MochaOptionsLoader from '../../src/MochaOptionsLoader'; import { expect } from 'chai'; -import * as log4js from 'log4js'; +import * as logging from 'stryker-api/logging'; import MochaRunnerOptions from '../../src/MochaRunnerOptions'; import { logger, Mock } from '../helpers/mockHelpers'; @@ -12,11 +12,11 @@ describe('MochaOptionsLoader', () => { let readFileStub: sinon.SinonStub; let config: Config; let sut: MochaOptionsLoader; - let log: Mock; + let log: Mock; beforeEach(() => { log = logger(); - sandbox.stub(log4js, 'getLogger').returns(log); + sandbox.stub(logging, 'getLogger').returns(log); readFileStub = sandbox.stub(fs, 'readFileSync'); sut = new MochaOptionsLoader(); config = new Config(); diff --git a/packages/stryker-mocha-runner/test/unit/MochaTestRunnerSpec.ts b/packages/stryker-mocha-runner/test/unit/MochaTestRunnerSpec.ts index f14ee8b0fb..dfbb531856 100644 --- a/packages/stryker-mocha-runner/test/unit/MochaTestRunnerSpec.ts +++ b/packages/stryker-mocha-runner/test/unit/MochaTestRunnerSpec.ts @@ -1,7 +1,7 @@ import * as path from 'path'; import { EventEmitter } from 'events'; import * as Mocha from 'mocha'; -import * as log4js from 'log4js'; +import * as logging from 'stryker-api/logging'; import { expect } from 'chai'; import { RunOptions } from 'stryker-api/test_runner'; import MochaTestRunner from '../../src/MochaTestRunner'; @@ -17,7 +17,7 @@ describe('MochaTestRunner', () => { let sut: MochaTestRunner; let requireStub: sinon.SinonStub; let multimatchStub: sinon.SinonStub; - let log: Mock; + let log: Mock; beforeEach(() => { MochaStub = sandbox.stub(LibWrapper, 'Mocha'); @@ -28,7 +28,7 @@ describe('MochaTestRunner', () => { mocha.suite = mock(EventEmitter); MochaStub.returns(mocha); log = logger(); - sandbox.stub(log4js, 'getLogger').returns(log); + sandbox.stub(logging, 'getLogger').returns(log); }); afterEach(() => { diff --git a/packages/stryker-mutator-specification/package.json b/packages/stryker-mutator-specification/package.json index ce5e18ef13..5a8a30f975 100644 --- a/packages/stryker-mutator-specification/package.json +++ b/packages/stryker-mutator-specification/package.json @@ -23,5 +23,6 @@ "node": ">=6" }, "homepage": "https://github.com/stryker-mutator/stryker/tree/master/packages/stryker-javascript-mutator#readme", - "license": "Apache-2.0" + "license": "Apache-2.0", + "dependencies": {} } diff --git a/packages/stryker-typescript/package.json b/packages/stryker-typescript/package.json index 52255ae3e1..39d94feac3 100644 --- a/packages/stryker-typescript/package.json +++ b/packages/stryker-typescript/package.json @@ -38,7 +38,6 @@ "license": "Apache-2.0", "dependencies": { "lodash.flatmap": "~4.5.0", - "log4js": "~1.1.1", "semver": "~5.5.0", "tslib": "~1.9.3" }, @@ -50,7 +49,7 @@ "surrial": "~0.1.3" }, "peerDependencies": { - "stryker-api": ">=0.15.0 <0.18.0", + "stryker-api": "^0.18.0", "typescript": "^2.4.2" }, "initStrykerConfig": { diff --git a/packages/stryker-typescript/src/TypescriptConfigEditor.ts b/packages/stryker-typescript/src/TypescriptConfigEditor.ts index 61fc68b289..345c9fb9a1 100644 --- a/packages/stryker-typescript/src/TypescriptConfigEditor.ts +++ b/packages/stryker-typescript/src/TypescriptConfigEditor.ts @@ -1,7 +1,7 @@ import * as os from 'os'; import * as path from 'path'; import * as ts from 'typescript'; -import { getLogger, setGlobalLogLevel } from 'log4js'; +import { getLogger } from 'stryker-api/logging'; import { ConfigEditor, Config } from 'stryker-api/config'; import { CONFIG_KEY_FILE, CONFIG_KEY } from './helpers/keys'; import * as fs from 'fs'; @@ -20,7 +20,6 @@ export default class TypescriptConfigEditor implements ConfigEditor { private log = getLogger(TypescriptConfigEditor.name); edit(strykerConfig: Config, host: ts.ParseConfigHost = ts.sys) { - setGlobalLogLevel(strykerConfig.logLevel); this.loadTSConfig(strykerConfig, host); } diff --git a/packages/stryker-typescript/src/TypescriptTranspiler.ts b/packages/stryker-typescript/src/TypescriptTranspiler.ts index 610dfc4e8e..d0a4e9a9e3 100644 --- a/packages/stryker-typescript/src/TypescriptTranspiler.ts +++ b/packages/stryker-typescript/src/TypescriptTranspiler.ts @@ -5,7 +5,6 @@ import { Transpiler, TranspilerOptions } from 'stryker-api/transpile'; import { File } from 'stryker-api/core'; import { getTSConfig, getProjectDirectory, guardTypescriptVersion, isHeaderFile } from './helpers/tsHelpers'; import TranspilingLanguageService from './transpiler/TranspilingLanguageService'; -import { setGlobalLogLevel } from 'log4js'; import TranspileFilter from './transpiler/TranspileFilter'; export default class TypescriptTranspiler implements Transpiler { @@ -16,7 +15,6 @@ export default class TypescriptTranspiler implements Transpiler { constructor(options: TranspilerOptions) { guardTypescriptVersion(); - setGlobalLogLevel(options.config.logLevel); this.config = options.config; this.produceSourceMaps = options.produceSourceMaps; this.filter = TranspileFilter.create(this.config); diff --git a/packages/stryker-typescript/src/transpiler/TranspilingLanguageService.ts b/packages/stryker-typescript/src/transpiler/TranspilingLanguageService.ts index 8f80c5759f..80ea868731 100644 --- a/packages/stryker-typescript/src/transpiler/TranspilingLanguageService.ts +++ b/packages/stryker-typescript/src/transpiler/TranspilingLanguageService.ts @@ -2,7 +2,7 @@ import * as os from 'os'; import * as path from 'path'; import * as fs from 'fs'; import * as ts from 'typescript'; -import { getLogger } from 'log4js'; +import { getLogger } from 'stryker-api/logging'; import flatMap = require('lodash.flatmap'); import ScriptFile from './ScriptFile'; import { normalizeFileFromTypescript, isJavaScriptFile, isMapFile, normalizeFileForTypescript } from '../helpers/tsHelpers'; diff --git a/packages/stryker-typescript/test/helpers/producers.ts b/packages/stryker-typescript/test/helpers/producers.ts index 81a1f50b48..2991148acd 100644 --- a/packages/stryker-typescript/test/helpers/producers.ts +++ b/packages/stryker-typescript/test/helpers/producers.ts @@ -1,5 +1,5 @@ import * as sinon from 'sinon'; -import { Logger } from 'log4js'; +import { Logger } from 'stryker-api/logging'; export type Mock = { [P in keyof T]: sinon.SinonStub; @@ -13,8 +13,6 @@ export function mock(constructorFn: Constructor): Mock { export const logger = (): Mock => { return { - setLevel: sinon.stub(), - isLevelEnabled: sinon.stub(), isTraceEnabled: sinon.stub(), isDebugEnabled: sinon.stub(), isInfoEnabled: sinon.stub(), diff --git a/packages/stryker-typescript/test/integration/allowJS.it.ts b/packages/stryker-typescript/test/integration/allowJS.it.ts index efd61f2528..232c06c11d 100644 --- a/packages/stryker-typescript/test/integration/allowJS.it.ts +++ b/packages/stryker-typescript/test/integration/allowJS.it.ts @@ -2,7 +2,6 @@ import * as path from 'path'; import * as fs from 'fs'; import { Config } from 'stryker-api/config'; import TypescriptConfigEditor from '../../src/TypescriptConfigEditor'; -import { setGlobalLogLevel } from 'log4js'; import { File } from 'stryker-api/core'; import { CONFIG_KEY } from '../../src/helpers/keys'; import TypescriptTranspiler from '../../src/TypescriptTranspiler'; @@ -15,7 +14,6 @@ describe('AllowJS integration', function () { let inputFiles: File[]; beforeEach(() => { - setGlobalLogLevel('error'); const configEditor = new TypescriptConfigEditor(); config = new Config(); config.set({ @@ -24,10 +22,6 @@ describe('AllowJS integration', function () { configEditor.edit(config); inputFiles = config[CONFIG_KEY].fileNames.map((fileName: string) => new File(fileName, fs.readFileSync(fileName, 'utf8'))); }); - - afterEach(() => { - setGlobalLogLevel('trace'); - }); it('should be able to transpile source code', async () => { const transpiler = new TypescriptTranspiler({ config, produceSourceMaps: false }); diff --git a/packages/stryker-typescript/test/integration/ownDogFoodSpec.ts b/packages/stryker-typescript/test/integration/ownDogFoodSpec.ts index 6d9c75cc4b..6bd3286b8c 100644 --- a/packages/stryker-typescript/test/integration/ownDogFoodSpec.ts +++ b/packages/stryker-typescript/test/integration/ownDogFoodSpec.ts @@ -5,7 +5,6 @@ import { Config } from 'stryker-api/config'; import { File } from 'stryker-api/core'; import TypescriptConfigEditor from '../../src/TypescriptConfigEditor'; import TypescriptTranspiler from '../../src/TypescriptTranspiler'; -import { setGlobalLogLevel } from 'log4js'; import { CONFIG_KEY } from '../../src/helpers/keys'; describe('stryker-typescript', function () { @@ -15,7 +14,6 @@ describe('stryker-typescript', function () { let inputFiles: File[]; beforeEach(() => { - setGlobalLogLevel('error'); const configEditor = new TypescriptConfigEditor(); config = new Config(); config.set({ @@ -25,10 +23,6 @@ describe('stryker-typescript', function () { inputFiles = config[CONFIG_KEY].fileNames.map((fileName: string) => new File(fileName, fs.readFileSync(fileName, 'utf8'))); }); - afterEach(() => { - setGlobalLogLevel('trace'); - }); - it('should be able to transpile itself', async () => { const transpiler = new TypescriptTranspiler({ config, produceSourceMaps: true }); const outputFiles = await transpiler.transpile(inputFiles); diff --git a/packages/stryker-typescript/test/integration/sampleSpec.ts b/packages/stryker-typescript/test/integration/sampleSpec.ts index 4845e6986f..5d6c98faaa 100644 --- a/packages/stryker-typescript/test/integration/sampleSpec.ts +++ b/packages/stryker-typescript/test/integration/sampleSpec.ts @@ -7,7 +7,6 @@ import { File } from 'stryker-api/core'; import TypescriptConfigEditor from '../../src/TypescriptConfigEditor'; import TypescriptMutator from '../../src/TypescriptMutator'; import TypescriptTranspiler from '../../src/TypescriptTranspiler'; -import { setGlobalLogLevel } from 'log4js'; import { CONFIG_KEY } from '../../src/helpers/keys'; describe('Sample integration', function () { @@ -17,7 +16,6 @@ describe('Sample integration', function () { let inputFiles: File[]; beforeEach(() => { - setGlobalLogLevel('error'); const configEditor = new TypescriptConfigEditor(); config = new Config(); config.set({ @@ -27,10 +25,6 @@ describe('Sample integration', function () { inputFiles = config[CONFIG_KEY].fileNames.map((fileName: string) => new File(fileName, fs.readFileSync(fileName, 'utf8'))); }); - afterEach(() => { - setGlobalLogLevel('trace'); - }); - it('should be able to generate mutants', () => { // Generate mutants const mutator = new TypescriptMutator(config); diff --git a/packages/stryker-typescript/test/integration/useHeaderFile.ts b/packages/stryker-typescript/test/integration/useHeaderFile.ts index 74aac8cb0b..dbe8fdba2c 100644 --- a/packages/stryker-typescript/test/integration/useHeaderFile.ts +++ b/packages/stryker-typescript/test/integration/useHeaderFile.ts @@ -5,7 +5,6 @@ import { Config } from 'stryker-api/config'; import { File } from 'stryker-api/core'; import TypescriptConfigEditor from '../../src/TypescriptConfigEditor'; import TypescriptTranspiler from '../../src/TypescriptTranspiler'; -import { setGlobalLogLevel } from 'log4js'; import { CONFIG_KEY } from '../../src/helpers/keys'; describe('Use header file integration', function () { @@ -14,7 +13,6 @@ describe('Use header file integration', function () { let inputFiles: File[]; beforeEach(() => { - setGlobalLogLevel('error'); const configEditor = new TypescriptConfigEditor(); config = new Config(); config.set({ @@ -24,10 +22,6 @@ describe('Use header file integration', function () { inputFiles = config[CONFIG_KEY].fileNames.map((fileName: string) => new File(fileName, fs.readFileSync(fileName, 'utf8'))); }); - afterEach(() => { - setGlobalLogLevel('trace'); - }); - it('should be able to transpile source code', async () => { const transpiler = new TypescriptTranspiler({ config, produceSourceMaps: false }); const outputFiles = await transpiler.transpile(inputFiles); diff --git a/packages/stryker-typescript/test/unit/TypescriptConfigEditorSpec.ts b/packages/stryker-typescript/test/unit/TypescriptConfigEditorSpec.ts index 21bb17161b..244356bb1d 100644 --- a/packages/stryker-typescript/test/unit/TypescriptConfigEditorSpec.ts +++ b/packages/stryker-typescript/test/unit/TypescriptConfigEditorSpec.ts @@ -1,6 +1,6 @@ import * as fs from 'fs'; import * as path from 'path'; -import * as log4js from 'log4js'; +import * as logging from 'stryker-api/logging'; import * as ts from 'typescript'; import { expect } from 'chai'; import { SinonStub, match } from 'sinon'; @@ -27,7 +27,7 @@ describe('TypescriptConfigEditor edit', () => { info: sandbox.stub(), error: sandbox.stub() }; - sandbox.stub(log4js, 'getLogger').returns(loggerStub); + sandbox.stub(logging, 'getLogger').returns(loggerStub); config = new Config(); sut = new TypescriptConfigEditor(); }); diff --git a/packages/stryker-typescript/test/unit/TypescriptTranspilerSpec.ts b/packages/stryker-typescript/test/unit/TypescriptTranspilerSpec.ts index 2913684571..1a0ace81f1 100644 --- a/packages/stryker-typescript/test/unit/TypescriptTranspilerSpec.ts +++ b/packages/stryker-typescript/test/unit/TypescriptTranspilerSpec.ts @@ -1,5 +1,4 @@ import TranspilingLanguageService, * as transpilingLanguageService from '../../src/transpiler/TranspilingLanguageService'; -import * as log4js from 'log4js'; import { expect } from 'chai'; import { Mock, mock } from '../helpers/producers'; import TypescriptTranspiler from '../../src/TypescriptTranspiler'; @@ -24,13 +23,6 @@ describe('TypescriptTranspiler', () => { transpileFilterMock.isIncluded = sandbox.stub(); sandbox.stub(TranspileFilter, 'create').returns(transpileFilterMock); sandbox.stub(transpilingLanguageService, 'default').returns(languageService); - sandbox.stub(log4js, 'setGlobalLogLevel'); - }); - - it('set global log level', () => { - config.logLevel = 'foobar'; - sut = new TypescriptTranspiler({ config, produceSourceMaps: true }); - expect(log4js.setGlobalLogLevel).calledWith('foobar'); }); describe('transpile', () => { diff --git a/packages/stryker-webpack-transpiler/package.json b/packages/stryker-webpack-transpiler/package.json index 15617af3cd..8afcb3c566 100644 --- a/packages/stryker-webpack-transpiler/package.json +++ b/packages/stryker-webpack-transpiler/package.json @@ -45,13 +45,12 @@ "webpack": "~4.16.0" }, "peerDependencies": { - "stryker-api": ">=0.15.0 <0.18.0", + "stryker-api": "^0.18.0", "webpack": ">=2.0.0" }, "dependencies": { "enhanced-resolve": "~4.0.0-beta.4", "lodash": "~4.17.4", - "log4js": "~1.1.1", "memory-fs": "~0.4.1" }, "initStrykerConfig": { diff --git a/packages/stryker-webpack-transpiler/src/WebpackTranspiler.ts b/packages/stryker-webpack-transpiler/src/WebpackTranspiler.ts index 7adefcfcc0..e735423210 100644 --- a/packages/stryker-webpack-transpiler/src/WebpackTranspiler.ts +++ b/packages/stryker-webpack-transpiler/src/WebpackTranspiler.ts @@ -2,7 +2,6 @@ import { TranspilerOptions, Transpiler } from 'stryker-api/transpile'; import { File } from 'stryker-api/core'; import WebpackCompiler from './compiler/WebpackCompiler'; import ConfigLoader from './compiler/ConfigLoader'; -import { setGlobalLogLevel } from 'log4js'; const DEFAULT_STRYKER_WEBPACK_CONFIG = Object.freeze({ configFile: undefined, silent: true, context: process.cwd() }); @@ -11,7 +10,6 @@ export default class WebpackTranspiler implements Transpiler { private webpackCompiler: WebpackCompiler; public constructor(options: TranspilerOptions) { - setGlobalLogLevel(options.config.logLevel); if (options.produceSourceMaps) { throw new Error(`Invalid \`coverageAnalysis\` "${options.config.coverageAnalysis}" is not supported by the stryker-webpack-transpiler (yet). It is not able to produce source maps yet. Please set it "coverageAnalysis" to "off".`); } diff --git a/packages/stryker-webpack-transpiler/src/compiler/ConfigLoader.ts b/packages/stryker-webpack-transpiler/src/compiler/ConfigLoader.ts index 4243db6878..014203e486 100644 --- a/packages/stryker-webpack-transpiler/src/compiler/ConfigLoader.ts +++ b/packages/stryker-webpack-transpiler/src/compiler/ConfigLoader.ts @@ -2,7 +2,7 @@ import * as path from 'path'; import * as fs from 'fs'; import { Configuration } from 'webpack'; import { StrykerWebpackConfig } from '../WebpackTranspiler'; -import { getLogger, Logger } from 'log4js'; +import { getLogger, Logger } from 'stryker-api/logging'; import { isFunction } from 'lodash'; const PROGRESS_PLUGIN_NAME = 'ProgressPlugin'; diff --git a/packages/stryker-webpack-transpiler/test/helpers/sinonInit.ts b/packages/stryker-webpack-transpiler/test/helpers/sinonInit.ts index 5148e8d332..99416bd9a7 100644 --- a/packages/stryker-webpack-transpiler/test/helpers/sinonInit.ts +++ b/packages/stryker-webpack-transpiler/test/helpers/sinonInit.ts @@ -1,5 +1,5 @@ import * as sinon from 'sinon'; -import * as log4js from 'log4js'; +import * as logging from 'stryker-api/logging'; beforeEach(() => { global.sandbox = sinon.sandbox.create(); @@ -9,7 +9,7 @@ beforeEach(() => { warn: sandbox.stub(), error: sandbox.stub() }; - sandbox.stub(log4js, 'getLogger').returns(global.logMock); + sandbox.stub(logging, 'getLogger').returns(global.logMock); }); afterEach(() => { diff --git a/packages/stryker-webpack-transpiler/test/unit/WebpackTranspilerSpec.ts b/packages/stryker-webpack-transpiler/test/unit/WebpackTranspilerSpec.ts index f827e3e044..d13c2b706a 100644 --- a/packages/stryker-webpack-transpiler/test/unit/WebpackTranspilerSpec.ts +++ b/packages/stryker-webpack-transpiler/test/unit/WebpackTranspilerSpec.ts @@ -6,7 +6,6 @@ import { Config } from 'stryker-api/config'; import { File } from 'stryker-api/core'; import { expect } from 'chai'; import { Configuration } from 'webpack'; -import * as log4js from 'log4js'; describe('WebpackTranspiler', () => { let webpackTranspiler: WebpackTranspiler; @@ -27,7 +26,6 @@ describe('WebpackTranspiler', () => { configLoaderStub = createMockInstance(ConfigLoader); configLoaderStub.load.returns(webpackConfig); - sandbox.stub(log4js, 'setGlobalLogLevel'); sandbox.stub(configLoaderModule, 'default').returns(configLoaderStub); sandbox.stub(webpackCompilerModule, 'default').returns(webpackCompilerStub); @@ -48,12 +46,6 @@ describe('WebpackTranspiler', () => { expect(configLoaderStub.load).calledWith(createStrykerWebpackConfig()); }); - it('should set global log level when compiler is called', () => { - config.logLevel = 'foobar level'; - new WebpackTranspiler({ config, produceSourceMaps: false }); - expect(log4js.setGlobalLogLevel).calledWith('foobar level'); - }); - it('should throw an error if `produceSourceMaps` is `true`', () => { expect(() => new WebpackTranspiler({ config, produceSourceMaps: true })).throws('Invalid `coverageAnalysis` "perTest" is not supported by the stryker-webpack-transpiler (yet). It is not able to produce source maps yet. Please set it "coverageAnalysis" to "off"'); }); diff --git a/packages/stryker/.vscode/launch.json b/packages/stryker/.vscode/launch.json index 8a8337fe5f..5d87b722b7 100644 --- a/packages/stryker/.vscode/launch.json +++ b/packages/stryker/.vscode/launch.json @@ -77,7 +77,7 @@ "run", "stryker.conf.js" ], - "cwd": "${workspaceRoot}/../../integrationTest/test/typescript-transpiling", + "cwd": "${workspaceRoot}", "runtimeExecutable": null, "runtimeArgs": [ "--nolazy" diff --git a/packages/stryker/README.md b/packages/stryker/README.md index 48439bce37..b07e9e6338 100644 --- a/packages/stryker/README.md +++ b/packages/stryker/README.md @@ -318,6 +318,15 @@ It is not allowed to only supply one value of the values (it's all or nothing). **Default value:** `info` **Mandatory**: no **Description:** - Set the `log4js` log level that Stryker uses (default is `info`). Possible values: `fatal`, `error`, `warn`, `info`, `debug`, `trace`, `all` and `off`. + Set the log level that Stryker uses to write to the console. Possible values: `off`, `fatal`, `error`, `warn`, `info`, `debug` and `trace` + *Note*: Test runners are run as child processes of the Stryker Node process. All output (stdout) of the `testRunner` is logged as `trace`. Thus, to see logging output from the test runner set the `logLevel` to `all` or `trace`. + +#### File log level +**Command line:** `--fileLogLevel info` +**Config file:** `fileLogLevel: 'info'` +**Default value:** `off` +**Mandatory**: no +**Description:** + Set the log level that Stryker uses to write to the "stryker.log" file. Possible values: `off`, `fatal`, `error`, `warn`, `info`, `debug` and `trace` diff --git a/packages/stryker/package.json b/packages/stryker/package.json index c18f9092f6..488ad962c2 100644 --- a/packages/stryker/package.json +++ b/packages/stryker/package.json @@ -9,7 +9,7 @@ "prebuild": "rimraf \"+(test|src)/**/*+(.d.ts|.js|.map)\" .nyc_output reports coverage", "build": "tsc -p .", "postbuild": "tslint -p tsconfig.json", - "test": "nyc --check-coverage --reporter=html --report-dir=reports/coverage --lines 80 --functions 80 --branches 75 mocha \"test/**/*.js\"", + "test": "nyc --check-coverage --reporter=html --report-dir=reports/coverage --lines 80 --functions 80 --branches 75 mocha \"test/helpers/**/*.js\" \"test/unit/**/*.js\" \"test/integration/**/*.js\"", "stryker": "node bin/stryker run" }, "repository": { @@ -57,11 +57,12 @@ "commander": "~2.16.0", "escodegen": "~1.8.0", "esprima": "~2.7.0", + "get-port": "~3.2.0", "glob": "~7.1.2", "inquirer": "~6.0.0", "istanbul-lib-instrument": "~1.10.1", "lodash": "~4.17.4", - "log4js": "~1.1.1", + "log4js": "~3.0.0", "mkdirp": "~0.5.1", "mz": "~2.7.0", "prettier": "~1.13.7", @@ -75,7 +76,8 @@ "typed-rest-client": "~1.0.7" }, "devDependencies": { - "@types/inquirer": "0.0.42", + "@types/get-port": "~3.2.0", + "@types/inquirer": "~0.0.42", "@types/istanbul-lib-instrument": "~1.7.0", "@types/node": "^6.0.114", "@types/prettier": "~1.13.1", @@ -83,6 +85,6 @@ "stryker-api": "~0.17.3" }, "peerDependencies": { - "stryker-api": ">=0.15.0 <0.18.0" + "stryker-api": "^0.18.0" } } diff --git a/packages/stryker/src/MutantTestMatcher.ts b/packages/stryker/src/MutantTestMatcher.ts index de0fa9778e..956d63c3d1 100644 --- a/packages/stryker/src/MutantTestMatcher.ts +++ b/packages/stryker/src/MutantTestMatcher.ts @@ -1,5 +1,5 @@ import * as _ from 'lodash'; -import { getLogger } from 'log4js'; +import { getLogger } from 'stryker-api/logging'; import { RunResult, CoverageCollection, StatementMap, CoveragePerTestResult, CoverageResult } from 'stryker-api/test_runner'; import { StrykerOptions, File } from 'stryker-api/core'; import { MatchedMutant } from 'stryker-api/report'; diff --git a/packages/stryker/src/PluginLoader.ts b/packages/stryker/src/PluginLoader.ts index 2678877aec..9efc457bc9 100644 --- a/packages/stryker/src/PluginLoader.ts +++ b/packages/stryker/src/PluginLoader.ts @@ -1,6 +1,6 @@ import * as fs from 'mz/fs'; import * as path from 'path'; -import { getLogger } from 'log4js'; +import { getLogger } from 'stryker-api/logging'; import * as _ from 'lodash'; import { importModule } from './utils/fileUtils'; diff --git a/packages/stryker/src/ReporterOrchestrator.ts b/packages/stryker/src/ReporterOrchestrator.ts index 85c3ddb0b4..ae32b03619 100644 --- a/packages/stryker/src/ReporterOrchestrator.ts +++ b/packages/stryker/src/ReporterOrchestrator.ts @@ -8,7 +8,7 @@ import EventRecorderReporter from './reporters/EventRecorderReporter'; import BroadcastReporter, { NamedReporter } from './reporters/BroadcastReporter'; import DashboardReporter from './reporters/DashboardReporter'; import StrictReporter from './reporters/StrictReporter'; -import { getLogger } from 'log4js'; +import { getLogger } from 'stryker-api/logging'; function registerDefaultReporters() { ReporterFactory.instance().register('progress-append-only', ProgressAppendOnlyReporter); diff --git a/packages/stryker/src/Sandbox.ts b/packages/stryker/src/Sandbox.ts index ae6c84855c..ee92e5fe24 100644 --- a/packages/stryker/src/Sandbox.ts +++ b/packages/stryker/src/Sandbox.ts @@ -1,18 +1,18 @@ import { Config } from 'stryker-api/config'; import * as path from 'path'; -import { getLogger } from 'log4js'; +import { getLogger } from 'stryker-api/logging'; import * as mkdirp from 'mkdirp'; -import { RunResult } from 'stryker-api/test_runner'; +import { RunResult, RunnerOptions } from 'stryker-api/test_runner'; import { File } from 'stryker-api/core'; import { TestFramework } from 'stryker-api/test_framework'; import { wrapInClosure, normalizeWhiteSpaces } from './utils/objectUtils'; import TestRunnerDecorator from './isolated-runner/TestRunnerDecorator'; import ResilientTestRunnerFactory from './isolated-runner/ResilientTestRunnerFactory'; -import IsolatedRunnerOptions from './isolated-runner/IsolatedRunnerOptions'; import { TempFolder } from './utils/TempFolder'; import { writeFile, findNodeModules, symlinkJunction } from './utils/fileUtils'; import TestableMutant, { TestSelectionResult } from './TestableMutant'; import TranspiledMutant from './TranspiledMutant'; +import LoggingClientContext from './logging/LoggingClientContext'; interface FileMap { [sourceFile: string]: string; @@ -24,11 +24,11 @@ export default class Sandbox { private testRunner: TestRunnerDecorator; private fileMap: FileMap; private files: File[]; - private workingFolder: string; + private workingDirectory: string; - private constructor(private options: Config, private index: number, files: ReadonlyArray, private testFramework: TestFramework | null, private timeOverheadMS: number) { - this.workingFolder = TempFolder.instance().createRandomFolder('sandbox'); - this.log.debug('Creating a sandbox for files in %s', this.workingFolder); + private constructor(private options: Config, private index: number, files: ReadonlyArray, private testFramework: TestFramework | null, private timeOverheadMS: number, private loggingContext: LoggingClientContext) { + this.workingDirectory = TempFolder.instance().createRandomFolder('sandbox'); + this.log.debug('Creating a sandbox for files in %s', this.workingDirectory); this.files = files.slice(); // Create a copy } @@ -38,9 +38,9 @@ export default class Sandbox { return this.initializeTestRunner(); } - public static create(options: Config, index: number, files: ReadonlyArray, testFramework: TestFramework | null, timeoutOverheadMS: number) + public static create(options: Config, index: number, files: ReadonlyArray, testFramework: TestFramework | null, timeoutOverheadMS: number, loggingContext: LoggingClientContext) : Promise { - const sandbox = new Sandbox(options, index, files, testFramework, timeoutOverheadMS); + const sandbox = new Sandbox(options, index, files, testFramework, timeoutOverheadMS, loggingContext); return sandbox.initialize().then(() => sandbox); } @@ -87,7 +87,7 @@ export default class Sandbox { const basePath = process.cwd(); const nodeModules = await findNodeModules(basePath); if (nodeModules) { - await symlinkJunction(nodeModules, path.join(this.workingFolder, 'node_modules')) + await symlinkJunction(nodeModules, path.join(this.workingDirectory, 'node_modules')) .catch((error: NodeJS.ErrnoException) => { if (error.code === 'EEXIST') { this.log.warn(normalizeWhiteSpaces(`Could not symlink "${nodeModules}" in sandbox directory, @@ -105,7 +105,7 @@ export default class Sandbox { private fillFile(file: File): Promise { const relativePath = path.relative(process.cwd(), file.name); - const folderName = path.join(this.workingFolder, path.dirname(relativePath)); + const folderName = path.join(this.workingDirectory, path.dirname(relativePath)); mkdirp.sync(folderName); const targetFile = path.join(folderName, path.basename(relativePath)); this.fileMap[file.name] = targetFile; @@ -113,14 +113,13 @@ export default class Sandbox { } private initializeTestRunner(): void | Promise { - const settings: IsolatedRunnerOptions = { + const settings: RunnerOptions = { fileNames: Object.keys(this.fileMap).map(sourceFileName => this.fileMap[sourceFileName]), strykerOptions: this.options, - port: this.options.port + this.index, - sandboxWorkingFolder: this.workingFolder + port: this.options.port + this.index }; this.log.debug(`Creating test runner %s using settings {port: %s}`, this.index, settings.port); - this.testRunner = ResilientTestRunnerFactory.create(settings.strykerOptions.testRunner || '', settings); + this.testRunner = ResilientTestRunnerFactory.create(settings.strykerOptions.testRunner || '', settings, this.workingDirectory, this.loggingContext); return this.testRunner.init(); } diff --git a/packages/stryker/src/SandboxPool.ts b/packages/stryker/src/SandboxPool.ts index 8e25bd0a51..ebc6355185 100644 --- a/packages/stryker/src/SandboxPool.ts +++ b/packages/stryker/src/SandboxPool.ts @@ -1,4 +1,4 @@ -import { getLogger } from 'log4js'; +import { getLogger } from 'stryker-api/logging'; import * as os from 'os'; import { Observable, range } from 'rxjs'; import { flatMap } from 'rxjs/operators'; @@ -6,13 +6,14 @@ import { Config } from 'stryker-api/config'; import { File } from 'stryker-api/core'; import { TestFramework } from 'stryker-api/test_framework'; import Sandbox from './Sandbox'; +import LoggingClientContext from './logging/LoggingClientContext'; export default class SandboxPool { private readonly log = getLogger(SandboxPool.name); private readonly sandboxes: Promise[] = []; - constructor(private options: Config, private testFramework: TestFramework | null, private initialFiles: ReadonlyArray, private overheadTimeMS: number) { + constructor(private options: Config, private testFramework: TestFramework | null, private initialFiles: ReadonlyArray, private overheadTimeMS: number, private loggingContext: LoggingClientContext) { } public streamSandboxes(): Observable { @@ -32,7 +33,7 @@ export default class SandboxPool { this.log.info(`Creating ${numConcurrentRunners} test runners (based on ${numConcurrentRunnersSource})`); const sandboxes = range(0, numConcurrentRunners) - .pipe(flatMap(n => this.registerSandbox(Sandbox.create(this.options, n, this.initialFiles, this.testFramework, this.overheadTimeMS)))); + .pipe(flatMap(n => this.registerSandbox(Sandbox.create(this.options, n, this.initialFiles, this.testFramework, this.overheadTimeMS, this.loggingContext)))); return sandboxes; } diff --git a/packages/stryker/src/ScoreResultCalculator.ts b/packages/stryker/src/ScoreResultCalculator.ts index 748e845ed7..010577c520 100644 --- a/packages/stryker/src/ScoreResultCalculator.ts +++ b/packages/stryker/src/ScoreResultCalculator.ts @@ -1,6 +1,6 @@ import * as path from 'path'; import * as _ from 'lodash'; -import { getLogger } from 'log4js'; +import { getLogger } from 'stryker-api/logging'; import { MutationScoreThresholds } from 'stryker-api/core'; import { MutantResult, MutantStatus, ScoreResult } from 'stryker-api/report'; import { freezeRecursively, setExitCode } from './utils/objectUtils'; diff --git a/packages/stryker/src/Stryker.ts b/packages/stryker/src/Stryker.ts index bcf6ff1c9a..acd4a4061f 100644 --- a/packages/stryker/src/Stryker.ts +++ b/packages/stryker/src/Stryker.ts @@ -1,3 +1,4 @@ +import { getLogger, Logger } from 'stryker-api/logging'; import { Config, ConfigEditorFactory } from 'stryker-api/config'; import { StrykerOptions, MutatorDescriptor } from 'stryker-api/core'; import { MutantResult } from 'stryker-api/report'; @@ -7,19 +8,19 @@ import ReporterOrchestrator from './ReporterOrchestrator'; import TestFrameworkOrchestrator from './TestFrameworkOrchestrator'; import MutantTestMatcher from './MutantTestMatcher'; import InputFileResolver from './input/InputFileResolver'; -import ConfigReader from './ConfigReader'; +import ConfigReader from './config/ConfigReader'; import PluginLoader from './PluginLoader'; import ScoreResultCalculator from './ScoreResultCalculator'; -import ConfigValidator from './ConfigValidator'; +import ConfigValidator from './config/ConfigValidator'; import { freezeRecursively, isPromise } from './utils/objectUtils'; import { TempFolder } from './utils/TempFolder'; -import * as log4js from 'log4js'; import Timer from './utils/Timer'; import StrictReporter from './reporters/StrictReporter'; import MutatorFacade from './MutatorFacade'; import InitialTestExecutor, { InitialTestRunResult } from './process/InitialTestExecutor'; import MutationTestExecutor from './process/MutationTestExecutor'; import InputFileCollection from './input/InputFileCollection'; +import LogConfigurator from './logging/LogConfigurator'; export default class Stryker { @@ -27,7 +28,7 @@ export default class Stryker { private timer = new Timer(); private reporter: StrictReporter; private testFramework: TestFramework | null; - private readonly log = log4js.getLogger(Stryker.name); + private readonly log: Logger; /** * The Stryker mutation tester. @@ -35,12 +36,14 @@ export default class Stryker { * @param {Object} [options] - Optional options. */ constructor(options: StrykerOptions) { + LogConfigurator.configureMainProcess(options.logLevel, options.fileLogLevel); + this.log = getLogger(Stryker.name); let configReader = new ConfigReader(options); this.config = configReader.readConfig(); - this.setGlobalLogLevel(); // logLevel could be changed + LogConfigurator.configureMainProcess(this.config.logLevel, this.config.fileLogLevel); // logLevel could be changed this.loadPlugins(); this.applyConfigEditors(); - this.setGlobalLogLevel(); // logLevel could be changed + LogConfigurator.configureMainProcess(this.config.logLevel, this.config.fileLogLevel); // logLevel could be changed this.freezeConfig(); this.reporter = new ReporterOrchestrator(this.config).createBroadcastReporter(); this.testFramework = new TestFrameworkOrchestrator(this.config).determineTestFramework(); @@ -48,16 +51,17 @@ export default class Stryker { } async runMutationTest(): Promise { + const loggingContext = await LogConfigurator.configureLoggingServer(this.config.logLevel, this.config.fileLogLevel); this.timer.reset(); const inputFiles = await new InputFileResolver(this.config.mutate, this.config.files, this.reporter).resolve(); if (inputFiles.files.length) { TempFolder.instance().initialize(); - const initialTestRunProcess = new InitialTestExecutor(this.config, inputFiles, this.testFramework, this.timer); + const initialTestRunProcess = new InitialTestExecutor(this.config, inputFiles, this.testFramework, this.timer, loggingContext); const initialTestRunResult = await initialTestRunProcess.run(); const testableMutants = await this.mutate(inputFiles, initialTestRunResult); if (initialTestRunResult.runResult.tests.length && testableMutants.length) { const mutationTestExecutor = new MutationTestExecutor(this.config, inputFiles.files, this.testFramework, this.reporter, - initialTestRunResult.overheadTimeMS); + initialTestRunResult.overheadTimeMS, loggingContext); const mutantResults = await mutationTestExecutor.run(testableMutants); this.reportScore(mutantResults); await this.wrapUpReporter(); @@ -147,10 +151,6 @@ export default class Stryker { this.log.info('Done in %s.', this.timer.humanReadableElapsed()); } - private setGlobalLogLevel() { - log4js.setGlobalLogLevel(this.config.logLevel); - } - private reportScore(mutantResults: MutantResult[]) { const calculator = new ScoreResultCalculator(); const score = calculator.calculate(mutantResults); diff --git a/packages/stryker/src/StrykerCli.ts b/packages/stryker/src/StrykerCli.ts index 017e84bec0..0da8904f2a 100644 --- a/packages/stryker/src/StrykerCli.ts +++ b/packages/stryker/src/StrykerCli.ts @@ -1,13 +1,12 @@ import * as program from 'commander'; -import { CONFIG_SYNTAX_HELP } from './ConfigReader'; +import { CONFIG_SYNTAX_HELP } from './config/ConfigReader'; import Stryker from './Stryker'; import StrykerInitializer from './initializer/StrykerInitializer'; -import { getLogger, setGlobalLogLevel } from 'log4js'; - +import { getLogger } from 'stryker-api/logging'; +import LogConfigurator from './logging/LogConfigurator'; export default class StrykerCli { - private readonly log = getLogger(StrykerCli.name); private command: string = ''; private strykerConfig: string | null = null; @@ -23,7 +22,7 @@ export default class StrykerCli { .usage(' [options] [stryker.conf.js]') .description(`Possible commands: run: Run mutation testing - init: Initalize Stryker for your project + init: Initialize Stryker for your project Optional location to the stryker.conf.js file as last argument. That file should export a function which accepts a "config" object\n${CONFIG_SYNTAX_HELP}`) .arguments(' [stryker.conf.js]') @@ -46,11 +45,12 @@ export default class StrykerCli { .option('--timeoutMs ', 'Tweak the absolute timeout used to wait for a test runner to complete', parseInt) .option('--timeoutFactor ', 'Tweak the standard deviation relative to the normal test run of a mutated test', parseFloat) .option('--maxConcurrentTestRunners ', 'Set the number of max concurrent test runner to spawn (default: cpuCount)', parseInt) - .option('--logLevel ', 'Set the log4js log level. Possible values: fatal, error, warn, info, debug, trace, all and off. Default is "info"') + .option('--logLevel ', 'Set the log level for the console. Possible values: fatal, error, warn, info, debug, trace, all and off. Default is "info"') + .option('--fileLogLevel ', 'Set the log4js log level for the "stryker.log" file. Possible values: fatal, error, warn, info, debug, trace, all and off. Default is "off"') .parse(this.argv); - setGlobalLogLevel(program['logLevel'] || 'info'); - + LogConfigurator.configureMainProcess(program['logLevel']); + const log = getLogger(StrykerCli.name); // Cleanup commander state delete program['options']; delete program['rawArgs']; @@ -68,10 +68,6 @@ export default class StrykerCli { program['configFile'] = this.strykerConfig; } - if (program['logLevel']) { - setGlobalLogLevel(program['logLevel']); - } - const commands: { [cmd: string]: () => Promise } = { init: () => new StrykerInitializer().initialize(), run: () => new Stryker(program).runMutationTest() @@ -79,12 +75,12 @@ export default class StrykerCli { if (Object.keys(commands).indexOf(this.command) >= 0) { commands[this.command]().catch(err => { - this.log.error(`an error occurred`, err); + log.error(`an error occurred`, err); process.exitCode = 1; process.kill(process.pid, 'SIGINT'); }); } else { - this.log.error('Unknown command: "%s", supported commands: [%s], or use `stryker --help`.', this.command, Object.keys(commands)); + log.error('Unknown command: "%s", supported commands: [%s], or use `stryker --help`.', this.command, Object.keys(commands)); } } } diff --git a/packages/stryker/src/TestFrameworkOrchestrator.ts b/packages/stryker/src/TestFrameworkOrchestrator.ts index f9b9bdb73d..26846384b5 100644 --- a/packages/stryker/src/TestFrameworkOrchestrator.ts +++ b/packages/stryker/src/TestFrameworkOrchestrator.ts @@ -1,6 +1,6 @@ import { TestFrameworkFactory, TestFramework } from 'stryker-api/test_framework'; import { StrykerOptions } from 'stryker-api/core'; -import { getLogger } from 'log4js'; +import { getLogger } from 'stryker-api/logging'; export default class TestFrameworkOrchestrator { private readonly log = getLogger(TestFrameworkOrchestrator.name); diff --git a/packages/stryker/src/child-proxy/ChildProcessProxy.ts b/packages/stryker/src/child-proxy/ChildProcessProxy.ts index 4270093d13..e5fd08ca90 100644 --- a/packages/stryker/src/child-proxy/ChildProcessProxy.ts +++ b/packages/stryker/src/child-proxy/ChildProcessProxy.ts @@ -1,9 +1,10 @@ import { fork, ChildProcess } from 'child_process'; import { File } from 'stryker-api/core'; +import { getLogger } from 'stryker-api/logging'; import { WorkerMessage, WorkerMessageKind, ParentMessage, autoStart, ParentMessageKind } from './messageProtocol'; import { serialize, deserialize } from '../utils/objectUtils'; import Task from '../utils/Task'; -import { getLogger } from 'log4js'; +import LoggingClientContext from '../logging/LoggingClientContext'; export type ChildProxy = { [K in keyof T]: (...args: any[]) => Promise; @@ -14,15 +15,16 @@ export default class ChildProcessProxy { private worker: ChildProcess; private initTask: Task; + private disposeTask: Task; private workerTasks: Task[] = []; private log = getLogger(ChildProcessProxy.name); - private constructor(requirePath: string, logLevel: string, plugins: string[], private constructorFunction: { new(...params: any[]): T }, constructorParams: any[]) { + private constructor(requirePath: string, loggingContext: LoggingClientContext, plugins: string[], private constructorFunction: { new(...params: any[]): T }, constructorParams: any[]) { this.worker = fork(require.resolve('./ChildProcessProxyWorker'), [autoStart], { silent: false, execArgv: [] }); this.initTask = new Task(); this.send({ kind: WorkerMessageKind.Init, - logLevel, + loggingContext, plugins, requirePath, constructorArgs: constructorParams @@ -34,16 +36,16 @@ export default class ChildProcessProxy { /** * Creates a proxy where each function of the object created using the constructorFunction arg is ran inside of a child process */ - static create(requirePath: string, logLevel: string, plugins: string[], constructorFunction: { new(arg: P1): T }, arg: P1): ChildProcessProxy; + static create(requirePath: string, loggingContext: LoggingClientContext, plugins: string[], constructorFunction: { new(arg: P1): T }, arg: P1): ChildProcessProxy; /** * Creates a proxy where each function of the object created using the constructorFunction arg is ran inside of a child process */ - static create(requirePath: string, logLevel: string, plugins: string[], constructorFunction: { new(arg: P1, arg2: P2): T }, arg1: P1, arg2: P2): ChildProcessProxy; + static create(requirePath: string, loggingContext: LoggingClientContext, plugins: string[], constructorFunction: { new(arg: P1, arg2: P2): T }, arg1: P1, arg2: P2): ChildProcessProxy; /** * Creates a proxy where each function of the object created using the constructorFunction arg is ran inside of a child process */ - static create(requirePath: string, logLevel: string, plugins: string[], constructorFunction: { new(...params: any[]): T }, ...constructorArgs: any[]) { - return new ChildProcessProxy(requirePath, logLevel, plugins, constructorFunction, constructorArgs); + static create(requirePath: string, loggingContext: LoggingClientContext, plugins: string[], constructorFunction: { new(...params: any[]): T }, ...constructorArgs: any[]) { + return new ChildProcessProxy(requirePath, loggingContext, plugins, constructorFunction, constructorArgs); } private send(message: WorkerMessage) { @@ -85,6 +87,9 @@ export default class ChildProcessProxy { case ParentMessageKind.Rejection: this.workerTasks[message.correlationId].reject(new Error(message.error)); break; + case ParentMessageKind.DisposeCompleted: + this.disposeTask.resolve(undefined); + break; default: this.logUnidentifiedMessage(message); break; @@ -92,8 +97,12 @@ export default class ChildProcessProxy { }); } - public dispose() { - this.worker.kill(); + public dispose(): Promise { + this.disposeTask = new Task(); + this.send({ kind: WorkerMessageKind.Dispose }); + return this.disposeTask.promise + .then(() => this.worker.kill()) + .catch(() => this.worker.kill()); } private logUnidentifiedMessage(message: never) { diff --git a/packages/stryker/src/child-proxy/ChildProcessProxyWorker.ts b/packages/stryker/src/child-proxy/ChildProcessProxyWorker.ts index db74f4e19e..d7a8d8c981 100644 --- a/packages/stryker/src/child-proxy/ChildProcessProxyWorker.ts +++ b/packages/stryker/src/child-proxy/ChildProcessProxyWorker.ts @@ -1,12 +1,13 @@ -import { setGlobalLogLevel, getLogger } from 'log4js'; +import { getLogger, Logger } from 'stryker-api/logging'; import { File } from 'stryker-api/core'; import { serialize, deserialize, errorToString } from '../utils/objectUtils'; import { WorkerMessage, WorkerMessageKind, ParentMessage, autoStart, ParentMessageKind } from './messageProtocol'; import PluginLoader from '../PluginLoader'; +import LogConfigurator from '../logging/LogConfigurator'; export default class ChildProcessProxyWorker { - private log = getLogger(ChildProcessProxyWorker.name); + private log: Logger; realSubject: any; @@ -26,7 +27,8 @@ export default class ChildProcessProxyWorker { const message = deserialize(serializedMessage, [File]); switch (message.kind) { case WorkerMessageKind.Init: - setGlobalLogLevel(message.logLevel); + LogConfigurator.configureChildProcess(message.loggingContext); + this.log = getLogger(ChildProcessProxyWorker.name); new PluginLoader(message.plugins).load(); const RealSubjectClass = require(message.requirePath).default; this.realSubject = new RealSubjectClass(...message.constructorArgs); @@ -50,6 +52,14 @@ export default class ChildProcessProxyWorker { }); this.removeAnyAdditionalMessageListeners(handler); break; + case WorkerMessageKind.Dispose: + const sendCompleted = () => { + this.send({ kind: ParentMessageKind.DisposeCompleted }); + }; + LogConfigurator.shutdown() + .then(sendCompleted) + .catch(sendCompleted); + break; } }; process.on('message', handler); diff --git a/packages/stryker/src/child-proxy/messageProtocol.ts b/packages/stryker/src/child-proxy/messageProtocol.ts index 980aba168b..b93669aec5 100644 --- a/packages/stryker/src/child-proxy/messageProtocol.ts +++ b/packages/stryker/src/child-proxy/messageProtocol.ts @@ -1,16 +1,20 @@ +import LoggingClientContext from '../logging/LoggingClientContext'; + export enum WorkerMessageKind { 'Init', - 'Work' + 'Work', + 'Dispose' } export enum ParentMessageKind { 'Initialized', 'Result', - 'Rejection' + 'Rejection', + 'DisposeCompleted' } -export type WorkerMessage = InitMessage | WorkMessage; -export type ParentMessage = WorkResult | { kind: ParentMessageKind.Initialized} | RejectionResult; +export type WorkerMessage = InitMessage | WorkMessage | { kind: WorkerMessageKind.Dispose }; +export type ParentMessage = WorkResult | { kind: ParentMessageKind.Initialized | ParentMessageKind.DisposeCompleted } | RejectionResult; // Make this an unlikely command line argument // (prevents incidental start of child process) @@ -18,7 +22,7 @@ export const autoStart = 'childProcessAutoStart12937129s7d'; export interface InitMessage { kind: WorkerMessageKind.Init; - logLevel: string; + loggingContext: LoggingClientContext; plugins: string[]; requirePath: string; constructorArgs: any[]; @@ -41,4 +45,4 @@ export interface WorkMessage { kind: WorkerMessageKind.Work; args: any[]; methodName: string; -} \ No newline at end of file +} diff --git a/packages/stryker/src/ConfigReader.ts b/packages/stryker/src/config/ConfigReader.ts similarity index 71% rename from packages/stryker/src/ConfigReader.ts rename to packages/stryker/src/config/ConfigReader.ts index 8afd4d8e28..3f2feecc8d 100644 --- a/packages/stryker/src/ConfigReader.ts +++ b/packages/stryker/src/config/ConfigReader.ts @@ -1,9 +1,10 @@ import { Config } from 'stryker-api/config'; import { StrykerOptions } from 'stryker-api/core'; import * as fs from 'mz/fs'; -import * as log4js from 'log4js'; +import { getLogger } from 'stryker-api/logging'; import * as path from 'path'; import * as _ from 'lodash'; +import StrykerError from '../utils/StrykerError'; export const CONFIG_SYNTAX_HELP = ' module.exports = function(config) {\n' + ' config.set({\n' + @@ -15,7 +16,7 @@ const DEFAULT_CONFIG_FILE = 'stryker.conf.js'; export default class ConfigReader { - private readonly log = log4js.getLogger(ConfigReader.name); + private readonly log = getLogger(ConfigReader.name); constructor(private cliOptions: StrykerOptions) { } @@ -25,8 +26,7 @@ export default class ConfigReader { try { configModule(config); } catch (e) { - this.log.fatal('Error in config file!\n', e); - process.exit(1); + throw new StrykerError('Error in config file!', e); } // merge the config from config file and cliOptions (precedence) @@ -51,26 +51,24 @@ export default class ConfigReader { if (this.cliOptions.configFile) { this.log.debug(`Loading config ${this.cliOptions.configFile}`); + const configFileName = path.resolve(this.cliOptions.configFile); try { - configModule = require(`${process.cwd()}/${this.cliOptions.configFile}`); + configModule = require(configFileName); } catch (e) { if (e.code === 'MODULE_NOT_FOUND' && e.message.indexOf(this.cliOptions.configFile) !== -1) { - this.log.fatal(`File ${process.cwd()}/${this.cliOptions.configFile} does not exist!`); - this.log.fatal(e); + throw new StrykerError(`File ${configFileName} does not exist!`, e); } else { - this.log.fatal('Invalid config file!\n ' + e.stack); + this.log.info('Stryker can help you setup a `stryker.conf` file for your project.'); + this.log.info('Please execute `stryker init` in your project\'s root directory.'); + throw new StrykerError('Invalid config file', e); } - this.log.info('Stryker can help you setup a `stryker.conf` file for your project.'); - this.log.info('Please execute `stryker init` in your project\'s root directory.'); - process.exit(1); } if (!_.isFunction(configModule)) { this.log.fatal('Config file must export a function!\n' + CONFIG_SYNTAX_HELP); - process.exit(1); + throw new StrykerError('Config file must export a function!'); } } return configModule; } - } \ No newline at end of file diff --git a/packages/stryker/src/ConfigValidator.ts b/packages/stryker/src/config/ConfigValidator.ts similarity index 88% rename from packages/stryker/src/ConfigValidator.ts rename to packages/stryker/src/config/ConfigValidator.ts index a5c836217e..02d6b278af 100644 --- a/packages/stryker/src/ConfigValidator.ts +++ b/packages/stryker/src/config/ConfigValidator.ts @@ -1,7 +1,8 @@ import { TestFramework } from 'stryker-api/test_framework'; -import { MutatorDescriptor, MutationScoreThresholds } from 'stryker-api/core'; +import { MutatorDescriptor, MutationScoreThresholds, LogLevel } from 'stryker-api/core'; import { Config } from 'stryker-api/config'; -import { getLogger } from 'log4js'; +import { getLogger } from 'stryker-api/logging'; +import StrykerError from '../utils/StrykerError'; export default class ConfigValidator { @@ -15,7 +16,8 @@ export default class ConfigValidator { this.validateTestFramework(); this.validateThresholds(); this.validateMutator(); - this.validateLogLevel(); + this.validateLogLevel('logLevel'); + this.validateLogLevel('fileLogLevel'); this.validateTimeout(); this.validateIsNumber('port', this.strykerConfig.port); this.validateIsNumber('maxConcurrentTestRunners', this.strykerConfig.maxConcurrentTestRunners); @@ -68,9 +70,9 @@ export default class ConfigValidator { } } - private validateLogLevel() { - const logLevel = this.strykerConfig.logLevel; - const VALID_LOG_LEVEL_VALUES = ['fatal', 'error', 'warn', 'info', 'debug', 'trace', 'all', 'off']; + private validateLogLevel(logProperty: 'logLevel' | 'fileLogLevel') { + const logLevel = this.strykerConfig[logProperty]; + const VALID_LOG_LEVEL_VALUES = [LogLevel.Fatal, LogLevel.Error, LogLevel.Warning, LogLevel.Information, LogLevel.Debug, LogLevel.Trace, LogLevel.Off]; if (VALID_LOG_LEVEL_VALUES.indexOf(logLevel) < 0) { this.invalidate(`Value "${logLevel}" is invalid for \`logLevel\`. Expected one of the following: ${this.joinQuotedList(VALID_LOG_LEVEL_VALUES)}`); } @@ -101,7 +103,7 @@ export default class ConfigValidator { private crashIfNeeded() { if (!this.isValid) { - process.exit(1); + throw new StrykerError('Stryker could not recover from this configuration error, see fatal log message(s) above.'); } } diff --git a/packages/stryker/src/initializer/NpmClient.ts b/packages/stryker/src/initializer/NpmClient.ts index bb41404ad6..a182f206fa 100644 --- a/packages/stryker/src/initializer/NpmClient.ts +++ b/packages/stryker/src/initializer/NpmClient.ts @@ -1,6 +1,6 @@ import { RestClient, IRestResponse } from 'typed-rest-client/RestClient'; import PromptOption from './PromptOption'; -import { getLogger } from 'log4js'; +import { getLogger } from 'stryker-api/logging'; import { errorToString } from '../utils/objectUtils'; interface NpmSearchPackageInfo { diff --git a/packages/stryker/src/initializer/StrykerConfigWriter.ts b/packages/stryker/src/initializer/StrykerConfigWriter.ts index 945b31c1f1..7f94379b10 100644 --- a/packages/stryker/src/initializer/StrykerConfigWriter.ts +++ b/packages/stryker/src/initializer/StrykerConfigWriter.ts @@ -1,6 +1,6 @@ import * as fs from 'mz/fs'; import * as _ from 'lodash'; -import { getLogger } from 'log4js'; +import { getLogger } from 'stryker-api/logging'; import { StrykerOptions } from 'stryker-api/core'; import PromptOption from './PromptOption'; import { format } from 'prettier'; diff --git a/packages/stryker/src/initializer/StrykerInitializer.ts b/packages/stryker/src/initializer/StrykerInitializer.ts index 994cc4917a..32ea40626d 100644 --- a/packages/stryker/src/initializer/StrykerInitializer.ts +++ b/packages/stryker/src/initializer/StrykerInitializer.ts @@ -2,7 +2,7 @@ import * as child from 'child_process'; import { StrykerInquirer } from './StrykerInquirer'; import NpmClient from './NpmClient'; import PromptOption from './PromptOption'; -import { getLogger } from 'log4js'; +import { getLogger } from 'stryker-api/logging'; import { filterEmpty } from '../utils/objectUtils'; import StrykerConfigWriter from './StrykerConfigWriter'; diff --git a/packages/stryker/src/input/InputFileCollection.ts b/packages/stryker/src/input/InputFileCollection.ts index c5ca416cc9..2eb13a2a84 100644 --- a/packages/stryker/src/input/InputFileCollection.ts +++ b/packages/stryker/src/input/InputFileCollection.ts @@ -1,5 +1,5 @@ import { File } from 'stryker-api/core'; -import { Logger } from 'log4js'; +import { Logger } from 'stryker-api/logging'; import { normalizeWhiteSpaces } from '../utils/objectUtils'; export default class InputFileCollection { diff --git a/packages/stryker/src/input/InputFileResolver.ts b/packages/stryker/src/input/InputFileResolver.ts index a15ec8dfeb..e53f9b06bf 100644 --- a/packages/stryker/src/input/InputFileResolver.ts +++ b/packages/stryker/src/input/InputFileResolver.ts @@ -1,7 +1,7 @@ import * as path from 'path'; import * as fs from 'mz/fs'; import { exec } from 'mz/child_process'; -import { getLogger } from 'log4js'; +import { getLogger } from 'stryker-api/logging'; import { File } from 'stryker-api/core'; import { glob } from '../utils/fileUtils'; import StrictReporter from '../reporters/StrictReporter'; diff --git a/packages/stryker/src/isolated-runner/IsolatedRunnerOptions.ts b/packages/stryker/src/isolated-runner/IsolatedRunnerOptions.ts deleted file mode 100644 index f7cfe31fbf..0000000000 --- a/packages/stryker/src/isolated-runner/IsolatedRunnerOptions.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { RunnerOptions } from 'stryker-api/test_runner'; - -interface IsolatedRunnerOptions extends RunnerOptions { - sandboxWorkingFolder: string; -} - -export default IsolatedRunnerOptions; \ No newline at end of file diff --git a/packages/stryker/src/isolated-runner/IsolatedTestRunnerAdapter.ts b/packages/stryker/src/isolated-runner/IsolatedTestRunnerAdapter.ts index c089fcd493..0f73744b0f 100644 --- a/packages/stryker/src/isolated-runner/IsolatedTestRunnerAdapter.ts +++ b/packages/stryker/src/isolated-runner/IsolatedTestRunnerAdapter.ts @@ -1,12 +1,13 @@ import { EventEmitter } from 'events'; -import { getLogger } from 'log4js'; +import { getLogger } from 'stryker-api/logging'; import * as _ from 'lodash'; import { fork, ChildProcess } from 'child_process'; -import { TestRunner, RunResult, RunOptions } from 'stryker-api/test_runner'; +import { TestRunner, RunResult, RunOptions, RunnerOptions } from 'stryker-api/test_runner'; import { serialize, kill } from '../utils/objectUtils'; import { AdapterMessage, WorkerMessage } from './MessageProtocol'; -import IsolatedRunnerOptions from './IsolatedRunnerOptions'; import Task from '../utils/Task'; +import StrykerError from '../utils/StrykerError'; +import LoggingClientContext from '../logging/LoggingClientContext'; const MAX_WAIT_FOR_DISPOSE = 2000; @@ -33,7 +34,7 @@ export default class TestRunnerChildProcessAdapter extends EventEmitter implemen private currentTask: WorkerTask; private lastMessagesQueue: string[] = []; - constructor(private realTestRunnerName: string, private options: IsolatedRunnerOptions) { + constructor(private realTestRunnerName: string, private options: RunnerOptions, private sandboxWorkingDirectory: string, private loggingContext: LoggingClientContext) { super(); this.startWorker(); } @@ -109,7 +110,7 @@ export default class TestRunnerChildProcessAdapter extends EventEmitter implemen if (code !== 0 && code !== null) { this.log.error(`Child process exited with non-zero exit code ${code}. Last 10 message from the child process were: \r\n${this.lastMessagesQueue.map(msg => `\t${msg}`).join('\r\n')}`); if (this.currentTask) { - this.currentTask.reject(`Test runner child process exited with non-zero exit code ${code}`); + this.currentTask.reject(new StrykerError(`Test runner child process exited with non-zero exit code ${code}`)); } } }); @@ -163,7 +164,9 @@ export default class TestRunnerChildProcessAdapter extends EventEmitter implemen this.send({ kind: 'start', runnerName: this.realTestRunnerName, - runnerOptions: this.options + runnerOptions: this.options, + sandboxWorkingDirectory: this.sandboxWorkingDirectory, + loggingContext: this.loggingContext }); } diff --git a/packages/stryker/src/isolated-runner/IsolatedTestRunnerAdapterWorker.ts b/packages/stryker/src/isolated-runner/IsolatedTestRunnerAdapterWorker.ts index 50d2d66d7d..8a21099ed7 100644 --- a/packages/stryker/src/isolated-runner/IsolatedTestRunnerAdapterWorker.ts +++ b/packages/stryker/src/isolated-runner/IsolatedTestRunnerAdapterWorker.ts @@ -1,12 +1,13 @@ import { AdapterMessage, RunMessage, StartMessage, WorkerMessage, InitDoneMessage } from './MessageProtocol'; import { TestRunner, RunStatus, TestRunnerFactory, RunResult } from 'stryker-api/test_runner'; import PluginLoader from '../PluginLoader'; -import { getLogger } from 'log4js'; +import { getLogger, Logger } from 'stryker-api/logging'; import { deserialize, errorToString } from '../utils/objectUtils'; +import LogConfigurator from '../logging/LogConfigurator'; class IsolatedTestRunnerAdapterWorker { - private readonly log = getLogger(IsolatedTestRunnerAdapterWorker.name); + private log: Logger; private underlyingTestRunner: TestRunner; constructor() { @@ -28,7 +29,8 @@ class IsolatedTestRunnerAdapterWorker { this.init(); break; case 'dispose': - this.dispose(); + const sendDisposeDone = this.sendDisposeDone.bind(this); + this.dispose().then(sendDisposeDone, sendDisposeDone); break; default: this.logReceivedMessageWarning(message); @@ -58,9 +60,11 @@ class IsolatedTestRunnerAdapterWorker { } start(message: StartMessage) { + LogConfigurator.configureChildProcess(message.loggingContext); + this.log = getLogger(IsolatedTestRunnerAdapterWorker.name); this.loadPlugins(message.runnerOptions.strykerOptions.plugins || []); - this.log.debug(`Changing current working directory for this process to ${message.runnerOptions.sandboxWorkingFolder}`); - process.chdir(message.runnerOptions.sandboxWorkingFolder); + this.log.debug(`Changing current working directory for this process to ${message.sandboxWorkingDirectory}`); + process.chdir(message.sandboxWorkingDirectory); this.underlyingTestRunner = TestRunnerFactory.instance().create(message.runnerName, message.runnerOptions); } @@ -83,10 +87,13 @@ class IsolatedTestRunnerAdapterWorker { } async dispose() { - if (this.underlyingTestRunner.dispose) { - await this.underlyingTestRunner.dispose(); + try { + if (this.underlyingTestRunner.dispose) { + await this.underlyingTestRunner.dispose(); + } + } finally { + await LogConfigurator.shutdown(); } - this.sendDisposeDone(); } sendDisposeDone() { diff --git a/packages/stryker/src/isolated-runner/MessageProtocol.ts b/packages/stryker/src/isolated-runner/MessageProtocol.ts index 5cbaa81984..05e6ae31d5 100644 --- a/packages/stryker/src/isolated-runner/MessageProtocol.ts +++ b/packages/stryker/src/isolated-runner/MessageProtocol.ts @@ -1,6 +1,6 @@ -import { RunResult } from 'stryker-api/test_runner'; +import { RunResult, RunnerOptions } from 'stryker-api/test_runner'; import { RunOptions } from 'stryker-api/test_runner'; -import IsolatedRunnerOptions from './IsolatedRunnerOptions'; +import LoggingClientContext from '../logging/LoggingClientContext'; export type AdapterMessage = RunMessage | StartMessage | EmptyAdapterMessage; export type WorkerMessage = ResultMessage | EmptyWorkerMessage | InitDoneMessage; @@ -18,7 +18,9 @@ export interface RunMessage { export interface StartMessage { kind: 'start'; runnerName: string; - runnerOptions: IsolatedRunnerOptions; + runnerOptions: RunnerOptions; + sandboxWorkingDirectory: string; + loggingContext: LoggingClientContext; } export interface InitDoneMessage { diff --git a/packages/stryker/src/isolated-runner/ResilientTestRunnerFactory.ts b/packages/stryker/src/isolated-runner/ResilientTestRunnerFactory.ts index f0c8bf1dfa..711e27361a 100644 --- a/packages/stryker/src/isolated-runner/ResilientTestRunnerFactory.ts +++ b/packages/stryker/src/isolated-runner/ResilientTestRunnerFactory.ts @@ -1,13 +1,14 @@ import IsolatedTestRunnerAdapter from './IsolatedTestRunnerAdapter'; -import IsolatedRunnerOptions from './IsolatedRunnerOptions'; import TimeoutDecorator from './TimeoutDecorator'; import RetryDecorator from './RetryDecorator'; import TestRunnerDecorator from './TestRunnerDecorator'; +import LoggingClientContext from '../logging/LoggingClientContext'; +import { RunnerOptions } from 'stryker-api/test_runner'; export default { - create(testRunnerName: string, settings: IsolatedRunnerOptions): TestRunnerDecorator { + create(testRunnerName: string, settings: RunnerOptions, sandboxWorkingDirectory: string, loggingContext: LoggingClientContext): TestRunnerDecorator { return new RetryDecorator(() => - new TimeoutDecorator(() => new IsolatedTestRunnerAdapter(testRunnerName, settings))); + new TimeoutDecorator(() => new IsolatedTestRunnerAdapter(testRunnerName, settings, sandboxWorkingDirectory, loggingContext))); } }; \ No newline at end of file diff --git a/packages/stryker/src/isolated-runner/TestRunnerDecorator.ts b/packages/stryker/src/isolated-runner/TestRunnerDecorator.ts index c6b61eff26..53bb984c5c 100644 --- a/packages/stryker/src/isolated-runner/TestRunnerDecorator.ts +++ b/packages/stryker/src/isolated-runner/TestRunnerDecorator.ts @@ -30,6 +30,4 @@ export default class TestRunnerDecorator implements TestRunner { return Promise.resolve(); } } - - } \ No newline at end of file diff --git a/packages/stryker/src/isolated-runner/TimeoutDecorator.ts b/packages/stryker/src/isolated-runner/TimeoutDecorator.ts index 7bcc666d03..d72f044c86 100644 --- a/packages/stryker/src/isolated-runner/TimeoutDecorator.ts +++ b/packages/stryker/src/isolated-runner/TimeoutDecorator.ts @@ -2,7 +2,7 @@ import { RunOptions, RunResult, RunStatus } from 'stryker-api/test_runner'; import { isPromise } from '../utils/objectUtils'; import Task from '../utils/Task'; import TestRunnerDecorator from './TestRunnerDecorator'; -import { getLogger } from 'log4js'; +import { getLogger } from 'stryker-api/logging'; const MAX_WAIT_FOR_DISPOSE = 2500; diff --git a/packages/stryker/src/logging/LogConfigurator.ts b/packages/stryker/src/logging/LogConfigurator.ts new file mode 100644 index 0000000000..e8192810da --- /dev/null +++ b/packages/stryker/src/logging/LogConfigurator.ts @@ -0,0 +1,143 @@ +import * as log4js from 'log4js'; +import { LoggerFactory } from 'stryker-api/logging'; +import { LogLevel } from 'stryker-api/core'; +import { minLevel } from './logUtils'; +import LoggingClientContext from './LoggingClientContext'; +import { getFreePort } from '../utils/netUtils'; + +enum AppenderName { + File = 'file', + FilteredFile = 'filteredFile', + Console = 'console', + FilteredConsole = 'filteredConsole', + All = 'all', + Server = 'server' +} + +const layouts: { color: log4js.PatternLayout, noColor: log4js.PatternLayout } = { + color: { + type: 'pattern', + pattern: '%[%r (%z) %p %c%] %m' + }, + noColor: { + type: 'pattern', + pattern: '%r (%z) %p %c %m' + } +}; + +interface AppendersConfiguration { + [name: string]: log4js.Appender; +} + +const LOG_FILE_NAME = 'stryker.log'; +export default class LogConfigurator { + + private static createMainProcessAppenders(consoleLogLevel: LogLevel, fileLogLevel: LogLevel): AppendersConfiguration { + + // Add the custom "multiAppender": https://log4js-node.github.io/log4js-node/appenders.html#other-appenders + const multiAppender = { type: require.resolve('./MultiAppender'), appenders: ['filteredConsole'] }; + + let allAppenders: AppendersConfiguration = { + [AppenderName.Console]: { type: 'stdout', layout: layouts.color }, + [AppenderName.FilteredConsole]: { type: 'logLevelFilter', appender: 'console', level: consoleLogLevel }, + [AppenderName.All]: multiAppender, + }; + + // only add file if it is needed. Otherwise log4js will create the file directly, pretty annoying. + if (fileLogLevel.toUpperCase() !== LogLevel.Off.toUpperCase()) { + const fileAppender: log4js.FileAppender = { type: 'file', filename: LOG_FILE_NAME, layout: layouts.noColor }; + const filteredFileAppender: log4js.LogLevelFilterAppender = { type: 'logLevelFilter', appender: 'file', level: fileLogLevel }; + + // Don't simply add the appenders, instead actually make sure they are ordinal "before" the others. + // See https://github.com/log4js-node/log4js-node/issues/746 + allAppenders = Object.assign({ [AppenderName.File]: fileAppender, [AppenderName.FilteredFile]: filteredFileAppender }, allAppenders); + + multiAppender.appenders.push(AppenderName.FilteredFile); + } + + return allAppenders; + } + + private static createLog4jsConfig(defaultLogLevel: LogLevel, appenders: AppendersConfiguration): log4js.Configuration { + return { + appenders, + categories: { + default: { + appenders: [AppenderName.All], level: defaultLogLevel + } + } + }; + } + + private static setImplementation(): void { + LoggerFactory.setLogImplementation(log4js.getLogger); + } + + /** + * Configure logging for the master process. Either call this method or `configureChildProcess` before any `getLogger` calls. + * @param consoleLogLevel The log level to configure for the console + * @param fileLogLevel The log level to configure for the "stryker.log" file + */ + static configureMainProcess(consoleLogLevel: LogLevel = LogLevel.Information, fileLogLevel: LogLevel = LogLevel.Off) { + this.setImplementation(); + const appenders = this.createMainProcessAppenders(consoleLogLevel, fileLogLevel); + log4js.configure(this.createLog4jsConfig(minLevel(consoleLogLevel, fileLogLevel), appenders)); + } + + /** + * Configure the logging for the server. Includes the master configuration. + * This method should only be called ONCE, as it starts the log4js server to listen for log events. + * It returns the logging client context that should be used to configure the child processes. + * + * @param consoleLogLevel the console log level + * @param fileLogLevel the file log level + * @returns the context + */ + static async configureLoggingServer(consoleLogLevel: LogLevel, fileLogLevel: LogLevel): Promise { + this.setImplementation(); + const loggerPort = await getFreePort(); + + // Include the appenders for the main Stryker process, as log4js has only one single `configure` method. + const appenders = this.createMainProcessAppenders(consoleLogLevel, fileLogLevel); + const multiProcessAppender: log4js.MultiprocessAppender = { + type: 'multiprocess', + mode: 'master', + appender: AppenderName.All, + loggerPort + }; + appenders[AppenderName.Server] = multiProcessAppender; + const defaultLogLevel = minLevel(consoleLogLevel, fileLogLevel); + log4js.configure(this.createLog4jsConfig(defaultLogLevel, appenders)); + + const context: LoggingClientContext = { + port: loggerPort, + level: defaultLogLevel + }; + return context; + } + + + /** + * Configures the logging for a worker process. Sends all logging to the master process. + * Either call this method or `configureMainProcess` before any `getLogger` calls. + * @param context the logging client context used to configure the logging client + */ + static configureChildProcess(context: LoggingClientContext) { + this.setImplementation(); + const clientAppender: log4js.MultiprocessAppender = { type: 'multiprocess', mode: 'worker', loggerPort: context.port }; + const appenders: AppendersConfiguration = { [AppenderName.All]: clientAppender }; + log4js.configure(this.createLog4jsConfig(context.level, appenders)); + } + + static shutdown(): Promise { + return new Promise((res, rej) => { + log4js.shutdown(err => { + if (err) { + rej(err); + } else { + res(); + } + }); + }); + } +} \ No newline at end of file diff --git a/packages/stryker/src/logging/LoggingClientContext.ts b/packages/stryker/src/logging/LoggingClientContext.ts new file mode 100644 index 0000000000..2c7a5d345f --- /dev/null +++ b/packages/stryker/src/logging/LoggingClientContext.ts @@ -0,0 +1,13 @@ +import { LogLevel } from 'stryker-api/core'; + + +export default interface LoggingClientContext { + /** + * The port where the logging server listens for logging events on the localhost + */ + readonly port: number; + /** + * The minimal log level to use for configuration + */ + readonly level: LogLevel; +} \ No newline at end of file diff --git a/packages/stryker/src/logging/MultiAppender.ts b/packages/stryker/src/logging/MultiAppender.ts new file mode 100644 index 0000000000..39f505ee93 --- /dev/null +++ b/packages/stryker/src/logging/MultiAppender.ts @@ -0,0 +1,28 @@ +import { LoggingEvent } from 'log4js'; + +export interface RuntimeAppender { + (loggingEvent: LoggingEvent): void; +} + +export class MultiAppender { + + constructor(private appenders: RuntimeAppender[]) { } + + append(loggingEvent: LoggingEvent) { + this.appenders.forEach(appender => appender(loggingEvent)); + } +} + +/** + * This method is expected by log4js to have this _exact_ name + * and signature. + * @see https://log4js-node.github.io/log4js-node/writing-appenders.html + * @param config The appender configuration delivered by log4js + * @param _ The layouts provided by log4js + * @param findAppender A method to locate other appenders + */ +export function configure(config: { appenders: string[] }, _: any, findAppender: (name: string) => RuntimeAppender ): RuntimeAppender { + const multiAppender = new MultiAppender(config.appenders.map(name => findAppender(name))); + return multiAppender.append.bind(multiAppender); +} + diff --git a/packages/stryker/src/logging/logUtils.ts b/packages/stryker/src/logging/logUtils.ts new file mode 100644 index 0000000000..4de24a60b5 --- /dev/null +++ b/packages/stryker/src/logging/logUtils.ts @@ -0,0 +1,15 @@ +import { LogLevel } from 'stryker-api/core'; +import * as log4js from 'log4js'; + +/** + * Determines the minimal log level (where trace < off) + * @param a one log level + * @param b other log level + */ +export function minLevel(a: LogLevel, b: LogLevel) { + if (log4js.levels.getLevel(b).isGreaterThanOrEqualTo(log4js.levels.getLevel(a))) { + return a; + } else { + return b; + } +} diff --git a/packages/stryker/src/mutators/ES5Mutator.ts b/packages/stryker/src/mutators/ES5Mutator.ts index 61ebf2d21b..a3ad8ea8e2 100644 --- a/packages/stryker/src/mutators/ES5Mutator.ts +++ b/packages/stryker/src/mutators/ES5Mutator.ts @@ -1,5 +1,5 @@ import * as _ from 'lodash'; -import { Logger, getLogger } from 'log4js'; +import { Logger, getLogger } from 'stryker-api/logging'; import { Config } from 'stryker-api/config'; import { File } from 'stryker-api/core'; import { Mutator, Mutant } from 'stryker-api/mutant'; diff --git a/packages/stryker/src/process/InitialTestExecutor.ts b/packages/stryker/src/process/InitialTestExecutor.ts index 8cef519502..3f6ae01749 100644 --- a/packages/stryker/src/process/InitialTestExecutor.ts +++ b/packages/stryker/src/process/InitialTestExecutor.ts @@ -5,13 +5,14 @@ import { Config } from 'stryker-api/config'; import { TranspilerOptions, Transpiler } from 'stryker-api/transpile'; import { File } from 'stryker-api/core'; import TranspilerFacade from '../transpiler/TranspilerFacade'; -import { getLogger } from 'log4js'; +import { getLogger } from 'stryker-api/logging'; import Sandbox from '../Sandbox'; import Timer from '../utils/Timer'; import CoverageInstrumenterTranspiler, { CoverageMapsByFile } from '../transpiler/CoverageInstrumenterTranspiler'; import InputFileCollection from '../input/InputFileCollection'; import SourceMapper from '../transpiler/SourceMapper'; import { coveragePerTestHooks } from '../transpiler/coverageHooks'; +import LoggingClientContext from '../logging/LoggingClientContext'; // The initial run might take a while. // For example: angular-bootstrap takes up to 45 seconds. @@ -45,7 +46,7 @@ export default class InitialTestExecutor { private readonly log = getLogger(InitialTestExecutor.name); - constructor(private options: Config, private inputFiles: InputFileCollection, private testFramework: TestFramework | null, private timer: Timer) { + constructor(private options: Config, private inputFiles: InputFileCollection, private testFramework: TestFramework | null, private timer: Timer, private loggingContext: LoggingClientContext) { } async run(): Promise { @@ -77,7 +78,7 @@ export default class InitialTestExecutor { } private async runInSandbox(files: ReadonlyArray): Promise<{ runResult: RunResult, grossTimeMS: number }> { - const sandbox = await Sandbox.create(this.options, 0, files, this.testFramework, 0); + const sandbox = await Sandbox.create(this.options, 0, files, this.testFramework, 0, this.loggingContext); this.timer.mark(INITIAL_TEST_RUN_MARKER); const runResult = await sandbox.run(INITIAL_RUN_TIMEOUT, this.getCollectCoverageHooksIfNeeded()); const grossTimeMS = this.timer.elapsedMs(INITIAL_TEST_RUN_MARKER); diff --git a/packages/stryker/src/process/MutationTestExecutor.ts b/packages/stryker/src/process/MutationTestExecutor.ts index 647b904ef7..c226e675fa 100644 --- a/packages/stryker/src/process/MutationTestExecutor.ts +++ b/packages/stryker/src/process/MutationTestExecutor.ts @@ -11,6 +11,7 @@ import TestableMutant from '../TestableMutant'; import TranspiledMutant from '../TranspiledMutant'; import StrictReporter from '../reporters/StrictReporter'; import MutantTranspiler from '../transpiler/MutantTranspiler'; +import LoggingClientContext from '../logging/LoggingClientContext'; export default class MutationTestExecutor { @@ -19,13 +20,14 @@ export default class MutationTestExecutor { private inputFiles: ReadonlyArray, private testFramework: TestFramework | null, private reporter: StrictReporter, - private overheadTimeMS: number) { + private overheadTimeMS: number, + private loggingContext: LoggingClientContext) { } async run(allMutants: TestableMutant[]): Promise { - const mutantTranspiler = new MutantTranspiler(this.config); + const mutantTranspiler = new MutantTranspiler(this.config, this.loggingContext); const transpiledFiles = await mutantTranspiler.initialize(this.inputFiles); - const sandboxPool = new SandboxPool(this.config, this.testFramework, transpiledFiles, this.overheadTimeMS); + const sandboxPool = new SandboxPool(this.config, this.testFramework, transpiledFiles, this.overheadTimeMS, this.loggingContext); const result = await this.runInsideSandboxes( sandboxPool.streamSandboxes(), mutantTranspiler.transpileMutants(allMutants)); diff --git a/packages/stryker/src/reporters/BroadcastReporter.ts b/packages/stryker/src/reporters/BroadcastReporter.ts index 59d8220ee9..b52993adc5 100644 --- a/packages/stryker/src/reporters/BroadcastReporter.ts +++ b/packages/stryker/src/reporters/BroadcastReporter.ts @@ -1,5 +1,5 @@ import { Reporter, SourceFile, MutantResult, MatchedMutant, ScoreResult } from 'stryker-api/report'; -import { getLogger } from 'log4js'; +import { getLogger } from 'stryker-api/logging'; import { isPromise } from '../utils/objectUtils'; import StrictReporter from './StrictReporter'; diff --git a/packages/stryker/src/reporters/ClearTextReporter.ts b/packages/stryker/src/reporters/ClearTextReporter.ts index 19115a0ddf..1d0ee6c45e 100644 --- a/packages/stryker/src/reporters/ClearTextReporter.ts +++ b/packages/stryker/src/reporters/ClearTextReporter.ts @@ -1,5 +1,5 @@ import chalk from 'chalk'; -import { getLogger } from 'log4js'; +import { getLogger } from 'stryker-api/logging'; import { Reporter, MutantResult, MutantStatus, ScoreResult } from 'stryker-api/report'; import { Config } from 'stryker-api/config'; import ClearTextScoreTable from './ClearTextScoreTable'; diff --git a/packages/stryker/src/reporters/DashboardReporter.ts b/packages/stryker/src/reporters/DashboardReporter.ts index 9239c1a321..c2ce3b1dec 100644 --- a/packages/stryker/src/reporters/DashboardReporter.ts +++ b/packages/stryker/src/reporters/DashboardReporter.ts @@ -1,8 +1,8 @@ import {Reporter, ScoreResult} from 'stryker-api/report'; import DashboardReporterClient from './dashboard-reporter/DashboardReporterClient'; import {getEnvironmentVariable} from '../utils/objectUtils'; +import { getLogger } from 'stryker-api/logging'; import { determineCIProvider } from './ci/Provider'; -import { getLogger } from 'log4js'; import { StrykerOptions } from 'stryker-api/core'; export default class DashboardReporter implements Reporter { diff --git a/packages/stryker/src/reporters/EventRecorderReporter.ts b/packages/stryker/src/reporters/EventRecorderReporter.ts index a6295b19a6..4a3ffe59e8 100644 --- a/packages/stryker/src/reporters/EventRecorderReporter.ts +++ b/packages/stryker/src/reporters/EventRecorderReporter.ts @@ -1,4 +1,4 @@ -import { getLogger } from 'log4js'; +import { getLogger } from 'stryker-api/logging'; import * as path from 'path'; import * as fs from 'mz/fs'; import { StrykerOptions } from 'stryker-api/core'; diff --git a/packages/stryker/src/reporters/dashboard-reporter/DashboardReporterClient.ts b/packages/stryker/src/reporters/dashboard-reporter/DashboardReporterClient.ts index 95c9781847..4d4a011417 100644 --- a/packages/stryker/src/reporters/dashboard-reporter/DashboardReporterClient.ts +++ b/packages/stryker/src/reporters/dashboard-reporter/DashboardReporterClient.ts @@ -1,4 +1,4 @@ -import { getLogger } from 'log4js'; +import { getLogger } from 'stryker-api/logging'; import { HttpClient } from 'typed-rest-client/HttpClient'; import { errorToString } from '../../utils/objectUtils'; diff --git a/packages/stryker/src/transpiler/MutantTranspiler.ts b/packages/stryker/src/transpiler/MutantTranspiler.ts index ed691e1853..18892af347 100644 --- a/packages/stryker/src/transpiler/MutantTranspiler.ts +++ b/packages/stryker/src/transpiler/MutantTranspiler.ts @@ -9,6 +9,7 @@ import { TranspilerOptions } from 'stryker-api/transpile'; import TranspiledMutant from '../TranspiledMutant'; import TranspileResult from './TranspileResult'; import { errorToString } from '../utils/objectUtils'; +import LoggingClientContext from '../logging/LoggingClientContext'; export default class MutantTranspiler { @@ -22,12 +23,12 @@ export default class MutantTranspiler { * Otherwise will just forward input as output in same process. * @param config The Stryker config */ - constructor(config: Config) { + constructor(config: Config, loggingContext: LoggingClientContext) { const transpilerOptions: TranspilerOptions = { config, produceSourceMaps: false }; if (config.transpilers.length) { this.transpilerChildProcess = ChildProcessProxy.create( require.resolve('./TranspilerFacade'), - config.logLevel, + loggingContext, config.plugins, TranspilerFacade, transpilerOptions diff --git a/packages/stryker/src/transpiler/SourceMapper.ts b/packages/stryker/src/transpiler/SourceMapper.ts index 739a535eb7..c16e18a540 100644 --- a/packages/stryker/src/transpiler/SourceMapper.ts +++ b/packages/stryker/src/transpiler/SourceMapper.ts @@ -3,7 +3,7 @@ import { SourceMapConsumer, RawSourceMap } from 'source-map'; import { File, Location, Position } from 'stryker-api/core'; import { Config } from 'stryker-api/config'; import { base64Decode } from '../utils/objectUtils'; -import { getLogger } from 'log4js'; +import { getLogger } from 'stryker-api/logging'; import StrykerError from '../utils/StrykerError'; const SOURCE_MAP_URL_REGEX = /\/\/\s*#\s*sourceMappingURL=(.*)/g; diff --git a/packages/stryker/src/utils/StrykerTempFolder.ts b/packages/stryker/src/utils/StrykerTempFolder.ts index dfc880600b..4561b1ff5b 100644 --- a/packages/stryker/src/utils/StrykerTempFolder.ts +++ b/packages/stryker/src/utils/StrykerTempFolder.ts @@ -1,7 +1,7 @@ import * as fs from 'mz/fs'; import * as path from 'path'; import * as mkdirp from 'mkdirp'; -import { getLogger } from 'log4js'; +import { getLogger } from 'stryker-api/logging'; import { deleteDir } from './fileUtils'; let baseTempFolder = path.join(process.cwd(), '.stryker-tmp'); diff --git a/packages/stryker/src/utils/TempFolder.ts b/packages/stryker/src/utils/TempFolder.ts index ee13d3eb2a..560d5837fc 100644 --- a/packages/stryker/src/utils/TempFolder.ts +++ b/packages/stryker/src/utils/TempFolder.ts @@ -1,7 +1,7 @@ import * as fs from 'mz/fs'; import * as path from 'path'; import * as mkdirp from 'mkdirp'; -import { getLogger } from 'log4js'; +import { getLogger } from 'stryker-api/logging'; import { deleteDir } from './fileUtils'; diff --git a/packages/stryker/src/utils/netUtils.ts b/packages/stryker/src/utils/netUtils.ts new file mode 100644 index 0000000000..1ef6b28ab9 --- /dev/null +++ b/packages/stryker/src/utils/netUtils.ts @@ -0,0 +1,6 @@ +import * as getPortModule from 'get-port'; + +/** + * A wrapper around `getPort` for testing purposes + */ +export const getFreePort = getPortModule; \ No newline at end of file diff --git a/packages/stryker/src/utils/objectUtils.ts b/packages/stryker/src/utils/objectUtils.ts index 2823b2a802..4bd520d8c8 100644 --- a/packages/stryker/src/utils/objectUtils.ts +++ b/packages/stryker/src/utils/objectUtils.ts @@ -86,12 +86,18 @@ export function normalizeWhiteSpaces(str: string) { export function kill(pid: number): Promise { return new Promise((res, rej) => { - treeKill(pid, 'SIGKILL', err => { - if (err) { + treeKill(pid, 'SIGKILL', (err: { code?: number } & Error) => { + if (err && !canIgnore(err.code)) { rej(err); } else { res(); } }); + + function canIgnore(code: number | undefined) { + // https://docs.microsoft.com/en-us/windows/desktop/Debug/system-error-codes--0-499- + // these error codes mean the program is _already_ closed. + return code === 255 || code === 128; + } }); } \ No newline at end of file diff --git a/packages/stryker/test/helpers/LoggingServer.ts b/packages/stryker/test/helpers/LoggingServer.ts new file mode 100644 index 0000000000..dc1e3dfdff --- /dev/null +++ b/packages/stryker/test/helpers/LoggingServer.ts @@ -0,0 +1,47 @@ +import * as net from 'net'; +import * as log4js from 'log4js'; +import { Subscriber, Observable } from 'rxjs'; + +export default class LoggingServer { + + private readonly server: net.Server; + private subscriber: Subscriber | undefined; + public readonly event$: Observable; + private disposed = false; + + constructor(public readonly port: number) { + this.server = net.createServer(socket => { + socket.on('data', data => { + // Log4js also sends "__LOG4JS__" to signal an event end. Ignore those. + const logEventStrings = data.toString().split('__LOG4JS__').filter(Boolean); + const loggingEvents: log4js.LoggingEvent[] = logEventStrings.map(logEventString => JSON.parse(logEventString)); + loggingEvents.forEach(event => this.subscriber && this.subscriber.next(event)); + }); + }); + this.server.listen(this.port); + + this.event$ = new Observable(subscriber => { + this.subscriber = subscriber; + this.server.on('close', () => { + subscriber.complete(); + }); + }); + } + + dispose(): Promise { + if (this.disposed) { + return Promise.resolve(); + } else { + this.disposed = true; + return new Promise((res, rej) => { + this.server.close((err: Error) => { + if (err) { + rej(err); + } else { + res(); + } + }); + }); + } + } +} \ No newline at end of file diff --git a/packages/stryker/test/helpers/log4jsMock.ts b/packages/stryker/test/helpers/logMock.ts similarity index 52% rename from packages/stryker/test/helpers/log4jsMock.ts rename to packages/stryker/test/helpers/logMock.ts index b9ef04786d..c236d55207 100644 --- a/packages/stryker/test/helpers/log4jsMock.ts +++ b/packages/stryker/test/helpers/logMock.ts @@ -1,11 +1,11 @@ -import * as log4js from 'log4js'; +import * as logging from 'stryker-api/logging'; import { logger, Mock } from './producers'; -let log: Mock; +let log: Mock; beforeEach(() => { log = logger(); - sandbox.stub(log4js, 'getLogger').returns(log); + sandbox.stub(logging, 'getLogger').returns(log); }); export default function currentLogMock() { diff --git a/packages/stryker/test/helpers/producers.ts b/packages/stryker/test/helpers/producers.ts index 9097e8f005..202d008e8a 100644 --- a/packages/stryker/test/helpers/producers.ts +++ b/packages/stryker/test/helpers/producers.ts @@ -1,6 +1,7 @@ import { TestResult, TestStatus, RunResult, RunStatus } from 'stryker-api/test_runner'; import { Mutant } from 'stryker-api/mutant'; import { Config } from 'stryker-api/config'; +import { Logger } from 'stryker-api/logging'; import * as sinon from 'sinon'; import { TestFramework, TestSelection } from 'stryker-api/test_framework'; import { MutantStatus, MatchedMutant, MutantResult, Reporter, ScoreResult } from 'stryker-api/report'; @@ -8,7 +9,6 @@ import { MutationScoreThresholds, File, Location } from 'stryker-api/core'; import TestableMutant from '../../src/TestableMutant'; import SourceFile from '../../src/SourceFile'; import TranspiledMutant from '../../src/TranspiledMutant'; -import { Logger } from 'log4js'; import { FileCoverageData } from 'istanbul-lib-coverage'; import { CoverageMaps } from '../../src/transpiler/CoverageInstrumenterTranspiler'; import { MappedLocation } from '../../src/transpiler/SourceMapper'; @@ -80,8 +80,6 @@ export const mutant = factoryMethod(() => ({ export const logger = (): Mock => { return { - setLevel: sinon.stub(), - isLevelEnabled: sinon.stub(), isTraceEnabled: sinon.stub(), isDebugEnabled: sinon.stub(), isInfoEnabled: sinon.stub(), diff --git a/packages/stryker/test/integration/child-proxy/ChildProcessProxy.it.ts b/packages/stryker/test/integration/child-proxy/ChildProcessProxy.it.ts index b601e181ac..43abe6b0c2 100644 --- a/packages/stryker/test/integration/child-proxy/ChildProcessProxy.it.ts +++ b/packages/stryker/test/integration/child-proxy/ChildProcessProxy.it.ts @@ -1,18 +1,27 @@ import { expect } from 'chai'; import Echo from './Echo'; import ChildProcessProxy from '../../../src/child-proxy/ChildProcessProxy'; -import { File } from 'stryker-api/core'; +import { File, LogLevel } from 'stryker-api/core'; +import * as log4js from 'log4js'; +import * as getPort from 'get-port'; +import Task from '../../../src/utils/Task'; +import LoggingServer from '../../helpers/LoggingServer'; -describe('ChildProcessProxy', () => { +describe('ChildProcessProxy', function () { + this.timeout(15000); let sut: ChildProcessProxy; + let loggingServer: LoggingServer; - beforeEach(() => { - sut = ChildProcessProxy.create(require.resolve('./Echo'), 'info', [], Echo, 'World'); + beforeEach(async () => { + const port = await getPort(); + loggingServer = new LoggingServer(port); + sut = ChildProcessProxy.create(require.resolve('./Echo'), { port, level: LogLevel.Debug }, [], Echo, 'World'); }); - afterEach(() => { - sut.dispose(); + afterEach(async () => { + await sut.dispose(); + await loggingServer.dispose(); }); it('should be able to get direct result', async () => { @@ -39,4 +48,29 @@ describe('ChildProcessProxy', () => { it('should be able to receive a promise rejection', () => { return expect(sut.proxy.reject('Foobar error')).rejectedWith('Foobar error'); }); + + it('should be able to log on debug when LogLevel.Debug is allowed', async () => { + const firstLogEventTask = new Task(); + loggingServer.event$.subscribe(firstLogEventTask.resolve.bind(firstLogEventTask)); + sut.proxy.debug('test message'); + const log = await firstLogEventTask.promise; + expect(log.categoryName).eq(Echo.name); + expect(log.data).deep.eq(['test message']); + }); + + it('should not log on trace if LogLevel.Debug is allowed as min log level', async () => { + const firstLogEventTask = new Task(); + loggingServer.event$.subscribe(firstLogEventTask.resolve.bind(firstLogEventTask)); + sut.proxy.trace('foo'); + sut.proxy.debug('bar'); + const log = await firstLogEventTask.promise; + expect(log.categoryName).eq(Echo.name); + expect(log.data).deep.eq(['bar']); + expect(toLogLevel(log.level)).eq(LogLevel.Debug); + }); }); + +function toLogLevel(level: log4js.Level) { + const levelName = (level as any).levelStr.toLowerCase(); + return [LogLevel.Debug, LogLevel.Error, LogLevel.Fatal, LogLevel.Information, LogLevel.Off, LogLevel.Trace, LogLevel.Warning].find(level => level === levelName); +} diff --git a/packages/stryker/test/integration/child-proxy/Echo.ts b/packages/stryker/test/integration/child-proxy/Echo.ts index 53452c85f3..71abd62a25 100644 --- a/packages/stryker/test/integration/child-proxy/Echo.ts +++ b/packages/stryker/test/integration/child-proxy/Echo.ts @@ -1,8 +1,12 @@ import { File } from 'stryker-api/core'; +import { getLogger } from 'stryker-api/logging'; export default class Echo { + private logger = getLogger(Echo.name); + constructor(private name: string) { + } say(value: string) { @@ -25,6 +29,14 @@ export default class Echo { return new File('foobar.txt', 'hello foobar'); } + debug(message: string) { + this.logger.debug(message); + } + + trace(message: string) { + this.logger.trace(message); + } + reject(error: string) { return Promise.reject(new Error(error)); } diff --git a/packages/stryker/test/integration/config-reader/ConfigReaderSpec.ts b/packages/stryker/test/integration/config-reader/ConfigReaderSpec.ts index 9a1092b756..aa4896eace 100644 --- a/packages/stryker/test/integration/config-reader/ConfigReaderSpec.ts +++ b/packages/stryker/test/integration/config-reader/ConfigReaderSpec.ts @@ -1,22 +1,24 @@ +import * as path from 'path'; import { expect } from 'chai'; -import * as log4js from 'log4js'; -import ConfigReader from '../../../src/ConfigReader'; +import * as logging from 'stryker-api/logging'; +import ConfigReader from '../../../src/config/ConfigReader'; import { Config } from 'stryker-api/config'; -import currentLogMock from '../../helpers/log4jsMock'; +import currentLogMock from '../../helpers/logMock'; import { Mock } from '../../helpers/producers'; -describe('ConfigReader', () => { +describe('ConfigReader', function() { + this.timeout(15000); + let sut: ConfigReader; - let log: Mock; + let log: Mock; beforeEach(() => { log = currentLogMock(); - sandbox.stub(process, 'exit'); }); it('should create a logger with the correct name', () => { sut = new ConfigReader({}); - expect(log4js.getLogger).to.have.been.calledWith('ConfigReader'); + expect(logging.getLogger).to.have.been.calledWith('ConfigReader'); }); describe('readConfig()', () => { @@ -87,27 +89,22 @@ describe('ConfigReader', () => { describe('with non-existing config file', () => { beforeEach(() => { - sut = new ConfigReader({ configFile: '/did/you/know/that/this/file/does/not/exists/questionmark' }); - result = sut.readConfig(); - }); - - it('should report a fatal error', () => { - expect(log.fatal).to.have.been.calledWith(`File ${process.cwd()}//did/you/know/that/this/file/does/not/exists/questionmark does not exist!`); + sut = new ConfigReader({ configFile: 'no-file.js' }); }); - it('should exit with 1', () => { - expect(process.exit).to.have.been.calledWith(1); + it('should throw an error', () => { + expect(() => sut.readConfig()).throws(`File ${path.resolve('no-file.js')} does not exist!`); }); }); - describe('with an existing file, but not a module', () => { + describe('with an existing file, but not a function', () => { beforeEach(() => { sut = new ConfigReader({ configFile: 'testResources/config-reader/invalid.conf.js' }); - result = sut.readConfig(); }); it('should report a fatal error', () => { + expect(() => sut.readConfig()).throws(); expect(log.fatal).to.have.been.calledWith(`Config file must export a function! module.exports = function(config) { config.set({ @@ -116,8 +113,8 @@ describe('ConfigReader', () => { };`); }); - it('should exit with 1', () => { - expect(process.exit).to.have.been.calledWith(1); + it('should throw an error', () => { + expect(() => sut.readConfig()).throws('Config file must export a function'); }); }); @@ -125,11 +122,10 @@ describe('ConfigReader', () => { beforeEach(() => { sut = new ConfigReader({ configFile: 'testResources/config-reader/syntax-error.conf.js' }); - result = sut.readConfig(); }); - it('should report a fatal error', () => { - expect(log.fatal).to.have.been.calledWithMatch(/Invalid config file!.*/); + it('should throw an error', () => { + expect(() => sut.readConfig()).throws('Invalid config file. Inner error: SyntaxError: Unexpected identifier'); }); }); }); diff --git a/packages/stryker/test/integration/isolated-runner/AdditionalTestRunners.ts b/packages/stryker/test/integration/isolated-runner/AdditionalTestRunners.ts index 6e84b2d593..303a9afe37 100644 --- a/packages/stryker/test/integration/isolated-runner/AdditionalTestRunners.ts +++ b/packages/stryker/test/integration/isolated-runner/AdditionalTestRunners.ts @@ -1,39 +1,36 @@ -import { EventEmitter } from 'events'; -import { RunResult, RunStatus, RunOptions, RunnerOptions, TestRunner, TestRunnerFactory } from 'stryker-api/test_runner'; +import { RunResult, RunStatus, RunnerOptions, TestRunner, TestRunnerFactory } from 'stryker-api/test_runner'; import { isRegExp } from 'util'; -class CoverageReportingTestRunner extends EventEmitter implements TestRunner { - run(options: RunOptions) { +class CoverageReportingTestRunner implements TestRunner { + run() { (global as any).__coverage__ = 'overridden'; return Promise.resolve({ status: RunStatus.Complete, tests: [], coverage: 'realCoverage' }); } } -class TimeBombTestRunner extends EventEmitter implements TestRunner { +class TimeBombTestRunner implements TestRunner { constructor() { - super(); // Setting a time bomb after 100 ms setTimeout(() => process.exit(), 100); } - run(options: RunOptions) { + run() { return Promise.resolve({ status: RunStatus.Complete, tests: [] }); } } -class DirectResolvedTestRunner extends EventEmitter implements TestRunner { - run(options: RunOptions) { +class DirectResolvedTestRunner implements TestRunner { + run() { (global as any).__coverage__ = 'coverageObject'; return Promise.resolve({ status: RunStatus.Complete, tests: [] }); } } -class DiscoverRegexTestRunner extends EventEmitter implements TestRunner { +class DiscoverRegexTestRunner implements TestRunner { constructor(private runnerOptions: RunnerOptions) { - super(); } - run(options: RunOptions): Promise { + run(): Promise { if (isRegExp(this.runnerOptions.strykerOptions['someRegex'])) { return Promise.resolve({ status: RunStatus.Complete, tests: [] }); } else { @@ -43,9 +40,9 @@ class DiscoverRegexTestRunner extends EventEmitter implements TestRunner { } -class ErroredTestRunner extends EventEmitter implements TestRunner { +class ErroredTestRunner implements TestRunner { - run(options: RunOptions) { + run() { let expectedError: any = null; try { throw new SyntaxError('This is invalid syntax!'); @@ -62,18 +59,18 @@ class RejectInitRunner implements TestRunner { return Promise.reject(new Error('Init was rejected')); } - run(options: RunOptions): Promise { + run(): Promise { throw new Error(); } } -class NeverResolvedTestRunner extends EventEmitter implements TestRunner { - run(options: RunOptions) { - return new Promise(res => { }); +class NeverResolvedTestRunner implements TestRunner { + run() { + return new Promise(() => { }); } } -class SlowInitAndDisposeTestRunner extends EventEmitter implements TestRunner { +class SlowInitAndDisposeTestRunner implements TestRunner { inInit: boolean; @@ -87,7 +84,7 @@ class SlowInitAndDisposeTestRunner extends EventEmitter implements TestRunner { }); } - run(options: RunOptions) { + run() { if (this.inInit) { throw new Error('Test should fail! Not yet initialized!'); } @@ -98,11 +95,11 @@ class SlowInitAndDisposeTestRunner extends EventEmitter implements TestRunner { return this.init(); } } -class VerifyWorkingFolderTestRunner extends EventEmitter implements TestRunner { +class VerifyWorkingFolderTestRunner implements TestRunner { runResult: RunResult = { status: RunStatus.Complete, tests: [] }; - run(options: RunOptions) { + run() { if (process.cwd().toLowerCase() === __dirname.toLowerCase()) { return Promise.resolve(this.runResult); } else { @@ -111,14 +108,14 @@ class VerifyWorkingFolderTestRunner extends EventEmitter implements TestRunner { } } -class AsyncronousPromiseRejectionHandlerTestRunner extends EventEmitter implements TestRunner { +class AsyncronousPromiseRejectionHandlerTestRunner implements TestRunner { promise: Promise; init() { this.promise = Promise.reject('Reject for now, but will be caught asynchronously'); } - run(options: RunOptions) { + run() { this.promise.catch(() => { }); return Promise.resolve({ status: RunStatus.Complete, tests: [] }); } diff --git a/packages/stryker/test/integration/isolated-runner/ResilientTestRunnerFactory.it.ts b/packages/stryker/test/integration/isolated-runner/ResilientTestRunnerFactory.it.ts new file mode 100644 index 0000000000..5687be8008 --- /dev/null +++ b/packages/stryker/test/integration/isolated-runner/ResilientTestRunnerFactory.it.ts @@ -0,0 +1,161 @@ +import * as path from 'path'; +import { expect } from 'chai'; +import * as getPort from 'get-port'; +import { RunStatus, RunnerOptions } from 'stryker-api/test_runner'; +import * as log4js from 'log4js'; +import ResilientTestRunnerFactory from '../../../src/isolated-runner/ResilientTestRunnerFactory'; +import TestRunnerDecorator from '../../../src/isolated-runner/TestRunnerDecorator'; +import { LogLevel } from 'stryker-api/core'; +import LoggingServer from '../../helpers/LoggingServer'; +import LoggingClientContext from '../../../src/logging/LoggingClientContext'; +import { toArray } from 'rxjs/operators'; + +function sleep(ms: number) { + return new Promise(res => { + setTimeout(res, ms); + }); +} + +describe('ResilientTestRunnerFactory integration', function () { + + this.timeout(15000); + + let sut: TestRunnerDecorator; + let options: RunnerOptions; + const sandboxWorkingDirectory = path.resolve('./test/integration/isolated-runner'); + let loggingContext: LoggingClientContext; + + let loggingServer: LoggingServer; + let alreadyDisposed: boolean; + + beforeEach(async () => { + // Make sure there is a logging server listening + const port = await getPort(); + loggingServer = new LoggingServer(port); + loggingContext = { port, level: LogLevel.Trace }; + options = { + strykerOptions: { + plugins: ['../../test/integration/isolated-runner/AdditionalTestRunners'], + testRunner: 'karma', + testFramework: 'jasmine', + port: 0, + 'someRegex': /someRegex/ + }, + port: 0, + fileNames: [] + }; + alreadyDisposed = false; + }); + + afterEach(async () => { + if (!alreadyDisposed) { + await sut.dispose(); + } + await loggingServer.dispose(); + }); + + function arrangeSut(name: string): TestRunnerDecorator { + return sut = ResilientTestRunnerFactory.create(name, options, sandboxWorkingDirectory, loggingContext); + } + + function actRun(timeout = 4000) { + return sut.run({ timeout }); + } + + it('should be able to receive a regex', async () => { + sut = arrangeSut('discover-regex'); + const result = await actRun(); + expect(result.status).eq(RunStatus.Complete); + }); + + it('should pass along the coverage result from the test runner behind', async () => { + sut = arrangeSut('coverage-reporting'); + const result = await actRun(); + expect(result.coverage).eq('realCoverage'); + }); + + it('should pass along the run result', async () => { + sut = arrangeSut('direct-resolved'); + const result = await actRun(); + expect(result.status).eq(RunStatus.Complete); + }); + + it('should try to report coverage from the global scope, even when the test runner behind does not', async () => { + sut = arrangeSut('direct-resolved'); + const result = await actRun(); + expect(result.coverage).eq('coverageObject'); + }); + + it('should resolve in a timeout if the test runner never resolves', async () => { + sut = arrangeSut('never-resolved'); + await sut.init(); + const result = await actRun(1000); + expect(RunStatus[result.status]).eq(RunStatus[RunStatus.Timeout]); + }); + + it('should be able to recover from a timeout by creating a new child process', async () => { + sut = arrangeSut('never-resolved'); + await sut.init(); + await actRun(1000); // first timeout + const result = await actRun(1000); + expect(RunStatus[result.status]).eq(RunStatus[RunStatus.Timeout]); + }); + + it('should convert any `Error` objects to string', async () => { + sut = arrangeSut('errored'); + await sut.init(); + const result = await actRun(1000); + expect(RunStatus[result.status]).to.be.eq(RunStatus[RunStatus.Error]); + expect(result.errorMessages).to.have.length(1); + expect((result.errorMessages as any)[0]).includes('SyntaxError: This is invalid syntax!').and.includes('at ErroredTestRunner.run'); + }); + + it('should run only after initialization, even when it is slow', async () => { + sut = arrangeSut('slow-init-dispose'); + await sut.init(); + const result = await actRun(1000); + expect(RunStatus[result.status]).eq(RunStatus[RunStatus.Complete]); + }); + + it('should be able to run twice in quick succession', async () => { + sut = arrangeSut('direct-resolved'); + await actRun(); + const result = await actRun(); + expect(RunStatus[result.status]).eq(RunStatus[RunStatus.Complete]); + }); + + it('should reject when `init` of test runner behind rejects', () => { + sut = arrangeSut('reject-init'); + return expect(sut.init()).rejectedWith('Init was rejected'); + }); + + it('should change the current working directory to the sandbox directory', async () => { + sut = arrangeSut('verify-working-folder'); + const result = await actRun(); + expect(result.errorMessages).undefined; + }); + + it('should be able to recover from any crash', async () => { + // time-bomb will crash after 100 ms + sut = arrangeSut('time-bomb'); + await sleep(101); + const result = await actRun(); + expect(RunStatus[result.status]).eq(RunStatus[RunStatus.Complete]); + expect(result.errorMessages).undefined; + }); + + it('should handle asynchronously handled promise rejections from the underlying test runner', async () => { + const logEvents = loggingServer.event$.pipe(toArray()).toPromise(); + sut = arrangeSut('async-promise-rejection-handler'); + await sut.init(); + await actRun(); + await sut.dispose(); + alreadyDisposed = true; + await loggingServer.dispose(); + const actualLogEvents = await logEvents; + expect(actualLogEvents.find(logEvent => + log4js.levels.DEBUG.isEqualTo(logEvent.level) && logEvent.data.toString().indexOf('UnhandledPromiseRejectionWarning: Unhandled promise rejection') > -1)).ok; + }); +}); + + diff --git a/packages/stryker/test/integration/isolated-runner/ResilientTestRunnerFactorySpec.ts b/packages/stryker/test/integration/isolated-runner/ResilientTestRunnerFactorySpec.ts deleted file mode 100644 index c06559c7af..0000000000 --- a/packages/stryker/test/integration/isolated-runner/ResilientTestRunnerFactorySpec.ts +++ /dev/null @@ -1,178 +0,0 @@ -import * as path from 'path'; -import { Logger } from 'log4js'; -import { expect } from 'chai'; -import { RunResult, RunStatus } from 'stryker-api/test_runner'; -import ResilientTestRunnerFactory from '../../../src/isolated-runner/ResilientTestRunnerFactory'; -import IsolatedRunnerOptions from '../../../src/isolated-runner/IsolatedRunnerOptions'; -import TestRunnerDecorator from '../../../src/isolated-runner/TestRunnerDecorator'; -import currentLogMock from '../../helpers/log4jsMock'; -import { Mock } from '../../helpers/producers'; - -function sleep(ms: number) { - return new Promise(res => { - setTimeout(res, ms); - }); -} - -describe('ResilientTestRunnerFactory', function () { - - this.timeout(15000); - let log: Mock; - let sut: TestRunnerDecorator; - let options: IsolatedRunnerOptions = { - strykerOptions: { - plugins: ['../../test/integration/isolated-runner/AdditionalTestRunners'], - testRunner: 'karma', - testFramework: 'jasmine', - port: 0, - 'someRegex': /someRegex/ - }, - port: 0, - fileNames: [], - sandboxWorkingFolder: path.resolve('./test/integration/isolated-runner') - }; - - beforeEach(() => { - log = currentLogMock(); - }); - - describe('when sending a regex in the options', () => { - before(() => sut = ResilientTestRunnerFactory.create('discover-regex', options)); - - it('correctly receive the regex on the other end', - () => expect(sut.run({ timeout: 4000 })).to.eventually.have.property('status', RunStatus.Complete)); - - after(() => sut.dispose()); - }); - - describe('when test runner behind reports coverage', () => { - before(() => sut = ResilientTestRunnerFactory.create('coverage-reporting', options)); - - it('should not be overridden by the worker', - () => expect(sut.run({ timeout: 3000 })).to.eventually.have.property('coverage', 'realCoverage')); - - after(() => sut.dispose()); - }); - - describe('when test runner behind responds quickly', () => { - before(() => { - sut = ResilientTestRunnerFactory.create('direct-resolved', options); - }); - - it('should run and resolve', () => - expect(sut.run({ timeout: 4000 })).to.eventually.have.property('status', RunStatus.Complete)); - - it('should report the coverage object if underlying test runner does not', () => - expect(sut.run({ timeout: 4000 })).to.eventually.have.property('coverage', 'coverageObject')); - - after(() => sut.dispose()); - }); - - describe('when test runner behind never responds', () => { - before(() => { - sut = ResilientTestRunnerFactory.create('never-resolved', options); - return sut.init(); - }); - - it('should run and resolve in a timeout', () => - expect(sut.run({ timeout: 1000 })).to.eventually.satisfy((result: RunResult) => result.status === RunStatus.Timeout)); - - it('should be able to recover from a timeout', () => - expect(sut.run({ timeout: 1000 }).then(() => sut.run({ timeout: 1000 }))).to.eventually.satisfy((result: RunResult) => result.status === RunStatus.Timeout)); - - after(() => sut.dispose()); - }); - - describe('when test runner behind reports an error as `Error` instead of `string`', () => { - before(() => { - sut = ResilientTestRunnerFactory.create('errored', options); - return sut.init(); - }); - - it('should report the error as `string`', () => - expect(sut.run({ timeout: 1000 })).to.eventually.satisfy((result: RunResult) => { - // Issue https://github.com/stryker-mutator/stryker/issues/141 - expect(result.status).to.be.eq(RunStatus.Error); - expect(result.errorMessages).to.have.length(1); - if (result.errorMessages) { - expect(result.errorMessages[0]).to.contain('SyntaxError: This is invalid syntax!\n at ErroredTestRunner.run'); - } - return true; - })); - - after(() => sut.dispose()); - }); - - describe('when test runner behind has a slow init and dispose cycle', () => { - before(() => { - sut = ResilientTestRunnerFactory.create('slow-init-dispose', options); - return sut.init(); - }); - it('should run only after it is initialized', - () => expect(sut.run({ timeout: 1000 })).to.eventually.satisfy((result: RunResult) => { - expect(result.status).to.be.eq(RunStatus.Complete, `Run status was ${RunStatus[result.status]}, while ${RunStatus[RunStatus.Complete]} expected`); - return true; - })); - - it('should be able to run twice in quick succession', - () => expect(sut.run({ timeout: 1000 }).then(() => sut.run({ timeout: 20 }))).to.eventually.satisfy((result: RunResult) => { - expect(result.status).to.be.eq(RunStatus.Complete, `Run status was ${RunStatus[result.status]}, while ${RunStatus[RunStatus.Complete]} expected`); - return true; - })); - - after(() => sut.dispose()); - }); - - describe('when test runner behind rejects init promise', () => { - before(() => { - sut = ResilientTestRunnerFactory.create('reject-init', options); - }); - after(() => sut.dispose()); - - it('should pass along the rejection', () => { - return expect(sut.init()).rejectedWith('Init was rejected'); - }); - }); - - describe('when test runner verifies the current working folder', () => { - before(() => { - sut = ResilientTestRunnerFactory.create('verify-working-folder', options); - return sut.init(); - }); - - it('should run and resolve', () => sut.run({ timeout: 4000 }) - .then(result => { - if (result.errorMessages && result.errorMessages.length) { - expect.fail(null, null, result.errorMessages[0]); - } - })); - after(() => sut.dispose()); - }); - - describe('when test runner is crashing after 100ms', () => { - before(() => sut = ResilientTestRunnerFactory.create('time-bomb', options)); - - it('should be able to recover from crash', () => { - return sleep(101) - .then(() => sut.run({ timeout: 4000 }) - .then(result => { - expect(result.status).to.be.eq(RunStatus.Complete); - expect(result.errorMessages).to.be.undefined; - })); - }); - after(() => sut.dispose()); - }); - - describe('when test runner handles promise rejections asynchronously', () => { - before(() => sut = ResilientTestRunnerFactory.create('async-promise-rejection-handler', options)); - - it('should be logging the unhandled rejection errors', async () => { - await sut.init(); - await sut.run({ timeout: 2000 }); - expect(log.error).not.called; - }); - - after(() => sut.dispose()); - - }); -}); diff --git a/packages/stryker/test/integration/source-mapper/SourceMapperIT.ts b/packages/stryker/test/integration/source-mapper/SourceMapperIT.ts index 5fc263982d..0e72f5ba98 100644 --- a/packages/stryker/test/integration/source-mapper/SourceMapperIT.ts +++ b/packages/stryker/test/integration/source-mapper/SourceMapperIT.ts @@ -14,8 +14,9 @@ function readFiles(...files: string[]): Promise { .map(fileName => fs.readFile(fileName).then(content => new File(fileName, content)))); } -describe('Source mapper integration', () => { +describe('Source mapper integration', function () { + this.timeout(15000); let sut: TranspiledSourceMapper; describe('with external source maps', () => { diff --git a/packages/stryker/test/integration/utils/fileUtilsSpec.ts b/packages/stryker/test/integration/utils/fileUtilsSpec.ts index 8f8e871c4b..e00933608e 100644 --- a/packages/stryker/test/integration/utils/fileUtilsSpec.ts +++ b/packages/stryker/test/integration/utils/fileUtilsSpec.ts @@ -2,7 +2,9 @@ import { expect } from 'chai'; import * as sinon from 'sinon'; import * as fileUtils from '../../../src/utils/fileUtils'; -describe('fileUtils', () => { +describe('fileUtils', function () { + + this.timeout(15000); let sandbox: sinon.SinonSandbox; diff --git a/packages/stryker/test/unit/MutantTestMatcherSpec.ts b/packages/stryker/test/unit/MutantTestMatcherSpec.ts index e1c01f7fe5..84951516d4 100644 --- a/packages/stryker/test/unit/MutantTestMatcherSpec.ts +++ b/packages/stryker/test/unit/MutantTestMatcherSpec.ts @@ -1,4 +1,4 @@ -import { Logger } from 'log4js'; +import { Logger } from 'stryker-api/logging'; import { Mutant } from 'stryker-api/mutant'; import { TestSelection } from 'stryker-api/test_framework'; import { expect } from 'chai'; @@ -6,7 +6,7 @@ import { RunResult, TestResult, RunStatus, TestStatus, CoverageCollection, Cover import { StrykerOptions, File } from 'stryker-api/core'; import { MatchedMutant } from 'stryker-api/report'; import MutantTestMatcher from '../../src/MutantTestMatcher'; -import currentLogMock from '../helpers/log4jsMock'; +import currentLogMock from '../helpers/logMock'; import { testResult, mutant, Mock, mock } from '../helpers/producers'; import TestableMutant, { TestSelectionResult } from '../../src/TestableMutant'; import SourceFile from '../../src/SourceFile'; diff --git a/packages/stryker/test/unit/PluginLoaderSpec.ts b/packages/stryker/test/unit/PluginLoaderSpec.ts index e876f341ff..2197f17291 100644 --- a/packages/stryker/test/unit/PluginLoaderSpec.ts +++ b/packages/stryker/test/unit/PluginLoaderSpec.ts @@ -1,11 +1,11 @@ import * as path from 'path'; import * as fs from 'mz/fs'; -import { Logger } from 'log4js'; +import { Logger } from 'stryker-api/logging'; import * as sinon from 'sinon'; import { expect } from 'chai'; import * as fileUtils from '../../src/utils/fileUtils'; import PluginLoader from '../../src/PluginLoader'; -import currentLogMock from '../helpers/log4jsMock'; +import currentLogMock from '../helpers/logMock'; import { Mock } from '../helpers/producers'; describe('PluginLoader', () => { diff --git a/packages/stryker/test/unit/SandboxPoolSpec.ts b/packages/stryker/test/unit/SandboxPoolSpec.ts index 31a3eb83f4..694fe0ce68 100644 --- a/packages/stryker/test/unit/SandboxPoolSpec.ts +++ b/packages/stryker/test/unit/SandboxPoolSpec.ts @@ -2,15 +2,20 @@ import { expect } from 'chai'; import * as os from 'os'; import { flatMap, toArray } from 'rxjs/operators'; import { Config } from 'stryker-api/config'; -import { File } from 'stryker-api/core'; +import { File, LogLevel } from 'stryker-api/core'; import { TestFramework } from 'stryker-api/test_framework'; import Sandbox from '../../src/Sandbox'; import SandboxPool from '../../src/SandboxPool'; import Task from '../../src/utils/Task'; import '../helpers/globals'; import { Mock, config, file, mock, testFramework } from '../helpers/producers'; +import LoggingClientContext from '../../src/logging/LoggingClientContext'; const OVERHEAD_TIME_MS = 42; +const LOGGING_CONTEXT: LoggingClientContext = Object.freeze({ + port: 4200, + level: LogLevel.Fatal +}); describe('SandboxPool', () => { let sut: SandboxPool; @@ -36,7 +41,7 @@ describe('SandboxPool', () => { .onCall(1).resolves(secondSandbox); expectedInputFiles = [file()]; - sut = new SandboxPool(options, expectedTestFramework, expectedInputFiles, OVERHEAD_TIME_MS); + sut = new SandboxPool(options, expectedTestFramework, expectedInputFiles, OVERHEAD_TIME_MS, LOGGING_CONTEXT); }); describe('streamSandboxes', () => { diff --git a/packages/stryker/test/unit/SandboxSpec.ts b/packages/stryker/test/unit/SandboxSpec.ts index 30810d2582..ebbaec40d6 100644 --- a/packages/stryker/test/unit/SandboxSpec.ts +++ b/packages/stryker/test/unit/SandboxSpec.ts @@ -1,26 +1,31 @@ -import { Logger } from 'log4js'; +import { Logger } from 'stryker-api/logging'; import { Mutant } from 'stryker-api/mutant'; import { Config } from 'stryker-api/config'; import * as sinon from 'sinon'; import * as path from 'path'; import * as mkdirp from 'mkdirp'; import { expect } from 'chai'; -import { File } from 'stryker-api/core'; +import { File, LogLevel } from 'stryker-api/core'; import { wrapInClosure, normalizeWhiteSpaces } from '../../src/utils/objectUtils'; import Sandbox from '../../src/Sandbox'; import { TempFolder } from '../../src/utils/TempFolder'; import ResilientTestRunnerFactory from '../../src/isolated-runner/ResilientTestRunnerFactory'; -import IsolatedRunnerOptions from '../../src/isolated-runner/IsolatedRunnerOptions'; import TestableMutant, { TestSelectionResult } from '../../src/TestableMutant'; import { mutant as createMutant, testResult, Mock, createFileAlreadyExistsError } from '../helpers/producers'; import SourceFile from '../../src/SourceFile'; import '../helpers/globals'; import TranspiledMutant from '../../src/TranspiledMutant'; import * as fileUtils from '../../src/utils/fileUtils'; -import currentLogMock from '../helpers/log4jsMock'; +import currentLogMock from '../helpers/logMock'; import TestRunnerDecorator from '../../src/isolated-runner/TestRunnerDecorator'; +import LoggingClientContext from '../../src/logging/LoggingClientContext'; +import { RunnerOptions } from 'stryker-api/test_runner'; const OVERHEAD_TIME_MS = 0; +const LOGGING_CONTEXT: LoggingClientContext = Object.freeze({ + port: 4200, + level: LogLevel.Fatal +}); const SANDBOX_INDEX = 3; describe('Sandbox', () => { @@ -30,7 +35,7 @@ describe('Sandbox', () => { let testFrameworkStub: any; let expectedFileToMutate: File; let notMutatedFile: File; - let sandboxFolder: string; + let sandboxDirectory: string; let expectedTargetFileToMutate: string; let expectedTestFrameworkHooksFile: string; let writeFileStub: sinon.SinonStub; @@ -46,14 +51,14 @@ describe('Sandbox', () => { }; expectedFileToMutate = new File(path.resolve('file1'), 'original code'); notMutatedFile = new File(path.resolve('file2'), 'to be mutated'); - sandboxFolder = path.resolve('random-folder-3'); - expectedTargetFileToMutate = path.join(sandboxFolder, 'file1'); - expectedTestFrameworkHooksFile = path.join(sandboxFolder, '___testHooksForStryker.js'); + sandboxDirectory = path.resolve('random-folder-3'); + expectedTargetFileToMutate = path.join(sandboxDirectory, 'file1'); + expectedTestFrameworkHooksFile = path.join(sandboxDirectory, '___testHooksForStryker.js'); files = [ expectedFileToMutate, notMutatedFile, ]; - sandbox.stub(TempFolder.instance(), 'createRandomFolder').returns(sandboxFolder); + sandbox.stub(TempFolder.instance(), 'createRandomFolder').returns(sandboxDirectory); writeFileStub = sandbox.stub(fileUtils, 'writeFile'); symlinkJunctionStub = sandbox.stub(fileUtils, 'symlinkJunction'); findNodeModulesStub = sandbox.stub(fileUtils, 'findNodeModules'); @@ -69,41 +74,40 @@ describe('Sandbox', () => { describe('create()', () => { it('should copy input files when created', async () => { - await Sandbox.create(options, SANDBOX_INDEX, files, null, OVERHEAD_TIME_MS); + await Sandbox.create(options, SANDBOX_INDEX, files, null, OVERHEAD_TIME_MS, LOGGING_CONTEXT); expect(fileUtils.writeFile).calledWith(expectedTargetFileToMutate, files[0].content); - expect(fileUtils.writeFile).calledWith(path.join(sandboxFolder, 'file2'), files[1].content); + expect(fileUtils.writeFile).calledWith(path.join(sandboxDirectory, 'file2'), files[1].content); }); it('should copy a local file when created', async () => { - await Sandbox.create(options, SANDBOX_INDEX, [new File('localFile.js', 'foobar')], null, OVERHEAD_TIME_MS); - expect(fileUtils.writeFile).calledWith(path.join(sandboxFolder, 'localFile.js'), Buffer.from('foobar')); + await Sandbox.create(options, SANDBOX_INDEX, [new File('localFile.js', 'foobar')], null, OVERHEAD_TIME_MS, LOGGING_CONTEXT); + expect(fileUtils.writeFile).calledWith(path.join(sandboxDirectory, 'localFile.js'), Buffer.from('foobar')); }); it('should have created the isolated test runner', async () => { - await Sandbox.create(options, SANDBOX_INDEX, files, null, OVERHEAD_TIME_MS); - const expectedSettings: IsolatedRunnerOptions = { + await Sandbox.create(options, SANDBOX_INDEX, files, null, OVERHEAD_TIME_MS, LOGGING_CONTEXT); + const expectedSettings: RunnerOptions = { port: 46, strykerOptions: options, - sandboxWorkingFolder: sandboxFolder, fileNames: [path.resolve('random-folder-3', 'file1'), path.resolve('random-folder-3', 'file2')] }; - expect(ResilientTestRunnerFactory.create).to.have.been.calledWith(options.testRunner, expectedSettings); + expect(ResilientTestRunnerFactory.create).to.have.been.calledWith(options.testRunner, expectedSettings, sandboxDirectory, LOGGING_CONTEXT); }); it('should have created a sandbox folder', async () => { - await Sandbox.create(options, SANDBOX_INDEX, files, testFrameworkStub, OVERHEAD_TIME_MS); + await Sandbox.create(options, SANDBOX_INDEX, files, testFrameworkStub, OVERHEAD_TIME_MS, LOGGING_CONTEXT); expect(TempFolder.instance().createRandomFolder).to.have.been.calledWith('sandbox'); }); it('should symlink node modules in sandbox directory if exists', async () => { - await Sandbox.create(options, SANDBOX_INDEX, files, testFrameworkStub, OVERHEAD_TIME_MS); + await Sandbox.create(options, SANDBOX_INDEX, files, testFrameworkStub, OVERHEAD_TIME_MS, LOGGING_CONTEXT); expect(findNodeModulesStub).calledWith(process.cwd()); - expect(symlinkJunctionStub).calledWith('node_modules', path.join(sandboxFolder, 'node_modules')); + expect(symlinkJunctionStub).calledWith('node_modules', path.join(sandboxDirectory, 'node_modules')); }); it('should not symlink node modules in sandbox directory if no node_modules exist', async () => { findNodeModulesStub.resolves(null); - await Sandbox.create(options, SANDBOX_INDEX, files, testFrameworkStub, OVERHEAD_TIME_MS); + await Sandbox.create(options, SANDBOX_INDEX, files, testFrameworkStub, OVERHEAD_TIME_MS, LOGGING_CONTEXT); expect(log.warn).calledWithMatch('Could not find a node_modules'); expect(log.warn).calledWithMatch(process.cwd()); expect(symlinkJunctionStub).not.called; @@ -112,7 +116,7 @@ describe('Sandbox', () => { it('should log a warning if "node_modules" already exists in the working folder', async () => { findNodeModulesStub.resolves('node_modules'); symlinkJunctionStub.rejects(createFileAlreadyExistsError()); - await Sandbox.create(options, SANDBOX_INDEX, files, testFrameworkStub, OVERHEAD_TIME_MS); + await Sandbox.create(options, SANDBOX_INDEX, files, testFrameworkStub, OVERHEAD_TIME_MS, LOGGING_CONTEXT); expect(log.warn).calledWithMatch(normalizeWhiteSpaces( `Could not symlink "node_modules" in sandbox directory, it is already created in the sandbox. Please remove the node_modules from your sandbox files. Alternatively, set \`symlinkNodeModules\` @@ -123,14 +127,14 @@ describe('Sandbox', () => { findNodeModulesStub.resolves('basePath/node_modules'); const error = new Error('unknown'); symlinkJunctionStub.rejects(error); - await Sandbox.create(options, SANDBOX_INDEX, files, testFrameworkStub, OVERHEAD_TIME_MS); + await Sandbox.create(options, SANDBOX_INDEX, files, testFrameworkStub, OVERHEAD_TIME_MS, LOGGING_CONTEXT); expect(log.warn).calledWithMatch(normalizeWhiteSpaces( `Unexpected error while trying to symlink "basePath/node_modules" in sandbox directory.`), error); }); it('should symlink node modules in sandbox directory if `symlinkNodeModules` is `false`', async () => { options.symlinkNodeModules = false; - await Sandbox.create(options, SANDBOX_INDEX, files, testFrameworkStub, OVERHEAD_TIME_MS); + await Sandbox.create(options, SANDBOX_INDEX, files, testFrameworkStub, OVERHEAD_TIME_MS, LOGGING_CONTEXT); expect(symlinkJunctionStub).not.called; expect(findNodeModulesStub).not.called; }); @@ -138,7 +142,7 @@ describe('Sandbox', () => { describe('run()', () => { it('should run the testRunner', async () => { - const sut = await Sandbox.create(options, SANDBOX_INDEX, files, null, 0); + const sut = await Sandbox.create(options, SANDBOX_INDEX, files, null, 0, LOGGING_CONTEXT); await sut.run(231313, 'hooks'); expect(testRunner.run).to.have.been.calledWith({ timeout: 231313, @@ -166,21 +170,21 @@ describe('Sandbox', () => { }); it('should save the mutant to disk', async () => { - const sut = await Sandbox.create(options, SANDBOX_INDEX, files, null, OVERHEAD_TIME_MS); + const sut = await Sandbox.create(options, SANDBOX_INDEX, files, null, OVERHEAD_TIME_MS, LOGGING_CONTEXT); await sut.runMutant(transpiledMutant); expect(fileUtils.writeFile).calledWith(expectedTargetFileToMutate, Buffer.from('mutated code')); expect(log.warn).not.called; }); it('should nog log a warning if test selection was failed but already reported', async () => { - const sut = await Sandbox.create(options, SANDBOX_INDEX, files, null, OVERHEAD_TIME_MS); + const sut = await Sandbox.create(options, SANDBOX_INDEX, files, null, OVERHEAD_TIME_MS, LOGGING_CONTEXT); transpiledMutant.mutant.testSelectionResult = TestSelectionResult.FailedButAlreadyReported; await sut.runMutant(transpiledMutant); expect(log.warn).not.called; }); it('should log a warning if tests could not have been selected', async () => { - const sut = await Sandbox.create(options, SANDBOX_INDEX, files, null, OVERHEAD_TIME_MS); + const sut = await Sandbox.create(options, SANDBOX_INDEX, files, null, OVERHEAD_TIME_MS, LOGGING_CONTEXT); transpiledMutant.mutant.testSelectionResult = TestSelectionResult.Failed; await sut.runMutant(transpiledMutant); const expectedLogMessage = `Failed find coverage data for this mutant, running all tests. This might have an impact on performance: ${transpiledMutant.mutant.toString()}`; @@ -188,7 +192,7 @@ describe('Sandbox', () => { }); it('should filter the scoped tests', async () => { - const sut = await Sandbox.create(options, SANDBOX_INDEX, files, testFrameworkStub, OVERHEAD_TIME_MS); + const sut = await Sandbox.create(options, SANDBOX_INDEX, files, testFrameworkStub, OVERHEAD_TIME_MS, LOGGING_CONTEXT); await sut.runMutant(transpiledMutant); expect(testFrameworkStub.filter).to.have.been.calledWith(transpiledMutant.mutant.selectedTests); }); @@ -197,14 +201,14 @@ describe('Sandbox', () => { options.timeoutMs = 1000; const overheadTimeMS = 42; const totalTimeSpend = 12; - const sut = await Sandbox.create(options, SANDBOX_INDEX, files, testFrameworkStub, overheadTimeMS); + const sut = await Sandbox.create(options, SANDBOX_INDEX, files, testFrameworkStub, overheadTimeMS, LOGGING_CONTEXT); await sut.runMutant(transpiledMutant); const expectedRunOptions = { testHooks: wrapInClosure(testFilterCodeFragment), timeout: totalTimeSpend * options.timeoutFactor + options.timeoutMs + overheadTimeMS }; expect(testRunner.run).calledWith(expectedRunOptions); }); it('should have reset the source file', async () => { - const sut = await Sandbox.create(options, SANDBOX_INDEX, files, null, OVERHEAD_TIME_MS); + const sut = await Sandbox.create(options, SANDBOX_INDEX, files, null, OVERHEAD_TIME_MS, LOGGING_CONTEXT); await sut.runMutant(transpiledMutant); let timesCalled = writeFileStub.getCalls().length - 1; let lastCall = writeFileStub.getCall(timesCalled); @@ -212,11 +216,10 @@ describe('Sandbox', () => { }); it('should not filter any tests when testFramework = null', async () => { - const sut = await Sandbox.create(options, SANDBOX_INDEX, files, null, OVERHEAD_TIME_MS); + const sut = await Sandbox.create(options, SANDBOX_INDEX, files, null, OVERHEAD_TIME_MS, LOGGING_CONTEXT); const mutant = new TestableMutant('2', createMutant(), new SourceFile(new File('', ''))); sut.runMutant(new TranspiledMutant(mutant, { outputFiles: [new File(expectedTargetFileToMutate, '')], error: null }, true)); expect(fileUtils.writeFile).not.calledWith(expectedTestFrameworkHooksFile); }); - }); }); diff --git a/packages/stryker/test/unit/ScoreResultCalculatorSpec.ts b/packages/stryker/test/unit/ScoreResultCalculatorSpec.ts index 52b8a4ed6e..8dc114906f 100644 --- a/packages/stryker/test/unit/ScoreResultCalculatorSpec.ts +++ b/packages/stryker/test/unit/ScoreResultCalculatorSpec.ts @@ -1,12 +1,12 @@ import * as path from 'path'; -import { Logger } from 'log4js'; +import { Logger } from 'stryker-api/logging'; import { expect } from 'chai'; import * as sinon from 'sinon'; import { MutantStatus, ScoreResult } from 'stryker-api/report'; import ScoreResultCalculator from '../../src/ScoreResultCalculator'; import * as objectUtils from '../../src/utils/objectUtils'; import { mutantResult, scoreResult, mutationScoreThresholds, Mock } from '../helpers/producers'; -import currentLogMock from '../helpers/log4jsMock'; +import currentLogMock from '../helpers/logMock'; describe('ScoreResult', () => { let log: Mock; diff --git a/packages/stryker/test/unit/StrykerSpec.ts b/packages/stryker/test/unit/StrykerSpec.ts index 598a88fd1b..687ae4e342 100644 --- a/packages/stryker/test/unit/StrykerSpec.ts +++ b/packages/stryker/test/unit/StrykerSpec.ts @@ -1,28 +1,31 @@ -import Stryker from '../../src/Stryker'; -import { File } from 'stryker-api/core'; +import * as sinon from 'sinon'; import { MutantResult } from 'stryker-api/report'; -import { Config, ConfigEditorFactory, ConfigEditor } from 'stryker-api/config'; +import { File, LogLevel } from 'stryker-api/core'; import { RunResult } from 'stryker-api/test_runner'; import { TestFramework } from 'stryker-api/test_framework'; +import Stryker from '../../src/Stryker'; +import { Config, ConfigEditorFactory, ConfigEditor } from 'stryker-api/config'; import { expect } from 'chai'; import InputFileResolver, * as inputFileResolver from '../../src/input/InputFileResolver'; -import ConfigReader, * as configReader from '../../src/ConfigReader'; +import ConfigReader, * as configReader from '../../src/config/ConfigReader'; import TestFrameworkOrchestrator, * as testFrameworkOrchestrator from '../../src/TestFrameworkOrchestrator'; import ReporterOrchestrator, * as reporterOrchestrator from '../../src/ReporterOrchestrator'; import MutatorFacade, * as mutatorFacade from '../../src/MutatorFacade'; import MutantRunResultMatcher, * as mutantRunResultMatcher from '../../src/MutantTestMatcher'; import InitialTestExecutor, * as initialTestExecutor from '../../src/process/InitialTestExecutor'; import MutationTestExecutor, * as mutationTestExecutor from '../../src/process/MutationTestExecutor'; -import ConfigValidator, * as configValidator from '../../src/ConfigValidator'; +import ConfigValidator, * as configValidator from '../../src/config/ConfigValidator'; import ScoreResultCalculator, * as scoreResultCalculatorModule from '../../src/ScoreResultCalculator'; import PluginLoader, * as pluginLoader from '../../src/PluginLoader'; import { TempFolder } from '../../src/utils/TempFolder'; -import currentLogMock from '../helpers/log4jsMock'; +import currentLogMock from '../helpers/logMock'; import { mock, Mock, testFramework as testFrameworkMock, config, runResult, testableMutant, mutantResult } from '../helpers/producers'; import BroadcastReporter from '../../src/reporters/BroadcastReporter'; import TestableMutant from '../../src/TestableMutant'; import '../helpers/globals'; import InputFileCollection from '../../src/input/InputFileCollection'; +import LogConfigurator from '../../src/logging/LogConfigurator'; +import LoggingClientContext from '../../src/logging/LoggingClientContext'; class FakeConfigEditor implements ConfigEditor { constructor() { } @@ -31,6 +34,11 @@ class FakeConfigEditor implements ConfigEditor { } } +const LOGGING_CONTEXT: LoggingClientContext = Object.freeze({ + port: 4200, + level: LogLevel.Debug +}); + describe('Stryker', function () { let sut: Stryker; let testFramework: TestFramework; @@ -47,6 +55,8 @@ describe('Stryker', function () { let reporter: Mock; let tempFolderMock: Mock; let scoreResultCalculator: ScoreResultCalculator; + let configureMainProcessStub: sinon.SinonStub; + let configureLoggingServerStub: sinon.SinonStub; beforeEach(() => { strykerConfig = config(); @@ -58,6 +68,9 @@ describe('Stryker', function () { const reporterOrchestratorMock = mock(ReporterOrchestrator); mutantRunResultMatcherMock = mock(MutantRunResultMatcher); mutatorMock = mock(MutatorFacade); + configureMainProcessStub = sandbox.stub(LogConfigurator, 'configureMainProcess'); + configureLoggingServerStub = sandbox.stub(LogConfigurator, 'configureLoggingServer'); + configureLoggingServerStub.resolves(LOGGING_CONTEXT); inputFileResolverMock = mock(InputFileResolver); reporterOrchestratorMock.createBroadcastReporter.returns(reporter); testFramework = testFrameworkMock(); @@ -94,6 +107,10 @@ describe('Stryker', function () { expect(sut.config.testRunner).to.be.eq('fakeTestRunner'); }); + it('should configure logging for master', () => { + expect(configureMainProcessStub).calledThrice; + }); + it('should freeze the config', () => { expect(Object.isFrozen(sut.config)).to.be.eq(true); }); @@ -147,6 +164,12 @@ describe('Stryker', function () { sut = new Stryker({}); }); + it('should reject when logging server rejects', async () => { + const expectedError = Error('expected error'); + configureLoggingServerStub.rejects(expectedError); + await expect(sut.runMutationTest()).rejectedWith(expectedError); + }); + it('should reject when input file globbing results in a rejection', async () => { const expectedError = Error('expected error'); inputFileResolverMock.resolve.rejects(expectedError); @@ -192,6 +215,10 @@ describe('Stryker', function () { return sut.runMutationTest(); }); + it('should configure the logging server', () => { + expect(configureLoggingServerStub).calledWith(strykerConfig.logLevel, strykerConfig.fileLogLevel); + }); + it('should report mutant score', () => { expect(reporter.onScoreCalculated).to.have.been.called; }); @@ -212,9 +239,9 @@ describe('Stryker', function () { expect(inputFileResolverMock.resolve).called; }); - it('should create the InitialTestRunner', () => { + it('should create the InitialTestExecutor', () => { expect(initialTestExecutor.default).calledWithNew; - expect(initialTestExecutor.default).calledWith(strykerConfig, inputFiles); + expect(initialTestExecutor.default).calledWith(strykerConfig, inputFiles, testFramework, sinon.match.any, LOGGING_CONTEXT); expect(initialTestExecutorMock.run).called; }); @@ -226,7 +253,7 @@ describe('Stryker', function () { it('should create the mutation test executor', () => { expect(mutationTestExecutor.default).calledWithNew; - expect(mutationTestExecutor.default).calledWith(strykerConfig, inputFiles.files, testFramework, reporter); + expect(mutationTestExecutor.default).calledWith(strykerConfig, inputFiles.files, testFramework, reporter, undefined, LOGGING_CONTEXT); expect(mutationTestExecutorMock.run).calledWith(mutants); }); diff --git a/packages/stryker/test/unit/TestFrameworkOrchestratorSpec.ts b/packages/stryker/test/unit/TestFrameworkOrchestratorSpec.ts index 2b77cd9576..4856b75268 100644 --- a/packages/stryker/test/unit/TestFrameworkOrchestratorSpec.ts +++ b/packages/stryker/test/unit/TestFrameworkOrchestratorSpec.ts @@ -1,10 +1,10 @@ import TestFrameworkOrchestrator from '../../src/TestFrameworkOrchestrator'; import { expect } from 'chai'; -import { Logger } from 'log4js'; +import { Logger } from 'stryker-api/logging'; import * as sinon from 'sinon'; import { TestFrameworkFactory } from 'stryker-api/test_framework'; import { StrykerOptions } from 'stryker-api/core'; -import currentLogMock from '../helpers/log4jsMock'; +import currentLogMock from '../helpers/logMock'; import { Mock } from '../helpers/producers'; describe('TestFrameworkOrchestrator', () => { diff --git a/packages/stryker/test/unit/child-proxy/ChildProcessProxySpec.ts b/packages/stryker/test/unit/child-proxy/ChildProcessProxySpec.ts index 9a604a6bee..7d02507055 100644 --- a/packages/stryker/test/unit/child-proxy/ChildProcessProxySpec.ts +++ b/packages/stryker/test/unit/child-proxy/ChildProcessProxySpec.ts @@ -4,7 +4,13 @@ import ChildProcessProxy from '../../../src/child-proxy/ChildProcessProxy'; import { autoStart, InitMessage, WorkerMessageKind, ParentMessage, WorkerMessage, ParentMessageKind } from '../../../src/child-proxy/messageProtocol'; import { serialize } from '../../../src/utils/objectUtils'; import HelloClass from './HelloClass'; +import LoggingClientContext from '../../../src/logging/LoggingClientContext'; +import { LogLevel } from 'stryker-api/core'; +const LOGGING_CONTEXT: LoggingClientContext = Object.freeze({ + port: 4200, + level: LogLevel.Fatal +}); describe('ChildProcessProxy', () => { @@ -27,28 +33,28 @@ describe('ChildProcessProxy', () => { describe('create', () => { it('should create child process', () => { - ChildProcessProxy.create('foobar', 'FATAL', ['examplePlugin', 'secondExamplePlugin'], HelloClass, 'something'); + ChildProcessProxy.create('foobar', LOGGING_CONTEXT, ['examplePlugin', 'secondExamplePlugin'], HelloClass, 'something'); expect(forkStub).calledWith(require.resolve('../../../src/child-proxy/ChildProcessProxyWorker'), [autoStart], { silent: false, execArgv: [] }); }); it('should send init message to child process', () => { const expectedMessage: InitMessage = { kind: WorkerMessageKind.Init, - logLevel: 'FATAL ;)', + loggingContext: LOGGING_CONTEXT, plugins: ['examplePlugin', 'secondExamplePlugin'], requirePath: 'foobar', constructorArgs: ['something'] }; // Act - ChildProcessProxy.create('foobar', 'FATAL ;)', ['examplePlugin', 'secondExamplePlugin'], HelloClass, 'something'); + ChildProcessProxy.create('foobar', LOGGING_CONTEXT, ['examplePlugin', 'secondExamplePlugin'], HelloClass, 'something'); // Assert expect(childProcessMock.send).calledWith(serialize(expectedMessage)); }); it('should listen to worker process', () => { - ChildProcessProxy.create('foobar', '', [], HelloClass, ''); + ChildProcessProxy.create('foobar', LOGGING_CONTEXT, [], HelloClass, ''); expect(childProcessMock.on).calledWith('message'); }); }); @@ -56,7 +62,7 @@ describe('ChildProcessProxy', () => { describe('when calling methods', () => { beforeEach(() => { - sut = ChildProcessProxy.create('', '', [], HelloClass, ''); + sut = ChildProcessProxy.create('', LOGGING_CONTEXT, [], HelloClass, ''); const initDoneResult: ParentMessage = { kind: ParentMessageKind.Initialized }; const msg = serialize(initDoneResult); childProcessMock.on.callArgWith(1, [msg]); diff --git a/packages/stryker/test/unit/child-proxy/ChildProcessWorkerSpec.ts b/packages/stryker/test/unit/child-proxy/ChildProcessWorkerSpec.ts index 435a4303f1..b4791db94c 100644 --- a/packages/stryker/test/unit/child-proxy/ChildProcessWorkerSpec.ts +++ b/packages/stryker/test/unit/child-proxy/ChildProcessWorkerSpec.ts @@ -2,17 +2,21 @@ import ChildProcessProxyWorker from '../../../src/child-proxy/ChildProcessProxyW import { expect } from 'chai'; import { serialize } from '../../../src/utils/objectUtils'; import { WorkerMessage, WorkerMessageKind, ParentMessage, WorkResult, WorkMessage, ParentMessageKind } from '../../../src/child-proxy/messageProtocol'; -import * as log4js from 'log4js'; import PluginLoader, * as pluginLoader from '../../../src/PluginLoader'; import { Mock, mock } from '../../helpers/producers'; import HelloClass from './HelloClass'; +import LogConfigurator from '../../../src/logging/LogConfigurator'; +import { LogLevel } from 'stryker-api/core'; +import LoggingClientContext from '../../../src/logging/LoggingClientContext'; + +const LOGGING_CONTEXT: LoggingClientContext = Object.freeze({ port: 4200, level: LogLevel.Fatal }); describe('ChildProcessProxyWorker', () => { let processOnStub: sinon.SinonStub; let processSendStub: sinon.SinonStub; let processListenersStub: sinon.SinonStub; - let setGlobalLogLevelStub: sinon.SinonStub; + let configureChildProcessStub: sinon.SinonStub; let processRemoveListenerStub: sinon.SinonStub; let pluginLoaderMock: Mock; let originalProcessSend: undefined | NodeJS.MessageListener; @@ -28,7 +32,7 @@ describe('ChildProcessProxyWorker', () => { // process.send is normally undefined originalProcessSend = process.send; process.send = processSendStub; - setGlobalLogLevelStub = sandbox.stub(log4js, 'setGlobalLogLevel'); + configureChildProcessStub = sandbox.stub(LogConfigurator, 'configureChildProcess'); pluginLoaderMock = mock(PluginLoader); sandbox.stub(pluginLoader, 'default').returns(pluginLoaderMock); }); @@ -51,7 +55,7 @@ describe('ChildProcessProxyWorker', () => { sut = new ChildProcessProxyWorker(); initMessage = { kind: WorkerMessageKind.Init, - logLevel: 'FooLevel', + loggingContext: LOGGING_CONTEXT, constructorArgs: ['FooBarName'], plugins: ['fooPlugin', 'barPlugin'], requirePath: require.resolve('./HelloClass') @@ -87,7 +91,7 @@ describe('ChildProcessProxyWorker', () => { it('should set global log level', () => { processOnStub.callArgWith(1, serialize(initMessage)); - expect(setGlobalLogLevelStub).calledWith('FooLevel'); + expect(configureChildProcessStub).calledWith(LOGGING_CONTEXT); }); it('should load plugins', () => { diff --git a/packages/stryker/test/unit/ConfigValidatorSpec.ts b/packages/stryker/test/unit/config/ConfigValidatorSpec.ts similarity index 83% rename from packages/stryker/test/unit/ConfigValidatorSpec.ts rename to packages/stryker/test/unit/config/ConfigValidatorSpec.ts index 84ae867cdd..a24d9bf471 100644 --- a/packages/stryker/test/unit/ConfigValidatorSpec.ts +++ b/packages/stryker/test/unit/config/ConfigValidatorSpec.ts @@ -1,16 +1,15 @@ import { expect } from 'chai'; import * as sinon from 'sinon'; -import { Logger } from 'log4js'; +import { Logger } from 'stryker-api/logging'; import { Config } from 'stryker-api/config'; -import ConfigValidator from './../../src/ConfigValidator'; -import currentLogMock from '../helpers/log4jsMock'; -import { testFramework, Mock } from '../helpers/producers'; +import ConfigValidator from '../../../src/config/ConfigValidator'; +import currentLogMock from '../../helpers/logMock'; +import { testFramework, Mock } from '../../helpers/producers'; describe('ConfigValidator', () => { let config: Config; let sandbox: sinon.SinonSandbox; - let exitStub: sinon.SinonStub; let sut: ConfigValidator; let log: Mock; @@ -22,7 +21,6 @@ describe('ConfigValidator', () => { log = currentLogMock(); config = new Config(); sandbox = sinon.createSandbox(); - exitStub = sandbox.stub(process, 'exit'); }); afterEach(() => { @@ -32,15 +30,13 @@ describe('ConfigValidator', () => { it('should validate with default config', () => { sut = new ConfigValidator(config, testFramework()); sut.validate(); - expect(exitStub).not.called; expect(log.fatal).not.called; }); it('should be invalid with coverageAnalysis "perTest" without a testFramework', () => { config.coverageAnalysis = 'perTest'; sut = new ConfigValidator(config, null); - sut.validate(); - expect(exitStub).calledWith(1); + actValidationError(); expect(log.fatal).calledWith('Configured coverage analysis "perTest" requires there to be a testFramework configured. Either configure a testFramework or set coverageAnalysis to "all" or "off".'); }); @@ -50,8 +46,7 @@ describe('ConfigValidator', () => { config.thresholds.high = -1; config.thresholds.low = 101; sut = new ConfigValidator(config, testFramework()); - sut.validate(); - expect(exitStub).calledWith(1); + actValidationError(); expect(log.fatal).calledWith('`thresholds.high` is lower than `thresholds.low` (-1 < 101)'); expect(log.fatal).calledWith('Value "-1" is invalid for `thresholds.high`. Expected a number between 0 and 100'); expect(log.fatal).calledWith('Value "101" is invalid for `thresholds.low`. Expected a number between 0 and 100'); @@ -61,8 +56,7 @@ describe('ConfigValidator', () => { (config.thresholds.high as any) = null; config.thresholds.low = 101; sut = new ConfigValidator(config, testFramework()); - sut.validate(); - expect(exitStub).calledWith(1); + actValidationError(); expect(log.fatal).calledWith('Value "null" is invalid for `thresholds.high`. Expected a number between 0 and 100'); }); }); @@ -72,34 +66,30 @@ describe('ConfigValidator', () => { config.transpilers.push('a second transpiler'); config.coverageAnalysis = 'all'; sut = new ConfigValidator(config, testFramework()); - sut.validate(); + actValidationError(); expect(log.fatal).calledWith('Value "all" for `coverageAnalysis` is invalid with multiple transpilers' + ' (configured transpilers: a transpiler, a second transpiler). Please report this to the Stryker team' + ' if you whish this feature to be implemented'); - expect(exitStub).calledWith(1); }); it('should be invalid with invalid logLevel', () => { - config.logLevel = 'thisTestPasses'; + config.logLevel = 'thisTestPasses' as any; sut = new ConfigValidator(config, testFramework()); - sut.validate(); - expect(exitStub).calledWith(1); - expect(log.fatal).calledWith('Value "thisTestPasses" is invalid for `logLevel`. Expected one of the following: "fatal", "error", "warn", "info", "debug", "trace", "all", "off"'); + actValidationError(); + expect(log.fatal).calledWith('Value "thisTestPasses" is invalid for `logLevel`. Expected one of the following: "fatal", "error", "warn", "info", "debug", "trace", "off"'); }); it('should be invalid with nonnumeric timeoutMs', () => { let brokenConfig = breakConfig(config, 'timeoutMs', 'break'); sut = new ConfigValidator(brokenConfig, testFramework()); - sut.validate(); - expect(exitStub).calledWith(1); + actValidationError(); expect(log.fatal).calledWith('Value "break" is invalid for `timeoutMs`. Expected a number'); }); it('should be invalid with nonnumeric timeoutFactor', () => { let brokenConfig = breakConfig(config, 'timeoutFactor', 'break'); sut = new ConfigValidator(brokenConfig, testFramework()); - sut.validate(); - expect(exitStub).calledWith(1); + actValidationError(); expect(log.fatal).calledWith('Value "break" is invalid for `timeoutFactor`. Expected a number'); }); @@ -107,16 +97,14 @@ describe('ConfigValidator', () => { it('should be invalid with non-array plugins', () => { let brokenConfig = breakConfig(config, 'plugins', 'stryker-typescript'); sut = new ConfigValidator(brokenConfig, testFramework()); - sut.validate(); - expect(exitStub).calledWith(1); + actValidationError(); expect(log.fatal).calledWith('Value "stryker-typescript" is invalid for `plugins`. Expected an array'); }); it('should be invalid with non-string array elements', () => { let brokenConfig = breakConfig(config, 'plugins', ['stryker-jest', 0]); sut = new ConfigValidator(brokenConfig, testFramework()); - sut.validate(); - expect(exitStub).calledWith(1); + actValidationError(); expect(log.fatal).calledWith('Value "0" is an invalid element of `plugins`. Expected a string'); }); }); @@ -125,8 +113,7 @@ describe('ConfigValidator', () => { it('should be invalid with non-string mutator', () => { let brokenConfig = breakConfig(config, 'mutator', 0); sut = new ConfigValidator(brokenConfig, testFramework()); - sut.validate(); - expect(exitStub).calledWith(1); + actValidationError(); expect(log.fatal).calledWith('Value "0" is invalid for `mutator`. Expected either a string or an object'); }); @@ -138,7 +125,6 @@ describe('ConfigValidator', () => { }); sut = new ConfigValidator(validConfig, testFramework()); sut.validate(); - expect(exitStub).not.called; expect(log.fatal).not.called; }); @@ -148,8 +134,7 @@ describe('ConfigValidator', () => { excludedMutations: [] }); sut = new ConfigValidator(brokenConfig, testFramework()); - sut.validate(); - expect(exitStub).calledWith(1); + actValidationError(); expect(log.fatal).calledWith('Value "0" is invalid for `mutator.name`. Expected a string'); }); @@ -159,8 +144,7 @@ describe('ConfigValidator', () => { excludedMutations: 'BooleanSubstitution' }); sut = new ConfigValidator(brokenConfig, testFramework()); - sut.validate(); - expect(exitStub).calledWith(1); + actValidationError(); expect(log.fatal).calledWith('Value "BooleanSubstitution" is invalid for `mutator.excludedMutations`. Expected an array'); }); @@ -170,8 +154,7 @@ describe('ConfigValidator', () => { excludedMutations: ['BooleanSubstitution', 0] }); sut = new ConfigValidator(brokenConfig, testFramework()); - sut.validate(); - expect(exitStub).calledWith(1); + actValidationError(); expect(log.fatal).calledWith('Value "0" is an invalid element of `mutator.excludedMutations`. Expected a string'); }); }); @@ -181,8 +164,7 @@ describe('ConfigValidator', () => { it('should be invalid with non-array reporter', () => { let brokenConfig = breakConfig(config, 'reporter', 'stryker-typescript'); sut = new ConfigValidator(brokenConfig, testFramework()); - sut.validate(); - expect(exitStub).calledWith(1); + actValidationError(); expect(log.fatal).calledWith('Value "stryker-typescript" is invalid for `reporter`. Expected an array'); }); @@ -192,8 +174,7 @@ describe('ConfigValidator', () => { 0 ]); sut = new ConfigValidator(brokenConfig, testFramework()); - sut.validate(); - expect(exitStub).calledWith(1); + actValidationError(); expect(log.fatal).calledWith('Value "0" is an invalid element of `reporter`. Expected a string'); }); }); @@ -202,8 +183,7 @@ describe('ConfigValidator', () => { it('should be invalid with non-array transpilers', () => { let brokenConfig = breakConfig(config, 'transpilers', 'stryker-typescript'); sut = new ConfigValidator(brokenConfig, testFramework()); - sut.validate(); - expect(exitStub).calledWith(1); + actValidationError(); expect(log.fatal).calledWith('Value "stryker-typescript" is invalid for `transpilers`. Expected an array'); }); @@ -213,8 +193,7 @@ describe('ConfigValidator', () => { 0 ]); sut = new ConfigValidator(brokenConfig, testFramework()); - sut.validate(); - expect(exitStub).calledWith(1); + actValidationError(); expect(log.fatal).calledWith('Value "0" is an invalid element of `transpilers`. Expected a string'); }); }); @@ -222,8 +201,12 @@ describe('ConfigValidator', () => { it('should be invalid with invalid coverageAnalysis', () => { let brokenConfig = breakConfig(config, 'coverageAnalysis', 'invalid'); sut = new ConfigValidator(brokenConfig, testFramework()); - sut.validate(); - expect(exitStub).calledWith(1); + actValidationError(); expect(log.fatal).calledWith('Value "invalid" is invalid for `coverageAnalysis`. Expected one of the following: "perTest", "all", "off"'); }); + + function actValidationError() { + expect(() => sut.validate()).throws('Stryker could not recover from this configuration error, see fatal log message(s) above.'); + } + }); diff --git a/packages/stryker/test/unit/initializer/StrykerInitializerSpec.ts b/packages/stryker/test/unit/initializer/StrykerInitializerSpec.ts index 15a1f08c60..9620404bda 100644 --- a/packages/stryker/test/unit/initializer/StrykerInitializerSpec.ts +++ b/packages/stryker/test/unit/initializer/StrykerInitializerSpec.ts @@ -1,12 +1,12 @@ import * as child from 'child_process'; import * as fs from 'mz/fs'; -import { Logger } from 'log4js'; +import { Logger } from 'stryker-api/logging'; import * as sinon from 'sinon'; import { expect } from 'chai'; import * as inquirer from 'inquirer'; import StrykerInitializer from '../../../src/initializer/StrykerInitializer'; import * as restClient from 'typed-rest-client/RestClient'; -import currentLogMock from '../../helpers/log4jsMock'; +import currentLogMock from '../../helpers/logMock'; import { Mock } from '../../helpers/producers'; describe('StrykerInitializer', () => { diff --git a/packages/stryker/test/unit/input/InputFileResolverSpec.ts b/packages/stryker/test/unit/input/InputFileResolverSpec.ts index 8af2b49cc8..97ef2545ca 100644 --- a/packages/stryker/test/unit/input/InputFileResolverSpec.ts +++ b/packages/stryker/test/unit/input/InputFileResolverSpec.ts @@ -2,13 +2,13 @@ import * as path from 'path'; import { expect } from 'chai'; import * as fs from 'mz/fs'; import * as childProcess from 'mz/child_process'; -import { Logger } from 'log4js'; +import { Logger } from 'stryker-api/logging'; import { File } from 'stryker-api/core'; import { SourceFile } from 'stryker-api/report'; import InputFileResolver from '../../../src/input/InputFileResolver'; import * as sinon from 'sinon'; import * as fileUtils from '../../../src/utils/fileUtils'; -import currentLogMock from '../../helpers/log4jsMock'; +import currentLogMock from '../../helpers/logMock'; import BroadcastReporter from '../../../src/reporters/BroadcastReporter'; import { Mock, mock, createFileNotFoundError } from '../../helpers/producers'; import { errorToString, normalizeWhiteSpaces } from '../../../src/utils/objectUtils'; @@ -32,8 +32,8 @@ describe('InputFileResolver', () => { reporter = mock(BroadcastReporter); globStub = sandbox.stub(fileUtils, 'glob'); readFileStub = sandbox.stub(fs, 'readFile') - .withArgs(sinon.match.string).resolves(new Buffer(0)) // fallback - .withArgs(sinon.match.string).resolves(new Buffer(0)) // fallback + .withArgs(sinon.match.string).resolves(Buffer.from('')) // fallback + .withArgs(sinon.match.string).resolves(Buffer.from('')) // fallback .withArgs(sinon.match('file1')).resolves(Buffer.from('file 1 content')) .withArgs(sinon.match('file2')).resolves(Buffer.from('file 2 content')) .withArgs(sinon.match('file3')).resolves(Buffer.from('file 3 content')) diff --git a/packages/stryker/test/unit/isolated-runner/IsolatedTestRunnerAdapterSpec.ts b/packages/stryker/test/unit/isolated-runner/IsolatedTestRunnerAdapterSpec.ts index 43cfef90ec..5a49298bee 100644 --- a/packages/stryker/test/unit/isolated-runner/IsolatedTestRunnerAdapterSpec.ts +++ b/packages/stryker/test/unit/isolated-runner/IsolatedTestRunnerAdapterSpec.ts @@ -4,12 +4,11 @@ import * as child_process from 'child_process'; import * as _ from 'lodash'; import * as sinon from 'sinon'; import { expect } from 'chai'; -import { RunResult, RunStatus } from 'stryker-api/test_runner'; +import { RunResult, RunStatus, RunnerOptions } from 'stryker-api/test_runner'; import IsolatedTestRunnerAdapter from '../../../src/isolated-runner/IsolatedTestRunnerAdapter'; -import IsolatedRunnerOptions from '../../../src/isolated-runner/IsolatedRunnerOptions'; import { WorkerMessage, RunMessage, ResultMessage } from '../../../src/isolated-runner/MessageProtocol'; import * as objectUtils from '../../../src/utils/objectUtils'; - +import { LogLevel } from 'stryker-api/core'; describe('IsolatedTestRunnerAdapter', () => { let sut: IsolatedTestRunnerAdapter; @@ -22,13 +21,12 @@ describe('IsolatedTestRunnerAdapter', () => { on: sinon.SinonStub; pid: number; }; - let runnerOptions: IsolatedRunnerOptions; + let runnerOptions: RunnerOptions; beforeEach(() => { runnerOptions = { fileNames: [], port: 42, - sandboxWorkingFolder: 'a working directory', strykerOptions: {} }; sinonSandbox = sinon.createSandbox(); @@ -46,7 +44,7 @@ describe('IsolatedTestRunnerAdapter', () => { describe('when constructed', () => { beforeEach(() => { - sut = new IsolatedTestRunnerAdapter('realRunner', runnerOptions); + sut = new IsolatedTestRunnerAdapter('realRunner', runnerOptions, 'a working directory', { port: 4200, level: LogLevel.Fatal }); }); it('should spawn a child process', () => { diff --git a/packages/stryker/test/unit/logging/LogConfiguratorSpec.ts b/packages/stryker/test/unit/logging/LogConfiguratorSpec.ts new file mode 100644 index 0000000000..edca8d9618 --- /dev/null +++ b/packages/stryker/test/unit/logging/LogConfiguratorSpec.ts @@ -0,0 +1,103 @@ +import * as log4js from 'log4js'; +import { expect } from 'chai'; +import { LogLevel } from 'stryker-api/core'; +import LogConfigurator from '../../../src/logging/LogConfigurator'; +import * as netUtils from '../../../src/utils/netUtils'; +import LoggingClientContext from '../../../src/logging/LoggingClientContext'; + +describe('LogConfigurator', () => { + + const sut = LogConfigurator; + let getFreePortStub: sinon.SinonStub; + let log4jsConfigure: sinon.SinonStub; + let log4jsShutdown: sinon.SinonStub; + + beforeEach(() => { + getFreePortStub = sandbox.stub(netUtils, 'getFreePort'); + log4jsConfigure = sandbox.stub(log4js, 'configure'); + log4jsShutdown = sandbox.stub(log4js, 'shutdown'); + }); + + describe('configureMainProcess', () => { + it('should configure console and file', () => { + sut.configureMainProcess(LogLevel.Information, LogLevel.Trace); + expect(log4jsConfigure).calledWith(createMasterConfig(LogLevel.Information, LogLevel.Trace, LogLevel.Trace)); + }); + + it('should not configure file if it is "off"', () => { + sut.configureMainProcess(LogLevel.Information, LogLevel.Off); + const masterConfig = createMasterConfig(LogLevel.Information, LogLevel.Off, LogLevel.Information); + delete masterConfig.appenders.file; + delete masterConfig.appenders.filteredFile; + (masterConfig.appenders.all as any).appenders = ['filteredConsole']; + expect(log4jsConfigure).calledWith(masterConfig); + }); + }); + + describe('configureLoggingServer', () => { + it('should configure console, file and server', async () => { + // Arrange + const expectedLoggingContext: LoggingClientContext = { port: 42, level: LogLevel.Error }; + getFreePortStub.resolves(expectedLoggingContext.port); + const expectedConfig = createMasterConfig(LogLevel.Error, LogLevel.Fatal, LogLevel.Error); + const serverAppender: log4js.MultiprocessAppender = { type: 'multiprocess', mode: 'master', loggerPort: 42, appender: 'all' }; + expectedConfig.appenders.server = serverAppender; + + // Act + const actualLoggingContext = await sut.configureLoggingServer(LogLevel.Error, LogLevel.Fatal); + + // Assert + expect(log4jsConfigure).calledWith(expectedConfig); + expect(getFreePortStub).called; + expect(actualLoggingContext).deep.eq(expectedLoggingContext); + }); + + }); + + describe('configureChildProcess', () => { + it('should configure the logging client', () => { + sut.configureChildProcess({ port: 42, level: LogLevel.Information }); + const multiProcessAppender: log4js.MultiprocessAppender = { type: 'multiprocess', mode: 'worker', loggerPort: 42 }; + const expectedConfig: log4js.Configuration = { + appenders: { + all: multiProcessAppender + }, + categories: { + default: { level: LogLevel.Information, appenders: ['all'] } + } + }; + expect(log4jsConfigure).calledWith(expectedConfig); + }); + }); + + describe('shutdown', () => { + it('should shutdown log4js', async () => { + log4jsShutdown.callsArg(0); + await sut.shutdown(); + expect(log4jsShutdown).called; + }); + }); + + function createMasterConfig(consoleLevel: LogLevel, fileLevel: LogLevel, defaultLevel: LogLevel): log4js.Configuration { + const coloredLayout: log4js.PatternLayout = { + type: 'pattern', + pattern: '%[%r (%z) %p %c%] %m' + }; + const notColoredLayout: log4js.PatternLayout = { + type: 'pattern', + pattern: '%r (%z) %p %c %m' + }; + return { + appenders: { + console: { type: 'stdout', layout: coloredLayout }, + file: { type: 'file', layout: notColoredLayout, filename: 'stryker.log' }, + filteredConsole: { type: 'logLevelFilter', appender: 'console', level: consoleLevel }, + filteredFile: { type: 'logLevelFilter', appender: 'file', level: fileLevel }, + all: { type: require.resolve('../../../src/logging/MultiAppender'), appenders: ['filteredConsole', 'filteredFile'] } + }, + categories: { + default: { level: defaultLevel, appenders: ['all'] } + } + }; + } +}); \ No newline at end of file diff --git a/packages/stryker/test/unit/logging/MultiAppenderSpec.ts b/packages/stryker/test/unit/logging/MultiAppenderSpec.ts new file mode 100644 index 0000000000..95d04547c4 --- /dev/null +++ b/packages/stryker/test/unit/logging/MultiAppenderSpec.ts @@ -0,0 +1,40 @@ +import { expect } from 'chai'; +import * as log4js from 'log4js'; +import { configure, RuntimeAppender } from '../../../src/logging/MultiAppender'; + +describe('MultiAppender', () => { + let sut: RuntimeAppender; + let fooLogEvents: log4js.LoggingEvent[]; + let barLogEvents: log4js.LoggingEvent[]; + + beforeEach(() => { + fooLogEvents = []; + barLogEvents = []; + sut = configure({ appenders: ['foo', 'bar'] }, null, name => { + switch (name) { + case 'foo': + return event => fooLogEvents.push(event); + case 'bar': + return event => barLogEvents.push(event); + default: + throw new Error(`${name} is not supported`); + } + }); + }); + + it('should fan out events to all appenders', () => { + const loggingEvent: log4js.LoggingEvent = { + categoryName: 'category', + context: null, + data: ['foo data'], + level: (log4js.levels as any).DEBUG, + pid: 42, + startTime: new Date(42) + }; + sut(loggingEvent); + expect(fooLogEvents).lengthOf(1); + expect(barLogEvents).lengthOf(1); + expect(fooLogEvents).contains(loggingEvent); + expect(barLogEvents).contains(loggingEvent); + }); +}); \ No newline at end of file diff --git a/packages/stryker/test/unit/process/InitialTestExecutorSpec.ts b/packages/stryker/test/unit/process/InitialTestExecutorSpec.ts index 0998dcbb3d..323e1d5313 100644 --- a/packages/stryker/test/unit/process/InitialTestExecutorSpec.ts +++ b/packages/stryker/test/unit/process/InitialTestExecutorSpec.ts @@ -1,9 +1,9 @@ import { EOL } from 'os'; import { expect } from 'chai'; -import { Logger } from 'log4js'; +import { Logger } from 'stryker-api/logging'; import { default as StrykerSandbox } from '../../../src/Sandbox'; import InitialTestExecutor, { InitialTestRunResult } from '../../../src/process/InitialTestExecutor'; -import { File } from 'stryker-api/core'; +import { File, LogLevel } from 'stryker-api/core'; import { Config } from 'stryker-api/config'; import * as producers from '../../helpers/producers'; import { TestFramework } from 'stryker-api/test_framework'; @@ -11,14 +11,20 @@ import CoverageInstrumenterTranspiler, * as coverageInstrumenterTranspiler from import TranspilerFacade, * as transpilerFacade from '../../../src/transpiler/TranspilerFacade'; import { TranspilerOptions } from 'stryker-api/transpile'; import { RunStatus, RunResult, TestStatus } from 'stryker-api/test_runner'; -import currentLogMock from '../../helpers/log4jsMock'; +import currentLogMock from '../../helpers/logMock'; import Timer from '../../../src/utils/Timer'; import { Mock, coverageMaps } from '../../helpers/producers'; import InputFileCollection from '../../../src/input/InputFileCollection'; import * as coverageHooks from '../../../src/transpiler/coverageHooks'; import SourceMapper, { PassThroughSourceMapper } from '../../../src/transpiler/SourceMapper'; +import LoggingClientContext from '../../../src/logging/LoggingClientContext'; const EXPECTED_INITIAL_TIMEOUT = 60 * 1000 * 5; +const LOGGING_CONTEXT: LoggingClientContext = Object.freeze({ + port: 4200, + level: LogLevel.Fatal +}); + describe('InitialTestExecutor run', () => { let log: Mock; @@ -67,7 +73,7 @@ describe('InitialTestExecutor run', () => { beforeEach(() => { inputFiles = new InputFileCollection([new File('mutate.js', ''), new File('mutate.spec.js', '')], ['mutate.js']); - sut = new InitialTestExecutor(options, inputFiles, testFrameworkMock, timer as any); + sut = new InitialTestExecutor(options, inputFiles, testFrameworkMock, timer as any, LOGGING_CONTEXT); }); it('should create a sandbox with correct arguments', async () => { @@ -194,7 +200,7 @@ describe('InitialTestExecutor run', () => { }); it('should result log a warning if coverage analysis is "perTest" and there is no testFramework', async () => { - sut = new InitialTestExecutor(options, inputFiles, /* test framework */ null, timer as any); + sut = new InitialTestExecutor(options, inputFiles, /* test framework */ null, timer as any, LOGGING_CONTEXT); sandbox.stub(coverageHooks, 'coveragePerTestHooks').returns('test hook foobar'); await sut.run(); expect(log.warn).calledWith('Cannot measure coverage results per test, there is no testFramework and thus no way of executing code right before and after each test.'); diff --git a/packages/stryker/test/unit/process/MutationTestExecutorSpec.ts b/packages/stryker/test/unit/process/MutationTestExecutorSpec.ts index e8bce94529..a61d78fcea 100644 --- a/packages/stryker/test/unit/process/MutationTestExecutorSpec.ts +++ b/packages/stryker/test/unit/process/MutationTestExecutorSpec.ts @@ -2,7 +2,7 @@ import { expect } from 'chai'; import * as _ from 'lodash'; import { empty, of } from 'rxjs'; import { Config } from 'stryker-api/config'; -import { File } from 'stryker-api/core'; +import { File, LogLevel } from 'stryker-api/core'; import { MutantStatus } from 'stryker-api/report'; import { TestFramework } from 'stryker-api/test_framework'; import { RunStatus, TestStatus } from 'stryker-api/test_runner'; @@ -15,6 +15,7 @@ import BroadcastReporter from '../../../src/reporters/BroadcastReporter'; import MutantTranspiler, * as mutantTranspiler from '../../../src/transpiler/MutantTranspiler'; import '../../helpers/globals'; import { Mock, config, file, mock, mutantResult, testFramework, testResult, testableMutant, transpiledMutant } from '../../helpers/producers'; +import LoggingClientContext from '../../../src/logging/LoggingClientContext'; const createTranspiledMutants = (...n: number[]) => { return n.map(n => { @@ -26,6 +27,12 @@ const createTranspiledMutants = (...n: number[]) => { }); }; +const LOGGING_CONTEXT: LoggingClientContext = Object.freeze({ + port: 4200, + level: LogLevel.Fatal +}); + + describe('MutationTestExecutor', () => { let sandboxPoolMock: Mock; @@ -57,7 +64,7 @@ describe('MutationTestExecutor', () => { describe('run', () => { beforeEach(async () => { - sut = new MutantTestExecutor(expectedConfig, inputFiles, testFrameworkMock, reporter, 42); + sut = new MutantTestExecutor(expectedConfig, inputFiles, testFrameworkMock, reporter, 42, LOGGING_CONTEXT); const sandbox = mock(Sandbox); sandbox.runMutant.resolves(mutantResult()); sandboxPoolMock.streamSandboxes.returns(of(sandbox)); @@ -97,7 +104,7 @@ describe('MutationTestExecutor', () => { mutantTranspilerMock.transpileMutants.returns(of(...transpiledMutants)); sandboxPoolMock.streamSandboxes.returns(of(firstSandbox, secondSandbox)); - sut = new MutantTestExecutor(config(), inputFiles, testFrameworkMock, reporter, 42); + sut = new MutantTestExecutor(config(), inputFiles, testFrameworkMock, reporter, 42, LOGGING_CONTEXT); // The uncovered, transpile error and changedAnyTranspiledFiles = false should not be ran in a sandbox // Mock first sandbox to return first success, then failed diff --git a/packages/stryker/test/unit/reporters/BroadcastReporterSpec.ts b/packages/stryker/test/unit/reporters/BroadcastReporterSpec.ts index 8ea19774a1..0ef6166a84 100644 --- a/packages/stryker/test/unit/reporters/BroadcastReporterSpec.ts +++ b/packages/stryker/test/unit/reporters/BroadcastReporterSpec.ts @@ -1,7 +1,7 @@ import * as sinon from 'sinon'; -import { Logger } from 'log4js'; +import { Logger } from 'stryker-api/logging'; import { expect } from 'chai'; -import currentLogMock from '../../helpers/log4jsMock'; +import currentLogMock from '../../helpers/logMock'; import BroadcastReporter from '../../../src/reporters/BroadcastReporter'; import { ALL_REPORTER_EVENTS, Mock } from '../../helpers/producers'; diff --git a/packages/stryker/test/unit/reporters/DashboardReporterSpec.ts b/packages/stryker/test/unit/reporters/DashboardReporterSpec.ts index 6ea0dfc70d..65cb4587db 100644 --- a/packages/stryker/test/unit/reporters/DashboardReporterSpec.ts +++ b/packages/stryker/test/unit/reporters/DashboardReporterSpec.ts @@ -5,8 +5,8 @@ import * as environmentVariables from '../../../src/utils/objectUtils'; import * as ciProvider from '../../../src/reporters/ci/Provider'; import StrykerDashboardClient, { StrykerDashboardReport } from '../../../src/reporters/dashboard-reporter/DashboardReporterClient'; import { scoreResult, mock, Mock } from '../../helpers/producers'; -import { Logger } from 'log4js'; -import currentLogMock from '../../helpers/log4jsMock'; +import { Logger } from 'stryker-api/logging'; +import currentLogMock from '../../helpers/logMock'; import { Config } from 'stryker-api/config'; describe('DashboardReporter', () => { diff --git a/packages/stryker/test/unit/reporters/EventRecorderReporterSpec.ts b/packages/stryker/test/unit/reporters/EventRecorderReporterSpec.ts index b348dbf9ac..c54f0852a4 100644 --- a/packages/stryker/test/unit/reporters/EventRecorderReporterSpec.ts +++ b/packages/stryker/test/unit/reporters/EventRecorderReporterSpec.ts @@ -4,7 +4,7 @@ import * as fs from 'mz/fs'; import { Reporter } from 'stryker-api/report'; import EventRecorderReporter from '../../../src/reporters/EventRecorderReporter'; import * as fileUtils from '../../../src/utils/fileUtils'; -import currentLogMock from '../../helpers/log4jsMock'; +import currentLogMock from '../../helpers/logMock'; import StrictReporter from '../../../src/reporters/StrictReporter'; import { ALL_REPORTER_EVENTS } from '../../helpers/producers'; diff --git a/packages/stryker/test/unit/reporters/dashboard-reporter/DashboardReporterClientSpec.ts b/packages/stryker/test/unit/reporters/dashboard-reporter/DashboardReporterClientSpec.ts index 4e43ab75a0..0a9b2608fb 100644 --- a/packages/stryker/test/unit/reporters/dashboard-reporter/DashboardReporterClientSpec.ts +++ b/packages/stryker/test/unit/reporters/dashboard-reporter/DashboardReporterClientSpec.ts @@ -2,8 +2,8 @@ import StrykerDashboardClient, { StrykerDashboardReport } from '../../../../src/ import { HttpClient } from 'typed-rest-client/HttpClient'; import { Mock, mock } from '../../../helpers/producers'; import { expect } from 'chai'; -import { Logger } from 'log4js'; -import currentLogMock from '../../../helpers/log4jsMock'; +import { Logger } from 'stryker-api/logging'; +import currentLogMock from '../../../helpers/logMock'; describe('DashboardReporterClient', () => { diff --git a/packages/stryker/test/unit/transpiler/MutantTranspilerSpec.ts b/packages/stryker/test/unit/transpiler/MutantTranspilerSpec.ts index fd764b592a..c0fd4186ad 100644 --- a/packages/stryker/test/unit/transpiler/MutantTranspilerSpec.ts +++ b/packages/stryker/test/unit/transpiler/MutantTranspilerSpec.ts @@ -1,6 +1,6 @@ import { expect } from 'chai'; import { toArray } from 'rxjs/operators'; -import { File } from 'stryker-api/core'; +import { File, LogLevel } from 'stryker-api/core'; import TranspiledMutant from '../../../src/TranspiledMutant'; import ChildProcessProxy from '../../../src/child-proxy/ChildProcessProxy'; import MutantTranspiler from '../../../src/transpiler/MutantTranspiler'; @@ -9,6 +9,12 @@ import TranspilerFacade, * as transpilerFacade from '../../../src/transpiler/Tra import { errorToString } from '../../../src/utils/objectUtils'; import '../../helpers/globals'; import { Mock, config, file, mock, testableMutant } from '../../helpers/producers'; +import LoggingClientContext from '../../../src/logging/LoggingClientContext'; + +const LOGGING_CONTEXT: LoggingClientContext = Object.freeze({ + port: 4200, + level: LogLevel.Fatal +}); describe('MutantTranspiler', () => { let sut: MutantTranspiler; @@ -32,21 +38,22 @@ describe('MutantTranspiler', () => { describe('with a transpiler', () => { it('should construct use the ChildProcessProxy to spawn a new MutantTranspiler in a separated process', () => { - const expectedConfig = config({ transpilers: ['transpiler'], plugins: ['plugin1'], logLevel: 'someLogLevel' }); - sut = new MutantTranspiler(expectedConfig); + const expectedConfig = config({ transpilers: ['transpiler'], plugins: ['plugin1'] }); + sut = new MutantTranspiler(expectedConfig, LOGGING_CONTEXT); expect(ChildProcessProxy.create).calledWith( require.resolve('../../../src/transpiler/TranspilerFacade'), - 'someLogLevel', + LOGGING_CONTEXT, ['plugin1'], TranspilerFacade, - { config: expectedConfig, produceSourceMaps: false }); + { config: expectedConfig, produceSourceMaps: false } + ); }); describe('initialize', () => { it('should transpile all files', () => { const expectedFiles = [file()]; - sut = new MutantTranspiler(config({ transpilers: ['transpiler'] })); + sut = new MutantTranspiler(config({ transpilers: ['transpiler'] }), LOGGING_CONTEXT); const actualResult = sut.initialize(expectedFiles); expect(transpilerFacadeMock.transpile).calledWith(expectedFiles); return expect(actualResult).eventually.eq(transpiledFilesOne); @@ -55,7 +62,7 @@ describe('MutantTranspiler', () => { describe('dispose', () => { it('should dispose the child process', () => { - sut = new MutantTranspiler(config({ transpilers: ['transpiler'] })); + sut = new MutantTranspiler(config({ transpilers: ['transpiler'] }), LOGGING_CONTEXT); sut.dispose(); expect(childProcessProxyMock.dispose).called; }); @@ -64,7 +71,7 @@ describe('MutantTranspiler', () => { describe('transpileMutants', () => { beforeEach(() => { - sut = new MutantTranspiler(config({ transpilers: ['transpiler'] })); + sut = new MutantTranspiler(config({ transpilers: ['transpiler'] }), LOGGING_CONTEXT); }); it('should transpile mutants', async () => { @@ -158,7 +165,7 @@ describe('MutantTranspiler', () => { describe('without a transpiler', () => { beforeEach(() => { - sut = new MutantTranspiler(config()); + sut = new MutantTranspiler(config(), LOGGING_CONTEXT); });