/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla Communicator client code, released * March 31, 1998. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1998 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Rob Ginda rginda@netscape.com * Bob Clary bob@bclary.com * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ // Spidermonkey shell now defaults to 1.8, so set the basic version to // 1.5 for backwards compatibility. if (typeof version != 'undefined') { version(150); } var STATUS = "STATUS: "; var VERBOSE = false; var SECT_PREFIX = 'Section '; var SECT_SUFFIX = ' of test - '; var callStack = new Array(); var gTestfile; var gTestPath; var gTestsuite; var gTestsubsuite; var gDelayTestDriverEnd = false; var gTestcases = new Array(); var gTc = gTestcases.length; var BUGNUMBER = ''; var summary = ''; var description = ''; var expected = ''; var actual = ''; var msg = ''; var SECTION = ""; var VERSION = ""; var BUGNUMBER = ""; /* * constant strings */ var GLOBAL = this + ''; var PASSED = " PASSED! "; var FAILED = " FAILED! "; var DEBUG = false; var DESCRIPTION; var EXPECTED; /* * wrapper for test case constructor that doesn't require the SECTION * argument. */ function AddTestCase( description, expect, actual ) { new TestCase( SECTION, description, expect, actual ); } /* * Set up test environment. * */ function startTest() { // print out bugnumber if ( BUGNUMBER ) { print ("BUGNUMBER: " + BUGNUMBER ); } if ( typeof version != 'function') { return; } // JavaScript 1.3 is supposed to be compliant ecma version 1.0 if ( VERSION == "ECMA_1" ) { version ( "130" ); } else if ( VERSION == "JS_1.8" || gTestsuite == 'js1_8') { version ( "180" ); } else if ( VERSION == "JS_1.7" || gTestsuite == 'js1_7') { version ( "170" ); } else if ( VERSION == "JS_1.6" || gTestsuite == 'js1_6') { version ( "160" ); } else if ( VERSION == "JS_1.5" || gTestsuite == 'js1_5') { version ( "150" ); } else if ( VERSION == "JS_1.4" || gTestsuite == 'js1_4') { version ( "140" ); } else if ( VERSION == "JS_1.3" || gTestsuite == 'js1_3') { version ( "130" ); } else if ( VERSION == "JS_1.2" || gTestsuite == 'js1_2') { version ( "120" ); } else if ( VERSION == "JS_1.1" || gTestsuite == 'js1_1') { version ( "110" ); } } function TestCase(n, d, e, a) { this.path = (typeof gTestPath == 'undefined') ? (gTestsuite + '/' + gTestsubsuite + '/' + gTestfile) : gTestPath; this.file = gTestfile; 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'); gTestcases[gTc++] = this; } TestCase.prototype.dump = function () { 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'); }; /* * 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 SECT_PREFIX + x + SECT_SUFFIX; } /* * 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>= 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 = ''; } else if (VERBOSE) { printStatus ("Comparing '" + description + "'"); } if (expected_t != actual_t) { output += "Type mismatch, expected type " + expected_t + ", actual type " + actual_t + " "; } else if (VERBOSE) { printStatus ("Expected type '" + expected_t + "' matched actual " + "type '" + actual_t + "'"); } if (expected != actual) { output += "Expected value '" + toPrinted(expected) + "', Actual value '" + toPrinted(actual) + "' "; } else if (VERBOSE) { printStatus ("Expected value '" + toPrinted(expected) + "' matched actual value '" + toPrinted(actual) + "'"); } var testcase = new TestCase(gTestfile, description, expected, actual); testcase.reason = output; 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 = ''; } else if (VERBOSE) { printStatus ("Comparing '" + description + "'"); } if (expected_t != actual_t) { output += "Type mismatch, expected type " + expected_t + ", actual type " + actual_t + " "; } else if (VERBOSE) { printStatus ("Expected type '" + expected_t + "' matched actual " + "type '" + actual_t + "'"); } var matches = expectedRegExp.test(actual); if (!matches) { output += "Expected match to '" + toPrinted(expectedRegExp) + "', Actual value '" + toPrinted(actual) + "' "; } else if (VERBOSE) { printStatus ("Expected match to '" + toPrinted(expectedRegExp) + "' matched actual value '" + toPrinted(actual) + "'"); } var testcase = new TestCase(gTestfile, description, true, matches); testcase.reason = output; if (testcase.passed) { print(PASSED + description); } else { reportFailure (description + " : " + output); } return testcase.passed; } /* * 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) { if (!funcName.match(/\(\)$/)) funcName += "()"; callStack.push(funcName); } /* * Pops the top funcName off the call stack. funcName is optional, and can be * used to check push-pop balance. */ function exitFunc (funcName) { var lastFunc = callStack.pop(); if (funcName) { if (!funcName.match(/\(\)$/)) funcName += "()"; if (lastFunc != funcName) reportCompare(funcName, lastFunc, "Test driver failure wrong exit function "); } } /* * Peeks at the top of the call stack. */ function currentFunc() { return callStack[callStack.length - 1]; } /* Calculate the "order" of a set of data points {X: [], Y: []} by computing successive "derivatives" of the data until the data is exhausted or the derivative is linear. */ function BigO(data) { var order = 0; var origLength = data.X.length; while (data.X.length > 2) { var lr = new LinearRegression(data); if (lr.b > 1e-6) { // only increase the order if the slope // is "great" enough order++; } if (lr.r > 0.98 || lr.Syx < 1 || lr.b < 1e-6) { // terminate if close to a line lr.r // small error lr.Syx // small slope lr.b break; } data = dataDeriv(data); } if (2 == origLength - order) { order = Number.POSITIVE_INFINITY; } return order; function LinearRegression(data) { /* y = a + bx for data points (Xi, Yi); 0 <= i < n b = (n*SUM(XiYi) - SUM(Xi)*SUM(Yi))/(n*SUM(Xi*Xi) - SUM(Xi)*SUM(Xi)) a = (SUM(Yi) - b*SUM(Xi))/n */ var i; if (data.X.length != data.Y.length) { throw 'LinearRegression: data point length mismatch'; } if (data.X.length < 3) { throw 'LinearRegression: data point length < 2'; } var n = data.X.length; var X = data.X; var Y = data.Y; this.Xavg = 0; this.Yavg = 0; var SUM_X = 0; var SUM_XY = 0; var SUM_XX = 0; var SUM_Y = 0; var SUM_YY = 0; for (i = 0; i < n; i++) { SUM_X += X[i]; SUM_XY += X[i]*Y[i]; SUM_XX += X[i]*X[i]; SUM_Y += Y[i]; SUM_YY += Y[i]*Y[i]; } this.b = (n * SUM_XY - SUM_X * SUM_Y)/(n * SUM_XX - SUM_X * SUM_X); this.a = (SUM_Y - this.b * SUM_X)/n; this.Xavg = SUM_X/n; this.Yavg = SUM_Y/n; var SUM_Ydiff2 = 0; var SUM_Xdiff2 = 0; var SUM_XdiffYdiff = 0; for (i = 0; i < n; i++) { var Ydiff = Y[i] - this.Yavg; var Xdiff = X[i] - this.Xavg; SUM_Ydiff2 += Ydiff * Ydiff; SUM_Xdiff2 += Xdiff * Xdiff; SUM_XdiffYdiff += Xdiff * Ydiff; } var Syx2 = (SUM_Ydiff2 - Math.pow(SUM_XdiffYdiff/SUM_Xdiff2, 2))/(n - 2); var r2 = Math.pow((n*SUM_XY - SUM_X * SUM_Y), 2) / ((n*SUM_XX - SUM_X*SUM_X)*(n*SUM_YY-SUM_Y*SUM_Y)); this.Syx = Math.sqrt(Syx2); this.r = Math.sqrt(r2); } function dataDeriv(data) { if (data.X.length != data.Y.length) { throw 'length mismatch'; } var length = data.X.length; if (length < 2) { throw 'length ' + length + ' must be >= 2'; } var X = data.X; var Y = data.Y; var deriv = {X: [], Y: [] }; for (var i = 0; i < length - 1; i++) { deriv.X[i] = (X[i] + X[i+1])/2; deriv.Y[i] = (Y[i+1] - Y[i])/(X[i+1] - X[i]); } return deriv; } return 0; } 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 var optionNames = options().split(','); for (var i = 0; i < optionNames.length; i++) { var optionName = optionNames[i]; if (optionName) { 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() { optionsClear(); // turn on initial settings for (optionName in options.initvalues) { options(optionName); } } if (typeof options == 'function') { optionsInit(); optionsClear(); } function getTestCaseResult(expected, actual) { var expected_t = typeof expected; var actual_t = typeof actual; var passed = true; // because ( NaN == NaN ) always returns false, need to do // a special compare to see if we got the right result. if ( actual != actual ) { if ( actual_t == "object" ) { actual = "NaN object"; } else { actual = "NaN number"; } } if ( expected != expected ) { if ( expected_t == "object" ) { expected = "NaN object"; } else { expected = "NaN number"; } } if (expected_t != actual_t) { passed = false; } else if (expected != actual) { if (expected_t != 'number' || (Math.abs(actual - expected) > 1E-10)) { passed = false; } } return passed; } if (typeof dump == 'undefined') { if (typeof window == 'undefined' && typeof print == 'function') { dump = print; } else { dump = (function () {}); } } 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); } } stopTest(); return ( gTestcases ); } /* * Begin printing functions. These functions use the shell's * print function. When running tests in the browser, these * functions, override these functions with functions that use * document.write. */ function writeTestCaseResult( expect, actual, string ) { var passed = getTestCaseResult( expect, actual ); writeFormattedResult( expect, actual, string, passed ); return passed; } function writeFormattedResult( expect, actual, string, passed ) { var s = ( passed ? PASSED : FAILED ) + string + ' expected: ' + expect; print(s); return passed; } function writeHeaderToLog( string ) { print( string ); } /* end of print functions */ /* * When running in the shell, run the garbage collector after the * test has completed. */ function stopTest() { var gc; if ( gc != undefined ) { gc(); } } /* * Convenience function for displaying failed test cases. Useful * when running tests manually. * */ function getFailedCases() { for ( var i = 0; i < gTestcases.length; i++ ) { if ( ! gTestcases[i].passed ) { print( gTestcases[i].description + " = " +gTestcases[i].actual + " expected: " + gTestcases[i].expect ); } } } 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(); } } function jit(on) { if (on && !options().match(/jit/)) { options('jit'); } else if (!on && options().match(/jit/)) { options('jit'); } } /* * Some tests need to know if we are in Rhino as opposed to SpiderMonkey */ function inRhino() { return (typeof defineClass == "function"); } /* these functions are useful for running tests manually in Rhino */ function GetContext() { return Packages.com.netscape.javascript.Context.getCurrentContext(); } function OptLevel( i ) { i = Number(i); var cx = GetContext(); cx.setOptimizationLevel(i); } /* end of Rhino functions */