diff --git a/package-lock.json b/package-lock.json index f4d6b19..266c52f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "jstestadapter", - "version": "1.1.0", + "version": "1.2.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 6820318..8016cfa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jstestadapter", - "version": "1.1.0", + "version": "1.2.0", "description": "", "bundledDependencies": [ "rewire", @@ -26,7 +26,8 @@ "lint:test": "tslint --project ./test/JSTest.Runner.UnitTests/tsconfig.json", "build:Debug": "./node_modules/.bin/tsc -p ./src/JSTest.Runner/tsconfig.json", "build:Release": "./node_modules/.bin/tsc -p ./src/JSTest.Runner/tsconfig.json --outDir ./src/JSTest.Runner/bin/Release", - "build:test": "./node_modules/.bin/tsc -p ./test/JSTest.Runner.UnitTests/tsconfig.json", + "tsc:test": "node_modules/.bin/tsc -p ./test/JSTest.Runner.UnitTests/tsconfig.json", + "build:test": "npm run lint:test && npm run tsc:test", "build": "npm run build:Debug", "test": "npm run build:test && node ./node_modules/mocha/bin/mocha -r source-map-support/register ./test/JSTest.Runner.UnitTests/bin/test/**/*Tests.js", "test:debug": "npm run build:test && node --inspect-brk=9229 ./node_modules/mocha/bin/_mocha -r source-map-support/register ./test/JSTest.Runner.UnitTests/bin/test/**/*Tests.js" diff --git a/src/JSTest.Runner/Constants.ts b/src/JSTest.Runner/Constants.ts index 8ac72c0..cfa638c 100644 --- a/src/JSTest.Runner/Constants.ts +++ b/src/JSTest.Runner/Constants.ts @@ -1,4 +1,5 @@ +// tslint:disable:variable-name export namespace Constants { - export const executorURI: string = 'executor://JSTestAdapter/v1'; - export const messageProtocolVersion: number = 1; + export const ExecutorURI: string = 'executor://JSTestAdapter/v1'; + export const MessageProtocolVersion: number = 1; } \ No newline at end of file diff --git a/src/JSTest.Runner/Environment/Browser/Environment.ts b/src/JSTest.Runner/Environment/Browser/Environment.ts index 253ec28..2dac38a 100644 --- a/src/JSTest.Runner/Environment/Browser/Environment.ts +++ b/src/JSTest.Runner/Environment/Browser/Environment.ts @@ -32,12 +32,4 @@ export class Environment implements IEnvironment { public exit(exitCode: number) { throw new Exception('Not implemented', ExceptionType.NotImplementedException); } - - public setupGlobalLogger() { - throw new Exception('Not implemented', ExceptionType.NotImplementedException); - } - - public reinitializeConsoleLogger() { - throw new Exception('Not implemented', ExceptionType.NotImplementedException); - } } \ No newline at end of file diff --git a/src/JSTest.Runner/Environment/Node/EventDispatcher.ts b/src/JSTest.Runner/Environment/Node/EventDispatcher.ts index ab95792..2ffc02c 100644 --- a/src/JSTest.Runner/Environment/Node/EventDispatcher.ts +++ b/src/JSTest.Runner/Environment/Node/EventDispatcher.ts @@ -13,6 +13,7 @@ export class EventDispatcher extends BaseEventDispatcher { constructor() { super(); this.events = new EventEmitter(); + this.events.setMaxListeners(20); } public subscribe(eventId: string, callback: IEventHandler): void { diff --git a/src/JSTest.Runner/ObjectModel/Attachment.ts b/src/JSTest.Runner/ObjectModel/Attachment.ts index 7886be8..726e9e6 100644 --- a/src/JSTest.Runner/ObjectModel/Attachment.ts +++ b/src/JSTest.Runner/ObjectModel/Attachment.ts @@ -4,8 +4,8 @@ export class Attachment implements ISerializable { private uri: string; private description: string; - constructor(uri: string, description: string) { - this.uri = uri; + constructor(url: string, description: string) { + this.uri = this.getFileUrl(url); this.description = description; } @@ -15,4 +15,15 @@ export class Attachment implements ISerializable { Description: this.description }; } + + private getFileUrl(url: string): string { + let pathName = url.replace(/\\/g, '/'); + + // Windows drive letter must be prefixed with a slash + if (pathName[0] !== '/') { + pathName = '/' + pathName; + } + + return encodeURI('file://' + pathName); + } } \ No newline at end of file diff --git a/src/JSTest.Runner/ObjectModel/JSTestSettings.ts b/src/JSTest.Runner/ObjectModel/JSTestSettings.ts index 67dffb0..93bcec0 100644 --- a/src/JSTest.Runner/ObjectModel/JSTestSettings.ts +++ b/src/JSTest.Runner/ObjectModel/JSTestSettings.ts @@ -1,14 +1,15 @@ import * as fs from 'fs'; import * as path from 'path'; -import { TestFrameworks } from './TestFramework'; import { IEnvironment } from '../Environment/IEnvironment'; import { Exception, ExceptionType } from '../Exceptions'; +import { TestFrameworks } from './TestFramework'; export class JSTestSettings { // tslint:disable:variable-name - public JavaScriptTestFramework: TestFrameworks; - public TestFrameworkConfigJson: JSON; - public AttachmentsFolder: string; + public readonly JavaScriptTestFramework: TestFrameworks; + public readonly TestFrameworkConfigJson: JSON; + public readonly AttachmentsFolder: string; + public readonly CoverageEnabled: boolean; constructor(json: any, environment: IEnvironment) { this.JavaScriptTestFramework = -1; @@ -25,19 +26,28 @@ export class JSTestSettings { ExceptionType.UnSupportedTestFramework); } - if (json.TestFrameworkConfigJson !== null && json.TestFrameworkConfigJson !== '') { + if (json.TestFrameworkConfigJson && json.TestFrameworkConfigJson !== null && json.TestFrameworkConfigJson !== '') { try { this.TestFrameworkConfigJson = JSON.parse(json.TestFrameworkConfigJson); } catch (e) { throw new Exception('Invalid TestFrameworkConfigJson: ' + e.message, ExceptionType.InvalidJSONException); } } else { - this.TestFrameworkConfigJson = JSON.parse('{}'); + this.TestFrameworkConfigJson = {}; } - let attachmentsFolder = json.AttachmentsFolder; + this.CoverageEnabled = json.CodeCoverageEnabled === true; + + // Set attachments folder and environment variable + // TODO: we should probably get the env key from jstestcontext package + /* tslint:disable:no-string-literal max-line-length */ + process.env['JSTEST_RESULTS_DIRECTORY'] = this.AttachmentsFolder = this.createAttachmentsFolder(json.AttachmentsFolder, environment.getTempDirectory()); + } + + private createAttachmentsFolder(baseAttachmentsFolder: string, tempDirectory: string): string { + let attachmentsFolder = baseAttachmentsFolder; if (!attachmentsFolder) { - attachmentsFolder = environment.getTempDirectory(); + attachmentsFolder = tempDirectory; } if (!fs.existsSync(attachmentsFolder)) { @@ -67,12 +77,6 @@ export class JSTestSettings { fs.mkdirSync(attachmentsFolder); } - // Set attachments folder - this.AttachmentsFolder = attachmentsFolder; - - // Set environment variable - // TODO: we should probably get the env key from jstestcontext package - /* tslint:disable:no-string-literal */ - process.env['JSTEST_RESULTS_DIRECTORY'] = attachmentsFolder; + return attachmentsFolder; } } \ No newline at end of file diff --git a/src/JSTest.Runner/ObjectModel/Message.ts b/src/JSTest.Runner/ObjectModel/Message.ts index d1e2ed5..9de0bdd 100644 --- a/src/JSTest.Runner/ObjectModel/Message.ts +++ b/src/JSTest.Runner/ObjectModel/Message.ts @@ -1,5 +1,6 @@ import { MessageType } from '.'; import { Exception, ExceptionType} from '../Exceptions'; +import { Constants } from '../Constants'; export class Message { /* These variables serialize to a JSON that will be deserialized @@ -13,6 +14,8 @@ export class Message { constructor(messageType: MessageType, payload: any, version?: number) { if (version) { this.Version = version; + } else { + this.Version = Constants.MessageProtocolVersion; } this.MessageType = messageType; diff --git a/src/JSTest.Runner/ObjectModel/MessageType.ts b/src/JSTest.Runner/ObjectModel/MessageType.ts index 01dc730..d32c741 100644 --- a/src/JSTest.Runner/ObjectModel/MessageType.ts +++ b/src/JSTest.Runner/ObjectModel/MessageType.ts @@ -11,5 +11,6 @@ export enum MessageType { ConsoleMessage = 'JSTest.ConsoleMessage', StartTestExecutionWithSources = 'JSTest.StartWithSources', StartTestExecutionWithTests = 'JSTest.StartWithTests', - StartDiscovery = 'JSTest.StartDiscovery' + StartDiscovery = 'JSTest.StartDiscovery', + RunAttachments = 'JSTest.RunAttachments' } \ No newline at end of file diff --git a/src/JSTest.Runner/ObjectModel/Payloads/TestRunAttachmentsPayload.ts b/src/JSTest.Runner/ObjectModel/Payloads/TestRunAttachmentsPayload.ts new file mode 100644 index 0000000..84e1881 --- /dev/null +++ b/src/JSTest.Runner/ObjectModel/Payloads/TestRunAttachmentsPayload.ts @@ -0,0 +1,5 @@ +import { AttachmentSet } from '../AttachmentSet'; + +export interface TestRunAttachmentsPayload { + Attachments: Array; +} \ No newline at end of file diff --git a/src/JSTest.Runner/ObjectModel/Payloads/index.ts b/src/JSTest.Runner/ObjectModel/Payloads/index.ts index e851f96..487cdd3 100644 --- a/src/JSTest.Runner/ObjectModel/Payloads/index.ts +++ b/src/JSTest.Runner/ObjectModel/Payloads/index.ts @@ -1,3 +1,4 @@ export { TestMessagePayload } from './TestMessagePayload'; +export { TestRunAttachmentsPayload } from './TestRunAttachmentsPayload'; export { TestCaseEndEventArgs, TestCaseFoundEventArgs, TestCaseStartEventArgs } from './TestRunEventArgs'; -export { StartDiscoveryPayload, StartExecutionWithSourcesPayload, StartExecutionWithTestsPayload } from './TestRunRequestPayload'; \ No newline at end of file +export { StartDiscoveryPayload, StartExecutionWithSourcesPayload, StartExecutionWithTestsPayload } from './TestRunRequestPayload'; diff --git a/src/JSTest.Runner/ObjectModel/TestFramework/ITestFramework.ts b/src/JSTest.Runner/ObjectModel/TestFramework/ITestFramework.ts index 9b1a5a0..3974125 100644 --- a/src/JSTest.Runner/ObjectModel/TestFramework/ITestFramework.ts +++ b/src/JSTest.Runner/ObjectModel/TestFramework/ITestFramework.ts @@ -1,14 +1,15 @@ -import { EnvironmentType } from '../Common/EnvironmentType'; -import { ITestFrameworkEvents } from './ITestFrameworkEvents'; import { TestCase } from '../Common'; +import { EnvironmentType } from '../Common/EnvironmentType'; +import { ITestFrameworkEvents, TestFrameworkOptions } from './'; export interface ITestFramework { readonly environmentType: EnvironmentType; readonly testFrameworkEvents: ITestFrameworkEvents; readonly canHandleMultipleSources: boolean; readonly supportsJsonOptions: boolean; + readonly supportsCodeCoverage: boolean; - initialize(): void; + initialize(options: TestFrameworkOptions): void; startExecutionWithSources(sources: Array, options: JSON): void; startExecutionWithTests(sources: Array, tests: Map, options: JSON): void; startDiscovery(sources: Array): void; diff --git a/src/JSTest.Runner/ObjectModel/TestFramework/ITestFrameworkEvents.ts b/src/JSTest.Runner/ObjectModel/TestFramework/ITestFrameworkEvents.ts index 7b6064c..f9349bb 100644 --- a/src/JSTest.Runner/ObjectModel/TestFramework/ITestFrameworkEvents.ts +++ b/src/JSTest.Runner/ObjectModel/TestFramework/ITestFrameworkEvents.ts @@ -1,4 +1,4 @@ -import { TestSpecEventArgs, TestSuiteEventArgs, TestSessionEventArgs, TestErrorMessageEventArgs } from '.'; +import { TestSpecEventArgs, TestSuiteEventArgs, TestSessionEventArgs, TestErrorMessageEventArgs, TestRunAttachmentEventArgs} from '.'; import { IEvent } from '../Common'; export interface ITestFrameworkEvents { @@ -9,4 +9,5 @@ export interface ITestFrameworkEvents { onTestSessionStart: IEvent; onTestSessionEnd: IEvent; onErrorMessage: IEvent; + onRunAttachment: IEvent; } \ No newline at end of file diff --git a/src/JSTest.Runner/ObjectModel/TestFramework/TestFrameworkCriteria.ts b/src/JSTest.Runner/ObjectModel/TestFramework/TestFrameworkCriteria.ts new file mode 100644 index 0000000..0303879 --- /dev/null +++ b/src/JSTest.Runner/ObjectModel/TestFramework/TestFrameworkCriteria.ts @@ -0,0 +1,4 @@ +export interface TestFrameworkCriteria { + RunAttachmentsDirectory: string; + CollectCoverage: boolean; +} \ No newline at end of file diff --git a/src/JSTest.Runner/ObjectModel/TestFramework/TestFrameworkEventArgs.ts b/src/JSTest.Runner/ObjectModel/TestFramework/TestFrameworkEventArgs.ts index 68315b0..a9e0790 100644 --- a/src/JSTest.Runner/ObjectModel/TestFramework/TestFrameworkEventArgs.ts +++ b/src/JSTest.Runner/ObjectModel/TestFramework/TestFrameworkEventArgs.ts @@ -1,5 +1,6 @@ import { TestCase, TestOutcome, IEventArgs } from '../Common'; import { FailedExpectation } from '.'; +import { AttachmentSet } from '..'; interface BaseTestEventArgs extends IEventArgs { StartTime: Date; @@ -38,4 +39,8 @@ export class TestSessionEventArgs implements BaseTestEventArgs { export interface TestErrorMessageEventArgs { Message: string; +} + +export interface TestRunAttachmentEventArgs { + AttachmentCollection: Array; } \ No newline at end of file diff --git a/src/JSTest.Runner/ObjectModel/TestFramework/index.ts b/src/JSTest.Runner/ObjectModel/TestFramework/index.ts index aee51c3..5e8679c 100644 --- a/src/JSTest.Runner/ObjectModel/TestFramework/index.ts +++ b/src/JSTest.Runner/ObjectModel/TestFramework/index.ts @@ -1,5 +1,7 @@ -export { ITestFramework } from './ITestFramework'; -export { TestSpecEventArgs, TestSessionEventArgs, TestSuiteEventArgs, TestErrorMessageEventArgs } from './TestFrameworkEventArgs'; export { FailedExpectation } from './FailedExpectation'; +export { ITestFramework } from './ITestFramework'; export { ITestFrameworkEvents } from './ITestFrameworkEvents'; -export { TestFrameworks } from './TestFrameworks'; \ No newline at end of file +export { TestErrorMessageEventArgs, TestSessionEventArgs, TestSpecEventArgs, + TestSuiteEventArgs, TestRunAttachmentEventArgs } from './TestFrameworkEventArgs'; +export { TestFrameworkCriteria as TestFrameworkOptions } from './TestFrameworkCriteria'; +export { TestFrameworks } from './TestFrameworks'; diff --git a/src/JSTest.Runner/TestRunner/ExecutionManagers/BaseExecutionManager.ts b/src/JSTest.Runner/TestRunner/ExecutionManagers/BaseExecutionManager.ts index ca706a8..623ecdd 100644 --- a/src/JSTest.Runner/TestRunner/ExecutionManagers/BaseExecutionManager.ts +++ b/src/JSTest.Runner/TestRunner/ExecutionManagers/BaseExecutionManager.ts @@ -1,12 +1,13 @@ import { IEnvironment } from '../../Environment/IEnvironment'; -import { MessageSender } from '../MessageSender'; -import { TestFrameworkFactory } from '../TestFrameworks/TestFrameworkFactory'; +import { Exception, ExceptionType } from '../../Exceptions'; +import { JSTestSettings } from '../../ObjectModel'; import { IEvent, IEventArgs } from '../../ObjectModel/Common'; +import { EqtTrace } from '../../ObjectModel/EqtTrace'; +import { ITestFramework, TestFrameworks } from '../../ObjectModel/TestFramework'; +import { MessageSender } from '../MessageSender'; import { TestFrameworkEventHandlers } from '../TestFrameworks/TestFrameworkEventHandlers'; +import { TestFrameworkFactory } from '../TestFrameworks/TestFrameworkFactory'; import { TestSessionManager } from './TestSessionManager'; -import { TestFrameworks, ITestFramework } from '../../ObjectModel/TestFramework'; -import { Exception, ExceptionType } from '../../Exceptions'; -import { EqtTrace } from '../../ObjectModel/EqtTrace'; export abstract class BaseExecutionManager { protected readonly environment: IEnvironment; @@ -14,7 +15,10 @@ export abstract class BaseExecutionManager { protected readonly testFrameworkFactory: TestFrameworkFactory; protected readonly onComplete: IEvent; protected readonly testSessionManager: TestSessionManager; - + + protected readonly abstract jsTestSettings: JSTestSettings; + protected readonly abstract testFramework: TestFrameworks; + protected abstract testFrameworkEventHandlers: TestFrameworkEventHandlers; constructor(environment: IEnvironment, messageSender: MessageSender, testFramework: TestFrameworks) { @@ -47,7 +51,10 @@ export abstract class BaseExecutionManager { protected createTestFramework(framework: TestFrameworks): ITestFramework { const testFrameworkInstance = this.testFrameworkFactory.createTestFramework(framework); try { - testFrameworkInstance.initialize(); + testFrameworkInstance.initialize({ + RunAttachmentsDirectory: this.jsTestSettings.AttachmentsFolder, + CollectCoverage: this.jsTestSettings.CoverageEnabled + }); } catch (e) { EqtTrace.error(`BaseExecutionManager: error initializing test framework`, e); throw new Exception('Error initializing test framework: ' + e.message, ExceptionType.TestFrameworkError); diff --git a/src/JSTest.Runner/TestRunner/ExecutionManagers/DiscoveryManager.ts b/src/JSTest.Runner/TestRunner/ExecutionManagers/DiscoveryManager.ts index ff75b8e..ffb21ba 100644 --- a/src/JSTest.Runner/TestRunner/ExecutionManagers/DiscoveryManager.ts +++ b/src/JSTest.Runner/TestRunner/ExecutionManagers/DiscoveryManager.ts @@ -1,14 +1,14 @@ -import { ITestFramework, TestSessionEventArgs, TestSpecEventArgs, TestFrameworks, - TestErrorMessageEventArgs } from '../../ObjectModel/TestFramework'; import { IEnvironment } from '../../Environment/IEnvironment'; +import { JSTestSettings, TestMessageLevel } from '../../ObjectModel'; +import { ITestFramework, TestErrorMessageEventArgs, TestFrameworks, TestSessionEventArgs, + TestSpecEventArgs } from '../../ObjectModel/TestFramework'; import { MessageSender } from '../MessageSender'; import { TestFrameworkEventHandlers } from '../TestFrameworks/TestFrameworkEventHandlers'; import { BaseExecutionManager } from './BaseExecutionManager'; -import { TestMessageLevel, JSTestSettings } from '../../ObjectModel'; export class DiscoveryManager extends BaseExecutionManager { - private jsTestSettings: JSTestSettings; - private testFramework: TestFrameworks; + protected readonly jsTestSettings: JSTestSettings; + protected readonly testFramework: TestFrameworks; constructor(environment: IEnvironment, messageSender: MessageSender, jsTestSettings: JSTestSettings) { super(environment, messageSender, jsTestSettings.JavaScriptTestFramework); diff --git a/src/JSTest.Runner/TestRunner/ExecutionManagers/ExecutionManager.ts b/src/JSTest.Runner/TestRunner/ExecutionManagers/ExecutionManager.ts index 9fd1e8d..2af97ac 100644 --- a/src/JSTest.Runner/TestRunner/ExecutionManagers/ExecutionManager.ts +++ b/src/JSTest.Runner/TestRunner/ExecutionManagers/ExecutionManager.ts @@ -1,19 +1,22 @@ -import * as path from 'path'; import * as fs from 'fs'; -import { ITestFramework, TestSessionEventArgs, TestSpecEventArgs, TestFrameworks, - TestErrorMessageEventArgs } from '../../ObjectModel/TestFramework'; -import { TestMessageLevel, TestResult, JSTestSettings, AttachmentSet } from '../../ObjectModel'; +import * as path from 'path'; +import { IEnvironment } from '../../Environment/IEnvironment'; +import { AttachmentSet, JSTestSettings, TestMessageLevel, TestResult } from '../../ObjectModel'; import { TestCase } from '../../ObjectModel/Common'; import { EqtTrace } from '../../ObjectModel/EqtTrace'; -import { IEnvironment } from '../../Environment/IEnvironment'; +import { + ITestFramework, TestErrorMessageEventArgs, TestFrameworks, + TestSessionEventArgs, TestSpecEventArgs, TestRunAttachmentEventArgs +} from '../../ObjectModel/TestFramework'; import { TimeSpan } from '../../Utils/TimeUtils'; import { MessageSender } from '../MessageSender'; -import { BaseExecutionManager } from './BaseExecutionManager'; import { TestFrameworkEventHandlers } from '../TestFrameworks/TestFrameworkEventHandlers'; +import { BaseExecutionManager } from './BaseExecutionManager'; export class ExecutionManager extends BaseExecutionManager { - private jsTestSettings: JSTestSettings; - private testFramework: TestFrameworks; + protected readonly jsTestSettings: JSTestSettings; + protected readonly testFramework: TestFrameworks; + private testCollection: Map; constructor(environment: IEnvironment, messageSender: MessageSender, jsTestSettings: JSTestSettings) { @@ -63,6 +66,7 @@ export class ExecutionManager extends BaseExecutionManager { framework.testFrameworkEvents.onTestCaseStart.subscribe(this.testFrameworkEventHandlers.TestCaseStart); framework.testFrameworkEvents.onTestCaseEnd.subscribe(this.testFrameworkEventHandlers.TestCaseEnd); framework.testFrameworkEvents.onErrorMessage.subscribe(this.testFrameworkEventHandlers.TestErrorMessage); + framework.testFrameworkEvents.onRunAttachment.subscribe(this.testFrameworkEventHandlers.TestRunAttachment); }, TestSessionStart: (sender: object, args: TestSessionEventArgs) => { @@ -103,6 +107,10 @@ export class ExecutionManager extends BaseExecutionManager { TestErrorMessage: (sender: object, args: TestErrorMessageEventArgs) => { this.messageSender.sendMessage(args.Message, TestMessageLevel.Error); + }, + + TestRunAttachment: (sender: object, args: TestRunAttachmentEventArgs) => { + this.messageSender.sendRunAttachments(args.AttachmentCollection); } }; @@ -111,7 +119,7 @@ export class ExecutionManager extends BaseExecutionManager { this.messageSender.sendMessage(err.stack ? err.stack : (err.constructor.name + ': ' + err.message), - TestMessageLevel.Error); + TestMessageLevel.Error); } } @@ -124,9 +132,9 @@ export class ExecutionManager extends BaseExecutionManager { framework.startExecutionWithSources(sources, this.jsTestSettings.TestFrameworkConfigJson); } }, - (e) => { - this.sessionError(sources, e); - }); + (e) => { + this.sessionError(sources, e); + }); } private executionComplete = () => { diff --git a/src/JSTest.Runner/TestRunner/MessageSender.ts b/src/JSTest.Runner/TestRunner/MessageSender.ts index b776a79..f12d7c4 100644 --- a/src/JSTest.Runner/TestRunner/MessageSender.ts +++ b/src/JSTest.Runner/TestRunner/MessageSender.ts @@ -1,8 +1,9 @@ +import { Constants } from '../Constants'; import { ICommunicationManager } from '../Environment/ICommunicationManager'; -import { TestMessageLevel, Message, MessageType, TestResult } from '../ObjectModel'; +import { AttachmentSet, Message, MessageType, TestMessageLevel, TestResult } from '../ObjectModel'; import { TestCase } from '../ObjectModel/Common'; -import { TestMessagePayload, TestCaseEndEventArgs, TestCaseFoundEventArgs, TestCaseStartEventArgs } from '../ObjectModel/Payloads'; -import { Constants } from '../Constants'; +import { TestCaseEndEventArgs, TestCaseFoundEventArgs, TestCaseStartEventArgs, + TestMessagePayload, TestRunAttachmentsPayload } from '../ObjectModel/Payloads'; export class MessageSender { private readonly commManager: ICommunicationManager; @@ -12,7 +13,7 @@ export class MessageSender { } public sendVersionCheck() { - const versionCheckMessage = new Message(MessageType.VersionCheck, Constants.messageProtocolVersion); + const versionCheckMessage = new Message(MessageType.VersionCheck, Constants.MessageProtocolVersion); this.commManager.sendMessage(versionCheckMessage); } @@ -22,7 +23,7 @@ export class MessageSender { Message: message }; - this.commManager.sendMessage(new Message(MessageType.TestMessage, testMessagePayload, Constants.messageProtocolVersion)); + this.commManager.sendMessage(new Message(MessageType.TestMessage, testMessagePayload)); } public sendTestCaseFound(testCase: TestCase) { @@ -30,7 +31,7 @@ export class MessageSender { TestCase: testCase }; - this.commManager.sendMessage(new Message(MessageType.TestCaseFound, testFoundPayload, Constants.messageProtocolVersion)); + this.commManager.sendMessage(new Message(MessageType.TestCaseFound, testFoundPayload)); } public sendTestCaseStart(testCase: TestCase) { @@ -38,7 +39,7 @@ export class MessageSender { TestCase: testCase }; - this.commManager.sendMessage(new Message(MessageType.TestCaseStart, testStartPayload, Constants.messageProtocolVersion)); + this.commManager.sendMessage(new Message(MessageType.TestCaseStart, testStartPayload)); } public sendTestCaseEnd(testResult: TestResult) { @@ -46,16 +47,21 @@ export class MessageSender { TestResult: testResult }; - this.commManager.sendMessage(new Message(MessageType.TestCaseEnd, testEndPayload, Constants.messageProtocolVersion)); + this.commManager.sendMessage(new Message(MessageType.TestCaseEnd, testEndPayload)); } public sendExecutionComplete() { - this.commManager.sendMessage(new Message(MessageType.ExecutionComplete, null, Constants.messageProtocolVersion)); + this.commManager.sendMessage(new Message(MessageType.ExecutionComplete, null)); } public sendDiscoveryComplete() { - const discoverCompleteMessage = new Message(MessageType.DiscoveryComplete, null, Constants.messageProtocolVersion); + const discoverCompleteMessage = new Message(MessageType.DiscoveryComplete, null); this.commManager.sendMessage(discoverCompleteMessage); } + public sendRunAttachments(attachmentCollection: Array) { + this.commManager.sendMessage(new Message(MessageType.RunAttachments, { + Attachments: attachmentCollection + })); + } } \ No newline at end of file diff --git a/src/JSTest.Runner/TestRunner/TestFrameworks/BaseTestFramework.ts b/src/JSTest.Runner/TestRunner/TestFrameworks/BaseTestFramework.ts index 1787ffd..017c009 100644 --- a/src/JSTest.Runner/TestRunner/TestFrameworks/BaseTestFramework.ts +++ b/src/JSTest.Runner/TestRunner/TestFrameworks/BaseTestFramework.ts @@ -1,14 +1,18 @@ -import { ITestFramework, TestSessionEventArgs, TestSuiteEventArgs, TestSpecEventArgs, - FailedExpectation, ITestFrameworkEvents, TestErrorMessageEventArgs } from '../../ObjectModel/TestFramework'; -import { TestCase, TestOutcome, EnvironmentType } from '../../ObjectModel/Common'; -import { SessionHash } from '../../Utils/Hashing/SessionHash'; import { Constants } from '../../Constants'; +import { AttachmentSet } from '../../ObjectModel'; +import { EnvironmentType, TestCase, TestOutcome } from '../../ObjectModel/Common'; import { EqtTrace } from '../../ObjectModel/EqtTrace'; +import { + FailedExpectation, ITestFramework, ITestFrameworkEvents, TestErrorMessageEventArgs, + TestFrameworkOptions, TestSessionEventArgs, TestSpecEventArgs, TestSuiteEventArgs +} from '../../ObjectModel/TestFramework'; +import { SessionHash } from '../../Utils/Hashing/SessionHash'; export abstract class BaseTestFramework implements ITestFramework { public readonly abstract environmentType: EnvironmentType; public readonly abstract canHandleMultipleSources: boolean; public readonly abstract supportsJsonOptions: boolean; + public readonly abstract supportsCodeCoverage: boolean; public readonly testFrameworkEvents: ITestFrameworkEvents; protected abstract sources: Array; @@ -29,7 +33,7 @@ export abstract class BaseTestFramework implements ITestFramework { public abstract startExecutionWithSources(sources: Array, options: JSON); public abstract startDiscovery(sources: Array); - public abstract initialize(); + public abstract initialize(options: TestFrameworkOptions); protected abstract skipSpec(specObject: any); @@ -53,7 +57,7 @@ export abstract class BaseTestFramework implements ITestFramework { } protected handleSessionDone() { - + if (this.sessionActive) { this.sessionEventArgs.EndTime = new Date(); this.sessionEventArgs.InProgress = false; @@ -105,17 +109,17 @@ export abstract class BaseTestFramework implements ITestFramework { } protected handleSpecStarted(fullyQualifiedName: string, - testCaseName: string, - sourceFile: string, - specObject: any, - fqnPostFix?: string, - attachmentId?: string) { + testCaseName: string, + sourceFile: string, + specObject: any, + fqnPostFix?: string, + attachmentId?: string) { const testCase = this.getTestCase(testCaseName, fullyQualifiedName, sourceFile, fqnPostFix, attachmentId); this.applyTestCaseFilter(testCase, specObject); // should check if spec was already active and not ended - this.activeSpec = { + this.activeSpec = { TestCase: testCase, FailedExpectations: [], Outcome: TestOutcome.None, @@ -143,18 +147,18 @@ export abstract class BaseTestFramework implements ITestFramework { } protected handleSpecResult(fullyQualifiedName: string, - testCaseName: string, - sourceFile: string, - testOutcome: TestOutcome, - failedExpectations: Array, - startTime: Date, - endTime: Date, - fqnPostFix?: string, - attachmentId?: string) { + testCaseName: string, + sourceFile: string, + testOutcome: TestOutcome, + failedExpectations: Array, + startTime: Date, + endTime: Date, + fqnPostFix?: string, + attachmentId?: string) { const testCase = this.getTestCase(testCaseName, fullyQualifiedName, sourceFile, fqnPostFix, attachmentId); - const specResult = { + const specResult = { TestCase: testCase, FailedExpectations: failedExpectations, Outcome: testOutcome, @@ -179,18 +183,28 @@ export abstract class BaseTestFramework implements ITestFramework { EqtTrace.warn(`BaseTestFramework: Error message was received from test framework: ${message}`); - this.testFrameworkEvents.onErrorMessage.raise(this, { + this.testFrameworkEvents.onErrorMessage.raise(this, { Message: message }); } + protected handleRunAttachments(attachmentCollection: Array) { + EqtTrace.info(`BaseTestFramework: Run attachments received ${JSON.stringify(attachmentCollection)}`); + + if (attachmentCollection instanceof Array) { + this.testFrameworkEvents.onRunAttachment.raise(this, { + AttachmentCollection: attachmentCollection + }); + } + } + private handleTestCaseEnd(testSpecEventArgs: TestSpecEventArgs) { if (this.executingWithTests && !this.testCollection.has(testSpecEventArgs.TestCase.Id)) { EqtTrace.verbose('BaseTestFramework: Skipping test result since it is not part of the slice. Test: ' + - JSON.stringify(testSpecEventArgs)); + JSON.stringify(testSpecEventArgs)); return; } - + this.testFrameworkEvents.onTestCaseEnd.raise(this, testSpecEventArgs); } @@ -208,7 +222,7 @@ export abstract class BaseTestFramework implements ITestFramework { EqtTrace.warn(`BaseTestFramework: Fqn length exceeding 512 characters with value: '${fqn}'`); } - const testCase = new TestCase(source, fqn, Constants.executorURI, attachmentId); + const testCase = new TestCase(source, fqn, Constants.ExecutorURI, attachmentId); testCase.DisplayName = testCaseName; return testCase; diff --git a/src/JSTest.Runner/TestRunner/TestFrameworks/Jasmine/JasmineTestFramework.ts b/src/JSTest.Runner/TestRunner/TestFrameworks/Jasmine/JasmineTestFramework.ts index 6435f49..b10223a 100644 --- a/src/JSTest.Runner/TestRunner/TestFrameworks/Jasmine/JasmineTestFramework.ts +++ b/src/JSTest.Runner/TestRunner/TestFrameworks/Jasmine/JasmineTestFramework.ts @@ -17,6 +17,7 @@ export class JasmineTestFramework extends BaseTestFramework implements ITestFram public readonly environmentType: EnvironmentType; public readonly canHandleMultipleSources: boolean = false; public readonly supportsJsonOptions: boolean = false; + public readonly supportsCodeCoverage: boolean = false; protected sources: Array; @@ -40,23 +41,23 @@ export class JasmineTestFramework extends BaseTestFramework implements ITestFram public initialize() { EqtTrace.info('JasmineTestFramework: initializing jasmine'); - + const jasmineLib = this.getJasmine(); this.jasmine = new jasmineLib(); - + // Jasmine forces node to close after completion // tslint:disable: no-empty - this.jasmine.exit = () => {}; + this.jasmine.exit = () => { }; this.jasmine.exitCodeCompletion = () => { }; // tslint:enable: no-empty this.initializeReporter(); } - public startExecutionWithSources(sources: Array, options: JSON): void { + public startExecutionWithSources(sources: Array, options: JSON): void { this.sources = sources; this.overrideJasmineExecute(false); - + this.jasmine.execute([sources[0]]); } @@ -64,8 +65,8 @@ export class JasmineTestFramework extends BaseTestFramework implements ITestFram this.sources = sources; // tslint:disable: no-empty - this.jasmine.jasmine.getEnv().beforeAll = () => {}; - this.jasmine.jasmine.getEnv().afterAll = () => {}; + this.jasmine.jasmine.getEnv().beforeAll = () => { }; + this.jasmine.jasmine.getEnv().afterAll = () => { }; // tslint:enable: no-emptys this.overrideJasmineExecute(true); @@ -134,10 +135,10 @@ export class JasmineTestFramework extends BaseTestFramework implements ITestFram protected skipSpec() { this.skipCurrentSpec = true; } - + private overrideJasmineExecute(discovery: boolean) { const executeSpecHandle = this.jasmine.jasmine.Spec.prototype.execute; - const skipSpecHandle = function(onComplete: any) { + const skipSpecHandle = function (onComplete: any) { this.onStart(this); if (!discovery) { this.result.status = 'disabled'; @@ -149,7 +150,7 @@ export class JasmineTestFramework extends BaseTestFramework implements ITestFram }; // tslint:disable-next-line - const getExecutor = function (fullyQualifiedName: string, testCaseName: string) { + const getExecutor = function (fullyQualifiedName: string, testCaseName: string) { this.skipCurrentSpec = false; this.handleSpecStarted(fullyQualifiedName, testCaseName, this.sources[0]); // Will eventually call skip if skip is required if (discovery || this.skipCurrentSpec === true) { @@ -164,7 +165,7 @@ export class JasmineTestFramework extends BaseTestFramework implements ITestFram executor(this.getFullName(), this.description).apply(this, arguments); }; } - + private initializeReporter() { this.jasmine.clearReporters(); this.jasmine.addReporter({ diff --git a/src/JSTest.Runner/TestRunner/TestFrameworks/Jest/JestTestFramework.ts b/src/JSTest.Runner/TestRunner/TestFrameworks/Jest/JestTestFramework.ts index 7695e88..98c3074 100644 --- a/src/JSTest.Runner/TestRunner/TestFrameworks/Jest/JestTestFramework.ts +++ b/src/JSTest.Runner/TestRunner/TestFrameworks/Jest/JestTestFramework.ts @@ -1,20 +1,25 @@ -import { ITestFrameworkEvents } from '../../../ObjectModel/TestFramework'; -import { EnvironmentType, TestCase } from '../../../ObjectModel/Common'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as rewire from 'rewire'; +import { Constants } from '../../../Constants'; import { Exception, ExceptionType } from '../../../Exceptions'; +import { AttachmentSet } from '../../../ObjectModel'; +import { EnvironmentType, TestCase } from '../../../ObjectModel/Common'; +import { EqtTrace } from '../../../ObjectModel/EqtTrace'; +import { ITestFrameworkEvents, TestFrameworkOptions } from '../../../ObjectModel/TestFramework'; import { BaseTestFramework } from '../BaseTestFramework'; import { JestCallbacks } from './JestCallbacks'; -import * as rewire from 'rewire'; -import * as path from 'path'; -import { EqtTrace } from '../../../ObjectModel/EqtTrace'; export class JestTestFramework extends BaseTestFramework { public readonly environmentType: EnvironmentType; // Even though we can handle multiple sources for all types of executions we'll leave this option be for now public readonly canHandleMultipleSources: boolean = true; public readonly supportsJsonOptions: boolean = true; + public readonly supportsCodeCoverage: boolean = false; protected sources: Array; + private options: TestFrameworkOptions; private jest: any; private jestArgv: any; private jestProjects: any; @@ -23,7 +28,6 @@ export class JestTestFramework extends BaseTestFramework { private getJest() { switch (this.environmentType) { case EnvironmentType.NodeJS: - // tslint:disable-next-line // tslint:disable-next-line:no-require-imports return require('jest'); default: @@ -37,18 +41,31 @@ export class JestTestFramework extends BaseTestFramework { } } + private getJestCLI() { + const jestjs = require.resolve('jest'); + return rewire(path.join(path.dirname(path.dirname(jestjs)), 'node_modules', 'jest-cli', 'build', 'cli')); + } + constructor(testFrameworkEvents: ITestFrameworkEvents, envrionmentType: EnvironmentType) { super(testFrameworkEvents); this.environmentType = envrionmentType; } - public initialize() { + public initialize(options: TestFrameworkOptions) { EqtTrace.info('JestTestFramework: initializing jest'); - this.jest = this.getJest(); + this.options = options; - const jestjs = require.resolve('jest'); - const jestCLI = rewire(path.join(path.dirname(path.dirname(jestjs)), 'node_modules', 'jest-cli', 'build', 'cli')); + if (this.options.CollectCoverage) { + if (this.options.RunAttachmentsDirectory) { + EqtTrace.info(`JestTestFramework: Attachments directory "${this.options.RunAttachmentsDirectory}"`); + } else { + EqtTrace.warn('JestTestFramework: Code coverage was enabled but run attachments directory was not provided.'); + } + } + + this.jest = this.getJest(); + const jestCLI = this.getJestCLI(); this.jestArgv = jestCLI.__get__('buildArgv')(); this.jestArgv.reporters = ['./JestReporter.js']; @@ -91,7 +108,7 @@ export class JestTestFramework extends BaseTestFramework { const source = path.normalize(path.dirname(configPath) + '\\' + fqnRegex[2]); if (configToSourceMap.has(configPath)) { configToTestNamesMap.get(configPath).push(fqnRegex[1]); - configToSourceMap.get(configPath)[source] = 1; + configToSourceMap.get(configPath)[source] = 1; } else { const sourceArray = []; sourceArray[source] = 1; @@ -209,6 +226,24 @@ export class JestTestFramework extends BaseTestFramework { jestArgv.rootDir = path.dirname(runConfigPath); jestArgv.reporters = [require.resolve('./JestReporter.js')]; + let coverageDirectory: string = null; + + if (this.options.CollectCoverage && this.options.RunAttachmentsDirectory && !discovery) { + coverageDirectory = path.join(this.options.RunAttachmentsDirectory, this.getPseudoGuid()); + + try { + fs.mkdirSync(coverageDirectory); + jestArgv.collectCoverage = true; + jestArgv.coverageReporters = ['clover']; + jestArgv.coverageDirectory = coverageDirectory; + + EqtTrace.info(`JestTestFramework: Generating coverage for jest at ${coverageDirectory}`); + } catch (e) { + EqtTrace.error(`JestTestFramework: Could not create directory ${coverageDirectory} for test results.`, e); + coverageDirectory = null; + } + } + const src = []; sources.forEach((source, i) => { src.push(source.replace(/\\/g, '/')); // Cannot run specific test files in jest unless path separator is '/' @@ -222,7 +257,22 @@ export class JestTestFramework extends BaseTestFramework { this.handleSessionStarted(); this.jestReporter.UPDATE_CONFIG(runConfigPath); - return this.jest.runCLI(jestArgv, this.jestProjects); + try { + await this.jest.runCLI(jestArgv, this.jestProjects); + EqtTrace.info('JestTestFramework: Execution complete'); + } catch (e) { + EqtTrace.error('JestTestFramework: Exception on await runCLI', e); + } + + if (coverageDirectory) { + const coverageFile = path.join(coverageDirectory, 'clover.xml'); + if (fs.existsSync(coverageFile)) { + this.handleRunAttachments([this.getAttachmentObject([coverageFile], 'Code Coverage')]); + } else { + EqtTrace.error(`JestTestFramework: Coverage file ${coverageFile} does not exist`, null); + } + } + return; } private getTestNamePattern(testCaseNames: Array) { @@ -235,6 +285,22 @@ export class JestTestFramework extends BaseTestFramework { return testCaseNames.join('|'); } + private getPseudoGuid() { + const s = () => { + // tslint:disable-next-line + return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1); + }; + + return (`${s()}${s()}-${s()}-${s()}-${s()}-${s()}${s()}${s()}`).toLowerCase(); + } + + private getAttachmentObject(attachments: Array, displayName: string): AttachmentSet { + const attachmentSet = new AttachmentSet(Constants.ExecutorURI, displayName); + attachments.forEach(filePath => attachmentSet.addAttachment(path.resolve(filePath), '')); + + return attachmentSet; + } + private reporterRunCompleteHandler() { // do nothing } diff --git a/src/JSTest.Runner/TestRunner/TestFrameworks/Mocha/MochaTestFramework.ts b/src/JSTest.Runner/TestRunner/TestFrameworks/Mocha/MochaTestFramework.ts index 54b3a29..860dbf5 100644 --- a/src/JSTest.Runner/TestRunner/TestFrameworks/Mocha/MochaTestFramework.ts +++ b/src/JSTest.Runner/TestRunner/TestFrameworks/Mocha/MochaTestFramework.ts @@ -1,8 +1,8 @@ -import { FailedExpectation, ITestFrameworkEvents } from '../../../ObjectModel/TestFramework'; -import { EnvironmentType, TestOutcome } from '../../../ObjectModel/Common'; import { Exception, ExceptionType } from '../../../Exceptions'; -import { BaseTestFramework } from '../BaseTestFramework'; +import { EnvironmentType, TestOutcome } from '../../../ObjectModel/Common'; import { EqtTrace } from '../../../ObjectModel/EqtTrace'; +import { FailedExpectation, ITestFrameworkEvents } from '../../../ObjectModel/TestFramework'; +import { BaseTestFramework } from '../BaseTestFramework'; enum ReporterEvent { SessionStarted, @@ -18,6 +18,7 @@ export class MochaTestFramework extends BaseTestFramework { public readonly environmentType: EnvironmentType; public readonly canHandleMultipleSources: boolean = true; public readonly supportsJsonOptions: boolean = true; + public readonly supportsCodeCoverage: boolean = false; protected sources: Array; @@ -28,7 +29,7 @@ export class MochaTestFramework extends BaseTestFramework { private getMocha() { switch (this.environmentType) { case EnvironmentType.NodeJS: - // tslint:disable-next-line + // tslint:disable-next-line return require('mocha'); default: throw new Exception('Not implemented.', ExceptionType.NotImplementedException); @@ -112,7 +113,7 @@ export class MochaTestFramework extends BaseTestFramework { outcome = TestOutcome.Passed; } else if (args.state === 'failed') { outcome = TestOutcome.Failed; - failedExpectations.push( { + failedExpectations.push({ Message: args.err.message, StackTrace: args.err.stack }); @@ -125,40 +126,40 @@ export class MochaTestFramework extends BaseTestFramework { this.handleSpecDone(outcome, failedExpectations); break; - - case ReporterEvent.SessionError: - EqtTrace.warn(`MochaTestFramework: Mocha session error: ${args.title}`); - - const match = args.title.match(/(\".*?\" hook).*/i); - const testHooks = ['\"before all\" hook', '\"after all\" hook', '\"before each\" hook', '\"after each\" hook']; - - if (match && testHooks.indexOf(match[1]) >= 0) { - switch (match[1]) { - case testHooks[0]: - this.handleErrorMessage(args.err.message, args.err.stack); - - args.parent.tests.forEach(test => { - this.handleSpecResult(test.fullTitle(), - test.title, - test.file, - TestOutcome.Failed, - [], new Date(), new Date()); - }); - break; - - case testHooks[2]: - - this.handleSpecDone(TestOutcome.Failed, [ { - Message: args.err.message, - StackTrace: args.err.stack - }]); - break; - - case testHooks[1]: - case testHooks[3]: - this.handleErrorMessage(args.err.message, args.err.stack); - } + + case ReporterEvent.SessionError: + EqtTrace.warn(`MochaTestFramework: Mocha session error: ${args.title}`); + + const match = args.title.match(/(\".*?\" hook).*/i); + const testHooks = ['\"before all\" hook', '\"after all\" hook', '\"before each\" hook', '\"after each\" hook']; + + if (match && testHooks.indexOf(match[1]) >= 0) { + switch (match[1]) { + case testHooks[0]: + this.handleErrorMessage(args.err.message, args.err.stack); + + args.parent.tests.forEach(test => { + this.handleSpecResult(test.fullTitle(), + test.title, + test.file, + TestOutcome.Failed, + [], new Date(), new Date()); + }); + break; + + case testHooks[2]: + + this.handleSpecDone(TestOutcome.Failed, [{ + Message: args.err.message, + StackTrace: args.err.stack + }]); + break; + + case testHooks[1]: + case testHooks[3]: + this.handleErrorMessage(args.err.message, args.err.stack); } + } } } diff --git a/src/JSTest.Runner/TestRunner/TestFrameworks/TestFrameworkEventHandlers.ts b/src/JSTest.Runner/TestRunner/TestFrameworks/TestFrameworkEventHandlers.ts index cfa24a5..fe748f3 100644 --- a/src/JSTest.Runner/TestRunner/TestFrameworks/TestFrameworkEventHandlers.ts +++ b/src/JSTest.Runner/TestRunner/TestFrameworks/TestFrameworkEventHandlers.ts @@ -1,5 +1,5 @@ import { ITestFramework, TestSessionEventArgs, TestSuiteEventArgs, TestSpecEventArgs, - TestErrorMessageEventArgs } from '../../ObjectModel/TestFramework'; + TestErrorMessageEventArgs, TestRunAttachmentEventArgs} from '../../ObjectModel/TestFramework'; export interface TestFrameworkEventHandlers { Subscribe: (framework: ITestFramework) => void; @@ -9,5 +9,6 @@ export interface TestFrameworkEventHandlers { TestSuiteEnd?: (sender: object, args: TestSuiteEventArgs) => void; TestCaseStart?: (sender: object, args: TestSpecEventArgs) => void; TestCaseEnd?: (sender: object, args: TestSpecEventArgs) => void; + TestRunAttachment?: (sender: object, args: TestRunAttachmentEventArgs) => void; TestErrorMessage: (sender: object, args: TestErrorMessageEventArgs) => void; } \ No newline at end of file diff --git a/src/JSTest.Runner/TestRunner/TestFrameworks/TestFrameworkFactory.ts b/src/JSTest.Runner/TestRunner/TestFrameworks/TestFrameworkFactory.ts index edd526a..894e304 100644 --- a/src/JSTest.Runner/TestRunner/TestFrameworks/TestFrameworkFactory.ts +++ b/src/JSTest.Runner/TestRunner/TestFrameworks/TestFrameworkFactory.ts @@ -42,7 +42,8 @@ export class TestFrameworkFactory { onTestSuiteEnd: this.environment.createEvent(), onTestSessionStart: this.environment.createEvent(), onTestSessionEnd: this.environment.createEvent(), - onErrorMessage: this.environment.createEvent() + onErrorMessage: this.environment.createEvent(), + onRunAttachment: this.environment.createEvent() }; } diff --git a/src/JSTest.Runner/TestRunner/TestRunner.ts b/src/JSTest.Runner/TestRunner/TestRunner.ts index 0437c7a..20dbcee 100644 --- a/src/JSTest.Runner/TestRunner/TestRunner.ts +++ b/src/JSTest.Runner/TestRunner/TestRunner.ts @@ -60,7 +60,7 @@ export class TestRunner { let error: Error = null; let errorMessage: string = null; - if (message.Version === Constants.messageProtocolVersion) { + if (message.Version === Constants.MessageProtocolVersion) { try { this.jsTestSettings = new JSTestSettings(message.Payload, this.environment); } catch (err) { @@ -69,7 +69,7 @@ export class TestRunner { } } else { errorMessage = `TestRunner: Message protocol version mismatch, version is` + - ` ${Constants.messageProtocolVersion}, provided was ${message.Version}`; + ` ${Constants.MessageProtocolVersion}, provided was ${message.Version}`; } if (errorMessage) { diff --git a/src/JSTest.TestAdapter/TestExecutor.cs b/src/JSTest.TestAdapter/TestExecutor.cs index 3820085..fd4286e 100644 --- a/src/JSTest.TestAdapter/TestExecutor.cs +++ b/src/JSTest.TestAdapter/TestExecutor.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Threading; namespace JSTest.TestAdapter @@ -92,6 +93,7 @@ private void SubscribeToEvents(ITestRunEvents testRunEvents) testRunEvents.onTestCaseEnd += this.OnTestCaseEndHandler; testRunEvents.onTestSessionEnd += this.OnTestSessionEndHandler; testRunEvents.onTestMessageReceived += this.OnTestMessageReceived; + testRunEvents.onTestRunAttachmentReceived += this.OnTestRunAttachmentReceivedHandler; } private void OnTestMessageReceived(object sender, TestMessagePayload e) @@ -116,5 +118,10 @@ private void OnTestSessionEndHandler(object sender, EventArgs e) { this.executionCompletion.Set(); } + + private void OnTestRunAttachmentReceivedHandler(object sender, TestRunAttachmentPayload e) + { + this.frameworkHandle.RecordAttachments(e.Attachments.ToList()); + } } } diff --git a/src/JSTest/Communication/MessageType.cs b/src/JSTest/Communication/MessageType.cs index eff212f..c0e51fe 100644 --- a/src/JSTest/Communication/MessageType.cs +++ b/src/JSTest/Communication/MessageType.cs @@ -19,5 +19,6 @@ internal static class MessageType public const string StartTestExecutionWithSources = "JSTest.StartWithSources"; public const string StartTestExecutionWithTests = "JSTest.StartWithTests"; public const string StartDiscovery = "JSTest.StartDiscovery"; + public const string RunAttachments = "JSTest.RunAttachments"; } } diff --git a/src/JSTest/Communication/Payloads/TestRunAttachmentPayload.cs b/src/JSTest/Communication/Payloads/TestRunAttachmentPayload.cs new file mode 100644 index 0000000..bbe8542 --- /dev/null +++ b/src/JSTest/Communication/Payloads/TestRunAttachmentPayload.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; + +namespace JSTest.Communication.Payloads +{ + [Serializable] + public class TestRunAttachmentPayload : EventArgs + { + public IEnumerable Attachments; + } +} diff --git a/src/JSTest/Constants.cs b/src/JSTest/Constants.cs index 127153f..3bd16ea 100644 --- a/src/JSTest/Constants.cs +++ b/src/JSTest/Constants.cs @@ -9,7 +9,7 @@ internal static class Constants public const string VsTestConnectionTimeout = "VsTestConnectionTimeout"; public const int MessageProtocolVersion = 1; public const int StreamBufferSize = 16384; - public const string JSTestVersionInfo = "1.1.0"; + public const string JSTestVersionInfo = "1.2.0"; public static class TestFrameworkStrings { diff --git a/src/JSTest/Interfaces/ITestRunEvents.cs b/src/JSTest/Interfaces/ITestRunEvents.cs index 58dff5a..6e4ef5a 100644 --- a/src/JSTest/Interfaces/ITestRunEvents.cs +++ b/src/JSTest/Interfaces/ITestRunEvents.cs @@ -12,6 +12,6 @@ public interface ITestRunEvents event EventHandler onTestCaseFound; event EventHandler onTestMessageReceived; event EventHandler onTestSessionEnd; - + event EventHandler onTestRunAttachmentReceived; } } diff --git a/src/JSTest/RuntimeManager.cs b/src/JSTest/RuntimeManager.cs index d6ce439..4904f20 100644 --- a/src/JSTest/RuntimeManager.cs +++ b/src/JSTest/RuntimeManager.cs @@ -235,6 +235,11 @@ private void OnMessageReceived(Message message) this.testRunEvents.InvokeMessageReceived(this, consolePayload); break; + case MessageType.RunAttachments: + var attachmentsPayload = this.dataSerializer.DeserializePayload(message); + this.testRunEvents.InvokeTestRunAttachmentReceived(this, attachmentsPayload); + break; + default: Console.WriteLine("JSTest: Unknown message type {0} with payload {1}", message.MessageType, message.Payload); break; diff --git a/src/JSTest/Settings/JSTestSettings.cs b/src/JSTest/Settings/JSTestSettings.cs index 9386056..2872e21 100644 --- a/src/JSTest/Settings/JSTestSettings.cs +++ b/src/JSTest/Settings/JSTestSettings.cs @@ -22,10 +22,24 @@ public class JSTestSettings [XmlIgnore] [DataMember] - public JSTestFramework JavaScriptTestFramework { get; set; } + public bool CodeCoverageEnabled { get; set; } - [XmlElement("NodePath")] - public string NodePath { get; set; } + [XmlElement("CodeCoverageEnabled")] + public string CodeCoverageEnabledAsString { + get + { + return CodeCoverageEnabled.ToString(); + } + set + { + bool result; + CodeCoverageEnabled = bool.TryParse(value, out result) ? result : false; + } + } + + [XmlIgnore] + [DataMember] + public JSTestFramework JavaScriptTestFramework { get; set; } [XmlElement("TestFramework")] public string JavaScriptTestFrameworkAsString @@ -65,6 +79,9 @@ public string JavaScriptTestFrameworkAsString } } + [XmlElement("NodePath")] + public string NodePath { get; set; } + [DataMember] public string TestFrameworkConfigJson { get; set; } diff --git a/src/JSTest/TestRunEvents.cs b/src/JSTest/TestRunEvents.cs index cfc5702..d4f1f2f 100644 --- a/src/JSTest/TestRunEvents.cs +++ b/src/JSTest/TestRunEvents.cs @@ -11,6 +11,7 @@ internal class TestRunEvents : ITestRunEvents public event EventHandler onTestCaseFound; public event EventHandler onTestMessageReceived; public event EventHandler onTestSessionEnd; + public event EventHandler onTestRunAttachmentReceived; internal Boolean DisableInvoke = false; @@ -53,5 +54,13 @@ internal void InvokeTestSessionEnd(object sender) this.onTestSessionEnd.Invoke(sender, null); } } + + internal void InvokeTestRunAttachmentReceived(object sender, TestRunAttachmentPayload args) + { + if (!this.DisableInvoke && this.onTestRunAttachmentReceived != null) + { + this.onTestRunAttachmentReceived.Invoke(sender, args); + } + } } } diff --git a/test/JSTest.AcceptanceTests/BaseFrameworkTest.cs b/test/JSTest.AcceptanceTests/BaseFrameworkTest.cs index 7280240..bd7bcd0 100644 --- a/test/JSTest.AcceptanceTests/BaseFrameworkTest.cs +++ b/test/JSTest.AcceptanceTests/BaseFrameworkTest.cs @@ -41,7 +41,7 @@ public abstract class BaseFrameworkTest #region Protected Variables protected abstract string[] ContainerExtension { get; } - + protected ExpectedOutput ExpectedOutput { get; set; } = new ExpectedOutput( new List { "test case a1", @@ -66,17 +66,12 @@ public abstract class BaseFrameworkTest new List { "Passed test case a1", - //"Skipped test case a2", "Passed test case b1", - //"Skipped test case b2", "Passed test case c1", - //"Skipped test case c2", "Total tests: 3. Passed: 3. Failed: 0. Skipped: 0." }, new List()); - protected ExpectedAttachments ExpectedAttachments { get; set; } = new ExpectedAttachments(new List()); - #endregion #region Constructor @@ -254,7 +249,7 @@ private static void InitializeTempFolder() Directory.CreateDirectory(testRepoPath); BaseFrameworkTest.InstallNpmPackage(BaseFrameworkTest.jstestPackage); } - + #endregion #region TestMethods @@ -286,7 +281,7 @@ public void TestDiscovery() this.ValidateOutput(output, expectedOutput); } - public void TestExecution(IDictionary cliArgs = null, List expectedOutput = null, List expectedAttachments = null) + public void TestExecution(IDictionary cliArgs = null, List expectedOutput = null, List expectedAttachments = null, bool codeCoverageEnabled = false) { var files = Directory.EnumerateFiles(BaseFrameworkTest.testRepoPath).Where((file) => this.ContainerExtension.Any((ext) => file.EndsWith(ext))); @@ -297,6 +292,11 @@ public void TestExecution(IDictionary cliArgs = null, List expectedAttachments) { this.TestExecution(new Dictionary() { { "Tests", "3" }, { "ResultsDirectory", GetTestResultsDir() }, { "Logger", "trx" } - }, this.ExpectedOutput.ExecutionWithAttachmentsOutput, this.ExpectedAttachments.Attachments); + }, this.ExpectedOutput.ExecutionWithAttachmentsOutput, expectedAttachments); } - public void TestExecutionWithTestsThroughTranslation() + public void TestExecutionWithCodeCoverage(List expectedAttachments) { - + this.TestExecution(new Dictionary() { + { "ResultsDirectory", GetTestResultsDir() }, + { "Logger", "trx" } + }, null, expectedAttachments, true); } #endregion diff --git a/test/JSTest.AcceptanceTests/ExpectedAttachments.cs b/test/JSTest.AcceptanceTests/ExpectedAttachments.cs deleted file mode 100644 index e6b6b9d..0000000 --- a/test/JSTest.AcceptanceTests/ExpectedAttachments.cs +++ /dev/null @@ -1,15 +0,0 @@ - -using System.Collections.Generic; - -namespace JSTest.AcceptanceTests -{ - public class ExpectedAttachments - { - public ExpectedAttachments(List Attachments) - { - this.Attachments = Attachments; - } - - public List Attachments; - } -} \ No newline at end of file diff --git a/test/JSTest.AcceptanceTests/JestFrameworkMultipleSourceTests.cs b/test/JSTest.AcceptanceTests/JestFrameworkMultipleSourceTests.cs index a27197b..49ef139 100644 --- a/test/JSTest.AcceptanceTests/JestFrameworkMultipleSourceTests.cs +++ b/test/JSTest.AcceptanceTests/JestFrameworkMultipleSourceTests.cs @@ -64,17 +64,10 @@ public JestFrameworkMultipleSourceTests() : base() this.ExpectedOutput.ExecutionWithTestsOutput = new List { "Passed suite bx > test case b1", - //"Skipped suite bx > test case b2", "Passed suite cx > test case c1", - //"Skipped suite cx > test case c2", "Passed suite ax > test case a1", - //"Skipped suite ax > test case a2", - //"Skipped suite ax > test case a3", "Passed suite c > test case c1", - //"Skipped suite c > test case c2", - //"Skipped suite c > test case c3", "Passed suite b > test case b1", - //"Skipped suite b > test case b2", "Passed suite a > test case a1", "Failed suite a > test case a2", "Passed suite a > test case a3", @@ -83,33 +76,13 @@ public JestFrameworkMultipleSourceTests() : base() this.ExpectedOutput.ExecutionWithAttachmentsOutput = new List { - //"Skipped suite a > test case a1", - //"Skipped suite a > test case a2", "Passed suite a > test case a3", - //"Skipped suite b > test case b1", - //"Skipped suite b > test case b2", "Passed suite c > test case c1", "Failed suite c > test case c2", "Passed suite c > test case c3", - //"Skipped suite ax > test case a1", - //"Skipped suite ax > test case a2", "Passed suite ax > test case a3", - //"Skipped suite bx > test case b1", - //"Skipped suite bx > test case b2", - //"Skipped suite cx > test case c1", - //"Skipped suite cx > test case c2", "Total tests: 5. Passed: 4. Failed: 1. Skipped: 0." }; - - this.ExpectedAttachments.Attachments = new List() - { - "suite-a.file1.log", - "suite-c.file1.log", - "suite-c.file2.log", - "suite-ax.file1.log", - "suite-ax.file2.log", - "suite-ax.file3.log", - }; } [ClassInitialize] @@ -133,7 +106,31 @@ public void TestExecutionWithTestsJest_MultipleSources() [TestMethod] public void TestExecutionWithAttachmentsJest_MultipleSources() { - this.TestExecutionWithAttachments(); + this.TestExecutionWithAttachments(new List() + { + "suite-a.file1.log", + "suite-c.file1.log", + "suite-c.file2.log", + "suite-ax.file1.log", + "suite-ax.file2.log", + "suite-ax.file3.log", + }); + } + + [TestMethod] + public void TestExecutionWithCoverageJest_MultipleSources() + { + this.TestExecutionWithCodeCoverage(new List() + { + "suite-a.file1.log", + "suite-c.file1.log", + "suite-c.file2.log", + "suite-ax.file1.log", + "suite-ax.file2.log", + "suite-ax.file3.log", + "clover.xml", + "clover[1].xml", + }); } [TestMethod] diff --git a/test/JSTest.AcceptanceTests/JestFrameworkSingleSourceTests.cs b/test/JSTest.AcceptanceTests/JestFrameworkSingleSourceTests.cs index 5090ed8..17a20b9 100644 --- a/test/JSTest.AcceptanceTests/JestFrameworkSingleSourceTests.cs +++ b/test/JSTest.AcceptanceTests/JestFrameworkSingleSourceTests.cs @@ -54,32 +54,18 @@ public JestFrameworkSingleSourceTests() : base() "Failed suite a > test case a2", "Passed suite a > test case a3", "Passed suite c > test case c1", - //"Skipped suite c > test case c2", - //"Skipped suite c > test case c3", "Passed suite b > test case b1", - //"Skipped suite b > test case b2", "Total tests: 5. Passed: 4. Failed: 1. Skipped: 0." }; this.ExpectedOutput.ExecutionWithAttachmentsOutput = new List { - //"Skipped suite a > test case a1", - //"Skipped suite a > test case a2", "Passed suite a > test case a3", - //"Skipped suite b > test case b1", - //"Skipped suite b > test case b2", "Passed suite c > test case c1", "Failed suite c > test case c2", "Passed suite c > test case c3", "Total tests: 4. Passed: 3. Failed: 1. Skipped: 0." }; - - this.ExpectedAttachments.Attachments = new List() - { - "suite-a.file1.log", - "suite-c.file1.log", - "suite-c.file2.log" - }; } [ClassInitialize] @@ -103,7 +89,24 @@ public void TestExecutionWithTestsJest_SingleSource() [TestMethod] public void TestExecutionWithAttachmentsJest_SingleSource() { - this.TestExecutionWithAttachments(); + this.TestExecutionWithAttachments(new List() + { + "suite-a.file1.log", + "suite-c.file1.log", + "suite-c.file2.log" + }); + } + + [TestMethod] + public void TestExecutionWithCoverageJest_SingleSource() + { + this.TestExecutionWithCodeCoverage(new List() + { + "suite-a.file1.log", + "suite-c.file1.log", + "suite-c.file2.log", + "clover.xml" + }); } [TestMethod] diff --git a/test/JSTest.Runner.UnitTests/Environment/Node/NodeEnvironmentTests.ts b/test/JSTest.Runner.UnitTests/Environment/Node/NodeEnvironmentTests.ts index 6eb48fb..e79a5d3 100644 --- a/test/JSTest.Runner.UnitTests/Environment/Node/NodeEnvironmentTests.ts +++ b/test/JSTest.Runner.UnitTests/Environment/Node/NodeEnvironmentTests.ts @@ -1,10 +1,10 @@ -import { Environment } from '../../../../src/JSTest.Runner/Environment/Node/Environment'; -import { CommunicationManager } from '../../../../src/JSTest.Runner/Environment/Node/CommunicationManager'; -import { IEventArgs } from '../../../../src/JSTest.Runner/ObjectModel/Common'; -import { Event } from '../../../../src/JSTest.Runner/Events/Event'; +import * as Assert from 'assert'; import { Socket } from 'net'; -import * as Assert from 'assert' import { Mock } from 'typemoq'; +import { CommunicationManager } from '../../../../src/JSTest.Runner/Environment/Node/CommunicationManager'; +import { Environment } from '../../../../src/JSTest.Runner/Environment/Node/Environment'; +import { Event } from '../../../../src/JSTest.Runner/Events/Event'; +import { IEventArgs } from '../../../../src/JSTest.Runner/ObjectModel/Common'; interface TestableEventArgs extends IEventArgs { arg: string; diff --git a/test/JSTest.Runner.UnitTests/Environment/TestEnvironment.ts b/test/JSTest.Runner.UnitTests/Environment/TestEnvironment.ts deleted file mode 100644 index 9f0c9d8..0000000 --- a/test/JSTest.Runner.UnitTests/Environment/TestEnvironment.ts +++ /dev/null @@ -1,4 +0,0 @@ -import * as OS from 'os'; -import { IEnvironment } from '../../../src/JSTest.Runner/Environment/IEnvironment'; - -export const defaultTestEnvironment = { getTempDirectory: () => OS.tmpdir() } as IEnvironment; diff --git a/test/JSTest.Runner.UnitTests/ObjectModel/JSTestSettingsTests.ts b/test/JSTest.Runner.UnitTests/ObjectModel/JSTestSettingsTests.ts index c6ae16f..8e34385 100644 --- a/test/JSTest.Runner.UnitTests/ObjectModel/JSTestSettingsTests.ts +++ b/test/JSTest.Runner.UnitTests/ObjectModel/JSTestSettingsTests.ts @@ -4,24 +4,37 @@ import * as path from 'path'; import { JSTestSettings } from '../../../src/JSTest.Runner/ObjectModel'; import { Exception, ExceptionType } from '../../../src/JSTest.Runner/Exceptions'; import { IEnvironment } from '../../../src/JSTest.Runner/Environment/IEnvironment'; -import { defaultTestEnvironment } from '../Environment/TestEnvironment'; + +const defaultTestEnvironment = { getTempDirectory: () => OS.tmpdir() }; describe('JSTestSettings suite', () => { it('constructor will throw if invalid test framework', (done) => { - Assert.throws(() => new JSTestSettings({ JavaScriptTestFramework: 'framework', TestFrameworkConfigJson: '{}' }, defaultTestEnvironment), + Assert.throws(() => new JSTestSettings({ + JavaScriptTestFramework: 'framework', + TestFrameworkConfigJson: '{}' + }, defaultTestEnvironment), (err) => err instanceof Exception && err.exceptionName === ExceptionType[ExceptionType.UnSupportedTestFramework], - 'Should throw on unsupported test framework.'); + 'Should throw on unsupported test framework.' + ); done(); }); it('constructor will throw if invalid config json passed', (done) => { - Assert.throws(() => new JSTestSettings({ JavaScriptTestFramework: 'jasmine', TestFrameworkConfigJson: '{' }, defaultTestEnvironment), + Assert.throws(() => new JSTestSettings({ + JavaScriptTestFramework: 'jasmine', + TestFrameworkConfigJson: '{' + }, defaultTestEnvironment), (err) => err instanceof Exception && err.exceptionName === ExceptionType[ExceptionType.InvalidJSONException], - 'Should throw on invalid config json.'); + 'Should throw on invalid config json.' + ); - Assert.doesNotThrow(() => new JSTestSettings({ JavaScriptTestFramework: 'jasmine', TestFrameworkConfigJson: null }, defaultTestEnvironment), + Assert.doesNotThrow(() => new JSTestSettings({ + JavaScriptTestFramework: 'jasmine', + TestFrameworkConfigJson: null + }, defaultTestEnvironment), (err) => err instanceof Exception && err.exceptionName === ExceptionType[ExceptionType.InvalidJSONException], - 'Should not throw on null config json'); + 'Should not throw on null config json' + ); done(); }); @@ -31,6 +44,14 @@ describe('JSTestSettings suite', () => { done(); }); + it('JSTestSettings will set CodeCoverageEnabled', () => { + let settings = new JSTestSettings({ JavaScriptTestFramework: 'jasmine', CodeCoverageEnabled: true }, defaultTestEnvironment); + Assert.equal(settings.CoverageEnabled, true); + + settings = new JSTestSettings({ JavaScriptTestFramework: 'jasmine', CodeCoverageEnabled: 'true' }, defaultTestEnvironment); + Assert.equal(settings.CoverageEnabled, false); + }); + it('will throw for not found attachments folder', (done) => { Assert.throws(() => new JSTestSettings({ JavaScriptTestFramework: 'jasmine', @@ -59,8 +80,8 @@ describe('JSTestSettings suite', () => { const tmpDir = OS.tmpdir(); const settings = new JSTestSettings({ JavaScriptTestFramework: 'jasmine', - TestFrameworkConfigJson: null, - }, { getTempDirectory: () => tmpDir } as IEnvironment); + TestFrameworkConfigJson: null + }, { getTempDirectory: () => tmpDir }); Assert.ok(settings.AttachmentsFolder.indexOf(tmpDir) === 0, 'Invalid attachments folder path'); }); diff --git a/test/JSTest.Runner.UnitTests/TestRunner/ExecutionManager/DiscoveryManagerTests.ts b/test/JSTest.Runner.UnitTests/TestRunner/ExecutionManager/DiscoveryManagerTests.ts index 46af3b6..0910fe4 100644 --- a/test/JSTest.Runner.UnitTests/TestRunner/ExecutionManager/DiscoveryManagerTests.ts +++ b/test/JSTest.Runner.UnitTests/TestRunner/ExecutionManager/DiscoveryManagerTests.ts @@ -1,16 +1,17 @@ -import { MessageSender } from '../../../../src/JSTest.Runner/TestRunner/MessageSender'; -import { JSTestSettings, TestMessageLevel } from '../../../../src/JSTest.Runner/ObjectModel'; -import { TestFrameworkFactory } from '../../../../src/JSTest.Runner/TestRunner/TestFrameworks/TestFrameworkFactory'; +import * as Assert from 'assert'; +import * as OS from 'os'; +import { IMock, It, Mock, Times } from 'typemoq'; +import { IEnvironment } from '../../../../src/JSTest.Runner/Environment/IEnvironment'; import { Environment } from '../../../../src/JSTest.Runner/Environment/Node/Environment'; +import { Exception, ExceptionType } from '../../../../src/JSTest.Runner/Exceptions'; +import { JSTestSettings, TestMessageLevel } from '../../../../src/JSTest.Runner/ObjectModel'; +import { ITestFramework, TestFrameworks } from '../../../../src/JSTest.Runner/ObjectModel/TestFramework'; import { TestSessionManager } from '../../../../src/JSTest.Runner/TestRunner/ExecutionManagers/TestSessionManager'; -import { TestFrameworks, ITestFramework } from '../../../../src/JSTest.Runner/ObjectModel/TestFramework'; +import { MessageSender } from '../../../../src/JSTest.Runner/TestRunner/MessageSender'; import { TestFrameworkEventHandlers } from '../../../../src/JSTest.Runner/TestRunner/TestFrameworks/TestFrameworkEventHandlers'; -import { Mock, IMock, Times, It } from 'typemoq'; -import * as Assert from 'assert'; +import { TestFrameworkFactory } from '../../../../src/JSTest.Runner/TestRunner/TestFrameworks/TestFrameworkFactory'; import { TestUtils } from '../../TestUtils'; -import { Exception, ExceptionType } from '../../../../src/JSTest.Runner/Exceptions'; import { TestableDiscoveryManager, TestableFramework, TestableTestFrameworkFactory, TestableTestSessionManager } from './Testable'; -import { defaultTestEnvironment } from '../../Environment/TestEnvironment'; describe('DiscoveryManager Suite', () => { let mockDM: IMock; @@ -48,7 +49,7 @@ describe('DiscoveryManager Suite', () => { settings = new JSTestSettings({ JavaScriptTestFramework: 'jest', TestFrameworkConfigJson: '{}' - }, defaultTestEnvironment); + }, { getTempDirectory: () => OS.tmpdir() }); mockMessageSender = Mock.ofType(MessageSender); mockDM = Mock.ofInstance(new TestableDiscoveryManager(new Environment(), mockMessageSender.object, @@ -156,7 +157,7 @@ describe('DiscoveryManager Suite', () => { // Validate execute job executeJob(); mockFactory.verify((x) => x.createTestFramework(TestFrameworks.Jest), Times.once()); - mockTestFramework.verify((x) => x.initialize(), Times.once()); + mockTestFramework.verify((x) => x.initialize(It.isAny()), Times.once()); mockTestFramework.verify((x) => x.startDiscovery(It.is((x) => TestUtils.assertDeepEqual(x, sources))), Times.once()); mockEventHandlers.verify((x) => x.Subscribe(It.is((x) => TestUtils.assertDeepEqual(x, mockTestFramework.object))), Times.once()); diff --git a/test/JSTest.Runner.UnitTests/TestRunner/ExecutionManager/ExecutionManagerTests.ts b/test/JSTest.Runner.UnitTests/TestRunner/ExecutionManager/ExecutionManagerTests.ts index 4a52f27..bd3bb8d 100644 --- a/test/JSTest.Runner.UnitTests/TestRunner/ExecutionManager/ExecutionManagerTests.ts +++ b/test/JSTest.Runner.UnitTests/TestRunner/ExecutionManager/ExecutionManagerTests.ts @@ -1,21 +1,21 @@ +import * as Assert from 'assert'; import * as fs from 'fs'; +import * as OS from 'os'; import * as path from 'path'; -import { MessageSender } from '../../../../src/JSTest.Runner/TestRunner/MessageSender'; -import { JSTestSettings, TestMessageLevel, TestResult, AttachmentSet } from '../../../../src/JSTest.Runner/ObjectModel'; -import { TestFrameworkFactory } from '../../../../src/JSTest.Runner/TestRunner/TestFrameworks/TestFrameworkFactory'; +import { IMock, It, Mock, Times } from 'typemoq'; +import { IEnvironment } from '../../../../src/JSTest.Runner/Environment/IEnvironment'; import { Environment } from '../../../../src/JSTest.Runner/Environment/Node/Environment'; +import { Exception, ExceptionType } from '../../../../src/JSTest.Runner/Exceptions'; +import { AttachmentSet, JSTestSettings, TestMessageLevel, TestResult } from '../../../../src/JSTest.Runner/ObjectModel'; +import { TestCase, TestOutcome } from '../../../../src/JSTest.Runner/ObjectModel/Common'; +import { ITestFramework, TestFrameworks, TestSpecEventArgs } from '../../../../src/JSTest.Runner/ObjectModel/TestFramework'; import { TestSessionManager } from '../../../../src/JSTest.Runner/TestRunner/ExecutionManagers/TestSessionManager'; -import { TestFrameworks, ITestFramework, TestSpecEventArgs } - from '../../../../src/JSTest.Runner/ObjectModel/TestFramework'; -import { TestOutcome, TestCase } from '../../../../src/JSTest.Runner/ObjectModel/Common'; +import { MessageSender } from '../../../../src/JSTest.Runner/TestRunner/MessageSender'; import { TestFrameworkEventHandlers } from '../../../../src/JSTest.Runner/TestRunner/TestFrameworks/TestFrameworkEventHandlers'; -import { Mock, IMock, Times, It } from 'typemoq'; -import * as Assert from 'assert'; -import { TestUtils } from '../../TestUtils'; -import { Exception, ExceptionType } from '../../../../src/JSTest.Runner/Exceptions'; -import { TestableExecutionManager, TestableTestFrameworkFactory, TestableTestSessionManager, TestableFramework } from './Testable'; +import { TestFrameworkFactory } from '../../../../src/JSTest.Runner/TestRunner/TestFrameworks/TestFrameworkFactory'; import { TimeSpan } from '../../../../src/JSTest.Runner/Utils/TimeUtils'; -import { defaultTestEnvironment } from '../../Environment/TestEnvironment'; +import { TestUtils } from '../../TestUtils'; +import { TestableExecutionManager, TestableFramework, TestableTestFrameworkFactory, TestableTestSessionManager } from './Testable'; describe('ExecutionManager Suite', () => { let mockEM: IMock; @@ -58,7 +58,7 @@ describe('ExecutionManager Suite', () => { settings = new JSTestSettings({ JavaScriptTestFramework: 'jest', TestFrameworkConfigJson: '{"key": "value"}' - }, defaultTestEnvironment); + }, { getTempDirectory: () => OS.tmpdir() }); mockMessageSender = Mock.ofType(MessageSender); mockEM = Mock.ofInstance(new TestableExecutionManager(new Environment(), mockMessageSender.object, @@ -222,7 +222,26 @@ describe('ExecutionManager Suite', () => { done(); }); + + it('testFrameworkEventHandlers will handle TestRunAttachment', (done) => { + const testableDiscoveryManager = new TestableExecutionManager(new Environment(), + mockMessageSender.object, + settings); + + const eventHandlers = testableDiscoveryManager.getEventHandlers(); + const sender = { sender: 'this' }; + + eventHandlers.Subscribe(mockTestFramework.object); + mockTestFramework.object.testFrameworkEvents.onRunAttachment.raise(sender, { + AttachmentCollection: 'attachments' + }); + + mockMessageSender.verify((x) => x.sendRunAttachments(It.is((x) => x === 'attachments')), Times.once()); + + done(); + }); + it('TestCaseEnd handles the attachments', (done) => { // Setup events const testableDiscoveryManager = new TestableExecutionManager(new Environment(), @@ -239,9 +258,9 @@ describe('ExecutionManager Suite', () => { } // Add 2 files to upload to attachments folder - const file1 = path.join(attachmentsFolder, "debug.log"); + const file1 = path.join(attachmentsFolder, 'debug.log'); fs.writeFileSync(file1, 'Hey there!'); - const file2 = path.join(attachmentsFolder, "screenshot.png"); + const file2 = path.join(attachmentsFolder, 'screenshot.png'); fs.writeFileSync(file2, 'Hey there!'); const sender = { sender: 'this' }; @@ -261,7 +280,7 @@ describe('ExecutionManager Suite', () => { FailedExpectations: [] }; - const attachmentSet = new AttachmentSet("uri", "Attachments"); + const attachmentSet = new AttachmentSet('uri', 'Attachments'); attachmentSet.addAttachment(file1); attachmentSet.addAttachment(file2); const attachments = [attachmentSet]; @@ -323,7 +342,7 @@ describe('ExecutionManager Suite', () => { // Validate execute job executeJob(); mockFactory.verify((x) => x.createTestFramework(TestFrameworks.Jest), Times.once()); - mockTestFramework.verify((x) => x.initialize(), Times.once()); + mockTestFramework.verify((x) => x.initialize(It.isAny()), Times.once()); if (testCollection) { mockTestFramework.verify((x) => x.startExecutionWithTests( diff --git a/test/JSTest.Runner.UnitTests/TestRunner/ExecutionManager/Testable.ts b/test/JSTest.Runner.UnitTests/TestRunner/ExecutionManager/Testable.ts index e153508..6a457ee 100644 --- a/test/JSTest.Runner.UnitTests/TestRunner/ExecutionManager/Testable.ts +++ b/test/JSTest.Runner.UnitTests/TestRunner/ExecutionManager/Testable.ts @@ -1,5 +1,5 @@ import { IEnvironment } from '../../../../src/JSTest.Runner/Environment/IEnvironment'; -import { ITestFrameworkEvents, ITestFramework } from '../../../../src/JSTest.Runner/ObjectModel/TestFramework'; +import { ITestFrameworkEvents, ITestFramework, TestFrameworks } from '../../../../src/JSTest.Runner/ObjectModel/TestFramework'; import { EnvironmentType, IEvent, IEventArgs } from '../../../../src/JSTest.Runner/ObjectModel/Common'; import { TestFrameworkEventHandlers } from '../../../../src/JSTest.Runner/TestRunner/TestFrameworks/TestFrameworkEventHandlers'; import { ExecutionManager, DiscoveryManager } from '../../../../src/JSTest.Runner/TestRunner/ExecutionManagers'; @@ -19,6 +19,7 @@ export class TestableFramework implements ITestFramework { public initialize = () => { return; }; public startDiscovery = () => { return; }; public testFrameworkEvents: ITestFrameworkEvents; + public supportsCodeCoverage: boolean = false; constructor(env: IEnvironment) { this.testFrameworkEvents = { @@ -28,7 +29,8 @@ export class TestableFramework implements ITestFramework { onTestSessionEnd: env.createEvent(), onTestSessionStart: env.createEvent(), onTestSuiteEnd: env.createEvent(), - onTestSuiteStart: env.createEvent() + onTestSuiteStart: env.createEvent(), + onRunAttachment: env.createEvent() }; } } @@ -81,6 +83,8 @@ export class TestableExecutionManager extends ExecutionManager { export class TestableBaseExecutionManager extends BaseExecutionManager { protected testFrameworkEventHandlers: TestFrameworkEventHandlers; + protected jsTestSettings: JSTestSettings; + protected testFramework: TestFrameworks; constructor(environment: IEnvironment) { super(environment, null, null); diff --git a/test/JSTest.Runner.UnitTests/TestRunner/TestFrameworks/BaseTestFrameworkTests.ts b/test/JSTest.Runner.UnitTests/TestRunner/TestFrameworks/BaseTestFrameworkTests.ts index 80681d7..a6f5ba6 100644 --- a/test/JSTest.Runner.UnitTests/TestRunner/TestFrameworks/BaseTestFrameworkTests.ts +++ b/test/JSTest.Runner.UnitTests/TestRunner/TestFrameworks/BaseTestFrameworkTests.ts @@ -1,20 +1,20 @@ -import { BaseTestFramework } from '../../../../src/JSTest.Runner/TestRunner/TestFrameworks/BaseTestFramework'; -import { EnvironmentType, TestOutcome, TestCase } from '../../../../src/JSTest.Runner/ObjectModel/Common'; -import { ITestFrameworkEvents, TestSessionEventArgs, TestSuiteEventArgs, TestSpecEventArgs } -from '../../../../src/JSTest.Runner/ObjectModel/TestFramework'; +import * as Assert from 'assert'; +import { It, Mock, Times } from 'typemoq'; +import { Constants } from '../../../../src/JSTest.Runner/Constants'; import { Environment } from '../../../../src/JSTest.Runner/Environment/Node/Environment'; +import { EnvironmentType, TestCase, TestOutcome } from '../../../../src/JSTest.Runner/ObjectModel/Common'; +import { EqtTrace } from '../../../../src/JSTest.Runner/ObjectModel/EqtTrace'; +import { ITestFrameworkEvents, TestSessionEventArgs, TestSpecEventArgs, TestSuiteEventArgs, TestRunAttachmentEventArgs } + from '../../../../src/JSTest.Runner/ObjectModel/TestFramework'; +import { BaseTestFramework } from '../../../../src/JSTest.Runner/TestRunner/TestFrameworks/BaseTestFramework'; import { SessionHash } from '../../../../src/JSTest.Runner/Utils/Hashing/SessionHash'; -import { Constants } from '../../../../src/JSTest.Runner/Constants'; import { TestUtils } from '../../TestUtils'; -import { Mock, It, Times } from 'typemoq'; -import * as Assert from 'assert'; -import { EqtTrace } from '../../../../src/JSTest.Runner/ObjectModel/EqtTrace'; -import { TestCaseEndEventArgs } from '../../../../src/JSTest.Runner/ObjectModel/Payloads'; class TestableBaseTestFramework extends BaseTestFramework { public readonly canHandleMultipleSources: boolean = true; public readonly environmentType: EnvironmentType; public readonly supportsJsonOptions: boolean = true; + public readonly supportsCodeCoverage: boolean = false; protected sources: Array; @@ -65,6 +65,9 @@ class TestableBaseTestFramework extends BaseTestFramework { public specResult(...args: Array) { this.handleSpecResult.apply(this, arguments); } + public runAttachments(...args: Array) { + this.handleRunAttachments.apply(this, arguments); + } public errorMessage(...args: Array) { this.handleErrorMessage.apply(this, arguments); } @@ -77,14 +80,15 @@ describe('BaseTestFramework suite', () => { const sources = ['file 1', 'file 2']; beforeEach(() => { - testFrameworkEvents = { + testFrameworkEvents = { onTestCaseEnd: env.createEvent(), onTestCaseStart: env.createEvent(), onTestSuiteStart: env.createEvent(), onTestSuiteEnd: env.createEvent(), onTestSessionStart: env.createEvent(), onTestSessionEnd: env.createEvent(), - onErrorMessage: env.createEvent() + onErrorMessage: env.createEvent(), + onRunAttachment: env.createEvent() }; baseTestFramework = new TestableBaseTestFramework(testFrameworkEvents, env.environmentType, sources); @@ -146,7 +150,7 @@ describe('BaseTestFramework suite', () => { }); it('handleSpecResult will raise onTestCaseEnd', (done) => { - const testCase: TestCase = new TestCase('source', 'fqnpostfix', Constants.executorURI, 'attachmentId'); + const testCase: TestCase = new TestCase('source', 'fqnpostfix', Constants.ExecutorURI, 'attachmentId'); testCase.DisplayName = 'testcase'; testFrameworkEvents.onTestCaseEnd.subscribe((sender: object, args: TestSpecEventArgs) => { @@ -160,11 +164,12 @@ describe('BaseTestFramework suite', () => { done(); }); + // tslint:disable:max-line-length baseTestFramework.specResult('fqn', 'testcase', 'source', TestOutcome.Passed, [], new Date(), new Date(), 'postfix', 'attachmentId'); }); it('handleSpecStarted/Done will raise onTestCaseStart/End', (done) => { - const testCase: TestCase = new TestCase('source', 'fqn', Constants.executorURI, 'attachmentId'); + const testCase: TestCase = new TestCase('source', 'fqn', Constants.ExecutorURI, 'attachmentId'); testCase.DisplayName = 'testcase'; let specArgs: TestSpecEventArgs; @@ -213,13 +218,26 @@ describe('BaseTestFramework suite', () => { done(); }); + it('handleRunAttachments will raise onTestRunAttachment', (done) => { + const logger = new TestUtils.MockDebugLogger(); + EqtTrace.initialize(logger, 'file'); + + testFrameworkEvents.onRunAttachment.subscribe((sender: object, args: TestRunAttachmentEventArgs) => { + Assert.equal(logger.logContains(/Information.*BaseTestFramework: Run attachments received \[\"attachments\"\]/), true); + Assert.deepEqual(args.AttachmentCollection, ['attachments']); + done(); + }); + + baseTestFramework.runAttachments(['attachments']); + }); + it('startExecutionWithTests will filter test cases', (done) => { const testCaseMap = new Map(); const testcase = new TestCase('file 1', 'fqn 1', 'uri'); testCaseMap.set(testcase.Id, testcase); - baseTestFramework.startExecutionWithTests(['file 1', 'file 2'], testCaseMap, 'json'); + baseTestFramework.startExecutionWithTests(['file 1', 'file 2'], testCaseMap, 'json'); const mockFramework = Mock.ofInstance(baseTestFramework); mockFramework.callBase = true; @@ -230,7 +248,7 @@ describe('BaseTestFramework suite', () => { baseTestFramework.specStarted('fqn', 'testcase', 'source', 'skip'); mockFramework.verify((x) => x.skipSpec( - It.is((x) => x === 'skip') + It.is((x) => x === 'skip') ), Times.once()); done(); @@ -247,7 +265,7 @@ describe('BaseTestFramework suite', () => { baseTestFramework = mockFramework.object; - baseTestFramework.startExecutionWithTests(['file 1', 'file 2'], testCaseMap, 'json'); + baseTestFramework.startExecutionWithTests(['file 1', 'file 2'], testCaseMap, 'json'); mockFramework.verify((x) => x.startExecutionWithSources( It.is((x) => TestUtils.assertDeepEqual(x, ['file 1', 'file 2'])), @@ -263,20 +281,20 @@ describe('BaseTestFramework suite', () => { baseTestFramework = mockFramework.object; - baseTestFramework.startExecutionWithSources([], 'json'); + baseTestFramework.startExecutionWithSources([], 'json'); Assert.equal(baseTestFramework.isExecutingWithTests(), false); - baseTestFramework.startExecutionWithTests(['file 1', 'file 2'], testCaseMap, 'json'); + baseTestFramework.startExecutionWithTests(['file 1', 'file 2'], testCaseMap, 'json'); Assert.equal(baseTestFramework.isExecutingWithTests(), true); }); - + it('handleSpecDone will not report test if not part of the slice', (done) => { const logger = new TestUtils.MockDebugLogger(); EqtTrace.initialize(logger, 'file'); const testCaseMap = new Map(); - baseTestFramework.startExecutionWithTests(['file 1', 'file 2'], testCaseMap, 'json'); + baseTestFramework.startExecutionWithTests(['file 1', 'file 2'], testCaseMap, 'json'); testFrameworkEvents.onTestCaseEnd.subscribe((sender: object, args: TestSpecEventArgs) => { Assert.equal('fqn 1', args.TestCase.FullyQualifiedName); diff --git a/test/JSTest.Runner.UnitTests/TestRunner/TestFrameworks/Jest/JestReporterTests.ts b/test/JSTest.Runner.UnitTests/TestRunner/TestFrameworks/Jest/JestReporterTests.ts index 13c79bf..dd53d21 100644 --- a/test/JSTest.Runner.UnitTests/TestRunner/TestFrameworks/Jest/JestReporterTests.ts +++ b/test/JSTest.Runner.UnitTests/TestRunner/TestFrameworks/Jest/JestReporterTests.ts @@ -1,11 +1,8 @@ -import { JestCallbacks } from '../../../../../src/JSTest.Runner/TestRunner/TestFrameworks/Jest/JestCallbacks'; -import { TestUtils } from '../../../TestUtils'; -import { } from '../../../../../src/JSTest.Runner/TestRunner/TestFrameworks/Jest/JestReporter'; import * as Assert from 'assert'; import { TestOutcome } from '../../../../../src/JSTest.Runner/ObjectModel/Common'; +import { } from '../../../../../src/JSTest.Runner/TestRunner/TestFrameworks/Jest/JestReporter'; describe('JestReporter suite', () => { - const logger = new TestUtils.MockDebugLogger(); // tslint:disable-next-line:no-require-imports const jestReporter = require('../../../../../src/JSTest.Runner/TestRunner/TestFrameworks/Jest/JestReporter'); @@ -76,6 +73,8 @@ describe('JestReporter suite', () => { it('JestReporter will call handleSpecResult', (done) => { let specResult = 0; + const startTime = new Date().getTime(); + const reporter = new jestReporter(); jestReporter.INITIALIZE_REPORTER({ handleSpecResult: (fullName, title, config, outcome, failures, start, end, postfix, attachmentId) => { @@ -119,12 +118,10 @@ describe('JestReporter suite', () => { } } }); + jestReporter.UPDATE_CONFIG('D:\\a\\b\\package.json'); jestReporter.discovery = false; - const startTime = new Date().getTime(); - - const reporter = new jestReporter(); reporter.onTestResult( { path: 'D:\\a\\b\\c\\somepath' diff --git a/test/JSTest.Runner.UnitTests/TestRunner/TestFrameworks/Jest/JestTestFrameworkTests.ts b/test/JSTest.Runner.UnitTests/TestRunner/TestFrameworks/Jest/JestTestFrameworkTests.ts index 5258163..a8e76cf 100644 --- a/test/JSTest.Runner.UnitTests/TestRunner/TestFrameworks/Jest/JestTestFrameworkTests.ts +++ b/test/JSTest.Runner.UnitTests/TestRunner/TestFrameworks/Jest/JestTestFrameworkTests.ts @@ -1,6 +1,12 @@ +import * as Assert from 'assert'; +import * as fs from 'fs'; import { EnvironmentType, TestCase } from '../../../../../src/JSTest.Runner/ObjectModel/Common'; +import { EqtTrace } from '../../../../../src/JSTest.Runner/ObjectModel/EqtTrace'; import { JestTestFramework } from '../../../../../src/JSTest.Runner/TestRunner/TestFrameworks/Jest/JestTestFramework'; -import * as Assert from 'assert'; +import { TestUtils } from '../../../TestUtils'; +import { TestFrameworkOptions } from '../../../../../src/JSTest.Runner/ObjectModel/TestFramework'; +import { AttachmentSet } from '../../../../../src/JSTest.Runner/ObjectModel'; +import { Constants } from '../../../../../src/JSTest.Runner/Constants'; describe('JestTestFramework suite', () => { let framework: any; @@ -29,8 +35,13 @@ describe('JestTestFramework suite', () => { }; framework.jestProjects = {}; - }); + framework.options = { + RunAttachmentsDirectory: 'C:\\temp', + CollectCoverage: false + }; + }); + it('startExecutionWithSources', (done) => { let runCount = 0; @@ -246,4 +257,113 @@ describe('JestTestFramework suite', () => { framework.startExecutionWithTests(['C:\\a\\package.json', 'C:\\a\\package2.json'], testCollection, {prop: 'value'} ); }); + + it('initialize', () => { + const logger = new TestUtils.MockDebugLogger(); + EqtTrace.initialize(logger, 'file'); + + const mockJest = {}; + const mockJestArgv = {}; + const mockJestProjects = {}; + const options = { + RunAttachmentsDirectory: 'C:\\temp', + CollectCoverage: true + }; + + framework.getJest = () => mockJest; + framework.getJestCLI = () => ({ + __get__: (str: string) => { + switch (str) { + case 'buildArgv': + return () => { + return mockJestArgv; + }; + case 'getProjectListFromCLIArgs': + return (arg: any) => { + if (arg !== mockJestArgv) { + Assert.fail(); + } + return mockJestProjects; + }; + default: + Assert.fail(); + } + } + }); + + framework.initialize(options); + + Assert.equal(logger.logContains(/Information.*initializing jest.*/), true); + Assert.equal(logger.logContains(/Information.*Attachments directory.*C:\\temp.*/), true); + Assert.deepEqual(options, framework.options); + + Assert.equal(mockJest, framework.jest); + Assert.equal(mockJestArgv, framework.jestArgv); + Assert.equal(mockJestProjects, framework.jestProjects); + + }); + + it('initialize, no run attachments dir', () => { + const logger = new TestUtils.MockDebugLogger(); + EqtTrace.initialize(logger, 'file'); + + const options = { + RunAttachmentsDirectory: '', + CollectCoverage: true + }; + + framework.getJest = () => ({}); + framework.getJestCLI = () => ({ + __get__: (str: string) => (() => ({ reporters: null})) + }); + + framework.initialize(options); + + Assert.equal(logger.logContains(/Warning.*Code coverage was enabled but run attachments directory was not provided.*/), true); + }); + + it('sets code coverage arguments for jest', (done) => { + const logger = new TestUtils.MockDebugLogger(); + let coverageDir: string = ''; + + Object.defineProperty(fs, 'existsSync', { + writable: false, + value: () => { + return true; + } + }); + Object.defineProperty(fs, 'mkdirSync', { + writable: false, + value: (dir) => { + coverageDir = dir; + } + }); + + EqtTrace.initialize(logger, 'file'); + + framework.options = { + RunAttachmentsDirectory: 'C:\\temp', + CollectCoverage: true + }; + framework.jest = { + runCLI: async (argv, projects) => { + return Promise.resolve(); + } + }; + + const guid = framework.getPseudoGuid(); + + const attachments = new AttachmentSet(Constants.ExecutorURI, 'Code Coverage'); + attachments.addAttachment(`C:\\temp\\${guid}\\clover.xml`, ''); + + framework.handleRunAttachments = (attachmentSet) => { + Assert.deepEqual(attachmentSet[0], attachments); + done(); + }; + + framework.getPseudoGuid = () => guid; + framework.startExecutionWithSources(['C:\\a\\package.json'], {prop: 'value'}); + + Assert.equal('C:\\temp\\' + guid, coverageDir); + }); }); \ No newline at end of file diff --git a/test/JSTest.Runner.UnitTests/TestRunner/TestFrameworks/TestFrameworkFactoryTests.ts b/test/JSTest.Runner.UnitTests/TestRunner/TestFrameworks/TestFrameworkFactoryTests.ts index b696720..f616b7b 100644 --- a/test/JSTest.Runner.UnitTests/TestRunner/TestFrameworks/TestFrameworkFactoryTests.ts +++ b/test/JSTest.Runner.UnitTests/TestRunner/TestFrameworks/TestFrameworkFactoryTests.ts @@ -30,6 +30,7 @@ describe('TestFrameworkFactory Suite', () => { Assert.strictEqual(tf.testFrameworkEvents.onTestSessionStart instanceof Event, true); Assert.strictEqual(tf.testFrameworkEvents.onTestSuiteEnd instanceof Event, true); Assert.strictEqual(tf.testFrameworkEvents.onTestSuiteStart instanceof Event, true); + Assert.strictEqual(tf.testFrameworkEvents.onRunAttachment instanceof Event, true); done(); });