diff --git a/README.md b/README.md index 13a1113..0aab1ee 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/lib/runner.js b/lib/runner.js index a08996d..c9c07f4 100644 --- a/lib/runner.js +++ b/lib/runner.js @@ -24,13 +24,74 @@ 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) { @@ -38,13 +99,11 @@ Runner.prototype.compile = function(test) { 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); @@ -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. @@ -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)"; diff --git a/lib/runners/console.js b/lib/runners/console.js index 33edfd8..f9818af 100644 --- a/lib/runners/console.js +++ b/lib/runners/console.js @@ -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) { @@ -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) { @@ -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') + '}'; }) @@ -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); } @@ -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();' diff --git a/lib/runners/node-ip.js b/lib/runners/node-ip.js index fa4322d..6eb01cc 100644 --- a/lib/runners/node-ip.js +++ b/lib/runners/node-ip.js @@ -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; @@ -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); diff --git a/lib/runners/node.js b/lib/runners/node.js index dd098d5..b5f924d 100644 --- a/lib/runners/node.js +++ b/lib/runners/node.js @@ -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(" "); @@ -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() { diff --git a/lib/runners/nodehost.js b/lib/runners/nodehost.js index 37f62b4..b90e6b1 100644 --- a/lib/runners/nodehost.js +++ b/lib/runners/nodehost.js @@ -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) { @@ -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); } diff --git a/test/collateral/rawNoStrict.js b/test/collateral/rawNoStrict.js new file mode 100644 index 0000000..623d479 --- /dev/null +++ b/test/collateral/rawNoStrict.js @@ -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.'); +} diff --git a/test/collateral/rawStrict.js b/test/collateral/rawStrict.js new file mode 100644 index 0000000..1c4abdc --- /dev/null +++ b/test/collateral/rawStrict.js @@ -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.'); +} diff --git a/test/expected.js b/test/expected.js index 527ae55..b47c6ad 100644 --- a/test/expected.js +++ b/test/expected.js @@ -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" },