681 lines
18 KiB
JavaScript

/*
Copyright (c) 2014, Oracle and/or its affiliates. All rights
reserved.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; version 2 of
the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
02110-1301 USA
*/
"use strict";
/* This test harness is documented in the README file.
*/
var path = require("path"),
fs = require("fs"),
assert = require("assert"),
util = require("util");
var udebug = unified_debug.getLogger("harness.js");
var re_matching_test_case = /Test\.js$/;
var disabledTests = {};
try {
disabledTests = require("../disabled-tests.conf").disabledTests;
}
catch(e) {}
/* Test
*/
function Test(name, phase) {
this.filename = "";
this.name = name;
this.phase = (typeof(phase) === 'number') ? phase : 2;
this.errorMessages = '';
this.index = 0;
this.failed = null;
this.skipped = false;
}
function SmokeTest(name) {
this.name = name;
this.phase = 0;
}
SmokeTest.prototype = new Test();
function ConcurrentTest(name) {
this.name = name;
this.phase = 1;
}
ConcurrentTest.prototype = new Test();
function SerialTest(name) {
this.name = name;
this.phase = 2;
}
SerialTest.prototype = new Test();
function ClearSmokeTest(name) {
this.name = name;
this.phase = 3;
}
ClearSmokeTest.prototype = new Test();
Test.prototype.test = function(result) {
udebug.log_detail('test starting:', this.suite.name, this.name);
this.result = result;
result.listener.startTest(this);
var runReturnCode;
udebug.log_detail('test.run:', this.suite.name, this.name);
try {
runReturnCode = this.run();
}
catch(e) {
console.log(this.name, 'threw exception & failed\n', e.stack);
this.failed = true;
result.fail(this, e);
return;
}
if(! runReturnCode) {
// async test must call Test.pass or Test.fail when done
udebug.log(this.name, 'started.');
return;
}
// Test ran synchronously. Fail if any error messages have been reported.
if(! this.skipped) {
if (this.errorMessages === '') {
udebug.log_detail(this.name, 'passed');
result.pass(this);
} else {
this.failed = true;
udebug.log_detail(this.name, 'failed');
result.fail(this, this.errorMessages);
}
}
};
Test.prototype.pass = function() {
if (this.failed !== null) {
console.log('Error: pass called with status already ' + (this.failed?'failed':'passed'));
assert(this.failed === null);
} else {
if (this.session && !this.session.isClosed()) {
// if session is open, close it
if (this.session.currentTransaction().isActive()) {
console.log('Test.pass found active transaction');
}
this.session.close();
}
this.failed = false;
this.result.pass(this);
}
};
Test.prototype.fail = function(message) {
if (this.failed !== null) {
console.log('Error: pass called with status already ' + (this.failed?'failed':'passed'));
assert(this.failed === null);
} else {
if (this.session && !this.session.isClosed()) {
// if session is open, close it
this.session.close();
}
this.failed = true;
if (message) {
this.appendErrorMessage(message);
this.stack = message.stack;
}
this.result.fail(this, { 'message' : this.errorMessages, 'stack': this.stack});
}
};
Test.prototype.appendErrorMessage = function(message) {
this.errorMessages += message;
this.errorMessages += '\n';
};
Test.prototype.error = Test.prototype.appendErrorMessage;
Test.prototype.failOnError = function() {
if (this.errorMessages !== '') {
this.fail();
} else {
this.pass();
}
};
Test.prototype.skip = function(message) {
this.skipped = true;
this.result.skip(this, message);
return true;
};
Test.prototype.isTest = function() { return true; };
Test.prototype.fullName = function() {
var n = "";
if(this.suite) { n = n + this.suite.name + " "; }
if(this.filename) { n = n + path.basename(this.filename) + " "; }
return n + this.name;
};
Test.prototype.run = function() {
throw {
"name" : "unimplementedTest",
"message" : "this test does not have a run() method"
};
};
function getType(obj) {
var type = typeof(obj);
if (type === 'object') return obj.constructor.name;
return type;
}
function compare(o1, o2) {
if (o1 == o2) return true;
if (o1 == null && o2 == null) return true;
if (typeof(o1) === 'undefined' && typeof(o2) === 'undefined') return true;
if (typeof(o1) !== typeof(o2)) return false;
if (o1.toString() === o2.toString()) return true;
return false;
}
Test.prototype.errorIfNotEqual = function(message, o1, o2) {
if (!compare(o1, o2)) {
var o1type = getType(o1);
var o2type = getType(o2);
message += ': expected (' + o1type + ') ' + o1 + '; actual (' + o2type + ') ' + o2 + '\n';
this.errorMessages += message;
}
};
Test.prototype.errorIfNotStrictEqual = function(message, o1, o2) {
if(o1 !== o2) {
var o1type = getType(o1);
var o2type = getType(o2);
message += ': expected (' + o1type + ') ' + o1 + '; actual (' + o2type + ') ' + o2 + '\n';
this.errorMessages += message;
}
};
Test.prototype.errorIfTrue = function(message, o1) {
if (o1) {
message += ': expected not true; actual ' + o1 + '\n';
this.errorMessages += message;
}
};
Test.prototype.errorIfNotTrue = function(message, o1) {
if (o1 !== true) {
message += ': expected true; actual ' + o1 + '\n';
this.errorMessages += message;
}
};
Test.prototype.errorIfNotError = function(message, o1) {
if (!o1) {
message += ' did not occur.\n';
this.errorMessages += message;
}
};
Test.prototype.errorIfNull = function(message, val) {
if(val === null) {
this.errorMessages += message;
}
};
Test.prototype.errorIfNotNull = function(message, val) {
if(val !== null) {
this.errorMessages += message;
}
};
/* Use this with the error argument in a callback */
Test.prototype.errorIfError = function(val) {
if(typeof val !== 'undefined' && val !== null) {
this.errorMessages += util.inspect(val);
}
};
/* Value must be defined and not-null
Function returns true if there was no error; false on error
*/
Test.prototype.errorIfUnset = function(message, value) {
var r = (typeof value === 'undefined' || value === null);
if(r) {
this.errorMessages += message;
}
return ! r;
};
Test.prototype.hasNoErrors = function() {
return this.errorMessages.length === 0;
};
/** Suite
* A suite consists of all tests in all test programs in a directory
*
*/
function Suite(name, path) {
this.name = name;
this.path = path;
this.tests = [];
this.currentTest = 0;
this.smokeTest = {};
this.concurrentTests = [];
this.numberOfConcurrentTests = 0;
this.numberOfConcurrentTestsCompleted = 0;
this.numberOfConcurrentTestsStarted = 0;
this.firstConcurrentTestIndex = -1;
this.serialTests = [];
this.numberOfSerialTests = 0;
this.firstSerialTestIndex = -1;
this.nextSerialTestIndex = -1;
this.clearSmokeTest = {};
this.testInFile = null;
this.suite = {};
this.numberOfRunningConcurrentTests = 0;
this.skipSmokeTest = false;
this.skipClearSmokeTest = false;
udebug.log_detail('Creating Suite for', name, path);
}
Suite.prototype.addTest = function(filename, test) {
this.filename = path.relative(mynode.fs.suites_dir, filename);
udebug.log_detail('Suite', this.name, 'adding test', test.name, 'from', this.filename);
test.filename = filename;
test.suite = this;
if(disabledTests && disabledTests[this.filename]) {
udebug.log("Skipping ", this.filename, "[DISABLED]");
}
else {
this.tests.push(test);
}
return test;
};
Suite.prototype.addTestsFromFile = function(f, onlyTests) {
var t, i, j, k, testList, testHash;
if(onlyTests) {
onlyTests = String(onlyTests);
testList = onlyTests.split(",");
testHash = [];
for(i = 0 ; i < testList.length ; i ++) {
k = Number(testList[i]) - 1;
testHash[k] = 1;
}
}
if(re_matching_test_case.test(f)) {
t = require(f);
if(typeof(t.tests) === 'object' && t.tests instanceof Array) {
for(j = 0 ; j < t.tests.length ; j++) {
if(onlyTests === null || testHash[j] === 1) {
this.addTest(f, t.tests[j]);
}
}
}
else if(typeof(t.isTest) === 'function' && t.isTest()) {
this.addTest(f, t);
}
else {
console.log("Warning: " + f + " does not export a Test.");
}
}
};
Suite.prototype.createTests = function() {
var stat = fs.statSync(this.path);
var suite, i;
if(stat.isFile()) {
var testFile = this.path;
this.path = path.dirname(testFile);
try {
this.addTestsFromFile(path.join(this.path, "SmokeTest.js"), null);
} catch(e1) {}
this.addTestsFromFile(testFile, this.testInFile);
try {
this.addTestsFromFile(path.join(this.path, "ClearSmokeTest.js"), null);
} catch(e2) {}
}
else if(stat.isDirectory()) {
var files = fs.readdirSync(this.path);
for(i = 0; i < files.length ; i++) {
this.addTestsFromFile(path.join(this.path, files[i]), null);
}
}
udebug.log_detail('Suite', this.name, 'found', this.tests.length, 'tests.');
this.tests.forEach(function(t, index) {
t.original = index;
});
this.tests.sort(function(a,b) {
// sort the tests by phase, preserving the original order within each phase
if(a.phase < b.phase) { return -1; }
if(a.phase === b.phase) { return (a.original < b.original)?-1:1; }
return 1;
});
suite = this;
this.tests.forEach(function(t, index) {
t.index = index;
t.suite = suite;
switch(t.phase) {
case 0:
suite.smokeTest = t;
break;
case 1:
suite.concurrentTests.push(t);
if (suite.firstConcurrentTestIndex === -1) {
suite.firstConcurrentTestIndex = t.index;
udebug.log_detail('Suite.createTests firstConcurrentTestIndex:', suite.firstConcurrentTestIndex);
}
break;
case 2:
suite.serialTests.push(t);
if (suite.firstSerialTestIndex === -1) {
suite.firstSerialTestIndex = t.index;
udebug.log_detail('Suite.createTests firstSerialTestIndex:', suite.firstSerialTestIndex);
}
break;
case 3:
suite.clearSmokeTest = t;
break;
}
udebug.log_detail('createTests sorted test case', t.name, ' ', t.phase, ' ', t.index);
});
suite.numberOfConcurrentTests = suite.concurrentTests.length;
udebug.log_detail('numberOfConcurrentTests for', suite.name, 'is', suite.numberOfConcurrentTests);
suite.numberOfSerialTests = suite.serialTests.length;
udebug.log_detail('numberOfSerialTests for', suite.name, 'is', suite.numberOfSerialTests);
};
Suite.prototype.runTests = function(result) {
var tc;
if (this.tests.length === 0) {
return false;
}
this.currentTest = 0;
tc = this.tests[this.currentTest];
switch (tc.phase) {
case 0:
// smoke test
// start the smoke test
if(this.skipSmokeTest) {
tc.result = result;
tc.skip("skipping SmokeTest");
}
else {
tc.test(result);
}
break;
case 1:
// concurrent test is the first test
// start all concurrent tests
this.startConcurrentTests(result);
break;
case 2:
// serial test is the first test
this.startSerialTests(result);
break;
case 3:
// clear smoke test is the first test
if(this.skipClearSmokeTest) {
tc.result = result;
tc.skip("skipping ClearSmokeTest");
}
else {
tc.test(result);
}
break;
}
return true;
};
Suite.prototype.startConcurrentTests = function(result) {
var self = this;
udebug.log_detail('Suite.startConcurrentTests');
if (this.firstConcurrentTestIndex !== -1) {
this.concurrentTests.forEach(function(testCase) {
udebug.log_detail('Suite.startConcurrentTests starting ', self.name, testCase.name);
testCase.test(result);
self.numberOfConcurrentTestsStarted++;
});
return false;
}
// else:
return this.startSerialTests(result);
};
Suite.prototype.startSerialTests = function(result) {
assert(result);
udebug.log_detail('Suite.startSerialTests');
if (this.firstSerialTestIndex !== -1) {
this.startNextSerialTest(this.firstSerialTestIndex, result);
return false;
}
// else:
return this.startClearSmokeTest(result);
};
Suite.prototype.startClearSmokeTest = function(result) {
assert(result);
udebug.log_detail('Suite.startClearSmokeTest');
if (this.skipClearSmokeTest) {
this.clearSmokeTest.result = result;
this.clearSmokeTest.skip("skipping ClearSmokeTest");
}
else if (this.clearSmokeTest && this.clearSmokeTest.test) {
this.clearSmokeTest.test(result);
return false;
}
return true;
};
Suite.prototype.startNextSerialTest = function(index, result) {
assert(result);
var testCase = this.tests[index];
testCase.test(result);
};
/* Notify the suite that a test has completed.
Returns false if there are more tests to be run,
true if suite is complete.
*/
Suite.prototype.testCompleted = function(testCase) {
var tc, index;
udebug.log_detail('Suite.testCompleted for', this.name, testCase.name, 'phase',
testCase.phase);
var result = testCase.result;
switch (testCase.phase) {
case 0: // the smoke test completed
if (testCase.failed) { // if the smoke test failed, we are done
return true;
}
udebug.log_detail('Suite.testCompleted; starting concurrent tests');
return this.startConcurrentTests(result);
case 1: // one of the concurrent tests completed
udebug.log_detail('Completed ', this.numberOfConcurrentTestsCompleted,
' out of ', this.numberOfConcurrentTests);
if (++this.numberOfConcurrentTestsCompleted === this.numberOfConcurrentTests) {
return this.startSerialTests(result); // go on to the serial tests
}
return false;
case 2: // one of the serial tests completed
index = testCase.index + 1;
if (index < this.tests.length) {
tc = this.tests[index];
if (tc.phase === 2) { // start another serial test
tc.test(result);
}
else if (tc.phase === 3) { // start the clear smoke test
this.startClearSmokeTest(result);
}
return false;
}
/* Done */
udebug.log_detail('Suite.testCompleted there is no ClearSmokeTest so we are done with ' + testCase.suite.name);
return true;
case 3: // the clear smoke test completed
udebug.log_detail('Suite.testCompleted completed ClearSmokeTest.');
return true;
}
};
/* Listener
*/
function Listener() {
this.started = 0;
this.ended = 0;
this.printStackTraces = false;
this.runningTests = {};
}
Listener.prototype.startTest = function(t) {
this.started++;
this.runningTests[t.fullName()] = 1;
};
Listener.prototype.pass = function(t) {
this.ended++;
delete this.runningTests[t.fullName()];
console.log("[pass]", t.fullName() );
};
Listener.prototype.skip = function(t, message) {
this.skipped++;
delete this.runningTests[t.fullName()];
console.log("[skipped]", t.fullName(), "\t", message);
};
Listener.prototype.fail = function(t, e) {
var message = "";
this.ended++;
delete this.runningTests[t.fullName()];
if (e) {
if (typeof(e.stack) !== 'undefined') {
t.stack = e.stack;
}
if (typeof(e.message) !== 'undefined') {
message = e.message;
} else {
message = e.toString();
}
}
if ((this.printStackTraces) && typeof(t.stack) !== 'undefined') {
message = t.stack;
}
if(t.phase === 0) {
console.log("[FailSmokeTest]", t.fullName(), "\t", message);
}
else {
console.log("[FAIL]", t.fullName(), "\t", message);
}
};
Listener.prototype.listRunningTests = function() {
console.log(this.runningTests);
};
/* QuietListener */
function QuietListener() {
this.started = 0;
this.ended = 0;
this.runningTests = {};
}
QuietListener.prototype.startTest = Listener.prototype.startTest;
QuietListener.prototype.pass = function(t) {
this.ended++;
delete this.runningTests[t.fullName()];
};
QuietListener.prototype.skip = QuietListener.prototype.pass;
QuietListener.prototype.fail = QuietListener.prototype.pass;
QuietListener.prototype.listRunningTests = Listener.prototype.listRunningTests;
/* FailOnlyListener */
function FailOnlyListener() {
this.fail = Listener.prototype.fail;
}
FailOnlyListener.prototype = new QuietListener();
/* Result
*/
function Result(driver) {
this.driver = driver;
this.passed = [];
this.failed = [];
this.skipped = [];
}
Result.prototype.pass = function(t) {
this.passed.push(t.name);
this.listener.pass(t);
this.driver.testCompleted(t);
};
Result.prototype.fail = function(t, e) {
this.failed.push(t.name);
this.listener.fail(t, e);
this.driver.testCompleted(t);
};
Result.prototype.skip = function(t, reason) {
this.skipped.push(t.name);
this.listener.skip(t, reason);
this.driver.testCompleted(t);
};
/* Exports from this module */
exports.Test = Test;
exports.Suite = Suite;
exports.Listener = Listener;
exports.QuietListener = QuietListener;
exports.FailOnlyListener = FailOnlyListener;
exports.Result = Result;
exports.SmokeTest = SmokeTest;
exports.ConcurrentTest = ConcurrentTest;
exports.SerialTest = SerialTest;
exports.ClearSmokeTest = ClearSmokeTest;