Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement support for raw frontmatter flag #42

Merged
merged 2 commits into from
Jul 21, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,11 @@ All runners have the same basic interface. Supporting new hosts, transpilers, or
#### Runner(args)
Runners are constructed by passing the current configuration object.

#### Runner.prototype.needsCtrlFlow
Boolean flag indicating whether control-flow-related code should be injected during test compilation (see `Runner.prototype.compile`).

#### Runner.prototype.compile(test)
Modifies the test contents to run in the target host. By default, it will append a call to $DONE if not already present, append any the environment dependencies (eg. $DONE, $LOG, etc) found in `this.deps`, append helpers, and add "use strict" if required.
Modifies the test contents to run in the target host. By default, it will append a call to $DONE if not already present, append the appropriate environment dependencies (eg. `$DONE`, `$LOG`, `$ERROR`, etc), append helpers, and add "use strict" if required.

#### Runner.prototype.link(test)
Recursively appends helpers required in the front-matter of the test.
Expand Down
95 changes: 68 additions & 27 deletions lib/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,27 +24,86 @@ function Runner(opts) {
throw new Error("Helper directory " + opts.includesDir + " not found");
}


this.helpers = loadHelpers(opts.includesDir);

this._errorSrc = this.test262ErrorSrc + "\n;" + this.errorFnSrc;

if (this.needsCtrlFlow) {
if (!this.logFnSrc) {
throw new Error('`$LOG` function not implemented.');
}

if (!this.doneFnSrc) {
throw new Error('`$DONE` function not implemented.');
}

this._ctrlFlowSrc = this.logFnSrc + "\n;" + this.doneFnSrc;
}
};

Runner.prototype.deps = [];
/**
* Boolean attribute which controls whether the runner should inject code for
* control flow. If true, the runner's `doneFnSrc` and `logFnSrc` will be
* inserted into each test prior to execution.
*/
Runner.prototype.needsCtrlFlow = true;

/**
* JavaScript source code that defines a function binding for the identifier
* `$LOG`. This must be defined by Runner subclasses that require control flow.
*/
Runner.prototype.logFnSrc = null;

/**
* JavaScript source code that defines a function binding for the identifier
* `$DONE`. This must be defined by Runner subclasses that require control
* flow.
*/
Runner.prototype.doneFnSrc = null;

/**
* JavaScript source code that defines a constructor function bound to the
* identifier `Test262Error`.
*/
Runner.prototype.test262ErrorSrc = function() {
function Test262Error(message) {
if (message) this.message = message;
}

Test262Error.prototype.name = "Test262Error";

Test262Error.prototype.toString = function () {
return "Test262Error: " + this.message;
};
}.toString().slice(14, -1);

/**
* JavaScript source code that defines a function binding for the identifier
* `$ERROR`.
*/
Runner.prototype.errorFnSrc = function $ERROR(err) {
if(typeof err === "object" && err !== null && "name" in err)
throw err;
else throw new Test262Error(err);
}.toString();

Runner.prototype.compile = function(test) {
if (test.attrs.flags.raw) {
return;
}

// add call to $DONE at the bottom of the test file if it's not
// present.
if(test.contents.indexOf("$DONE") === -1) {
// lead with a semicolon to prevent ASI nonsense.
test.contents += "\n;$DONE();\n"
}

test.contents = [
this.test262ErrorSrc, this.errorFnSrc, test.contents
].join(";\n");
test.contents = this._errorSrc + "\n;" + test.contents;

this.deps.forEach(function(dep) {
test.contents = dep + "\n" + test.contents;
})
if (this.needsCtrlFlow) {
test.contents = this._ctrlFlowSrc + "\n;" + test.contents;
}

this.link(test);

Expand Down Expand Up @@ -88,24 +147,6 @@ Runner.prototype.link = function(test) {
test.contents = includeContent + test.contents;
}

Runner.prototype.test262ErrorSrc = function() {
function Test262Error(message) {
if (message) this.message = message;
}

Test262Error.prototype.name = "Test262Error";

Test262Error.prototype.toString = function () {
return "Test262Error: " + this.message;
};
}.toString().slice(14, -1);

Runner.prototype.errorFnSrc = function $ERROR(err) {
if(typeof err === "object" && err !== null && "name" in err)
throw err;
else throw new Test262Error(err);
}.toString();

var errorLogRe = /^test262\/error (.*)$/;
// Result is expected to have the following keys:
// errorString: Error of the form ErrorName: ErrorMessage. Optional.
Expand Down Expand Up @@ -178,7 +219,7 @@ Runner.prototype.validateResult = function(test, result) {
}
} else {
// ensure $DONE was called if there wasn't an error reported
if(!result.doneCalled) {
if(!result.doneCalled && !test.attrs.flags.raw) {
test.pass = false;
test.errorName = "Test262 Error";
test.errorMessage = "Test did not run to completion ($DONE not called)";
Expand Down
35 changes: 17 additions & 18 deletions lib/runners/console.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,6 @@ var fs = require('fs');
var cp = require('child_process');
var counter = 0;

var doneFn = function $DONE(err) {
if(err) $ERROR(err);
$LOG('test262/done');
}.toString()

var batchDoneFn = function $DONE(err) {
if(err) {
if(typeof err === "object" && err !== null && "name" in err) {
Expand All @@ -32,22 +27,16 @@ function ConsoleRunner(args) {
this.printCommand = args.consolePrintCommand || "console.log";

if(args.batch) {
// Done comes from the parent context
this.deps = [
this.logFn
]
// Control flow functions are defined by the parent context
this.needsCtrlFlow = false;

if(args.batchConfig) {
this._createEnv = args.batchConfig.createEnv;
this._runBatched = args.batchConfig.runBatched;
this._setRealmValue = args.batchConfig.setRealmValue;
}
} else {
this.deps = [
doneFn,
this.logFn
]
}

if(!this.command) throw "--consoleCommand option required for console runner";
if (!this._setRealmValue) {
this._setRealmValue = function(env, property, value) {
Expand All @@ -58,10 +47,16 @@ function ConsoleRunner(args) {
Runner.apply(this, arguments);
}
ConsoleRunner.prototype = Object.create(Runner.prototype);

ConsoleRunner.prototype.doneFnSrc = function $DONE(err) {
if(err) $ERROR(err);
$LOG('test262/done');
}.toString();

ConsoleRunner.prototype._print = function(str) {
return this.printCommand + '(' + str + ');\n';
}
Object.defineProperty(ConsoleRunner.prototype, 'logFn', {
Object.defineProperty(ConsoleRunner.prototype, 'logFnSrc', {
get: memoize(function() {
return 'function $LOG(str) { ' + this._print('str') + '}';
})
Expand All @@ -73,13 +68,17 @@ Object.defineProperty(ConsoleRunner.prototype, 'runNextFn', {
if(!this._runBatched) throw "Don't know how to run a batched tests";

var runNextFn = function runNext() {
var test = tests.shift();
var testInfo = tests.shift();
var test = testInfo.contents;
var env = $1;
$setRealmValue(env, "$DONE", $DONE);

try {
$LOG('test262/test-start')
$2;
if (testInfo.attrs.flags.raw) {
$DONE();
}
} catch(e) {
$DONE(e);
}
Expand Down Expand Up @@ -115,13 +114,13 @@ ConsoleRunner.prototype.execute = function(test, cb) {
ConsoleRunner.prototype.executeBatch = function(batch, batchDone) {
var runner = this;
var scriptFile = '__tmp' + counter++ + '.js';
var script = this.logFn + '\n' +
var script = this.logFnSrc + '\n' +
batchDoneFn + '\n' +
'var $setRealmValue = ' + this._setRealmValue + ';\n' +
this.runNextFn + '\n';

script += 'var tests = ' + JSON.stringify(batch.map(function(test, i) {
return test.contents
return test
})).replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029") + '\n';

script += 'runNext();'
Expand Down
5 changes: 5 additions & 0 deletions lib/runners/node-ip.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ var Runner = require('../runner');

function NodeRunner() { Runner.apply(this, arguments); }
NodeRunner.prototype = Object.create(Runner.prototype);

NodeRunner.prototype.needsCtrlFlow = false;

NodeRunner.prototype.execute = function(test, cb) {
var contents = test.contents;
var error;
Expand Down Expand Up @@ -41,6 +44,8 @@ NodeRunner.prototype.execute = function(test, cb) {
result.errorName = "Error";
result.errorMessage = error;
}
} else if (test.attrs.flags.raw) {
context.$DONE();
}

this.validateResult(test, result);
Expand Down
6 changes: 3 additions & 3 deletions lib/runners/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ function NodeRunner(args) {
args.consoleCommand = args.consoleCommand || "node";
var runner = this;

this.needsCtrlFlow = !!args.compileOnly;

ConsoleRunner.apply(this, arguments);

if(!args.compileOnly) {
this.deps = []; // all env deps provided by nodehost.js

// HACK: Probably doesn't handle quoted arguments and other
// complexities.
var parts = args.consoleCommand.split(" ");
Expand Down Expand Up @@ -42,7 +42,7 @@ NodeRunner.prototype = Object.create(ConsoleRunner.prototype);
NodeRunner.prototype.execute = function(test, cb) {
this._test = test;
this._testDone = cb;
this._instance.stdin.write(test.contents);
this._instance.stdin.write(JSON.stringify(test));
}

NodeRunner.prototype.end = function() {
Expand Down
9 changes: 7 additions & 2 deletions lib/runners/nodehost.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
var vm = require('vm');

process.stdin.resume();
process.stdin.on('data', function(test) {
process.stdin.on('data', function(testJSON) {
var test = JSON.parse(testJSON);
var result = { log: [] }
var context = {
$DONE: function(error) {
Expand All @@ -25,7 +26,11 @@ process.stdin.on('data', function(test) {
};

try {
vm.runInNewContext(test, context, {displayErrors: false});
vm.runInNewContext(test.contents, context, {displayErrors: false});

if (test.attrs.flags.raw) {
context.$DONE();
}
} catch(e) {
context.$DONE(e);
}
Expand Down
14 changes: 14 additions & 0 deletions test/collateral/rawNoStrict.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*---
description: Should not test in strict mode
flags: [raw]
---*/
var seemsStrict;
try {
x = 1;
} catch (err) {
seemsStrict = err.constructor === ReferenceError;
}

if (seemsStrict) {
throw new Error('Script erroneously interpreted in strict mode.');
}
15 changes: 15 additions & 0 deletions test/collateral/rawStrict.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*---
description: Should not test in strict mode
flags: [raw]
---*/
'use strict';
var seemsStrict;
try {
x = 1;
} catch (err) {
seemsStrict = err.constructor === ReferenceError;
}

if (!seemsStrict) {
throw new Error('Script erroneously not interpreted in strict mode.');
}
4 changes: 4 additions & 0 deletions test/expected.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ var all = [
{ file: 'test/collateral/bothStrict.js', strictMode: true, pass: true },
{ file: 'test/collateral/strict.js', strictMode: true, pass: true },
{ file: 'test/collateral/noStrict.js', strictMode: false, pass: true },
{ file: 'test/collateral/rawStrict.js', strictMode: false, pass: true },
{ file: 'test/collateral/rawStrict.js', strictMode: true, pass: true },
{ file: 'test/collateral/rawNoStrict.js', strictMode: false, pass: true },
{ file: 'test/collateral/rawNoStrict.js', strictMode: true, pass: true },
{ file: 'test/collateral/error.js', strictMode: false, pass: false, errorMessage: 'failure message', errorName: 'Test262Error' },
{ file: 'test/collateral/error.js', strictMode: true, pass: false, errorMessage: 'failure message', errorName: 'Test262Error' },
{ file: 'test/collateral/thrownError.js', strictMode: false, pass: false, errorMessage: 'failure message', errorName: 'Error', topOfStack: "foo" },
Expand Down