Skip to content
Snippets Groups Projects
Commit 15586369 authored by Mark Miller's avatar Mark Miller
Browse files

Converts test cases to proposed new canonical form

parent 1f6f66cb
No related branches found
No related tags found
No related merge requests found
(function(global) {
"use strict";
var t262 = global.t262;
var platform = t262.platform;
var regExp = platform.regExp;
var headerPattern = /(?:(?:\/\/.*)?\s*\n)*/;
var captureCommentPattern = /\/\*\*?((?:\s|\S)*?)\*\/\s*\n/;
var anyPattern = /(?:\s|\S)*/;
var blanksPattern = /(?:\s|\n)*/;
// Should match anything
var testEnvelopePattern =
regExp('^(', headerPattern,
')(?:', captureCommentPattern,
')?(', anyPattern,
')$');
var registerPattern =
regExp('^(', anyPattern, '?)(',
/ES5Harness\.registerTest\s*\(\s*\{/, anyPattern,
/\}\s*\)/, ')',
/\s*;?(?:\s|\n)*$/);
var captureFuncBodyPattern =
regExp(/^function(?:\s+\w*)?\(\s*\)\s*\{/,
'(', anyPattern, ')',
/;?/, blanksPattern,
/\}$/);
var captureExprBodyPattern =
regExp(/^return\s+/,
'(', anyPattern, '?)',
/;$/);
var capturePredicatePattern =
regExp(/^if\s+\((.*?)\)\s*\{/, blanksPattern,
/return\s+true;?/, blanksPattern,
/\}$/);
/**
* Strip the left margin "*"s that are found in the body of a
* multiline doc-comment like this one.
*/
function stripStars(text) {
return text.replace(/\s*\n\s*\*\s?/g, '\n').trim();
}
/**
* Parses the source of a test262 test case file into a JSON
* envelope record.
*
* <p>The input can be in old sputnik or ietestcenter style, or in
* the canonical test262 style. In all cases, we have an optional
* header, an optional "/*" comment possibly containing properties
* of the form<pre>
* @propName: propValue;
* </pre>which populate the test record. This is followed by the
* rest of the text, which is the test itself. In the case of an
* ietestcenter style test, this is followed by a call to
* <code>ES5Harness\.registerTest</code> to register a test record.
*/
function parseTestEnvelope(src, name) {
var envelope = { testRecord: {} };
var envelopeMatch = testEnvelopePattern.exec(src);
if (!envelopeMatch) {
// Can't happen?
throw new Error('unrecognized: ' + name);
}
envelope.header = envelopeMatch[1].trim();
if (envelopeMatch[2]) {
var propTexts = envelopeMatch[2].split(/\s*\n\s*\*\s*@/);
envelope.comment = stripStars(propTexts.shift()), // notice side effect
propTexts.forEach(function(propText) {
var propName = propText.match(/^\w+/)[0];
var propVal = propText.substring(propName.length);
var propMatch = /^:?([^;]*);?\s*$/.exec(propVal);
if (propMatch) { propVal = propMatch[1]; }
propVal = stripStars(propVal);
if (propName in envelope.testRecord) {
throw new Error('duplicate: ' + propName);
}
envelope.testRecord[propName] = propVal;
});
}
envelope.rest = envelopeMatch[3]; // Do not trim
var registerMatch = registerPattern.exec(envelope.rest);
if (registerMatch) {
envelope.rest = registerMatch[1].trim();
envelope.registerExpr = registerMatch[2].trim();
} else if (envelope.rest.indexOf('ES5Harness.registerTest') >= 0) {
print(' \n--header---\n|' + envelope.header +
'|\n--rest-----\n|' + envelope.rest +
'|\n--harness--\n|' + envelope.registerExpr +
'|\n-----------\n');
throw new Error('Malformed harness? ' + name);
}
return envelope;
}
/**
* Given a function, return the source for an expression that, when
* evaluated in the environment the function assumes, will behave
* the same as calling that function in that environment.
*/
function expressionize(func) {
var funcSrc = '' + func;
var cfbMatch = captureFuncBodyPattern.exec(funcSrc);
if (cfbMatch) {
// Look for special cases
var body = cfbMatch[1].trim();
var cebMatch = captureExprBodyPattern.exec(body);
if (cebMatch) { return '(' + cebMatch[1].trim() + ')'; }
var cpMatch = capturePredicatePattern.exec(body);
if (cpMatch) { return '(' + cpMatch[1].trim() + ')'; }
} else {
// signal an error?
}
return '(' + funcSrc + ').call(this)';
}
/**
* Given an ietestcenter style test, this <b>evaluates</b> the
* registration expression in order to gather the test record.
*/
function gatherOne(envelope, name) {
if (envelope.testRecord) {
var propNames = Object.keys(envelope.testRecord);
if (propNames.length >= 1) {
// This need not be an error. It's just here so we notice the
// first time it happens. This would happen if an
// ietestcenter style test also had a comment with "@"
// property definitions.
throw new Error('unexpected in ' + name + ': ' + propNames);
}
}
var testRecords = [];
// Evaluating!!!!
platform.evalExprIn(envelope.registerExpr,
{
ES5Harness: {
registerTest: function(testRecord) {
testRecords.push(testRecord);
}
}
},
'forceNonStrict');
if (testRecords.length !== 1) {
// We plan to lift this restriction in order to support test
// generators.
throw new Error('not singleton: ' + name);
}
var testRecord = testRecords[0];
if (typeof testRecord.test === 'function') {
testRecord.test = envelope.rest +
'assertTrue(' + expressionize(testRecord.test) + ');\n';
}
if (typeof testRecord.precondition === 'function') {
var precondition = expressionize(testRecord.precondition);
if (precondition === '(true)') {
delete testRecord.precondition;
} else {
testRecord.precondition = precondition;
}
}
return testRecord;
};
/**
* Normalizes the properties of testRecord to be the canonical
* test262 style properties, that will be assumed by the new test
* runners.
*/
function normalizeProps(testRecord) {
if (!testRecord.id && testRecord.name) {
testRecord.id = testRecord.name;
delete testRecord.name;
}
if (!('strict_only' in testRecord) && testRecord.strict === 1) {
testRecord.strict_only = '';
delete testRecord.strict;
}
if ('strict_mode_negative' in testRecord) {
if (!('strict_only' in testRecord)) {
testRecord.strict_only = '';
}
if (!'negative' in testRecord) {
testRecord.negative = testRecord.strict_mode_negative;
delete testRecord.strict_mode_negative;
}
}
if (!testRecord.negative && 'errortype' in testRecord) {
testRecord.negative = testRecord.errortype;
delete testRecord.errortype;
}
if (!testRecord.description && testRecord.assertion) {
testRecord.description = testRecord.assertion;
delete testRecord.assertion;
}
if (!testRecord.comment && testRecord.assertion) {
testRecord.comment = testRecord.assertion;
delete testRecord.assertion;
}
};
t262.normalizeProps = normalizeProps;
/**
* Parses the source of a test262 test case file into a normalized
* JSON test record.
*/
function parseTestRecord(path, name) {
var nextPath = path.concat([name]);
var src = platform.read(nextPath);
var testRecord;
if (!src) { throw new Error('no src: ' + nextPath.join('/')); }
var envelope = parseTestEnvelope(src, name);
if (envelope.registerExpr) {
testRecord = gatherOne(envelope, name);
} else {
testRecord = envelope.testRecord;
if (!testRecord.test) {
testRecord.test = envelope.rest;
}
}
testRecord.header = envelope.header;
testRecord.comment = envelope.comment;
normalizeProps(testRecord);
return testRecord;
};
t262.parseTestRecord = parseTestRecord;
// The known ones will be rendered first, and in this order.
var KNOWN_PROPS = ['id', 'section', 'path', 'description',
'strict_only', 'negative'];
/**
* Turns the (assumed) normalized test record into its string form
* in canonical test262 style.
*
* NOTE: This is currently destructive of testRecord. Easy to fix
*if it becomes a problem.
*/
function formatTestRecord(testRecord) {
var test = testRecord.test;
delete testRecord.test;
function addProp(pname) {
if (pname in testRecord) {
result += ' * @' + pname;
if (testRecord[pname]) {
result += ': ' + testRecord[pname].replace(/\n/g, '\n * ');
}
result += ';\n';
delete testRecord[pname];
}
}
var result = testRecord.header + '\n\n';
delete testRecord.header;
result += '/**\n';
if (testRecord.comment) {
result += ' * ' + testRecord.comment.replace(/\n/g, '\n * ') + '\n *\n';
}
delete testRecord.comment;
KNOWN_PROPS.concat(['precondition']).forEach(addProp);
Object.keys(testRecord).forEach(addProp);
result += ' */\n\n' + test;
return result;
};
t262.formatTestRecord = formatTestRecord;
/**
* Reads the test case at pathStr and returns the source of that
* test case converted to canonical test262 style.
*/
function convertTest(pathStr) {
var path = platform.toPath(pathStr);
var name = path.pop();
var testRecord = parseTestRecord(path, name);
var result = formatTestRecord(testRecord);
return result;
};
t262.convertTest = convertTest;
var SRC_DIRS = [
['test', 'suite', 'other'],
['test', 'suite', 'sputnik', 'Conformance'],
['test', 'suite', 'ietestcenter']
];
var CONV_DIR = ['test', 'suite', 'converted'];
var OUT_DIR = ['website', 'resources', 'scripts', 'testcases2'];
var ME_PATH = platform.CONVERTER_PATH.concat('convert.js');
/**
* Convert all the testcases found at inBase+relDir to test cases
* in canonical test262 style, to be stored at corresponding
* positions in outBase+relPath.
*/
function convertAll(inBase, outBase, relPath) {
var inPath = inBase.concat(relPath);
var outPath = outBase.concat(relPath);
platform.mkdir(outPath);
platform.ls(inPath).forEach(function(name) {
var nextRelPath = relPath.concat([name]);
if (platform.isDirectory(inBase.concat(nextRelPath))) {
convertAll(inBase, outBase, nextRelPath);
} else if (/\.js$/.test(name)) {
var inFilePath = inPath.concat([name]);
var outFilePath = outPath.concat([name]);
platform.writeSpawn(
[ME_PATH],
't262.convertTest("' + platform.toPathStr(inFilePath) + '")',
void 0,
outFilePath);
}
});
};
t262.convertAll = convertAll;
/**
* Do all the conversions (from sputnik style, ietestcenter style,
* or other to canonical test262 style) matching relPath.
*/
function convert(opt_relPath) {
SRC_DIRS.forEach(function(srcDir) {
convertAll(srcDir, CONV_DIR, opt_relPath || []);
});
};
t262.convert = convert;
/**
* Reads all the test case records for the section corresponding to
* the directory at pathStr, and return a JSON record for a test
* case section, as would be uploaded to a browser-based test
* runner.
*/
function buildSection(pathStr) {
var path = platform.toPath(pathStr);
if (!platform.isDirectory(path)) { throw new Error('not dir: ' + path); }
var jsFiles = platform.ls(path).filter(function(name) {
return /\.js$/.test(name);
});
var testRecords = jsFiles.map(function(name) {
var testRecord = parseTestRecord(path, name);
delete testRecord.header;
delete testRecord.comment;
return testRecord;
});
testRecords = testRecords.filter(function(testRecord) {
return testRecord !== null;
});
return {
testCollection: {
name: path[path.length -1],
numTests: testRecords.length,
tests: testRecords
}
};
};
t262.buildSection = buildSection;
/**
* Use the test cases at inBase+relPath to build the test
* collection portion of the website, at outBase.
*/
function buildAll(inBase, outBase, relPath) {
var inPath = inBase.concat(relPath);
var hasJS = false;
platform.ls(inPath).forEach(function(name) {
var nextRelPath = relPath.concat([name]);
if (platform.isDirectory(inBase.concat(nextRelPath))) {
buildAll(inBase, outBase, nextRelPath);
} else if (/\.js$/.test(name)) {
hasJS = true;
}
});
if (hasJS) {
var name = relPath[relPath.length -1] + '.json';
var outFilePath = outBase.concat([name]);
platform.writeSpawn(
[ME_PATH],
't262.asJSONTxt(t262.buildSection("' +
platform.toPathStr(inPath) + '"))',
void 0,
outFilePath);
}
};
t262.buildAll = buildAll;
/**
* Build those test case files for the website corresponding to the
* test cases matching relPath.
*
* <p>Right now it's building from the pre-converted test
* files. Once we switch over to converted as the maintained
* sources, we should change this.
*/
function buildWebSite(opt_relPath) {
SRC_DIRS.forEach(function(srcDir) {
buildAll(srcDir, OUT_DIR, opt_relPath || []);
});
// buildAll(CONV_DIR, OUT_DIR, opt_relPath || []);
};
t262.buildWebSite = buildWebSite;
})(this);
/**
* Each implementation of *Platform.js abstracts the underlying OS and JS
* engine peculiarities.
*
* <p>The implementation here is specific to the v8 shell running on a
* Posix platform.
*/
(function (global) {
"use strict";
/////////////////// Development Switches /////////////////
var VERBOSE = true;
// Affects side effecting os operations,
// currently only platform.writeSpawn and platform.mkdir.
var DRY_RUN = false;
// When converting paths to path strings, should the pathstring be
// relative to the TEST262_ROOT, or should it be relative to the
// current working directory?
var ABSOLUTE_PATHSTR = false;
////////////////////////////////////////////////////////
global.t262 = global.t262 || {};
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 string.
*
* <p>The arguments can be any mixture of RegExps and strings. The
* strings are added as is without escaping -- BEWARE. If
* arguments[0] is a RegExp, we use its flag on the resuting RegExp.
*
* <p>Not platform dependent, so does not really belong in this
* file.
*/
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;
////////////////// Needed for building and running //////////////
try {
read('tools/converter/v8PosixPlatform.js');
} catch (err) {
throw new Error('Must run in a test262 source root');
}
var ABS_ROOT = os.system('pwd', ['-P']).trim().split('/');
var TEST262_ROOT = ABSOLUTE_PATHSTR ? ABS_ROOT : [];
var TEST262_ROOT_STR = TEST262_ROOT.join('/');
var CONVERTER_PATH = ['tools', 'converter'];
platform.CONVERTER_PATH = CONVERTER_PATH;
var ME_PATH = CONVERTER_PATH.concat('v8PosixPlatform.js');
/**
*
*/
function validatePath(path) {
var pathStr = path.join('/');
path.forEach(function(segment) {
if (segment === '') {
throw new Error('A path cannot have empty segment: ' + pathStr);
}
if (segment === '/') {
throw new Error('Path insufficiently parsed: ' + pathStr);
}
if (segment === '..') {
throw new Error('Cannot use "..": ' + pathStr);
}
});
return path;
}
/**
* Converts a path to a pathStr.
*
* A path is an array of filenames relative to TEST262_ROOT. A
* pathStr is a (possibly fully qualified string) for referring to
* that string on the current platform, according to the operations
* in this *Platform.js file.
*/
function toPathStr(path) {
validatePath(path);
return TEST262_ROOT.concat(path).join('/');
};
platform.toPathStr = toPathStr;
/**
* Returns the text found at path, with newlines normalized and
* any initial BOM (Unicode Byte Order Mark) removed.
*/
platform.read = function(path) {
var text = read(toPathStr(path)).
replace(/\r\n/g, '\n').
replace(/\r/g, '\n');
if (text.charCodeAt(0) === 0xfeff) { return text.substring(1); }
return text;
};
/**
* How one JavaScript script possibly spawns another and possibly
* redirects its printed form to a chosen file (or resource).
*
* <p>For example, if !DRY_RUN, then<pre>
* writeSpawn([], '+arguments[0] + +arguments[1]', ['3', '5'])
* </pre>
* should return the string "8", whether or not writeSpawn decides
* to spawn.
*
* @param scriptPaths An array of path arrays of JavaScript source
* files to be loaded into the spawned JS engine (in addition to
* the spawning platform file) if we are indeed spawning.
* @param opt_exprSrc An expression to be evaluated in an
* environment in which "arguments" is bound to the list of strings
* provided by opt_args. The result is the value of the expression
* coerced to a string, unfortunately, as prepended by whatever
* these scripts (if spawned) have already written to their
* stdout. On platforms (like SES) where this can be a safely
* confining evaluation, it should be. The implementation here is
* not safe.
* @param opt_args A list of strings to be bound to 'arguments'
* both in opt_expr and in the possibly spawed scripts.
* @param opt_targetPath A path array naming a file where the
* result of opt_exprSrc should be written. On v8 currently, if
* this is provided, then writeSpawn will spawn, since we have no
* other way to implement this functionality. In the browser
* context, the result is PUT (using XHR) to the target resource.
* @param opt_spawn_required If truthy, forces spawning.
* @returns If there is a target, then the null string. Otherwise,
* the string result of evaluating opt_exprSrc.
*/
platform.writeSpawn = function(scriptPaths,
opt_exprSrc,
opt_args,
opt_targetPath,
opt_spawn_required,
opt_forceNonStrict) {
if (opt_exprSrc && !opt_targetPath && !opt_spawn_required) {
var str = '(function(/*var_args*/) {';
if (opt_forceNonStrict !== 'forceNonStrict') {
str += '"use strict";';
}
str += ' return (' + opt_exprSrc + '); })';
return ''+(1,eval)(str).apply(void 0, opt_args || []);
}
var cmd = 'v8 ' + toPathStr(ME_PATH) + ' ';
cmd += scriptPaths.map(toPathStr).join(' ');
if (opt_exprSrc) {
cmd += ' -e ' + JSON.stringify('print(' + opt_exprSrc + ')');
}
if (opt_args) {
cmd += ' -- ' + opt_args.map(JSON.stringify).join(' ');
}
if (opt_targetPath) {
cmd += ' > ' + toPathStr(opt_targetPath);
}
if (VERBOSE || DRY_RUN) { print(cmd); }
if (DRY_RUN) { return ''; }
return os.system('bash', ['-c', cmd]);
};
////////////////// Only needed for building /////////////////////
/**
* Calls a non-strict indirect eval function on exprSrc.
*
* On platforms (like SES) where this can be a safely confining
* evaluation, it should be. The implementation here is not safe.
*/
platform.evalExprIn = function(exprSrc, env, opt_forceNonStrict) {
var varNames = Object.getOwnPropertyNames(env);
var str = '(function(' + varNames.join(',') + ') {';
if (opt_forceNonStrict !== 'forceNonStrict') {
str += '"use strict";';
}
str += ' return (' + exprSrc + '); })';
return (1,eval)(str).apply(void 0, varNames.map(function(varName) {
return env[varName];
}));
};
/**
* Converts a pathStr to a path.
*
* See toPathStr.
*/
function toPath(pathStr) {
if (pathStr[0] === '/') {
if (pathStr.indexOf(TEST262_ROOT_STR + '/') !== 0) {
throw new Error('"' + pathStr + '" must start with "' +
TEST262_ROOT_STR + '/"');
}
pathStr = pathStr.substring(TEST262_ROOT_STR.length + 1);
}
return validatePath(pathStr.split('/'));
}
platform.toPath = toPath;
/**
* Does path name a directory?
*/
platform.isDirectory = function(path) {
var fileOut = os.system('file', [toPathStr(path)]);
var fileMatch = fileOut.match(/:\s*([^:]*)\s*$/);
if (!fileMatch) { return null; }
var fileType = fileMatch[1].trim();
return fileType === 'directory';
};
/**
* A list of the filenames found in path, which must name a
* directory.
*/
platform.ls = function(path) {
var pathStr = toPathStr(path);
var lines = os.system('ls', [pathStr]).trim();
if (lines === '') { return []; }
return lines.split('\n');
};
/**
* Emits the jsonRecord serialized as JSON, either compactly or
* readably according to VERBOSE.
*/
function asJSONTxt(jsonRecord) {
if (VERBOSE) {
return JSON.stringify(jsonRecord, void 0, ' ');
} else {
return JSON.stringify(jsonRecord);
}
}
global.t262.asJSONTxt = platform.asJSONTxt = asJSONTxt;
platform.mkdir = function(path) {
var pathStr = toPathStr(path);
if (DRY_RUN) {
print('mkdir ' + pathStr);
return;
}
try {
os.mkdirp(pathStr);
} catch (err) {
print('***could not mkdir: ' + pathStr);
throw err;
}
};
////////////////// Only needed for running //////////////////////
})(this);
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment