Skip to content

Commit

Permalink
Merge pull request #42 from pereckerdal/teamcity-reporter
Browse files Browse the repository at this point in the history
Add TeamCity reporter
  • Loading branch information
per-gron committed Dec 13, 2014
2 parents 8c8080e + 69c7d1f commit d954f54
Show file tree
Hide file tree
Showing 18 changed files with 1,084 additions and 75 deletions.
12 changes: 9 additions & 3 deletions lib/interface/bdd_mocha.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,20 @@ module.exports = function(file, runtimeContext) {
};
}

function testForDuplicate(type, name) {
if (_.find(suite.contents, function(test) { return test.name === name; })) {
throw new Error('Redefining ' + type + ' "' + name + '"');
}
}

// Since we always run only one test per process, there is no difference between
// a hook that runs before every test and a hook that runs before a test suite.
global.before = global.beforeEach = hookHandler('before');
global.after = global.afterEach = hookHandler('after');

function describe(options, name, fn) {
testForDuplicate('suite', name);

var subSuite = newSuite(fn ? options.skipped : true, options.only, name);
var parentSuite = suite;
suite = subSuite;
Expand All @@ -134,9 +142,7 @@ module.exports = function(file, runtimeContext) {
global.describe.only = describe.bind(this, { only: true });

function it(options, name, fn) {
if (name in suite.contents) {
throw new Error('Redefining test ' + name);
}
testForDuplicate('test', name);

var test = {
type: 'test',
Expand Down
47 changes: 5 additions & 42 deletions lib/reporter/serializer.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,47 +18,14 @@

var _ = require('lodash');
var testPathUtil = require('../test_path_util');

function incrementValue(obj, key, by) {
if (!(key in obj)) {
obj[key] = 0;
}
obj[key] += _.isUndefined(by) ? 1 : by;
}
var TestCount = require('../test_count');

function suiteIsAncestorOfOrSameAs(ancestor, descendant) {
return (ancestor.file === descendant.file &&
ancestor.path.length <= descendant.path.length &&
_.isEqual(ancestor.path, descendant.path.slice(0, ancestor.path.length)));
}

/**
* For a given suite path, increment the value in obj for it and its
* ancestors by a given amount.
*/
function incrementValueForSuitePath(obj, suitePath, by) {
if (suitePath.path.length === 0) {
return;
} else {
incrementValue(obj, JSON.stringify(suitePath), by);
incrementValueForSuitePath(obj, testPathUtil.suitePathOf(suitePath), by);
}
}

/**
* Takes an array of testPaths and returns the number of tests per suite, in
* the format of Serializer._numRemainingTests should be in.
*/
function calculateNumTestsForSuites(testPaths) {
var result = {};

testPaths.forEach(function(testPath) {
incrementValueForSuitePath(result, testPathUtil.suitePathOf(testPath));
});

return result;
}

/**
* Serializer is a reporter that takes a stream of tests running potentially
* in parallel and converts it to a stream of messages that seem like the tests
Expand All @@ -80,11 +47,7 @@ function Serializer(reporter) {
this._canPickNewTest = true;
// Map from serialized test path to array of suppressed messages
this._pendingTestMessages = {};
// Map from serialized suite path to the number of tests that are remaining
// in that suite. (Suite path is simply a test path without the test included).
//
// Is set in registerTests
this._numRemainingTests = null;
this._remainingTests = new TestCount();

// Used for providing helpful errors
this._finishedTests = {};
Expand Down Expand Up @@ -116,7 +79,7 @@ Serializer.prototype._pendingTestHasFinished = function(testPath) {

Serializer.prototype._currentTestFinished = function() {
this._finishedTests[this._currentTest] = true;
incrementValueForSuitePath(this._numRemainingTests, testPathUtil.suitePathOf(JSON.parse(this._currentTest)), -1);
this._remainingTests.removeTest(JSON.parse(this._currentTest));
this._canPickNewTest = true;
};

Expand All @@ -129,7 +92,7 @@ Serializer.prototype._currentTestFinished = function() {
Serializer.prototype._getPremissibleSuiteForNextTest = function(currentSuite) {
if (currentSuite.path.length === 0) {
return null;
} else if (this._numRemainingTests[JSON.stringify(currentSuite)] !== 0) {
} else if (this._remainingTests.numberOfTestsInSuite(currentSuite) !== 0) {
return currentSuite;
} else {
return this._getPremissibleSuiteForNextTest(testPathUtil.suitePathOf(currentSuite));
Expand Down Expand Up @@ -175,7 +138,7 @@ Serializer.prototype._pickNewCurrentTest = function() {
};

Serializer.prototype.registerTests = function(testPaths) {
this._numRemainingTests = calculateNumTestsForSuites(testPaths);
this._remainingTests.addTests(testPaths);

if (this._reporter.registerTests) {
this._reporter.registerTests(testPaths);
Expand Down
36 changes: 10 additions & 26 deletions lib/reporter/spec_progress.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,34 +86,18 @@ function SpecProgress(stream) {
this._lastLineIdForSuite = {};
}

SpecProgress.prototype._notYetSeenSuitePaths = function(suitePath) {
if (suitePath.path.length === 0) {
return [];
}

var suiteString = JSON.stringify(suitePath);
var notYetSeenParentSuitePaths = this._notYetSeenSuitePaths(testPathUtil.suitePathOf(suitePath));

if (!(suiteString in this._lastLineIdForSuite)) {
return notYetSeenParentSuitePaths.concat([suitePath]);
} else {
return notYetSeenParentSuitePaths;
}
};

SpecProgress.prototype._printNotYetSeenSuiteNames = function(testPath) {
var self = this;
var notYetSeenSuitePaths = this._notYetSeenSuitePaths(testPathUtil.suitePathOf(testPath));
notYetSeenSuitePaths.forEach(function(suitePath) {
SpecProgress.prototype.gotMessage = function(testPath, message) {
if (message.type === 'suiteStart') {
var suitePath = message.suite;
var extraNewline = suitePath.path.length === 1 ? '\n' : '';
var suitePathString = JSON.stringify(suitePath);
var name = _.last(suitePath.path);
self._log.log(extraNewline + spacesForPath(suitePath) + name, suitePathString);
self._lastLineIdForSuite[suitePathString] = suitePathString;
});
};
var suiteName = _.last(suitePath.path);
if (suiteName) {
this._log.log(extraNewline + spacesForPath(suitePath) + suiteName, suitePathString);
this._lastLineIdForSuite[suitePathString] = suitePathString;
}
}

SpecProgress.prototype.gotMessage = function(testPath, message) {
if (message.type !== 'start' && message.type !== 'finish') {
return;
}
Expand All @@ -129,8 +113,8 @@ SpecProgress.prototype.gotMessage = function(testPath, message) {
var sign = symbols[status] || '?';
var line = prefixSpace + symbolColor(sign) + ' ' + nameColor(name);

this._printNotYetSeenSuiteNames(testPath);
if (message.type === 'start') {
require('chai').expect(this._lastLineIdForSuite[suitePathAsString]).to.exist;
this._log.logAfter(this._lastLineIdForSuite[suitePathAsString], line, pathAsString);
this._lastLineIdForSuite[suitePathAsString] = pathAsString;
} else if (message.type === 'finish') {
Expand Down
95 changes: 95 additions & 0 deletions lib/reporter/suite_marker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright 2014 Per Eckerdal
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

var TestCount = require('../test_count');
var testPathUtil = require('../test_path_util');
var Combined = require('./combined');

/**
* SuiteMarker is a reporter that emits extra messages that contain information
* about when a test suite has started and when it is finished. Before any test
* that is within a suite (direct child or ancestor) runs, a 'suiteStart'
* message is emitted. After the last test in a suite has run, a 'suiteFinish'
* message is emitted. The messages are emitted with null testPaths and have the
* format
*
* { "type": ["suiteStart"|"suiteFinish"], "suite": [suite path] }
*
* Suites that don't have any tests are not reported.
*
* The SuiteMarker reporter can be run directly, but I expect it to most often
* be used together with the Serializer reporter; that way, the receiving
* reporter will get messages as if the tests are run serially and with
* information about when suites start and end.
*/
function SuiteMarker(reporter) {
Combined.call(this, [reporter]);
this._reporter = reporter;
this._totalTests = new TestCount();
this._remainingTests = new TestCount();
}
SuiteMarker.prototype = Object.create(Combined.prototype);

SuiteMarker.prototype.registerTests = function(tests) {
Combined.prototype.registerTests.call(this, tests);
this._totalTests.addTests(tests);
this._remainingTests.addTests(tests);
};

SuiteMarker.prototype._maybeEmitSuiteStart = function(suitePath) {
if (suitePath === null) {
return;
}

var totalTestsInSuite = this._totalTests.numberOfTestsInSuite(suitePath);
var remainingTestsInSuite = this._remainingTests.numberOfTestsInSuite(suitePath);
if (totalTestsInSuite === remainingTestsInSuite) {
this._maybeEmitSuiteStart(testPathUtil.suitePathOf(suitePath));
Combined.prototype.gotMessage.call(this, null, {
type: 'suiteStart',
suite: suitePath
});
}
};

SuiteMarker.prototype._maybeEmitSuiteFinish = function(suitePath) {
if (suitePath !== null && this._remainingTests.numberOfTestsInSuite(suitePath) === 0) {
Combined.prototype.gotMessage.call(this, null, {
type: 'suiteFinish',
suite: suitePath
});
this._maybeEmitSuiteFinish(testPathUtil.suitePathOf(suitePath));
}
};

SuiteMarker.prototype.gotMessage = function(testPath, message) {
var suitePath = testPathUtil.suitePathOf(testPath);

if (message.type === 'start') {
this._maybeEmitSuiteStart(suitePath);
this._remainingTests.removeTest(testPath);
}

Combined.prototype.gotMessage.call(this, testPath, message);

if (message.type === 'finish') {
this._maybeEmitSuiteFinish(suitePath);
}
};

module.exports = SuiteMarker;
Loading

0 comments on commit d954f54

Please sign in to comment.