Adapted the sputnik command line testRunner to a command line

testRunner, test262.py, that can run all the converted tests.
This commit is contained in:
Mark Miller 2011-09-11 21:12:01 -07:00
parent d0f40b63a2
commit a2ca5b512b
8 changed files with 782 additions and 129 deletions

90
test/harness/framework.js Normal file
View File

@ -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');
}
}

View File

@ -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.

View File

@ -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;
}
}

View File

@ -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 () {};

View File

@ -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.
*
* <p>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.
*
* <p>Not platform dependent, so does not really belong in this
* file.
*
* <p>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.
* <p>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);

View File

@ -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;

View File

@ -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);

530
tools/test262.py Normal file
View File

@ -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)