Mypal/js/src/tests/shell.js
2019-03-11 13:26:37 +03:00

875 lines
22 KiB
JavaScript

/* -*- 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
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// 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) {
/**********************************************************************
* CACHED PRIMORDIAL FUNCTIONALITY (before a test might overwrite it) *
**********************************************************************/
var undefined; // sigh
var Error = global.Error;
var Number = global.Number;
var String = global.String;
var TypeError = global.TypeError;
var ArrayIsArray = global.Array.isArray;
var ObjectCreate = global.Object.create;
var ObjectDefineProperty = global.Object.defineProperty;
var ReflectApply = global.Reflect.apply;
var StringPrototypeEndsWith = global.String.prototype.endsWith;
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 runningInShell = typeof window === "undefined";
/****************************
* GENERAL HELPER FUNCTIONS *
****************************/
// We could use Array.prototype.pop, but we don't so it's clear exactly what
// dependencies this function has on test-modifiable behavior (i.e. none).
function ArrayPop(arr) {
assertEq(ArrayIsArray(arr), true,
"ArrayPop must only be used on actual arrays");
var len = arr.length;
if (len === 0)
return undefined;
var v = arr[len - 1];
arr.length--;
return v;
}
// 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 StringEndsWith(str, needle) {
return ReflectApply(StringPrototypeEndsWith, str, [needle]);
}
/****************************
* 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) {
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 quit = global.quit;
if (typeof quit !== "function") {
// XXX There's something very strange about quit() in browser runs being a
// function that doesn't quit at all (!). We should get rid of quit()
// as an integral part of tests in favor of something else.
quit = function quit() {};
global.quit = quit;
}
/******************************************************
* TEST METADATA EXPORTS (these are of dubious value) *
******************************************************/
global.SECTION = "";
global.VERSION = "";
global.BUGNUMBER = "";
/*************************************************************************
* HARNESS-CENTRIC EXPORTS (we should generally work to eliminate these) *
*************************************************************************/
var PASSED = " PASSED! ";
global.PASSED = PASSED;
var FAILED = " FAILED! ";
global.FAILED = FAILED;
/** Set up test environment. */
function startTest() {
if (global.BUGNUMBER)
global.print("BUGNUMBER: " + global.BUGNUMBER);
}
global.startTest = startTest;
var callStack = [];
/**
* Puts funcName at the top of the call stack. This stack is used to show
* a function-reported-from field when reporting failures.
*/
function enterFunc(funcName) {
assertEq(typeof funcName, "string",
"enterFunc must be given a string funcName");
if (!StringEndsWith(funcName, "()"))
funcName += "()";
ArrayPush(callStack, funcName);
}
global.enterFunc = enterFunc;
/**
* Pops the top funcName off the call stack. funcName, if provided, is used
* to check push-pop balance.
*/
function exitFunc(funcName) {
assertEq(typeof funcName === "string" || typeof funcName === "undefined",
true,
"exitFunc must be given no arguments or a string");
var lastFunc = ArrayPop(callStack);
assertEq(typeof lastFunc, "string", "exitFunc called too many times");
if (funcName) {
if (!StringEndsWith(funcName, "()"))
funcName += "()";
if (lastFunc !== funcName) {
// XXX Eliminate this dependency on global.reportCompare's identity.
global.reportCompare(funcName, lastFunc,
"Test driver failure wrong exit function ");
}
}
}
global.exitFunc = exitFunc;
/** Peeks at the top of the call stack. */
function currentFunc() {
if (callStack.length == 0)
return "top level script";
return callStack[callStack.length - 1];
}
global.currentFunc = currentFunc;
// XXX This function is *only* used in harness functions and really shouldn't
// be exported.
var writeFormattedResult =
function writeFormattedResult(expect, actual, string, passed) {
print((passed ? PASSED : FAILED) + string + ' expected: ' + expect);
};
global.writeFormattedResult = writeFormattedResult;
/*****************************************************
* RHINO-SPECIFIC EXPORTS (are these used any more?) *
*****************************************************/
function inRhino() {
return typeof global.defineClass === "function";
}
global.inRhino = inRhino;
function GetContext() {
return global.Packages.com.netscape.javascript.Context.getCurrentContext();
}
global.GetContext = GetContext;
function OptLevel(i) {
i = Number(i);
var cx = GetContext();
cx.setOptimizationLevel(i);
}
global.OptLevel = OptLevel;
})(this);
(function(global) {
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;
}
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;
})(this);
var STATUS = "STATUS: ";
var gDelayTestDriverEnd = false;
var gTestcases = new Array();
var gTc = gTestcases.length;
var summary = '';
var description = '';
var expected = '';
var actual = '';
var msg = '';
/*
* constant strings
*/
var GLOBAL = this + '';
var DESCRIPTION;
var EXPECTED;
/*
* Signals to results.py that the current test case should be considered to
* have passed if it doesn't throw an exception.
*
* When the test suite is run in the browser, this function gets overridden by
* the same-named function in browser.js.
*/
function testPassesUnlessItThrows() {
print(PASSED);
}
/*
* wrapper for test case constructor that doesn't require the SECTION
* argument.
*/
function AddTestCase( description, expect, actual ) {
new TestCase( SECTION, description, expect, actual );
}
function TestCase(n, d, e, a)
{
this.name = n;
this.description = d;
this.expect = e;
this.actual = a;
this.passed = getTestCaseResult(e, a);
this.reason = '';
this.bugnumber = typeof(BUGNUMER) != 'undefined' ? BUGNUMBER : '';
this.type = (typeof window == 'undefined' ? 'shell' : 'browser');
({}).constructor.defineProperty(
gTestcases,
gTc++,
{
value: this,
enumerable: true,
configurable: true,
writable: true
}
);
}
gFailureExpected = false;
TestCase.prototype.dump = function () {
// let reftest handle error reporting, otherwise
// output a summary line.
if (typeof document != "object" ||
!document.location.href.match(/jsreftest.html/))
{
dump('\njstest: ' + this.path + ' ' +
'bug: ' + this.bugnumber + ' ' +
'result: ' + (this.passed ? 'PASSED':'FAILED') + ' ' +
'type: ' + this.type + ' ' +
'description: ' + toPrinted(this.description) + ' ' +
// 'expected: ' + toPrinted(this.expect) + ' ' +
// 'actual: ' + toPrinted(this.actual) + ' ' +
'reason: ' + toPrinted(this.reason) + '\n');
}
};
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 getTestCases()
{
return gTestcases;
}
/*
* 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 + ' ---');
}
/*
* Statuses current section of a test
*/
function inSection(x)
{
return "Section " + x + " of test - ";
}
/*
* Report a failure in the 'accepted' manner
*/
function reportFailure (msg)
{
var lines = msg.split ("\n");
var l;
var funcName = currentFunc();
var prefix = (funcName) ? "[reported from " + funcName + "] ": "";
for (var i=0; i<lines.length; i++)
print (FAILED + prefix + lines[i]);
}
/*
* Print a non-failure message.
*/
function printStatus (msg)
{
/* js1_6 had...
msg = String(msg);
msg = msg.toString();
*/
msg = msg.toString();
var lines = msg.split ("\n");
var l;
for (var i=0; i<lines.length; i++)
print (STATUS + lines[i]);
}
/*
* Print a bugnumber message.
*/
function printBugNumber (num)
{
BUGNUMBER = num;
print ('BUGNUMBER: ' + num);
}
function toPrinted(value)
{
value = String(value);
value = value.replace(/\\n/g, 'NL')
.replace(/\n/g, 'NL')
.replace(/\\r/g, 'CR')
.replace(/[^\x20-\x7E]+/g, escapeString);
return value;
}
function escapeString (str)
{
var a, b, c, d;
var len = str.length;
var result = "";
var digits = ["0", "1", "2", "3", "4", "5", "6", "7",
"8", "9", "A", "B", "C", "D", "E", "F"];
for (var i=0; i<len; i++)
{
var ch = str.charCodeAt(i);
a = digits[ch & 0xf];
ch >>= 4;
b = digits[ch & 0xf];
ch >>= 4;
if (ch)
{
c = digits[ch & 0xf];
ch >>= 4;
d = digits[ch & 0xf];
result += "\\u" + d + c + b + a;
}
else
{
result += "\\x" + b + a;
}
}
return result;
}
/*
* 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) + "' ";
}
var testcase = new TestCase("unknown-test-name", description, expected, actual);
testcase.reason = output;
// if running under reftest, let it handle result reporting.
if (typeof document != "object" ||
!document.location.href.match(/jsreftest.html/)) {
if (testcase.passed)
{
print(PASSED + description);
}
else
{
reportFailure (description + " : " + output);
}
}
return testcase.passed;
}
/*
* 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 = expectedRegExp.test(actual);
if (!matches) {
output += "Expected match to '" + toPrinted(expectedRegExp) +
"', Actual value '" + toPrinted(actual) + "' ";
}
var testcase = new TestCase("unknown-test-name", description, true, matches);
testcase.reason = output;
// if running under reftest, let it handle result reporting.
if (typeof document != "object" ||
!document.location.href.match(/jsreftest.html/)) {
if (testcase.passed)
{
print(PASSED + description);
}
else
{
reportFailure (description + " : " + output);
}
}
return testcase.passed;
}
/*
* An xorshift pseudo-random number generator see:
* https://en.wikipedia.org/wiki/Xorshift#xorshift.2A
* 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;
}
}
function compareSource(expect, actual, summary)
{
// compare source
var expectP = expect.
replace(/([(){},.:\[\]])/mg, ' $1 ').
replace(/(\w+)/mg, ' $1 ').
replace(/<(\/)? (\w+) (\/)?>/mg, '<$1$2$3>').
replace(/\s+/mg, ' ').
replace(/new (\w+)\s*\(\s*\)/mg, 'new $1');
var actualP = actual.
replace(/([(){},.:\[\]])/mg, ' $1 ').
replace(/(\w+)/mg, ' $1 ').
replace(/<(\/)? (\w+) (\/)?>/mg, '<$1$2$3>').
replace(/\s+/mg, ' ').
replace(/new (\w+)\s*\(\s*\)/mg, 'new $1');
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;
eval(expect);
try
{
eval(actual);
actualCompile = 'No Error';
}
catch(ex1)
{
actualCompile = ex1 + '';
}
reportCompare(expectCompile, actualCompile,
summary + ': compile actual');
}
catch(ex)
{
}
}
function optionsInit() {
// record initial values to support resetting
// options to their initial values
options.initvalues = {};
// record values in a stack to support pushing
// and popping options
options.stackvalues = [];
var optionNames = options().split(',');
for (var i = 0; i < optionNames.length; i++)
{
var optionName = optionNames[i];
if (optionName)
{
options.initvalues[optionName] = '';
}
}
}
function optionsClear() {
// turn off current settings
// except jit.
var optionNames = options().split(',');
for (var i = 0; i < optionNames.length; i++)
{
var optionName = optionNames[i];
if (optionName &&
optionName != "methodjit" &&
optionName != "methodjit_always" &&
optionName != "ion")
{
options(optionName);
}
}
}
function optionsPush()
{
var optionsframe = {};
options.stackvalues.push(optionsframe);
var optionNames = options().split(',');
for (var i = 0; i < optionNames.length; i++)
{
var optionName = optionNames[i];
if (optionName)
{
optionsframe[optionName] = '';
}
}
optionsClear();
}
function optionsPop()
{
var optionsframe = options.stackvalues.pop();
optionsClear();
for (optionName in optionsframe)
{
options(optionName);
}
}
function optionsReset() {
try
{
optionsClear();
// turn on initial settings
for (var optionName in options.initvalues)
{
if (!options.hasOwnProperty(optionName))
continue;
options(optionName);
}
}
catch(ex)
{
print('optionsReset: caught ' + ex);
}
}
if (typeof options == 'function')
{
optionsInit();
optionsClear();
}
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 Math.abs(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 test() {
for ( gTc=0; gTc < gTestcases.length; gTc++ ) {
// temporary hack to work around some unknown issue in 1.7
try
{
gTestcases[gTc].passed = writeTestCaseResult(
gTestcases[gTc].expect,
gTestcases[gTc].actual,
gTestcases[gTc].description +" = "+ gTestcases[gTc].actual );
gTestcases[gTc].reason += ( gTestcases[gTc].passed ) ? "" : "wrong value ";
}
catch(e)
{
print('test(): empty testcase for gTc = ' + gTc + ' ' + e);
}
}
return ( gTestcases );
}
/*
* Begin printing functions. These functions use the shell's
* print function. When running tests in the browser, browser.js
* overrides these functions to write to the page.
*/
function writeTestCaseResult( expect, actual, string ) {
var passed = getTestCaseResult( expect, actual );
// if running under reftest, let it handle result reporting.
if (typeof document != "object" ||
!document.location.href.match(/jsreftest.html/)) {
writeFormattedResult( expect, actual, string, passed );
}
return passed;
}
function writeHeaderToLog( string ) {
print( string );
}
/* end of print functions */
function jsTestDriverEnd()
{
// gDelayTestDriverEnd is used to
// delay collection of the test result and
// signal to Spider so that tests can continue
// to run after page load has fired. They are
// responsible for setting gDelayTestDriverEnd = true
// then when completed, setting gDelayTestDriverEnd = false
// then calling jsTestDriverEnd()
if (gDelayTestDriverEnd)
{
return;
}
try
{
optionsReset();
}
catch(ex)
{
dump('jsTestDriverEnd ' + ex);
}
for (var i = 0; i < gTestcases.length; i++)
{
gTestcases[i].dump();
}
}