-
-
Notifications
You must be signed in to change notification settings - Fork 3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
resolves #2149, #1257: Make TAP reporter conform to TAP13 #3452
Conversation
Hi y'all, thought I'd get the ball rolling on having mocha's tap reporter output v13 spec compliant reports since I need it. 😬 Any thoughts? Technically v13 is not backwards compatible with v12 so this is a breaking change for any TAP Consumers that don't also support v13 but I'd defer to a maintainer to deem this being a patch or minor update. (Kind of wished bots ignored WIP PRs) |
@mollstam thanks for taking your time to work on this here. Just a thought, we can provide the new version under a new flag, until we eventually deprecate the old one. I looked at the v13 spec But it does not list/mention the breaking changes, can you provide the breaking changes to give us a better idea about what we're dealing with? Also if this gets pushed, we'll include those breaking changes in the release notes. |
@Bamieh Actually you're right, the v12 is quite clear:
I thought the version string would confuse it but it seems I was wrong, good good. I starting thinking I'd hide the 13 spec under a reporter option but that doesn't seem to be necessary then. |
All right, I've at this point created a couple of integration tests to ensure that the output conforms to spec v13 and also some other Nice Things regarding the structure of the YAML block containing error message and stack. Only a few changes had to be made to tap.js to bring it up to spec. The YAML test in itself is a bit hairy but I feel like it's good enough for now. I'd like to create a new fixture that generates a report with "a little bit of everything" so I can make sure that it looks good, and so that I can also stop using uncaught.fixture.js. I'd like to also keep looking a bit at putting more data about errors into the YAML block for some nice reporting by tap-diff, tap-spec and other tape/node-tap related consumers. The TAP spec gives implementors free reign of content in YAML block would be nice to inventory what most consumers expect and see if we can provide it. Finally I'm going to mull over issue #2410 a bit as well. I'm off for today. 🐨 |
All right, I feel like I'm done here and I'd like to open up for review. Also the semver-major label is no longer required. |
Since there's no huge major difference between TAP12 and YAP13/14 output, any consideration given to providing the TAP version via Add As TAP12 is apparently ancient nowadays, perhaps TAP13 should be unspecified default. Or leave the default "as is" and allow this to be added to a "semver-minor" Mocha release. |
Have these changes been run against the TAP 14 draft? |
Without personally really diving into TAP, does this reporter's output match that which would be produced by Node-based TAP producers |
lib/reporters/tap.js
Outdated
@@ -32,6 +32,7 @@ function TAP(runner) { | |||
var failures = 0; | |||
|
|||
runner.on('start', function() { | |||
console.log('TAP version 13'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider using self.write()
and self.writeln()
so that file-based output via reporterOptions
could be a trivially implemented possibility.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
xunit reporter has a write()
and I find nothing about writeln()
. Can you point me in the right direction? console.log
is what most reporters seem to be doing. 😱
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, using functions from another proposed PR.
Until that's ready, you could implement those as:
/**
* Write out the given string.
*
* @param {string} s
*/
TAP.prototype.write = function(s) {
if (this.fileStream) {
this.fileStream.write(s);
} else {
process.stdout.write(s);
}
};
/**
* Write out the given string and terminate the line.
*
* @param {string} s
*/
TAP.prototype.writeln = function(s) {
this.write(s + '\n');
};
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Glancing your console.log
usage, will also need to add an sprintf()
function to handle your string substitutions.
As a (hopefully non-digressive) follow-on question, what TAP consumers were used to verify output from this PR? While I don't think these should be added to Mocha's build toolchain, any related test scripts you used for such can be preserved here as comment attachments. |
lib/reporters/tap.js
Outdated
} | ||
if (err.stack) { | ||
console.log(err.stack.replace(/^/gm, ' ')); | ||
var emitYamlBlock = err.message != null || err.stack != null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see "diff"-able Error
s in Mocha codebase with actual
and expected
properties. Would they be considered "interesting" as output?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is a great feature to have but it doesn't really have much to do with TAP13, rather a "while we're at it" thing and if I remember correctly I ran into some problems investigating it, but this was I while back. I think this feature shouldn't ride on this PR tbh.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But wasn't that exactly part of the tape
output we should be attempting to match?
On A la this unbuffered subtest reference output.
|
Hello friend, thanks for having a look! :)
As the TAP13 spec is both forward and backward compatible I'm not sure keeping the TAP12 output around is necessary, what's your thoughts?
I had a look at the TAP14 draft back when implementing this and decided not to implement it since it is only a draft and I wanted to keep the footprint of the PR as small as possible.
I did recreate the fixture in As you can see the running theme is me wanting to make as little of change as possible to reach what I set out to do. This iteration of the TAP reporter is not by any means a "final form" but a lot of the future work relies on the YAML format being standardized (consumers vary wildly) and for that I think TAP14 needs to get ratified, or more effort to accomodate the popular consumers out there. Let me know your thoughts and thanks again. |
Was failing since it matched entire output array and first line is now the version line per TAP v13 spec.
Only expect now is that version line is first line, more to come. Removed the previously added unit test which now is superflous.
Plan:
@mollstam, what do you think? |
Sounds good! I named the option |
@plroebuck I think the I would like to also clean up the |
Base.call(this, runner); | ||
|
||
var self = this; | ||
var n = 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
n
is same as runner.stats.tests
. Remove.
}; | ||
|
||
TAPProducer.prototype.writeEnd = function(runner) { | ||
console.log('# tests ' + (runner.stats.passes + runner.stats.failures)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just noticed this, but think this is possibly an unreported existing bug.
runner.stats.tests
will not agree with this math if pending tests occurred.
console.log('# tests ' + (runner.stats.passes + runner.stats.failures)); | ||
console.log('# pass ' + runner.stats.passes); | ||
console.log('# fail ' + runner.stats.failures); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this include a summary line for pending tests?
var passes = 0; | ||
var failures = 0; | ||
|
||
var tapSpec = '12'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be numeric instead?
|
||
runner.on('start', function() { | ||
var total = runner.grepTotal(runner.suite); | ||
console.log('%d..%d', 1, total); | ||
self.producer.writeStart(runner); | ||
}); | ||
|
||
runner.on('test end', function() { | ||
++n; | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove test end
event handler.
expectedString = string; | ||
return expectedTotal; | ||
}; | ||
TAP.call({}, runner); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TAP.call({}, runner, options);
it('should be able to survive global mass extinction events', function(done) { | ||
throw new Error("How do we even test for this without causing one?") | ||
done(); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we add a pending test somewhere in this fixture?
it('just might one day');
console.log('# tests ' + (passes + failures)); | ||
console.log('# pass ' + passes); | ||
console.log('# fail ' + failures); | ||
self.producer.writeEnd(runner); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
self.producer.writeEnd(stats);
console.log('ok %d %s', n, title(test)); | ||
}; | ||
|
||
TAPProducer.prototype.writeEnd = function(runner) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TAPProducer.prototype.writeEnd = function(stats) {
this.producer = new TAP13Producer(); | ||
} else { | ||
this.producer = new TAP12Producer(); | ||
} | ||
|
||
runner.on('start', function() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
runner.on('start', function() {
var ntests = runner.grepTotal(runner.suite);
self.producer.writeStart(ntests);
});
Base.call(this, runner); | ||
|
||
var self = this; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add this line:
var stats = runner.stats;
}); | ||
|
||
runner.on('test end', function() { | ||
++n; | ||
}); | ||
|
||
runner.on('pending', function(test) { | ||
console.log('ok %d %s # SKIP -', n, title(test)); | ||
self.producer.writePending(n, test); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
var n = stats.tests;
self.producer.writePending(n, test);
In "test/reporters/tap.spec.js", let's make use of this function: var showOutput = false;
/**
* Run reporter using stream reassignment to capture output.
*
* @param {Object} stubSelf - Reporter-like stub instance
* @param {Runner} runner - Mock instance
* @param {boolean} [tee=false] - If `true`, echo captured output to screen
*/
function runReporter(stubSelf, runner, tee) {
// Reassign stream in order to make a copy of all reporter output
var stdoutWrite = process.stdout.write;
process.stdout.write = function(string, enc, callback) {
stdout.push(string);
if (tee) {
stdoutWrite.call(process.stdout, string, enc, callback);
}
};
// Invoke reporter
TAP.call(stubSelf, runner);
// Revert stream reassignment here so reporter output
// can't be corrupted if any test assertions throw
process.stdout.write = stdoutWrite;
} |
Description of the Change
The TAP reporter has been made to conform with TAP13.
Changes to the actual reporter are small: a version string is emitted as the first line, and error
message
andstack
are both emitted inside a YAML block to be parsed by any TAP Consumer.A quite large integration test has been added that encodes the expectations of the TAP13 spec, and the existing TAP reporter unit tests have been updated to be compatible with the new spec.
Finally 'reporters.fixture.js' was added to have something to run that generates test results with varying properties.
Alternate Designs
It was considered to add either a new reporter (e.g.
tap13
) or hide the new spec under a reporter option but since there are no breaking changes in TAP13 this was deemed unnecessary.Why should this be in core?
Mocha relies on a default selection of usable reporters. A bunch of TAP Consumers that I've surveyed are poorly written and expect TAP13 or will otherwise break thus rendering the current TAP reporter in mocha less useful.
Benefits
A larger ecosystem of tools that are only compatible with TAP13 are made available to use together with mocha. Taking a step back, benefits in the TAP13 spec vs v12 are inherited, such as being able to encode richer error information in the now allowed YAML block in the TAP report is a Good Thing.
Possible Drawbacks
Many TAP Consumers I've encountered are not as flexible as one might wish so it is possible they don't fully conform to the old v12 spec, thus not being compatible with the TAP13 spec resulting in undefined behaviour. For example, currently in mocha the error stack is simply inserted into the report following the failed test, something that is allowed by the v12 spec as an "unknown" line that shouldn't be an error and may be handled by the test harness in any way. If a test harness handles mocha's stacks by e.g. using regex it will probably break now that the stack is wrapped in what TAP13 calls a "YAML block".
Applicable issues
#2149
#1257
Note
I've decided to not address issue #2410 with this PR as it involves communicating with third party tools (e.g. tap-spec) over TAP13 in a new way which I feel is outside the scope of simply bringing the TAP reporter up to spec, whereas 2410 is more about establishing a new spec regarding the structure of the YAML blocks (unspecified by TAP13).