diff --git a/test/harness/framework.js b/test/harness/framework.js new file mode 100644 index 0000000000..bf799b801c --- /dev/null +++ b/test/harness/framework.js @@ -0,0 +1,90 @@ +// Copyright 2009 the Sputnik authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +function Test262Error(message) { + this.message = message; +} + +Test262Error.prototype.toString = function () { + return "Test262 Error: " + this.message; +}; + +function testFailed(message) { + throw new Test262Error(message); +} + +function testPrint(message) { + +} + +function $PRINT(message) { + testPrint(message); +} +function $INCLUDE(message) { } +function $ERROR(message) { + testFailed(message); +} +function $FAIL(message) { + testFailed(message); +} + +/** + * It is not yet clear that runTestCase should pass the global object + * as the 'this' binding in the call to testcase. + */ +var runTestCase = (function(global) { + return function(testcase) { + if (!testcase.call(global)) { + testFailed('test function returned falsy'); + } + }; +})(this); + +function assertTruthy(value) { + if (!value) { + testFailed('test return falsy'); + } +} + + +/** + * falsy means we expect no error. + * truthy means we expect some error. + * A non-empty string means we expect an error whose .name is that string. + */ +var expectedErrorName = false; + +/** + * What was thrown, or the string 'Falsy' if something falsy was thrown. + * null if test completed normally. + */ +var actualError = null; + +function testStarted(expectedErrName) { + expectedErrorName = expectedErrName; +} + +function testFinished() { + var actualErrorName = actualError && (actualError.name || + 'SomethingThrown'); + if (actualErrorName) { + if (expectedErrorName) { + if (typeof expectedErrorName === 'string') { + if (expectedErrorName === actualErrorName) { + return; + } + testFailed('Threw ' + actualErrorName + + ' instead of ' + expectedErrorName); + } + return; + } + throw actualError; + } + if (expectedErrorName) { + if (typeof expectedErrorName === 'string') { + testFailed('Completed instead of throwing ' + + expectedErrorName); + } + testFailed('Completed instead of throwing'); + } +} diff --git a/test/harness/sputnikLib.js b/test/harness/sputnikLib.js index 447a03bdfc..5a22af167d 100644 --- a/test/harness/sputnikLib.js +++ b/test/harness/sputnikLib.js @@ -2,16 +2,16 @@ // Copyright 2009 the Sputnik authors. All rights reserved. // This code is governed by the BSD license found in the LICENSE file. -function SputnikError(message) { +function Test262Error(message) { if (message) this.message = message; } -SputnikError.prototype.toString = function () { +Test262Error.prototype.toString = function () { return "Test262 Error: " + this.message; }; function testFailed(message) { - throw new SputnikError(message); + throw new Test262Error(message); } @@ -47,8 +47,8 @@ function $FAIL(message) { function getPrecision(num) { //TODO: Create a table of prec's, - // because using Math for testing Math isn't that correct. - + // because using Math for testing Math isn't that correct. + log2num = Math.log(Math.abs(num))/Math.LN2; pernum = Math.ceil(log2num); return(2 * Math.pow(2, -52 + pernum)); @@ -71,7 +71,7 @@ function isEqual(num1, num2) { return(true); } - prec = getPrecision(Math.min(Math.abs(num1), Math.abs(num2))); + prec = getPrecision(Math.min(Math.abs(num1), Math.abs(num2))); return(Math.abs(num1 - num2) <= prec); //return(num1 === num2); } @@ -86,10 +86,10 @@ function ToInteger(p) { if(isNaN(x)){ return +0; } - - if((x === +0) - || (x === -0) - || (x === Number.POSITIVE_INFINITY) + + if((x === +0) + || (x === -0) + || (x === Number.POSITIVE_INFINITY) || (x === Number.NEGATIVE_INFINITY)){ return x; } @@ -170,10 +170,10 @@ function YearFromTime(t) { t = Number(t); var sign = ( t < 0 ) ? -1 : 1; var year = ( sign < 0 ) ? 1969 : 1970; - + for(var time = 0;;year += sign){ time = TimeFromYear(year); - + if(sign > 0 && time > t){ year -= sign; break; @@ -184,11 +184,11 @@ function YearFromTime(t) { }; return year; } - + function InLeapYear(t){ if(DaysInYear(YearFromTime(t)) == 365) return 0; - + if(DaysInYear(YearFromTime(t)) == 366) return 1; } @@ -247,7 +247,7 @@ var LocalTZA = $LocalTZ*msPerHour; function DaysInMonth(m, leap) { m = m%12; - + //April, June, Sept, Nov if(m == 3 || m == 5 || m == 8 || m == 10 ) { return 30; @@ -266,7 +266,7 @@ function GetSundayInMonth(t, m, count){ var year = YearFromTime(t); var leap = InLeapYear(t); var day = 0; - + if(m >= 1) day += DaysInMonth(0, leap); if(m >= 2) day += DaysInMonth(1, leap); if(m >= 3) day += DaysInMonth(2, leap); @@ -278,25 +278,25 @@ function GetSundayInMonth(t, m, count){ if(m >= 9) day += DaysInMonth(8, leap); if(m >= 10) day += DaysInMonth(9, leap); if(m >= 11) day += DaysInMonth(10, leap); - + var month_start = TimeFromYear(year)+day*msPerDay; var sunday = 0; - + if(count === "last"){ - for(var last_sunday = month_start+DaysInMonth(m, leap)*msPerDay; + for(var last_sunday = month_start+DaysInMonth(m, leap)*msPerDay; WeekDay(last_sunday)>0; last_sunday -= msPerDay ){}; sunday = last_sunday; } else { - for(var first_sunday = month_start; + for(var first_sunday = month_start; WeekDay(first_sunday)>0; first_sunday += msPerDay ){}; sunday = first_sunday+7*msPerDay*(count-1); } - + return sunday; } @@ -306,9 +306,9 @@ function DaylightSavingTA(t) { var DST_start = GetSundayInMonth(t, $DST_start_month, $DST_start_sunday) +$DST_start_hour*msPerHour +$DST_start_minutes*msPerMinute; - + var k = new Date(DST_start); - + var DST_end = GetSundayInMonth(t, $DST_end_month, $DST_end_sunday) +$DST_end_hour*msPerHour +$DST_end_minutes*msPerMinute; @@ -412,7 +412,7 @@ function MakeDate( day, time ) { if(!isFinite(day) || !isFinite(time)) { return Number.NaN; } - + return day*msPerDay+time; } @@ -445,20 +445,20 @@ function ConstructDate(year, month, date, hours, minutes, seconds, ms){ var r1 = Number(year); var r2 = Number(month); var r3 = ((date && arguments.length > 2) ? Number(date) : 1); - var r4 = ((hours && arguments.length > 3) ? Number(hours) : 0); - var r5 = ((minutes && arguments.length > 4) ? Number(minutes) : 0); - var r6 = ((seconds && arguments.length > 5) ? Number(seconds) : 0); + var r4 = ((hours && arguments.length > 3) ? Number(hours) : 0); + var r5 = ((minutes && arguments.length > 4) ? Number(minutes) : 0); + var r6 = ((seconds && arguments.length > 5) ? Number(seconds) : 0); var r7 = ((ms && arguments.length > 6) ? Number(ms) : 0); - + var r8 = r1; - + if(!isNaN(r1) && (0 <= ToInteger(r1)) && (ToInteger(r1) <= 99)) r8 = 1900+r1; - + var r9 = MakeDay(r8, r2, r3); var r10 = MakeTime(r4, r5, r6, r7); var r11 = MakeDate(r9, r10); - + return TimeClip(UTC(r11)); } @@ -466,9 +466,9 @@ function ConstructDate(year, month, date, hours, minutes, seconds, ms){ /**** Python code for initialize the above constants // We may want to replicate the following in JavaScript. -// However, using JS date operations to generate parameters that are then used to +// However, using JS date operations to generate parameters that are then used to // test those some date operations seems unsound. However, it isn't clear if there -//is a good interoperable alternative. +//is a good interoperable alternative. # Copyright 2009 the Sputnik authors. All rights reserved. # This code is governed by the BSD license found in the LICENSE file. diff --git a/test/harness/sta.js b/test/harness/sta.js index 19547c223d..b1f4f1e7a4 100644 --- a/test/harness/sta.js +++ b/test/harness/sta.js @@ -1,14 +1,14 @@ -/// Copyright (c) 2009 Microsoft Corporation -/// +/// Copyright (c) 2009 Microsoft Corporation +/// /// Redistribution and use in source and binary forms, with or without modification, are permitted provided -/// that the following conditions are met: +/// that the following conditions are met: /// * Redistributions of source code must retain the above copyright notice, this list of conditions and -/// the following disclaimer. -/// * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and -/// the following disclaimer in the documentation and/or other materials provided with the distribution. +/// the following disclaimer. +/// * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and +/// the following disclaimer in the documentation and/or other materials provided with the distribution. /// * Neither the name of Microsoft nor the names of its contributors may be used to /// endorse or promote products derived from this software without specific prior written permission. -/// +/// /// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR /// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS /// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE @@ -16,10 +16,10 @@ /// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS /// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, /// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -/// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +/// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. //Simple Test APIs -var SimpleTestAPIs = [] +var SimpleTestAPIs = []; //----------------------------------------------------------------------------- function compareArray(aExpected, aActual) { @@ -128,9 +128,9 @@ function fnSupportsStrict() { if (supportsStrict !== undefined) { return supportsStrict; } - + try { - eval('with ({}) {}'); + eval('with ({}) {}'); supportsStrict = false; } catch (e) { supportsStrict = true; @@ -148,10 +148,10 @@ SimpleTestAPIs.push(fnGlobalObject); //----------------------------------------------------------------------------- function fnSupportsStrict() { "use strict"; - try { - eval('with ({}) {}'); + try { + eval('with ({}) {}'); return false; - } catch (e) { + } catch (e) { return true; } } diff --git a/test/harness/sth.js b/test/harness/sth.js index 9053ea6427..e19166adde 100644 --- a/test/harness/sth.js +++ b/test/harness/sth.js @@ -49,7 +49,7 @@ function BrowserRunner() { currentTest.error = "Failed to Load"; } else if(typeof currentTest.error !== "undefined") { // We have an error logged from testRun. - if(currentTest.error instanceof SputnikError) { + if(currentTest.error instanceof Test262Error) { currentTest.error = currentTest.message; } else { currentTest.error = currentTest.error.name + ": " + currentTest.error.message; @@ -96,7 +96,7 @@ function BrowserRunner() { win.testFinished = testFinished; //TODO: these should be moved to sta.js - win.SputnikError = SputnikError; + win.Test262Error = Test262Error; win.$ERROR = $ERROR; win.$FAIL = $FAIL; win.$PRINT = function () {}; diff --git a/tools/converter/browserPlatform.js b/tools/converter/browserPlatform.js index d2ba7540d0..ff9e392bda 100644 --- a/tools/converter/browserPlatform.js +++ b/tools/converter/browserPlatform.js @@ -32,61 +32,36 @@ var platform = global.t262.platform = {}; - /** - * Appends a bunch of RegExps together into a single RegExp, - * solving both the RegExp-one-liner problem and the doubled - * backslash problem when composing literal strings. - * - *

The arguments can be any mixture of RegExps and strings. By - * expressing the portions that should be well formed regexps as - * regexps, we catch well-formedness errors within such a portion - * separately. The strings are added as is without escaping -- - * BEWARE. By not escaping the strings, we can use them to - * represent the individually unbalanced fragments, like capturing - * parens, around other regexps. If arguments[0] is a RegExp, we - * use its flags on the resuting RegExp. - * - *

Not platform dependent, so does not really belong in this - * file. - * - *

TODO(erights): must rewrite to avoid depending on anything - * not in ES3+Reality. - */ - function regExp(var_args) { - var args = [].slice.call(arguments, 0); - var reSrc = args.map(function(arg) { - return (typeof arg === 'string') ? arg : arg.source; - }).join(''); - var flags = ''; - if (typeof args[0] === 'object') { - var parts = (''+args[0]).split('/'); - flags = parts[parts.length -1]; - } - return new RegExp(reSrc, flags); - } - platform.regExp = regExp; + var utils = global.t262.utils; + var forEach = utils.forEach; + var map = utils.map; - ////////////////// Needed for building and running ////////////// + // Someday this will be https: + var ABS_ROOT_STR = 'http://test262.ecmascript.org/'; + var TEST262_ROOT_STR = ABSOLUTE_PATHSTR ? ABS_ROOT : ''; - // Someday these will be https: - var ABS_ROOT = ['http://test262.ecmascript.org']; + var HARNESS_DIR = ['resources', 'scripts', 'global']; + platform.HARNESS_DIR = HARNESS_DIR; - var TEST262_ROOT = ABSOLUTE_PATHSTR ? ABS_ROOT : ['http:']; + var CONVERTER_DIR = ['resources', 'scripts', 'global']; + platform.CONVERTER_DIR = CONVERTER_DIR; - var TEST262_ROOT_STR = TEST262_ROOT.join('/'); + var PLATFORM_PATHS = [ + HARNESS_DIR.concat('jquery-1.4.2.min.js'), + CONVERTER_DIR.concat('utils.js'), + CONVERTER_DIR.concat('v8PosixPlatform.js') + ]; + platform.PLATFORM_PATHS = PLATFORM_PATHS; - var CONVERTER_PATH = ['resources', 'scripts', 'global']; - platform.CONVERTER_PATH = CONVERTER_PATH; - - var ME_PATH = CONVERTER_PATH.concat('browserPlatform.js'); + ////////////////// Needed for building and running tests ////////////// /** * */ function validatePath(path) { var pathStr = path.join('/'); - path.forEach(function(segment) { + forEach(path, function(segment) { if (segment === '') { throw new Error('A path cannot have empty segments: ' + pathStr); } @@ -122,7 +97,7 @@ */ function toPathStr(path) { validatePath(path); - return TEST262_ROOT.concat(path).join('/'); + return TEST262_ROOT_STR + path.join('/'); } platform.toPathStr = toPathStr; @@ -130,17 +105,22 @@ * Returns the text found at path, with newlines normalized and * any initial BOM (Unicode Byte Order Mark) removed. * - * Note: Don't simply revise this (without renamings) to follow the - * general pattern of also defining a local 'read' function, as it - * will mask the v8 shell's read function, which we use. + *

Note that sync remote reading is a terrible idea, but that + * the way test262 was designed and it's hard to change after the + * fact. */ - platform.read = function(path) { - var text = "TBD". - replace(/\r\n/g, '\n'). - replace(/\r/g, '\n'); + function getText(path) { + var text; + $.ajax({ + async: false, + url: toPathStr(path), + success: function(s) { text = s; } + }); + text = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n'); if (text.charCodeAt(0) === 0xfeff) { return text.substring(1); } return text; - }; + } + platform.getText = getText; /** * How one JavaScript script possibly spawns another and possibly @@ -185,7 +165,7 @@ platform.writeSpawn = writeSpawn; - ////////////////// Only needed for running ////////////////////// + ////////////////// Only needed for running tests ////////////////////// })(this); diff --git a/tools/converter/convert.js b/tools/converter/convert.js index 6c99aac4a7..902551e72d 100644 --- a/tools/converter/convert.js +++ b/tools/converter/convert.js @@ -169,12 +169,12 @@ var cebMatch = captureExprBodyPattern.exec(body); if (cebMatch) { - return 'assertTrue(' + trim(cebMatch[1]) + ');'; + return 'assertTruthy(' + trim(cebMatch[1]) + ');'; } var cpMatch = capturePredicatePattern.exec(body); if (cpMatch) { - return 'assertTrue(' + trim(cpMatch[1]) + ');'; + return 'assertTruthy(' + trim(cpMatch[1]) + ');'; } // General case @@ -314,7 +314,7 @@ * in canonical test262 style. * * NOTE: This is currently destructive of testRecord. Easy to fix - *if it becomes a problem. + * if it becomes a problem. */ function formatTestRecord(testRecord) { var test = testRecord.test; @@ -324,9 +324,9 @@ if (pname in testRecord) { result += ' * @' + pname; if (testRecord[pname]) { - result += ': ' + testRecord[pname].replace(/\n/g, '\n * '); + result += ' ' + testRecord[pname].replace(/\n/g, '\n * '); } - result += ';\n'; + result += '\n'; delete testRecord[pname]; } } @@ -346,8 +346,8 @@ t262.formatTestRecord = formatTestRecord; /** - * Reads the test case at pathStr and returns the source of that - * test case converted to canonical test262 style. + * Reads the test case at inBaseStr+relPathStr and returns the + * source of that test case converted to canonical test262 style. */ function convertTest(inBaseStr, relPathStr) { var inBase = toPath(inBaseStr); @@ -420,15 +420,17 @@ * case section, as would be uploaded to a browser-based test * runner. */ - function buildSection(pathStr) { - var path = toPath(pathStr); + function buildSection(inBaseStr, relPathStr) { + var inBase = toPath(inBaseStr); + var relPath = platform.toRelPath(relPathStr); + var path = inBase.concat(relPath); if (!platform.isDirectory(path)) { throw new Error('not dir: ' + path); } var jsFiles = filter(platform.ls(path), function(name) { return /\.js$/.test(name); }); var testRecords = map(jsFiles, function(name) { - var testRecord = parseTestRecord(path, name); + var testRecord = parseTestRecord(inBase, relPath, name); delete testRecord.header; delete testRecord.comment; @@ -468,7 +470,8 @@ try { platform.writeSpawn( [CONVERT_PATH], - 't262.showJSON(t262.buildSection("' + toPathStr(inPath) + '"));', + 't262.showJSON(t262.buildSection("' + toPathStr(inBase) + + '", "' + toRelPathStr(nextRelPath) + '"));', void 0, outFilePath); } catch (err) { @@ -491,10 +494,18 @@ */ function buildWebSite(opt_relPathStr) { var relPath = opt_relPathStr ? toRelPath(opt_relPathStr) : []; + writeSpawnFailures = []; forEach(CONTRIB_DIRS, function(srcDir) { buildAll(srcDir, OUT_DIR, relPath); }); -// buildAll(CONVERTED_DIR, OUT_DIR, relPath); +// buildAll(CONVERTED_DIR, OUT_DIR, relPath); + if (writeSpawnFailures.length >= 1) { + print('********* failures **********'); + forEach(writeSpawnFailures, function(failure) { + print(failure.error + ': ' + toRelPathStr(failure.relPath)); + }); + throw writeSpawnFailures[0].error; + } } t262.buildWebSite = buildWebSite; diff --git a/tools/converter/v8PosixPlatform.js b/tools/converter/v8PosixPlatform.js index f2d9b64510..0864fd7322 100644 --- a/tools/converter/v8PosixPlatform.js +++ b/tools/converter/v8PosixPlatform.js @@ -121,6 +121,14 @@ } platform.getText = getText; + /** + * + */ + function bashQuote(str) { + var escaped = JSON.stringify(str).replace(/'/g, "\\'"); + return "$'" + escaped.substring(1, escaped.length -1) + "'"; + } + /** * How one JavaScript script possibly spawns another and possibly * redirects its printed form to a chosen file (or resource). @@ -164,7 +172,9 @@ opt_targetPath, opt_spawn_required, opt_forceNonStrict) { - if (opt_src && !opt_targetPath && !opt_spawn_required) { + if (typeof opt_src === 'string' && + !opt_targetPath && + !opt_spawn_required) { var str = '(function(/*var_args*/) { '; if (opt_forceNonStrict !== 'forceNonStrict') { str += '"use strict"; '; @@ -173,31 +183,52 @@ return ''+(1,eval)(str).apply(void 0, opt_args || []); } + var sys = os.system; + if (DRY_RUN) { + sys = function(command, args) { + print(command + ' ' + args.join(' ')); + }; + } + var allScriptPaths = PLATFORM_PATHS.concat(scriptPaths); var cmd = 'v8 ' + map(allScriptPaths, toPathStr).join(' '); - if (opt_src) { - cmd += ' -e ' + JSON.stringify(opt_src); + if (typeof opt_src === 'string') { + cmd += ' -e ' + bashQuote(opt_src); } if (opt_args) { - cmd += ' -- ' + map(opt_args, JSON.stringify).join(' '); + cmd += ' -- ' + map(opt_args, bashQuote).join(' '); } - if (opt_targetPath) { - cmd += ' > ' + toPathStr(opt_targetPath); - } - if (VERBOSE || DRY_RUN) { print(cmd); } - if (DRY_RUN) { return ''; } + + if (VERBOSE && !DRY_RUN) { print(cmd); } + + // We write the output to a temporary file for two reasons: + // * If the spawned script dies with a useful diagnostic, + // os.system will throw an error omitting that diagnostic + // text. However, bash ">" will both redirect to the output file + // and preserve the error code of the command to the left. Bash + // does not preserve the error code with "|" redirection. + // * If we actually have a target, we only want to write into it + // if our command runs successfully. + var tempPathStr = os.system('mktemp', ['-t', 'temp.']).trim(); + cmd += ' > ' + tempPathStr; + + var result; try { - return os.system('bash', ['-c', cmd]); - } catch (err) { - if (opt_targetPath) { + try { + result = sys('bash', ['-c', cmd]); + } catch (err) { // The error we catch is almost certainly less interesting // than the one unfortunately written to the target file. - var message = 'failed: ' + cmd + '\n' + getText(opt_targetPath); - os.system('rm', [toPathStr(opt_targetPath)]); + var message = 'failed: ' + cmd + '\n---\n' + read(tempPathStr); throw new Error(message); } - throw err; + if (opt_targetPath) { + sys('cp', [tempPathStr, toPathStr(opt_targetPath)]); + } + return result; + } finally { + sys('rm', ['-f', tempPathStr]); } } platform.writeSpawn = writeSpawn; @@ -322,5 +353,16 @@ ////////////////// Only needed for running tests ////////////////////// + if (!global.$PRINT) { + global.$PRINT = t262.show; + } + + if (!global.$INCLUDE) { + global.$INCLUDE = function(name) { + // does nothing even locally, since the platform independent + // include processing picks these up anyway. + // load(toPathStr(HARNESS_DIR.concat([name]))); + }; + } })(this); diff --git a/tools/test262.py b/tools/test262.py new file mode 100644 index 0000000000..8d50c68993 --- /dev/null +++ b/tools/test262.py @@ -0,0 +1,530 @@ +#!/usr/bin/python +# Copyright 2009 the Sputnik authors. All rights reserved. +# This code is governed by the BSD license found in the LICENSE file. + + +import logging +import optparse +import os +from os import path +import platform +import re +import subprocess +import sys +import tempfile +import time + + +class Test262Error(Exception): + + def __init__(self, message): + self.message = message + + +def ReportError(s): + raise Test262Error(s) + + +def BuildOptions(): + result = optparse.OptionParser() + result.add_option("--command", default=None, help="The command-line to run") + result.add_option("--tests", default=path.abspath('.'), + help="Path to the tests") + result.add_option("--cat", default=False, action="store_true", + help="Print test source code") + result.add_option("--summary", default=False, action="store_true", + help="Print summary after running tests") + result.add_option("--full-summary", default=False, action="store_true", + help="Print summary and test output after running tests") + result.add_option("--enable-strict-mode", default=False, action="store_true", + help="Run the mode also in ES5 strict mode") + + return result + + +def ValidateOptions(options): + if not options.command: + ReportError("A --command must be specified.") + if not path.exists(options.tests): + ReportError("Couldn't find test path '%s'" % options.tests) + + +_PLACEHOLDER_PATTERN = re.compile(r"\{\{(\w+)\}\}") +_INCLUDE_PATTERN = re.compile(r"\$INCLUDE\(\"(.*)\"\);") +_SPECIAL_CALL_PATTERN = re.compile(r"\$([A-Z]+)(?=\()") + + +_SPECIAL_CALLS = { + 'ERROR': 'testFailed', + 'FAIL': 'testFailed', + 'PRINT': 'testPrint' +} + + +def IsWindows(): + p = platform.system() + return (p == 'Windows') or (p == 'Microsoft') + + +def StripHeader(str): + while str.startswith('//') and "\n" in str: + str = str[str.index("\n")+1:] + return str.lstrip() + + +class TempFile(object): + + def __init__(self, suffix="", prefix="tmp", text=False): + self.suffix = suffix + self.prefix = prefix + self.text = text + self.fd = None + self.name = None + self.is_closed = False + self.Open() + + def Open(self): + (self.fd, self.name) = tempfile.mkstemp( + suffix = self.suffix, + prefix = self.prefix, + text = self.text + ) + + def Write(self, str): + os.write(self.fd, str) + + def Read(self): + f = file(self.name) + result = f.read() + f.close() + return result + + def Close(self): + if not self.is_closed: + self.is_closed = True + os.close(self.fd) + + def Dispose(self): + try: + self.Close() + os.unlink(self.name) + except OSError, e: + logging.error("Error disposing temp file: %s", str(e)) + + +class TestResult(object): + + def __init__(self, exit_code, stdout, stderr, case): + self.exit_code = exit_code + self.stdout = stdout + self.stderr = stderr + self.case = case + + def ReportOutcome(self, long_format): + name = self.case.GetName() + if self.HasUnexpectedOutcome(): + if self.case.IsNegative(): + print "%s was expected to fail but didn't" % name + elif (self.case.strict_mode and self.case.IsStrictModeNegative()): + print "%s was expected to fail in strict mode, but didn't" % name + else: + if long_format: + print "=== %s failed ===" % name + else: + print "%s: " % name + out = self.stdout.strip() + if len(out) > 0: + print "--- output ---" + print out + err = self.stderr.strip() + if len(err) > 0: + print "--- errors ---" + print err + if long_format: + print "===" + elif self.case.IsNegative(): + print "%s failed as expected" % name + elif self.case.strict_mode: + if self.case.IsStrictModeNegative(): + print "%s failed in strict mode as expected" % name + else: + print "%s passed in strict mode" % name + else: + print "%s passed" % name + + def HasFailed(self): + return self.exit_code != 0 + + def HasUnexpectedOutcome(self): + if self.case.IsNegative(): + return not self.HasFailed() + if self.case.IsStrictModeNegative(): + return not self.HasFailed() + else: + return self.HasFailed() + + +class TestCase(object): + + def __init__(self, suite, name, full_path, strict_mode=False): + self.suite = suite + self.name = name + self.full_path = full_path + self.contents = None + self.is_negative = None + self.strict_mode = strict_mode + self.is_strict_mode_negative = None + + def GetName(self): + return path.join(*self.name) + + def GetPath(self): + return self.name + + def GetRawContents(self): + if self.contents is None: + f = open(self.full_path) + self.contents = f.read() + f.close() + return self.contents + + def IsNegative(self): + if self.is_negative is None: + self.is_negative = ("@negative" in self.GetRawContents()) + return self.is_negative + + def IsStrictModeNegative(self): + if self.strict_mode and self.is_strict_mode_negative is None: + self.is_strict_mode_negative = \ + ("@strict_mode_negative" in self.GetRawContents()) + return self.is_strict_mode_negative + + def GetSource(self): + source = self.suite.GetInclude("framework.js", False) + source += StripHeader(self.GetRawContents()) + def IncludeFile(match): + return self.suite.GetInclude(match.group(1)) + source = _INCLUDE_PATTERN.sub(IncludeFile, source) + def SpecialCall(match): + key = match.group(1) + return _SPECIAL_CALLS.get(key, match.group(0)) + if self.strict_mode: + source = '"use strict";\nvar strict_mode = true;\n' + \ + _SPECIAL_CALL_PATTERN.sub(SpecialCall, source) + else: + source = "var strict_mode = false; \n" + \ + _SPECIAL_CALL_PATTERN.sub(SpecialCall, source) + return source + + def InstantiateTemplate(self, template, params): + def GetParameter(match): + key = match.group(1) + return params.get(key, match.group(0)) + return _PLACEHOLDER_PATTERN.sub(GetParameter, template) + + def RunTestIn(self, command_template, tmp): + tmp.Write(self.GetSource()) + tmp.Close() + command = self.InstantiateTemplate(command_template, { + 'path': tmp.name + }) + (code, out, err) = self.Execute(command) + return TestResult(code, out, err, self) + + def Execute(self, command): + if IsWindows(): + args = '"%s"' % command + else: + args = command.split(" ") + stdout = TempFile(prefix="test262-out-") + stderr = TempFile(prefix="test262-err-") + try: + logging.info("exec: %s", str(args)) + process = subprocess.Popen( + args, + shell = IsWindows(), + stdout = stdout.fd, + stderr = stderr.fd + ) + code = process.wait() + out = stdout.Read() + err = stderr.Read() + finally: + stdout.Dispose() + stderr.Dispose() + return (code, out, err) + + def Run(self, command_template): + tmp = TempFile(suffix=".js", prefix="test262-", text=True) + try: + result = self.RunTestIn(command_template, tmp) + finally: + tmp.Dispose() + return result + + def Print(self): + print self.GetSource() + + +class ProgressIndicator(object): + + def __init__(self, count): + self.count = count + self.succeeded = 0 + self.failed = 0 + self.failed_tests = [] + + def HasRun(self, result): + result.ReportOutcome(True) + if result.HasUnexpectedOutcome(): + self.failed += 1 + self.failed_tests.append(result) + else: + self.succeeded += 1 + + +def MakePlural(n): + if (n == 1): + return (n, "") + else: + return (n, "s") + + +class TestSuite(object): + + def __init__(self, root, stric_mode): +# self.test_root = path.join(root, 'test', 'suite', 'Sputnik', 'Conformance') +# self.test_root = path.join(root, 'test', 'suite', 'other') + self.test_root = path.join(root, 'test', 'suite', 'converted') + self.lib_root = path.join(root, 'test', 'harness') + self.strict_mode = stric_mode + self.include_cache = { } + + def Validate(self): + if not path.exists(self.test_root): + ReportError("No test repository found") + if not path.exists(self.lib_root): + ReportError("No test library found") + + def IsHidden(self, path): + return path.startswith('.') or path == 'CVS' + + def IsTestCase(self, path): + return path.endswith('.js') + + def ShouldRun(self, rel_path, tests): + if len(tests) == 0: + return True + for test in tests: + if test in rel_path: + return True + return False + + def GetTimeZoneInfoInclude(self): + dst_attribs = GetDaylightSavingsAttribs() + if not dst_attribs: + return None + lines = [] + for key in sorted(dst_attribs.keys()): + lines.append('var $DST_%s = %s;' % (key, str(dst_attribs[key]))) + localtz = time.timezone / -3600 + lines.append('var $LocalTZ = %i;' % localtz) + return "\n".join(lines) + + def GetSpecialInclude(self, name): + if name == "environment.js": + return self.GetTimeZoneInfoInclude() + else: + return None + + def GetInclude(self, name, strip_header=True): + key = (name, strip_header) + if not key in self.include_cache: + value = self.GetSpecialInclude(name) + if value: + self.include_cache[key] = value + else: + static = path.join(self.lib_root, name) + if path.exists(static): + f = open(static) + contents = f.read() + if strip_header: + contents = StripHeader(contents) + self.include_cache[key] = contents + "\n" + f.close() + else: + self.include_cache[key] = "" + return self.include_cache[key] + + def EnumerateTests(self, tests): + logging.info("Listing tests in %s", self.test_root) + cases = [] + for root, dirs, files in os.walk(self.test_root): + for f in [x for x in dirs if self.IsHidden(x)]: + dirs.remove(f) + dirs.sort() + for f in sorted(files): + if self.IsTestCase(f): + full_path = path.join(root, f) + if full_path.startswith(self.test_root): + rel_path = full_path[len(self.test_root)+1:] + else: + logging.warning("Unexpected path %s", full_path) + rel_path = full_path + if self.ShouldRun(rel_path, tests): + basename = path.basename(full_path)[:-3] + name = rel_path.split(path.sep)[:-1] + [basename] + cases.append(TestCase(self, name, full_path, False)) + if self.strict_mode: + cases.append(TestCase(self, name, full_path, True)) + logging.info("Done listing tests") + return cases + + def PrintSummary(self, progress): + print + print "=== Summary ===" + count = progress.count + succeeded = progress.succeeded + failed = progress.failed + print " - Ran %i test%s" % MakePlural(count) + if progress.failed == 0: + print " - All tests succeeded" + else: + percent = ((100.0 * succeeded) / count,) + print " - Passed %i test%s (%.1f%%)" % (MakePlural(succeeded) + percent) + percent = ((100.0 * failed) / count,) + print " - Failed %i test%s (%.1f%%)" % (MakePlural(failed) + percent) + positive = [c for c in progress.failed_tests if not c.case.IsNegative()] + negative = [c for c in progress.failed_tests if c.case.IsNegative()] + if len(positive) > 0: + print + print "Failed tests" + for result in positive: + print " %s" % result.case.GetName() + if len(negative) > 0: + print + print "Expected to fail but passed ---" + for result in negative: + print " %s" % result.case.GetName() + + def PrintFailureOutput(self, progress): + for result in progress.failed_tests: + print + result.ReportOutcome(False) + + def Run(self, command_template, tests, print_summary, full_summary): + if not "{{path}}" in command_template: + command_template += " {{path}}" + cases = self.EnumerateTests(tests) + if len(cases) == 0: + ReportError("No tests to run") + progress = ProgressIndicator(len(cases)) + for case in cases: + result = case.Run(command_template) + progress.HasRun(result) + if print_summary: + self.PrintSummary(progress) + if full_summary: + self.PrintFailureOutput(progress) + else: + print + print "Use --full-summary to see output from failed tests" + print + + def Print(self, tests): + cases = self.EnumerateTests(tests) + if len(cases) > 0: + cases[0].Print() + + +def GetDaylightSavingsTimes(): + # Is the given floating-point time in DST? + def IsDst(t): + return time.localtime(t)[-1] + # Binary search to find an interval between the two times no greater than + # delta where DST switches, returning the midpoint. + def FindBetween(start, end, delta): + while end - start > delta: + middle = (end + start) / 2 + if IsDst(middle) == IsDst(start): + start = middle + else: + end = middle + return (start + end) / 2 + now = time.time() + one_month = (30 * 24 * 60 * 60) + # First find a date with different daylight savings. To avoid corner cases + # we try four months before and after today. + after = now + 4 * one_month + before = now - 4 * one_month + if IsDst(now) == IsDst(before) and IsDst(now) == IsDst(after): + logging.warning("Was unable to determine DST info.") + return None + # Determine when the change occurs between now and the date we just found + # in a different DST. + if IsDst(now) != IsDst(before): + first = FindBetween(before, now, 1) + else: + first = FindBetween(now, after, 1) + # Determine when the change occurs between three and nine months from the + # first. + second = FindBetween(first + 3 * one_month, first + 9 * one_month, 1) + # Find out which switch is into and which if out of DST + if IsDst(first - 1) and not IsDst(first + 1): + start = second + end = first + else: + start = first + end = second + return (start, end) + + +def GetDaylightSavingsAttribs(): + times = GetDaylightSavingsTimes() + if not times: + return None + (start, end) = times + def DstMonth(t): + return time.localtime(t)[1] - 1 + def DstHour(t): + return time.localtime(t - 1)[3] + 1 + def DstSunday(t): + if time.localtime(t)[2] > 15: + return "'last'" + else: + return "'first'" + def DstMinutes(t): + return (time.localtime(t - 1)[4] + 1) % 60 + attribs = { } + attribs['start_month'] = DstMonth(start) + attribs['end_month'] = DstMonth(end) + attribs['start_sunday'] = DstSunday(start) + attribs['end_sunday'] = DstSunday(end) + attribs['start_hour'] = DstHour(start) + attribs['end_hour'] = DstHour(end) + attribs['start_minutes'] = DstMinutes(start) + attribs['end_minutes'] = DstMinutes(end) + return attribs + + +def Main(): + parser = BuildOptions() + (options, args) = parser.parse_args() + ValidateOptions(options) + test_suite = TestSuite(options.tests, options.enable_strict_mode) + test_suite.Validate() + if options.cat: + test_suite.Print(args) + else: + test_suite.Run(options.command, args, + options.summary or options.full_summary, + options.full_summary) + + +if __name__ == '__main__': + try: + Main() + sys.exit(0) + except Test262Error, e: + print "Error: %s" % e.message + sys.exit(1)