From ecf4a66004a253cc33f78575e14cc0a22d78035b Mon Sep 17 00:00:00 2001 From: hath995 Date: Sun, 1 May 2016 00:27:03 -0700 Subject: [PATCH] Adding test harness to current implementation --- interpreter.js | 60 ++++++++++ tests/test.js | 311 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 371 insertions(+) create mode 100644 tests/test.js diff --git a/interpreter.js b/interpreter.js index ccfe1777..cfc03123 100644 --- a/interpreter.js +++ b/interpreter.js @@ -31,6 +31,7 @@ * global scope object. * @constructor */ +var acorn = acorn || require('./acorn'); var Interpreter = function(code, opt_initFunc) { if (typeof code == 'string') { code = acorn.parse(code); @@ -2674,9 +2675,68 @@ Interpreter.prototype['stepWithStatement'] = function() { Interpreter.prototype['stepWhileStatement'] = Interpreter.prototype['stepDoWhileStatement']; +Interpreter.prototype.extract = function extract(node) { + if(node.isPrimitive) { + return this.extractPrimitive(node); + }else if(node.parent === this.ARRAY) { + return this.extractArray(node); + }else if(node.parent === this.OBJECT) { + return this.extractObject(node); + }else if(node.parent === this.REGEXP) { + return this.extractPrimitive(node); + }else if(node.type === 'function') { + return this.extractFunction(node); + }else if(node.type === 'object' && node.parent.type === 'function') { + return this.extractClassObject(node); + } +} + +Interpreter.prototype.extractFunction = function extractFunction(node) { + return { + type: 'function', + funcText: escodegen.generate(node.node), + createdIn: node.parentScope.scopeName + } +} + +Interpreter.prototype.extractPrimitive = function extractPrimitive(node) { + return node.data; +} + +Interpreter.prototype.extractArray = function extractArray(node) { + var result = []; + for(var index in node.properties) { + result[index] = this.extract(node.properties[index]); + }; + return result; +} + +Interpreter.prototype.extractClassObject = function extractClassObject(node) { + var result = { + type: 'object', + constructor: node.parent.node.id.name, properties: {}}; + for(var prop in node.properties) { + result.properties[prop] = this.extract(node.properties[prop]); + } + return result; +} + +Interpreter.prototype.extractObject = function extractObject(node) { + var result = { + type: 'object', + constructor: 'object', properties: {}}; + for(var prop in node.properties) { + result.properties[prop] = this.extract(node.properties[prop]); + } + return result; +} + // Preserve top-level API functions from being pruned by JS compilers. // Add others as needed. +var window = window || {}; window['Interpreter'] = Interpreter; Interpreter.prototype['appendCode'] = Interpreter.prototype.appendCode; Interpreter.prototype['step'] = Interpreter.prototype.step; Interpreter.prototype['run'] = Interpreter.prototype.run; +var module = module || {}; +module.exports = Interpreter; diff --git a/tests/test.js b/tests/test.js new file mode 100644 index 00000000..b58d60f5 --- /dev/null +++ b/tests/test.js @@ -0,0 +1,311 @@ +var Interpreter = require('../interpreter.js'); +var expect = require('chai').expect; + +var setup_code = "function Writer() {\ + this.output = [];\ + this.log = function(text) {\ + this.output.push(text);\ + };\ +};\ + var c = new Writer();"; + + +function getOutput(code) { + var test = new Interpreter(setup_code + code); + var state = null; + var count = 0; + while(test.step()) { + //console.log(count , test.stateStack[0]); + if(test.stateStack.length > 0) { + state = test.stateStack[0]; + } + count ++; + } + var results = test.extract(state.scope.properties.c.properties.output) + return results; +} + +describe("try/catch/finally", () => { + it("Should print the results of the try block before the finally block", () => { + var test_code = '\ + try {\ + c.log("tried");\ + } finally {\ + c.log("finally");\ + }'; + var results = getOutput(test_code); + expect(results).to.deep.equal(["tried","finally"]); + }); + + it("Should have an uncaught exception", () => { + var test_code = '\ + try {\ + c.log("tried");\ + throw "oops";\ + } finally {\ + c.log("finally");\ + }'; + expect( () => { + var results = getOutput(test_code); + }).to.throw(Error); + }); + + it("Should print the results of the try block before the finally block, and not the catch branch", () => { + var test_code = '\ + try {\ + c.log("tried");\ + } catch (e) {\ + c.log("caught");\ + } finally {\ + c.log("finally");\ + }'; + var results = getOutput(test_code); + expect(results).to.deep.equal(["tried","finally"]); + }); + + + it("Should print the results of the caught block before the finally block, and not the rest of the try branch", () => { + var test_code = '\ + try {\ + c.log("tried");\ + throw false;\ + c.log("tried again");\ + } catch (e) {\ + c.log("caught");\ + } finally {\ + c.log("finally");\ + }'; + var results = getOutput(test_code); + expect(results).to.deep.equal(["tried","caught","finally"]); + }); + + it("Should handle the exception in the outer try catch exception, and finish inner finally", () => { + var test_code = '\ + try {\ + try {\ + throw "oops";\ + }\ + finally {\ + c.log("finally");\ + }\ + }\ + catch (ex) {\ + c.log("outer");\ + c.log(ex);\ + }'; + var results = getOutput(test_code); + expect(results).to.deep.equal(["finally","outer","oops"]); + }); + + it("Should handle inner try catch, and not call outer catch", () => { + var test_code = '\ + try {\ + try {\ + throw "oops";\ + }\ + catch (ex) {\ + c.log("inner");\ + c.log(ex);\ + }\ + finally {\ + c.log("finally");\ + }\ + }\ + catch (ex) {\ + c.log("outer");\ + c.log(ex);\ + }'; + var results = getOutput(test_code); + expect(results).to.deep.equal(["inner","oops","finally"]); + }); + + it("Should handle rethrowing the error, and catching in the outer", () => { + var test_code = '\ + try {\ + try {\ + throw "oops";\ + }\ + catch (ex) {\ + c.log("inner");\ + c.log(ex);\ + throw ex;\ + }\ + finally {\ + c.log("finally");\ + }\ + }\ + catch (ex) {\ + c.log("outer");\ + c.log(ex);\ + }'; + var results = getOutput(test_code); + expect(results).to.deep.equal(["inner","oops","finally","outer","oops"]); + }); + + it("Should not catch if returning early in a finally block", () => { + var test_code = '\ + function test() {\ + try {\ + try {\ + throw "oops";\ + }\ + catch (ex) {\ + c.log("inner");\ + c.log(ex);\ + throw ex;\ + }\ + finally {\ + c.log("finally");\ + return;\ + }\ + }\ + catch (ex) {\ + c.log("outer");\ + }\ + }\ + test();'; + + var test = new Interpreter(setup_code + test_code); + var state = null; + var count = 0; + expect(() => { + while(test.step()) { + //console.log(count , test.stateStack[0]); + if(test.stateStack.length > 0) { + state = test.stateStack[0]; + } + count++; + } + }).to.throw(Error); + var results = test.extract(test.getScope().properties.c.properties.output); + expect(results).to.deep.equal(['inner','oops','finally']); + }); + + it("It should not leave undone finally statements for uncaught exceptions", () => { + var test_code = '\ + function test() {\ + try {\ + c.log("try");\ + throw "oops";\ + c.log("shouldn\'t be here");\ + } finally {\ + c.log("finally");\ + }\ + c.log("got here?");\ + return 4;\ + }\ + var hmm = test();'; + + var test = new Interpreter(setup_code + test_code); + var state = null; + var count = 0; + expect(() => { + while(test.step()) { + //console.log(count , test.stateStack[0]); + if(test.stateStack.length > 0) { + state = test.stateStack[0]; + } + count++; + } + }).to.throw(Error); + var results = test.extract(test.stateStack[test.stateStack.length-1].scope.properties.c.properties.output); + expect(results).to.deep.equal(['try','finally']); + }) +}) + +describe('return', () => { + it("Should return a simple value", () => { + var test_code = '\ + function id(x) {\ + return x;\ + }\ + c.log(id(10));'; + var results = getOutput(test_code); + expect(results).to.deep.equal([10]); + }); + + it("Should return early", () => { + var test_code = '\ + function test(bool) {\ + if(bool) {\ + c.log(bool);\ + return bool;\ + c.log("nope");\ + }else{\ + c.log("negative");\ + return bool;\ + }\ + }\ + c.log(test(true));'; + var results = getOutput(test_code); + expect(results).to.deep.equal([true, true]); + }); + + it("Should respect finally", () => { + var test_code = '\ + function example() {\ + try {\ + return true;\ + }\ + finally {\ + return false;\ + }\ + }\ + c.log(example());'; + var results = getOutput(test_code); + expect(results).to.deep.equal([false]); + }); + + + it("Should respect catch and finally", () => { + var test_code = '\ + function example() {\ + try {\ + throw "oops";\ + } catch (e) {\ + return e;\ + } finally {\ + return false;\ + }\ + }\ + c.log(example());'; + var results = getOutput(test_code); + expect(results).to.deep.equal([false]); + }); + + it("Should handle continue statements", () => { + var test_code = '\ + for(var i = 0; i < 2; i++) {\ + try {\ + c.log(i);\ + continue;\ + } finally {\ + c.log("end");\ + }\ + }'; + var results = getOutput(test_code); + expect(results).to.deep.equal([0,'end', 1, 'end']) + }) + + xit("should handle the scopes created by catch", () => { + var test_code = 'function capturedFoo() {return foo};\ + foo = "prior to throw";\ + try {\ + throw "Error";\ + }\ + catch (foo) {\ + var foo = "initializer in catch";\ + }'; + + var test = new Interpreter(setup_code + test_code); + var state = null; + while(test.step()) { + //console.log(count , test.stateStack[0]); + if(test.stateStack.length > 0) { + state = test.stateStack[0]; + } + } + var results = test.extractScopeValues(state.scope); + expect(results.properties.foo).to.equal('prior to throw'); + }) +});