David Wheeler - Test.Simple-0.29
Name
Test.Harness - Run TAP standard JavaScript test scripts with statistics
Synopsis
Test.Harness.runTests('foo.js', bar.js');
Description
STOP! If all you want to do is write a test script, consider using Test.Simple. Test.Harness is the module that reads the output from Test.Simple, Test.More and other modules based on Test.Builder. You don't need to know about Test.Harness to use those modules.
STOP AGAIN! Test.Harness is an abstract base class. See Test.Harness.Browser for a concrete subclass offering a browser-based harness.
Test.Harness runs tests and expects to get the results from the TestResults attribute of the Test.Builder object. These results conform to a format called TAP, the Test Anything Protocol. It is defined in http://search.cpan.org/dist/Test-Harness/lib/Test/Harness/TAP.pod.
Failure
When tests fail, analyze the summary report:
base.html..............ok
nonumbers.js...........ok
ok.js..................ok
test-harness.js........ok
waterloo.html..........dubious
DIED. FAILED tests 1, 3, 5, 7, 9, 11, 13, 15, 17, 19
Failed 10/20 tests, 50.00% okay
Failed Test Total Fail Failed List of Failed
-------------------------------------------------------------
waterloo.html 20 10 50.00% 1 3 5 7 9 11 13 15 17 19
Failed 1/5 test scripts, 80.00% okay. 10/44 subtests failed, 77.27% okay.
Everything passed but waterloo.html. It failed 10 of 20 tests.
The columns in the summary report mean:
- Failed Test
-
The test file which failed.
- Total
-
Total number of tests expected to run.
- Fail
-
Number that failed, either from "not ok" or because they never ran.
- Failed
-
Percentage of the total tests that failed.
- List of Failed
-
A list of the tests that failed. Successive failures may be abbreviated (ie. 15-20 to indicate that tests 15, 16, 17, 18, 19 and 20 failed).
Class Methods
Test.Harness.runTests('testone.js', 'testtwo.js');
Constructs a new Test.Harness object and calls its runTests() instance method, passing all arguments along.
Constructors
var harness = new Test.Harness();
Constructs a new Test.Harness object.
Instance Methods
- runTests
-
harness.runTests('testone.js', 'testtwo.js');This method runs all the given test files and divines whether they passed or failed based on the contents of the
TestRusultsattribute of their globalTest.Builder.Testobject. It prints out each individual test that failed along with a summary report and a how long it all took. When all tests have been run, a diagnostic message will be output. - encoding
-
var encoding = harness.encoding(); harness = harness.encoding(encoding);
Gets or sets the encoding to use for the test scripts. Returns the harness object when setting the encoding. The encoding is
nullby default, and therefore unset.
Diagnostics
All tests successful.\nFiles=%d, Tests=%d, %s-
If all tests are successful some statistics about the performance are output.
FAILED tests %s\n\tFailed %d/%d tests, %.2f%% okay.-
For any single script that has failing subtests statistics like the above are printed.
Failed 1 test, %.2f%% okay. %sFailed %d/%d tests, %.2f%% okay. %sIf not all tests were successful, the script dies with one of the above messages.
See Also
Test.Simple and Test.More, modules with which to write tests.
Authors
David Wheeler <david@kineticode.com>, based on the original Test::Harness included with Perl.
Copyright
Copyright 2005 by David Wheeler <david@kineticode.com>
This program is free software; you can redistribute it and/or modify it under the terms of the Perl Artistic License or the GNU GPL.
See http://www.perl.com/perl/misc/Artistic.html and http://www.gnu.org/copyleft/gpl.html.
// $Id$
/*global JSAN, Test, _global, _player */
// Set up namespace.
if (typeof self != 'undefined') {
//Browser
if (typeof Test == 'undefined') Test = {PLATFORM: 'browser'};
else Test.PLATFORM = 'browser';
} else if (typeof _player != 'undefined'){
//Director
if (typeof _global.Test != "object") _global.Test = {PLATFORM: 'director'};
else _global.Test.PLATFORM = 'director';
} else {
throw new Error("Test.Harness does not support your platform");
}
Test.Harness = function () {};
Test.Harness.VERSION = '0.29';
Test.Harness.Done = 0;
// Stoopid IE.
Test.Harness.LF = typeof navigator != "undefined"
&& navigator.userAgent.toLowerCase().indexOf('msie') + 1
&& ( ( typeof JSAN != "undefined" && JSAN.globalScope.opera == undefined )
|| ( Test.PLATFORM == 'browser' && window.opera == undefined ) )
? "\r"
: "\n";
Test.Harness.prototype.isDone = Test.Harness.isDone;
/*
bonus Number of individual todo tests unexpectedly passed
ran Number of individual tests ran
ok Number of individual tests passed
subSkipped Number of individual tests skipped
todo Number of individual todo tests
files Number of test files ran
good Number of test files passed
bad Number of test files failed
tests Number of test files originally given
skipped Number of test files skipped
*/
Test.Harness.prototype.bonus = 0;
Test.Harness.prototype.ran = 0;
Test.Harness.prototype.ok = 0;
Test.Harness.prototype.subSkipped = 0;
Test.Harness.prototype.todo = 0;
Test.Harness.prototype.files = 0;
Test.Harness.prototype.good = 0;
Test.Harness.prototype.bad = 0;
Test.Harness.prototype.tests = 0;
Test.Harness.prototype.skipped = 0;
Test.Harness.prototype.failures = [];
Test.Harness.prototype._encoding = null;
Test.Harness.prototype.encoding = function (enc) {
if (!enc) return this._encoding;
this._encoding = enc;
return this;
};
Test.Harness.runTests = function () {
// XXX Can't handle inheritance, right? Or can we?
var harness = new Test.Harness();
harness.runTests.apply(harness, arguments);
};
Test.Harness.prototype.outFileNames = function (files) {
var len = 0;
for (var i = 0; i < files.length; i++) {
if (files[i].length > len) len = files[i].length;
}
len += 3;
var ret = [];
for (var k = 0; k < files.length; k++) {
var outName = files[k];
var add = len - files[k].length;
// Where is Perl's x operator when I need it??
for (var j = 0; j < add; j++) {
outName += '.';
}
ret.push(outName);
}
return ret;
};
Test.Harness.prototype.outputResults = function (test, file, out, attrs) {
this.tests++;
this.ran += test.TestResults.length;
var fails = [];
var track = {
fn: file,
total: test.expectedTests(),
ok: 0,
failList: []
};
if (test.TestResults.length) {
this.files++;
var pass = true;
for (var i = 0; i < test.TestResults.length; i++) {
var result = test.TestResults[i];
var resultType = result ? result.getType() : '';
// Start out assuming passage.
if (result && result.getOK()) {
if (attrs.verbose) out.pass(result.getOutput());
this.ok++;
track.ok++
if (resultType == 'todo') {
// Handle unexpected pass.
if (result.getActualOK()) this.bonus++;
this.todo ++;
} else if (resultType == 'skip') this.subSkipped++;
} else {
if (resultType == 'todo') {
// Expected failure.
this.todo++;
if (attrs.verbose) out.pass(result.getOutput());
} else {
pass = false;
track.failList.push(i + 1);
out.fail(result.getOutput());
}
}
}
if (pass) {
this.good++;
out.pass("ok" + Test.Harness.LF);
} else {
this.bad++;
var err = "NOK # Failed ";
if (track.failList.length == 1) {
err += "test " + track.failList[0];
} else {
err += "tests " + this._failList(track.failList);
}
out.fail(err + " in " + file + Test.Harness.LF);
}
} else if (test.SkipAll){
// All tests skipped.
this.skipped++;
this.good++;
out.pass(test.Buffer.join('').replace(/[^#]+#\s+Skip /, 'all skipped: '));
} else {
// Wha happened? Tests ran, but no results!
this.files++;
this.bad++;
out.fail("FAILED before any test output arrived" + Test.Harness.LF);
}
if (track.failList.length) this.failures.push(track);
};
Test.Harness.prototype._allOK = function () {
return this.bad == 0 && (this.ran || this.skipped) ? true : false;
};
Test.Harness.prototype.outputSummary = function (fn, time) {
var bonusmsg = this._bonusmsg();
var pct;
if (this._allOK()) {
fn("All tests successful" + bonusmsg + '.' + Test.Harness.LF);
} else if (!this.tests) {
fn("FAILED -- no tests were run for some reason." + Test.Harness.LF);
} else if (!this.ran) {
var blurb = this.tests == 1 ? "file" : "files";
fn("FAILED -- " + this.tests + " test " + blurb + " could be run: "
+ "alas, no output ever seen." + Test.Harness.LF);
} else {
pct = this.good / this.tests * 100;
var pctOK = 100 * this.ok / this.ran;
var subpct = (this.ran - this.ok) + "/" + this.ran
+ " subtests failed, " + pctOK.toPrecision(4) + "% okay.";
if (this.bad) {
bonusmsg = bonusmsg.replace(/^,?\s*/, '');
if (bonusmsg) fn(bonusmsg + '.' + Test.Harness.LF);
fn("Failed " + this.bad + "/" + this.tests + " test scripts, "
+ pct.toPrecision(4) + "% okay. " + subpct + Test.Harness.LF);
}
this.formatFailures(fn);
}
fn("Files=" + this.tests + ", Tests=" + this.ran + ", " + (time / 1000)
+ " seconds" + Test.Harness.LF);
};
Test.Harness.prototype.formatFailures = function () {
var table = '';
var failedStr = "Failed Test";
var middleStr = " Total Fail Failed ";
var listStr = "List of Failed";
var cols = 80;
// Figure out our longest name string for formatting purposes.
var maxNamelen = failedStr.length;
for (var i = 0; i < this.failures.length; i++) {
var len = this.failures[i].length;
if (len > maxNamelen) maxNamelen = len;
}
var listLen = cols - middleStr.length - maxNamelen.length;
if (listLen < listStr.length) {
listLen = listStr.length;
maxNamelen = cols - middleStr.length - listLen;
if (maxNamelen < failedStr.length) {
maxNamelen = failedStr.length;
cols = maxNamelen + middleStr.length + listLen;
}
}
var out = failedStr;
if (out.length < maxNamelen) {
for (var j = out.length; j < maxNamelen; j++) {
out += ' ';
}
}
out += ' ' + middleStr;
// XXX Need to finish implementing the text-only version of the failures
// table.
};
Test.Harness.prototype._bonusmsg = function () {
var bonusmsg = '';
if (this.bonus) {
bonusmsg = (" (" + this.bonus + " subtest" + (this.bonus > 1 ? 's' : '')
+ " UNEXPECTEDLY SUCCEEDED)");
}
if (this.skipped) {
bonusmsg += ", " + this.skipped + " test"
+ (this.skipped != 1 ? 's' : '');
if (this.subSkipped) {
bonusmsg += " and " + this.subSkipped + " subtest"
+ (this.subSkipped != 1 ? 's' : '');
}
bonusmsg += ' skipped';
} else if (this.subSkipped) {
bonusmsg += ", " + this.subSkipped + " subtest"
+ (this.subSkipped != 1 ? 's' : '') + " skipped";
}
return bonusmsg;
}
Test.Harness.prototype._failList = function (fails) {
var last = -1;
var dash = '';
var list = [];
for (var i = 0; i < fails.length; i++) {
if (dash) {
// We're in a series of numbers.
if (fails[i] - 1 == last) {
// We're still in it.
last = fails[i];
} else {
// End of the line.
list[list.length-1] += dash + last;
last = -1;
list.push(fails[i]);
dash = '';
}
} else if (fails[i] - 1 == last) {
// We're in a new series.
last = fails[i];
dash = '-';
} else {
// Not in a sequence.
list.push(fails[i]);
last = fails[i];
}
}
if (dash) list[list.length-1] += dash + last;
return list.join(' ');
}