diff --git a/package.json b/package.json index 57ef57e9e..9ea28b921 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "lines": 50, "statements": 50, "functions": 50, - "branches": 50, + "branches": 45, "sourceMap": true, "include": [ "packages/*/src/*.ts", diff --git a/packages/testring-logger/interfaces.d.ts b/packages/testring-logger/interfaces.d.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/testring-logger/src/abstract-logger-client.ts b/packages/testring-logger/src/abstract-logger-client.ts index 3e170e2c4..0f3397ef1 100644 --- a/packages/testring-logger/src/abstract-logger-client.ts +++ b/packages/testring-logger/src/abstract-logger-client.ts @@ -1,4 +1,5 @@ import * as util from 'util'; +import { merge } from 'lodash'; import { transport } from '@testring/transport'; import { ITransport, ILogEntry, ILoggerClient, LoggerMessageTypes, LogTypes, LogLevel } from '@testring/types'; @@ -13,8 +14,8 @@ const formatLog = (logLevel: LogLevel, time: Date, content: Array): string export abstract class AbstractLoggerClient implements ILoggerClient { constructor( protected transportInstance: ITransport = transport, - protected logNesting: number = 0, - protected logLevel: LogLevel = LogLevel.info + protected logLevel: LogLevel = LogLevel.info, + protected logEnvironment?: any, ) { } @@ -35,7 +36,8 @@ export abstract class AbstractLoggerClient implements ILoggerClient { protected buildEntry( type: LogTypes, content: Array, - logLevel: LogLevel = this.logLevel + logLevel: LogLevel = this.logLevel, + logEnvironment: any = this.logEnvironment, ): ILogEntry { const time = new Date(); const formattedMessage = formatLog(logLevel, time, content); @@ -58,15 +60,17 @@ export abstract class AbstractLoggerClient implements ILoggerClient { formattedMessage, stepUid, parentStep, + logEnvironment, }; } protected createLog( type: LogTypes, content: Array, - logLevel: LogLevel = this.logLevel + logLevel: LogLevel = this.logLevel, + logEnvironment: any = this.logEnvironment, ): void { - const logEntry = this.buildEntry(type, content, logLevel); + const logEntry = this.buildEntry(type, content, logLevel, logEnvironment); if (this.getCurrentStep()) { this.logBatch.push(logEntry); @@ -107,6 +111,21 @@ export abstract class AbstractLoggerClient implements ILoggerClient { this.createLog(LogTypes.debug, args, LogLevel.debug); } + public media(filename: string, content: Buffer): void { + this.createLog(LogTypes.media, [ filename, content ], LogLevel.info); + } + + public withLogEnvironment(logEnvironment: any) { + return { + log: (...args) => this.createLog(LogTypes.log, args, LogLevel.info, logEnvironment), + info: (...args) => this.createLog(LogTypes.info, args, LogLevel.info, logEnvironment), + warn: (...args) => this.createLog(LogTypes.warning, args, LogLevel.warning, logEnvironment), + error: (...args) => this.createLog(LogTypes.error, args, LogLevel.error, logEnvironment), + debug: (...args) => this.createLog(LogTypes.debug, args, LogLevel.debug, logEnvironment), + media: (...args) => this.createLog(LogTypes.media, args, LogLevel.info, logEnvironment), + }; + } + public startStep(message: string): void { const step = nanoid(); @@ -137,4 +156,8 @@ export abstract class AbstractLoggerClient implements ILoggerClient { this.endStep(); } + + public setLogEnvironment(logEnvironment: object): void { + this.logEnvironment = merge(this.logEnvironment, logEnvironment); + } } diff --git a/packages/testring-logger/test/logger-client.spec.ts b/packages/testring-logger/test/logger-client.spec.ts index d6e4d8ede..722924444 100644 --- a/packages/testring-logger/test/logger-client.spec.ts +++ b/packages/testring-logger/test/logger-client.spec.ts @@ -4,7 +4,7 @@ import * as chai from 'chai'; import * as sinon from 'sinon'; import { TransportMock } from '@testring/test-utils'; -import { LoggerMessageTypes, LogTypes } from '@testring/types'; +import { LoggerMessageTypes, LogTypes, LogLevel } from '@testring/types'; import { LoggerClient } from '../src/logger-client'; import { report } from './fixtures/constants'; @@ -252,4 +252,83 @@ describe('Logger client', () => { callback(); }, 0); }); + + it('should allow to set logEnvironment which will be sent with every log', () => { + const logEnvironment = { + foo: 'bar', + }; + const transport = new TransportMock(); + const loggerClient = new LoggerClient( + transport, + LogLevel.verbose, + logEnvironment, + ); + + transport.on( + LoggerMessageTypes.REPORT, + (message) => { + chai.expect(message).to.have.deep.property('logEnvironment', logEnvironment); + } + ); + + loggerClient.log('foobar'); + }); + + + it('should allow to modify logEnvironment', () => { + const logEnvironment = { + foo: 'bar', + }; + const logEnvironmentMod = { + baz: 'qux', + }; + const transport = new TransportMock(); + const loggerClient = new LoggerClient( + transport, + LogLevel.verbose, + logEnvironment, + ); + + transport.on( + LoggerMessageTypes.REPORT, + (message) => { + chai.expect(message) + .to.have.deep.property('logEnvironment', { ...logEnvironment, ...logEnvironmentMod }); + } + ); + + loggerClient.setLogEnvironment(logEnvironmentMod); + loggerClient.log('foobar'); + }); + + it('should should allow to log message with log environment without affecting instance logEnvironment', () => { + const logEnvironment = { + foo: 'bar', + }; + const logEnvironmentMod = { + baz: 'qux', + }; + const transport = new TransportMock(); + const loggerClient = new LoggerClient( + transport, + LogLevel.verbose, + logEnvironment, + ); + + transport.on( + LoggerMessageTypes.REPORT, + (message) => { + if (message.type === LogTypes.debug) { + chai.expect(message) + .to.have.deep.property('logEnvironment', logEnvironmentMod); + } else { + chai.expect(message) + .to.have.deep.property('logEnvironment', logEnvironment); + } + } + ); + + loggerClient.withLogEnvironment(logEnvironmentMod).debug('modified'); + loggerClient.log('unmodified'); + }); }); diff --git a/packages/testring-plugin-api/src/modules/test-run-controller.ts b/packages/testring-plugin-api/src/modules/test-run-controller.ts index 3f7193438..d4365e0db 100644 --- a/packages/testring-plugin-api/src/modules/test-run-controller.ts +++ b/packages/testring-plugin-api/src/modules/test-run-controller.ts @@ -3,7 +3,19 @@ import { AbstractAPI } from './abstract'; export class TestRunControllerAPI extends AbstractAPI { - beforeRun(handler: (queue: Array) => Array) { - this.registrySyncPlugin(TestRunControllerHooks.beforeRun, handler); + beforeRun(handler: (queue: Array) => Promise) { + this.registryAsyncPlugin(TestRunControllerHooks.beforeRun, handler); + } + + beforeTest(handler: (test: IQueuedTest) => Promise) { + this.registryAsyncPlugin(TestRunControllerHooks.beforeTest, handler); + } + + afterTest(handler: (params: IQueuedTest) => Promise) { + this.registryAsyncPlugin(TestRunControllerHooks.afterTest, handler); + } + + afterRun(handler: (queue: Array) => Promise) { + this.registryAsyncPlugin(TestRunControllerHooks.afterRun, handler); } } diff --git a/packages/testring-test-run-controller/src/test-run-controller.ts b/packages/testring-test-run-controller/src/test-run-controller.ts index fd9bbb4a6..044c144ae 100644 --- a/packages/testring-test-run-controller/src/test-run-controller.ts +++ b/packages/testring-test-run-controller/src/test-run-controller.ts @@ -24,13 +24,18 @@ export class TestRunController extends PluggableModule implements ITestRunContro ) { super([ TestRunControllerHooks.beforeRun, + TestRunControllerHooks.beforeTest, + TestRunControllerHooks.afterTest, + TestRunControllerHooks.afterRun, ]); } public async runQueue(testSet: Array): Promise { const testQueue = this.prepareTests(testSet); const testQueueAfterHook = await this.callHook(TestRunControllerHooks.beforeRun, testQueue); + loggerClientLocal.debug('Run controller: tests queue created.'); + const configWorkerLimit = this.config.workerLimit || 0; const workerLimit = configWorkerLimit < testQueueAfterHook.length ? @@ -38,12 +43,15 @@ export class TestRunController extends PluggableModule implements ITestRunContro testQueueAfterHook.length; const workers = this.createWorkers(workerLimit); + loggerClientLocal.debug(`Run controller: ${workerLimit} worker(s) created.`); try { await Promise.all( workers.map(worker => this.executeWorker(worker, testQueueAfterHook)) ); + + await this.callHook(TestRunControllerHooks.afterRun, testQueue); } catch (e) { loggerClientLocal.error(...this.errors); throw e; @@ -66,11 +74,11 @@ export class TestRunController extends PluggableModule implements ITestRunContro private prepareTests(testFiles: Array): Array { const testQueue = new Array(testFiles.length); - const retryCount = this.config.retryCount || 0; for (let index = 0; index < testFiles.length; index++) { testQueue[index] = { - retryCount: retryCount, + retryCount: 0, + retryErrors: [], test: testFiles[index] }; } @@ -97,8 +105,8 @@ export class TestRunController extends PluggableModule implements ITestRunContro throw exception.error; } - if (test.retryCount > 0) { - test.retryCount--; + if (test.retryCount < (this.config.retryCount || 0)) { + test.retryCount++; await delay(this.config.retryDelay || 0); @@ -108,6 +116,8 @@ export class TestRunController extends PluggableModule implements ITestRunContro } else { this.errors.push(exception.error); + await this.callHook(TestRunControllerHooks.afterTest, test); + await this.occupyWorker(worker, queue); } } @@ -120,8 +130,12 @@ export class TestRunController extends PluggableModule implements ITestRunContro } try { + await this.callHook(TestRunControllerHooks.beforeTest, queuedTest); await worker.execute(queuedTest.test.content, queuedTest.test.path, queuedTest.test.meta); + await this.callHook(TestRunControllerHooks.afterTest, queuedTest); } catch (error) { + queuedTest.retryErrors.push(error); + await this.onTestFailed(error, worker, queuedTest, queue); } finally { await this.occupyWorker(worker, queue); diff --git a/packages/testring-types/src/logger/index.ts b/packages/testring-types/src/logger/index.ts index 3a824cb17..74364f48b 100644 --- a/packages/testring-types/src/logger/index.ts +++ b/packages/testring-types/src/logger/index.ts @@ -5,6 +5,7 @@ export const enum LogTypes { error = 'error', debug = 'debug', step = 'step', + media = 'media', } export const enum LogLevel { @@ -40,6 +41,7 @@ export interface ILogEntry { formattedMessage: string, stepUid?: string, parentStep: string | null, + logEnvironment?: any, } export interface ILoggerServer { diff --git a/packages/testring-types/src/test-run-controller/index.ts b/packages/testring-types/src/test-run-controller/index.ts index 9f79b9fce..212f2e3cf 100644 --- a/packages/testring-types/src/test-run-controller/index.ts +++ b/packages/testring-types/src/test-run-controller/index.ts @@ -2,10 +2,14 @@ import { ITestFile } from '../test-finder'; export const enum TestRunControllerHooks { beforeRun = 'beforeRun', + beforeTest = 'beforeTest', + afterTest = 'afterTest', + afterRun = 'afterRun', } export interface IQueuedTest { retryCount: number, + retryErrors: Array, test: ITestFile }