Source code
Revision control
Copy as Markdown
Other Tools
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
// NOTE: If you're adding new test harness functionality -- first, should you
// at all? Most stuff is better in specific tests, or in nested shell.js
// or browser.js. Second, supposing you should, please add it to this
// IIFE for better modularity/resilience against tests that must do
// particularly bizarre things that might break the harness.
(function(global) {
"use strict";
/**********************************************************************
* CACHED PRIMORDIAL FUNCTIONALITY (before a test might overwrite it) *
**********************************************************************/
var undefined; // sigh
var Error = global.Error;
var Function = global.Function;
var Number = global.Number;
var RegExp = global.RegExp;
var String = global.String;
var Symbol = global.Symbol;
var TypeError = global.TypeError;
var ArrayIsArray = global.Array.isArray;
var MathAbs = global.Math.abs;
var ObjectCreate = global.Object.create;
var ObjectDefineProperty = global.Object.defineProperty;
var ReflectApply = global.Reflect.apply;
var RegExpPrototypeExec = global.RegExp.prototype.exec;
var StringPrototypeCharCodeAt = global.String.prototype.charCodeAt;
var StringPrototypeIndexOf = global.String.prototype.indexOf;
var StringPrototypeSubstring = global.String.prototype.substring;
var runningInBrowser = typeof global.window !== "undefined";
if (runningInBrowser) {
// Certain cached functionality only exists (and is only needed) when
// running in the browser. Segregate that caching here.
var SpecialPowersSetGCZeal =
global.SpecialPowers ? global.SpecialPowers.setGCZeal : undefined;
}
var evaluate = global.evaluate;
var options = global.options;
/****************************
* GENERAL HELPER FUNCTIONS *
****************************/
// We *cannot* use Array.prototype.push for this, because that function sets
// the new trailing element, which could invoke a setter (left by a test) on
// Array.prototype or Object.prototype.
function ArrayPush(arr, val) {
assertEq(ArrayIsArray(arr), true,
"ArrayPush must only be used on actual arrays");
var desc = ObjectCreate(null);
desc.value = val;
desc.enumerable = true;
desc.configurable = true;
desc.writable = true;
ObjectDefineProperty(arr, arr.length, desc);
}
function StringCharCodeAt(str, index) {
return ReflectApply(StringPrototypeCharCodeAt, str, [index]);
}
function StringSplit(str, delimiter) {
assertEq(typeof str === "string" && typeof delimiter === "string", true,
"StringSplit must be called with two string arguments");
assertEq(delimiter.length > 0, true,
"StringSplit doesn't support an empty delimiter string");
var parts = [];
var last = 0;
while (true) {
var i = ReflectApply(StringPrototypeIndexOf, str, [delimiter, last]);
if (i < 0) {
if (last < str.length)
ArrayPush(parts, ReflectApply(StringPrototypeSubstring, str, [last]));
return parts;
}
ArrayPush(parts, ReflectApply(StringPrototypeSubstring, str, [last, i]));
last = i + delimiter.length;
}
}
function shellOptionsClear() {
assertEq(runningInBrowser, false, "Only called when running in the shell.");
// Return early if no options are set.
var currentOptions = options ? options() : "";
if (currentOptions === "")
return;
// Turn off current settings.
var optionNames = StringSplit(currentOptions, ",");
for (var i = 0; i < optionNames.length; i++) {
options(optionNames[i]);
}
}
/****************************
* TESTING FUNCTION EXPORTS *
****************************/
function SameValue(v1, v2) {
// We could |return Object.is(v1, v2);|, but that's less portable.
if (v1 === 0 && v2 === 0)
return 1 / v1 === 1 / v2;
if (v1 !== v1 && v2 !== v2)
return true;
return v1 === v2;
}
var assertEq = global.assertEq;
if (typeof assertEq !== "function") {
assertEq = function assertEq(actual, expected, message) {
if (!SameValue(actual, expected)) {
throw new TypeError(`Assertion failed: got "${actual}", expected "${expected}"` +
(message ? ": " + message : ""));
}
};
global.assertEq = assertEq;
}
function assertEqArray(actual, expected) {
var len = actual.length;
assertEq(len, expected.length, "mismatching array lengths");
var i = 0;
try {
for (; i < len; i++)
assertEq(actual[i], expected[i], "mismatch at element " + i);
} catch (e) {
throw new Error(`Exception thrown at index ${i}: ${e}`);
}
}
global.assertEqArray = assertEqArray;
function assertThrows(f) {
if (arguments.length != 1) {
throw new Error("Too many arguments to assertThrows; maybe you meant assertThrowsInstanceOf?");
}
var ok = false;
try {
f();
} catch (exc) {
ok = true;
}
if (!ok)
throw new Error(`Assertion failed: ${f} did not throw as expected`);
}
global.assertThrows = assertThrows;
function assertThrowsInstanceOf(f, ctor, msg) {
var fullmsg;
try {
f();
} catch (exc) {
if (exc instanceof ctor)
return;
fullmsg = `Assertion failed: expected exception ${ctor.name}, got ${exc}`;
}
if (fullmsg === undefined)
fullmsg = `Assertion failed: expected exception ${ctor.name}, no exception thrown`;
if (msg !== undefined)
fullmsg += " - " + msg;
throw new Error(fullmsg);
}
global.assertThrowsInstanceOf = assertThrowsInstanceOf;
/****************************
* UTILITY FUNCTION EXPORTS *
****************************/
var dump = global.dump;
if (typeof global.dump === "function") {
// A presumptively-functional |dump| exists, so no need to do anything.
} else {
// We don't have |dump|. Try to simulate the desired effect another way.
if (runningInBrowser) {
// We can't actually print to the console: |global.print| invokes browser
// printing functionality here (it's overwritten just below), and
// |global.dump| isn't a function that'll dump to the console (presumably
// because the preference to enable |dump| wasn't set). Just make it a
// no-op.
dump = function() {};
} else {
// |print| prints to stdout: make |dump| do likewise.
dump = global.print;
}
global.dump = dump;
}
var print;
if (runningInBrowser) {
// We're executing in a browser. Using |global.print| would invoke browser
// printing functionality: not what tests want! Instead, use a print
// function that syncs up with the test harness and console.
print = function print() {
var s = "TEST-INFO | ";
for (var i = 0; i < arguments.length; i++)
s += String(arguments[i]) + " ";
// Dump the string to the console for developers and the harness.
dump(s + "\n");
// AddPrintOutput doesn't require HTML special characters be escaped.
global.AddPrintOutput(s);
};
global.print = print;
} else {
// We're executing in a shell, and |global.print| is the desired function.
print = global.print;
}
var gczeal = global.gczeal;
if (typeof gczeal !== "function") {
if (typeof SpecialPowersSetGCZeal === "function") {
gczeal = function gczeal(z) {
SpecialPowersSetGCZeal(z);
};
} else {
gczeal = function() {}; // no-op if not available
}
global.gczeal = gczeal;
}
// Evaluates the given source code as global script code. browser.js provides
// a different implementation for this function.
var evaluateScript = global.evaluateScript;
if (typeof evaluate === "function" && typeof evaluateScript !== "function") {
evaluateScript = function evaluateScript(code) {
evaluate(String(code));
};
global.evaluateScript = evaluateScript;
}
function toPrinted(value) {
value = String(value);
var digits = "0123456789ABCDEF";
var result = "";
for (var i = 0; i < value.length; i++) {
var ch = StringCharCodeAt(value, i);
if (ch === 0x5C && i + 1 < value.length) {
var d = value[i + 1];
if (d === "n") {
result += "NL";
i++;
} else if (d === "r") {
result += "CR";
i++;
} else {
result += "\\";
}
} else if (ch === 0x0A) {
result += "NL";
} else if (ch < 0x20 || ch > 0x7E) {
var a = digits[ch & 0xf];
ch >>= 4;
var b = digits[ch & 0xf];
ch >>= 4;
if (ch) {
var c = digits[ch & 0xf];
ch >>= 4;
var d = digits[ch & 0xf];
result += "\\u" + d + c + b + a;
} else {
result += "\\x" + b + a;
}
} else {
result += value[i];
}
}
return result;
}
/*
* An xorshift pseudo-random number generator see:
* This generator will always produce a value, n, where
* 0 <= n <= 255
*/
function *XorShiftGenerator(seed, size) {
let x = seed;
for (let i = 0; i < size; i++) {
x ^= x >> 12;
x ^= x << 25;
x ^= x >> 27;
yield x % 256;
}
}
global.XorShiftGenerator = XorShiftGenerator;
/*************************************************************************
* HARNESS-CENTRIC EXPORTS (we should generally work to eliminate these) *
*************************************************************************/
var PASSED = " PASSED! ";
var FAILED = " FAILED! ";
/*
* Same as `new TestCase(description, expect, actual)`, except it doesn't
* return the newly created test case object.
*/
function AddTestCase(description, expect, actual) {
new TestCase(description, expect, actual);
}
global.AddTestCase = AddTestCase;
var testCasesArray = [];
function TestCase(d, e, a, r) {
this.description = d;
this.expect = e;
this.actual = a;
this.passed = getTestCaseResult(e, a);
this.reason = typeof r !== 'undefined' ? String(r) : '';
ArrayPush(testCasesArray, this);
}
global.TestCase = TestCase;
TestCase.prototype = ObjectCreate(null);
TestCase.prototype.testPassed = (function TestCase_testPassed() { return this.passed; });
TestCase.prototype.testFailed = (function TestCase_testFailed() { return !this.passed; });
TestCase.prototype.testDescription = (function TestCase_testDescription() { return this.description + ' ' + this.reason; });
function getTestCaseResult(expected, actual) {
if (typeof expected !== typeof actual)
return false;
if (typeof expected !== 'number')
// Note that many tests depend on the use of '==' here, not '==='.
return actual == expected;
// Distinguish NaN from other values. Using x !== x comparisons here
// works even if tests redefine isNaN.
if (actual !== actual)
return expected !== expected;
if (expected !== expected)
return false;
// Tolerate a certain degree of error.
if (actual !== expected)
return MathAbs(actual - expected) <= 1E-10;
// Here would be a good place to distinguish 0 and -0, if we wanted
// to. However, doing so would introduce a number of failures in
// areas where they don't seem important. For example, the WeekDay
// function in ECMA-262 returns -0 for Sundays before the epoch, but
// the Date functions in SpiderMonkey specified in terms of WeekDay
// often don't. This seems unimportant.
return true;
}
function reportTestCaseResult(description, expected, actual, output) {
var testcase = new TestCase(description, expected, actual, output);
// if running under reftest, let it handle result reporting.
if (!runningInBrowser) {
if (testcase.passed) {
print(PASSED + description);
} else {
reportFailure(description + " : " + output);
}
}
}
function getTestCases() {
return testCasesArray;
}
global.getTestCases = getTestCases;
/*
* The test driver searches for such a phrase in the test output.
* If such phrase exists, it will set n as the expected exit code.
*/
function expectExitCode(n) {
print('--- NOTE: IN THIS TESTCASE, WE EXPECT EXIT CODE ' + n + ' ---');
}
global.expectExitCode = expectExitCode;
/*
* Statuses current section of a test
*/
function inSection(x) {
return "Section " + x + " of test - ";
}
global.inSection = inSection;
/*
* Report a failure in the 'accepted' manner
*/
function reportFailure(msg) {
msg = String(msg);
var lines = StringSplit(msg, "\n");
for (var i = 0; i < lines.length; i++)
print(FAILED + " " + lines[i]);
}
global.reportFailure = reportFailure;
/*
* Print a non-failure message.
*/
function printStatus(msg) {
msg = String(msg);
var lines = StringSplit(msg, "\n");
for (var i = 0; i < lines.length; i++)
print("STATUS: " + lines[i]);
}
global.printStatus = printStatus;
/*
* Print a bugnumber message.
*/
function printBugNumber(num) {
print('BUGNUMBER: ' + num);
}
global.printBugNumber = printBugNumber;
/*
* Compare expected result to actual result, if they differ (in value and/or
* type) report a failure. If description is provided, include it in the
* failure report.
*/
function reportCompare(expected, actual, description) {
var expected_t = typeof expected;
var actual_t = typeof actual;
var output = "";
if (typeof description === "undefined")
description = "";
if (expected_t !== actual_t)
output += `Type mismatch, expected type ${expected_t}, actual type ${actual_t} `;
if (expected != actual)
output += `Expected value '${toPrinted(expected)}', Actual value '${toPrinted(actual)}' `;
reportTestCaseResult(description, expected, actual, output);
}
global.reportCompare = reportCompare;
/*
* Attempt to match a regular expression describing the result to
* the actual result, if they differ (in value and/or
* type) report a failure. If description is provided, include it in the
* failure report.
*/
function reportMatch(expectedRegExp, actual, description) {
var expected_t = "string";
var actual_t = typeof actual;
var output = "";
if (typeof description === "undefined")
description = "";
if (expected_t !== actual_t)
output += `Type mismatch, expected type ${expected_t}, actual type ${actual_t} `;
var matches = ReflectApply(RegExpPrototypeExec, expectedRegExp, [actual]) !== null;
if (!matches) {
output +=
`Expected match to '${toPrinted(expectedRegExp)}', Actual value '${toPrinted(actual)}' `;
}
reportTestCaseResult(description, true, matches, output);
}
global.reportMatch = reportMatch;
function compareSource(expect, actual, summary) {
// compare source
var expectP = String(expect);
var actualP = String(actual);
print('expect:\n' + expectP);
print('actual:\n' + actualP);
reportCompare(expectP, actualP, summary);
// actual must be compilable if expect is?
try {
var expectCompile = 'No Error';
var actualCompile;
Function(expect);
try {
Function(actual);
actualCompile = 'No Error';
} catch(ex1) {
actualCompile = ex1 + '';
}
reportCompare(expectCompile, actualCompile,
summary + ': compile actual');
} catch(ex) {
}
}
global.compareSource = compareSource;
function test() {
var testCases = getTestCases();
for (var i = 0; i < testCases.length; i++) {
var testCase = testCases[i];
testCase.reason += testCase.passed ? "" : "wrong value ";
// if running under reftest, let it handle result reporting.
if (!runningInBrowser) {
var message = `${testCase.description} = ${testCase.actual} expected: ${testCase.expect}`;
print((testCase.passed ? PASSED : FAILED) + message);
}
}
}
global.test = test;
// This function uses the shell's print function. When running tests in the
// browser, browser.js overrides this function to write to the page.
function writeHeaderToLog(string) {
print(string);
}
global.writeHeaderToLog = writeHeaderToLog;
/************************************
* PROMISE TESTING FUNCTION EXPORTS *
************************************/
function getPromiseResult(promise) {
var result, error, caught = false;
promise.then(r => { result = r; },
e => { caught = true; error = e; });
drainJobQueue();
if (caught)
throw error;
return result;
}
global.getPromiseResult = getPromiseResult;
function assertEventuallyEq(promise, expected) {
assertEq(getPromiseResult(promise), expected);
}
global.assertEventuallyEq = assertEventuallyEq;
function assertEventuallyThrows(promise, expectedErrorType) {
assertThrowsInstanceOf(() => getPromiseResult(promise), expectedErrorType);
};
global.assertEventuallyThrows = assertEventuallyThrows;
function assertEventuallyDeepEq(promise, expected) {
assertDeepEq(getPromiseResult(promise), expected);
};
global.assertEventuallyDeepEq = assertEventuallyDeepEq;
/*******************************************
* RUN ONCE CODE TO SETUP ADDITIONAL STATE *
*******************************************/
// Clear all options before running any tests. browser.js performs this
// set-up as part of its jsTestDriverBrowserInit function.
if (!runningInBrowser) {
shellOptionsClear();
}
if (!runningInBrowser) {
// Set the minimum heap size for parallel marking to zero for testing
// purposes. We don't have access to gcparam in the browser.
gcparam('parallelMarkingThresholdMB', 0);
}
})(this);
var DESCRIPTION;