adder-script
Version:
Python like language to execute untrusted codes in browsers and Node.js.
1,196 lines (1,018 loc) • 84.2 kB
JavaScript
// get the global object (either for browser or node.js)
var _window = typeof window === "undefined" ? global : window;
// for tests in nodejs
if (typeof require != 'undefined')
{
if (typeof _window.AdderScript == 'undefined') {
_window.AdderScript = require("./../dist/node-adder");
}
}
// like parseFloat, but returns NaN if contain english characters
function strictParseFloat(value) {
if (typeof value !== "string" && typeof value !== "number") {return NaN;}
if(/^(\-|\+)?([0-9]+(\.[0-9]+)?|Infinity)$/
.test(value))
return Number(value);
return NaN;
}
// supported operators
var operators = ["+=", "-=", "*=", "/=", "|=", "&=", "%=", "**", "==", "!=", ">=", "<=", ">", "<", "+", "-", "*", "/", "|", "&", "%", "=", "."];
var wordsOperators = ["is not", "not in", "not", "in", "or", "and", "is"];
operators = operators.concat(wordsOperators);
// return if value is a number
function isNumber(value)
{
return (!isNaN(strictParseFloat(value)));
}
// execute code and return [lastStatementCode, lastValue]
function executeAndReturn(code) {
// create interpreter
var interpreter = new AdderScript._internals.Interpreter({throwErrors: true});
// to disable print() output
interpreter.output = function() {};
// load all modules
interpreter.addModule("ALL");
// on browsers only, connect output (on node.js we don't want it it will spam results)
if (typeof window !== "undefined") {
AdderScript._internals.Console.bindToNativeConsole();
}
// execute
interpreter.eval(code);
interpreter.propagateExecutionErrors();
var ret = interpreter.getLastValue();
// make sure scope is back to global
if (interpreter._context._stack.length !== 1) {
throw "Context is not back to global scope!";
}
// sanity test - run again to make sure second time we got the same value
interpreter.execute();
var ret2 = interpreter.getLastValue();
if (ret.isSimple && ret.getValue() !== ret2.getValue()) {
throw "Different return value on second run (" + ret.getValue() + ", " + ret2.getValue() + ")!";
}
// get last statement
var statement = interpreter.getLastStatement();
statement = statement ? statement.identifier : null
// parse return value
ret = ret && ret.getValue ? ret._value : ret;
if (ret instanceof Array) {ret = ret.map(function(x) {return x ? x._value : x;});};
// return
return [statement, ret];
}
// convert return value into native js
function toNativeJs(val) {
// get value from variable
val = val && val.getValue ? val._value : val;
// handle list
if (val && val.constructor == AdderScript._internals.Core.List)
{
val = val._list;
return val.map(function(x) {
return toNativeJs(x);
})
}
// handle set
if (val && val.constructor == AdderScript._internals.Core.Set)
{
val = val._set;
var ret = new Set();
val.forEach(function(x) {
ret.add(toNativeJs(x));
});
return ret;
}
// handle dictionary
if (val && val.constructor == AdderScript._internals.Core.Dict)
{
val = val._dict;
var ret = {};
for (var key in val) {
ret[key] = toNativeJs(val[key]);
}
return ret;
}
// handle plain variable
return val;
}
// use assert.deepEqual or assert.strictEqual based on expected type
function smartCompare(assert, ret, expected)
{
// special case - check if NaN
if (isNaN(expected) && typeof expected === "number")
{
assert.ok(isNaN(ret) && typeof(ret) === "number");
}
// check result for list
else if ((expected !== null) && (expected !== undefined) &&
(expected instanceof Array || expected instanceof Set || expected.constructor == Object))
{
assert.deepEqual(ret, expected);
}
// check result for plain objects
else
{
assert.strictEqual(ret, expected);
}
}
// execute code and check returned value
// processResults - if provided will run this function on result and on expected
function testReturnValue(assert, code, expected, processResults)
{
// execute and get value
var ret = executeAndReturn(code)[1];
// convert return value to plain js object
ret = toNativeJs(ret);
// run result process
if (processResults) {
ret = processResults(ret);
expected = processResults(expected);
}
// check return value
smartCompare(assert, ret, expected);
}
// round result to few digits after dot
function roundMathResult(res)
{
if (typeof res !== "number") {return res;}
return Math.round(res * 100000.0) / 100000.0;
}
// execute arithmetic expression and compare rounded results
function compareExpression(assert, code, expected)
{
testReturnValue(assert, code, expected, roundMathResult)
}
function listToTokensList(expression)
{
for (var i = 0; i < expression.length; ++i)
{
var curr = expression[i];
if (curr === "," || curr === ":") {curr = {t: "p", "v": curr};}
else if (operators.indexOf(curr) !== -1) {curr = {t: "o", "v": curr};}
else if (isNumber(curr)) {curr = {t: "n", "v": curr};}
else if (curr[0] === '"' && curr[curr.length-1] === '"') {curr = {t: "s", "v": curr};}
else if (curr === "(" || curr === ")") {curr = {t: "o", "v": curr};}
else if (curr === "\n" || curr === ";") {curr = {t: "b", "v": curr};}
else if (curr[0] === '~') {curr = {t: "_", "v": parseInt(curr[2])};}
else {curr = {t: "v", "v": curr};}
expression[i] = curr;
}
return expression;
}
// parse utils - parse expression
QUnit.test("lexer", function( assert ) {
// create lexer
var lexer = new AdderScript._internals.Lexer();
// check basic expression
assert.deepEqual(lexer.parseExpression(
"5 + 5"),
listToTokensList(["5", "+", "5"]));
// with line break
assert.deepEqual(lexer.parseExpression(
"5 + 5\na"),
listToTokensList(["5", "+", "5", "\n", "a"]));
assert.deepEqual(lexer.parseExpression(
"5 + 5;a"),
listToTokensList(["5", "+", "5", ";", "a"]));
assert.deepEqual(lexer.parseExpression(
"5 + 5\\\na"),
listToTokensList(["5", "+", "5", "a"]));
// with different blocks
assert.deepEqual(lexer.parseExpression(
"5 + 5\n a;a"),
listToTokensList(["5", "+", "5", "\n", "~:1", "a", ";", "a"]));
assert.deepEqual(lexer.parseExpression(
"5 + 5; a"),
listToTokensList(["5", "+", "5", ";", "a"]));
assert.deepEqual(lexer.parseExpression(
"5 + 5\\\n a"),
listToTokensList(["5", "+", "5", "a"]));
// with comment
assert.deepEqual(lexer.parseExpression(
"5 + 5 # comment lol"),
listToTokensList(["5", "+", "5"]));
assert.deepEqual(lexer.parseExpression(
"5 + 5#comment lol"),
listToTokensList(["5", "+", "5"]));
assert.deepEqual(lexer.parseExpression(
'5 + 5 + "not # comment"'),
listToTokensList(["5", "+", "5", "+", '"not # comment"']));
// some statements
assert.deepEqual(lexer.parseExpression(
'print "test"'),
listToTokensList(["print", '"test"']));
assert.deepEqual(lexer.parseExpression(
'if True == a:'),
listToTokensList(["if", "True", "==", "a", ":"]));
assert.deepEqual(lexer.parseExpression(
'for i in range(50)'),
listToTokensList(["for", "i", "in", "range", "(", "50", ")"]));
// check more complicated expression
assert.deepEqual(lexer.parseExpression(
"5 * 2 / 4 + 1 - 5 / 1.1 + 2.2 * 1.4 + -1"),
listToTokensList(["5", "*", "2", "/", "4", "+", "1", "-", "5", "/", "1.1", "+", "2.2", "*", "1.4", "+", "-", "1"]));
// check tricky spaces and tabs
assert.deepEqual(lexer.parseExpression(
"5 * 2 / 4 + 1 - 5 / 1.1 + 2.2 * 1.4 + -1"),
listToTokensList(["5", "*", "2", "/", "4", "+", "1", "-", "5", "/", "1.1", "+", "2.2", "*", "1.4", "+", "-", "1"]));
// check brackets
assert.deepEqual(lexer.parseExpression(
"5 * 2 + ( 6 +4)"),
listToTokensList(["5", "*", "2", "+", "(", "6", "+", "4", ")"]));
assert.deepEqual(lexer.parseExpression(
"5 * 2 + ( 6 +(2-4))"),
listToTokensList(["5", "*", "2", "+", "(", "6", "+", "(", "2", "-", "4", ")", ")"]));
assert.deepEqual(lexer.parseExpression(
"(5 * 2) + ( 6 +(2-4))"),
listToTokensList(["(", "5", "*", "2", ")", "+", "(", "6", "+", "(", "2","-","4", ")", ")"]));
assert.deepEqual(lexer.parseExpression(
"((5 * 2))"),
listToTokensList(["(", "(", "5", "*", "2", ")", ")"]));
// check with strings
assert.deepEqual(lexer.parseExpression(
'"5 + 2"'),
listToTokensList(['"5 + 2"']));
assert.deepEqual(lexer.parseExpression(
'"5 \n 2"'),
listToTokensList(['"5 \n 2"']));
assert.deepEqual(lexer.parseExpression(
'"5 \\ \r \n 2"'),
listToTokensList(['"5 \\ \r \n 2"']));
assert.deepEqual(lexer.parseExpression(
'"5 + 2" + "" + 5'),
listToTokensList(['"5 + 2"', "+", '""', "+", "5"]));
assert.deepEqual(lexer.parseExpression(
'5 + "5 +, 2" * 2'),
listToTokensList(["5", "+", '"5 +, 2"', "*", "2"]));
assert.deepEqual(lexer.parseExpression(
'5 + " (5 + #, 2" * 2'),
listToTokensList(["5", "+", '" (5 + #, 2"', "*", "2"]));
assert.deepEqual(lexer.parseExpression(
'5 + (" (5 + #, 2 )") * 2'),
listToTokensList(["5", "+", "(", '" (5 + #, 2 )"', ")", "*", "2"]));
// long, complex expression
assert.deepEqual(lexer.parseExpression("(5 + 2) + foo(5)(2)*foo(4)+5*2"),
listToTokensList(["(", "5","+","2", ")","+","foo","(", "5", ")", "(", "2", ")","*","foo","(", "4", ")","+","5","*","2"]));
// try weird yet legal stuff
assert.deepEqual(lexer.parseExpression("5 +- 2"), listToTokensList(["5", "+", "-", "2"]));
assert.deepEqual(lexer.parseExpression("5 -+ 2"), listToTokensList(["5", "-", "+", "2"]));
assert.deepEqual(lexer.parseExpression("5 *+ 2"), listToTokensList(["5", "*", "+", "2"]));
assert.deepEqual(lexer.parseExpression("5 * +2"), listToTokensList(["5", "*", "+", "2"]));
assert.deepEqual(lexer.parseExpression("5 / -2"), listToTokensList(["5", "/", "-", "2"]));
assert.deepEqual(lexer.parseExpression("5 + - + + -2"), listToTokensList(["5", "+", "-", "+", "+", "-", "2"]));
assert.deepEqual(lexer.parseExpression("5 +-+ 2"), listToTokensList(["5", "+", "-", "+", "2"]));
assert.deepEqual(lexer.parseExpression("5 * + + 2"), listToTokensList(["5", "*", "+", "+", "2"]));
// function calls
assert.deepEqual(lexer.parseExpression("5 + func()"), listToTokensList(["5", "+", "func", "(", ")"]));
assert.deepEqual(lexer.parseExpression("5 + func(2 * 3)"), listToTokensList(["5", "+", "func", "(", '2', '*', '3', ")"]));
assert.deepEqual(lexer.parseExpression("5 + func(aa)"), listToTokensList(["5", "+", "func", "(", 'aa', ")"]));
// check using variables
assert.deepEqual(lexer.parseExpression("5 / test-2"), listToTokensList(["5", "/", "test", "-", "2"]));
assert.deepEqual(lexer.parseExpression("5 / test5 -2"), listToTokensList(["5", "/", "test5", "-", "2"]));
// check some illegal stuff
assert.throws(function(){lexer.parseExpression("6 + 0test - 4")}, AdderScript.Errors.IllegalExpression);
assert.throws(function(){lexer.parseExpression("6 + 5test - 4")}, AdderScript.Errors.IllegalExpression);
assert.throws(function(){lexer.parseExpression("6 + 9test - 4")}, AdderScript.Errors.IllegalExpression);
assert.throws(function(){lexer.parseExpression('4 + "bla')}, AdderScript.Errors.IllegalExpression);
});
// test parser and AST
QUnit.test("parser", function( assert ) {
// create parser
var lexer = new AdderScript._internals.Lexer();
// remove undefined values by json dumps-loads
function cleanup(data)
{
return JSON.parse(JSON.stringify(data));
}
assert.deepEqual(cleanup(AdderScript._internals.Parser.parse(lexer.parseExpression("5 + 2"))),
[{"type":"+","left":{"type":"number","value":"5"},"right":{"type":"number","value":"2"}}]);
assert.deepEqual(cleanup(AdderScript._internals.Parser.parse(lexer.parseExpression("5 + 2 * 1"))),
[{"left": {"type": "number","value": "5"},"right": {"left": {"type": "number","value": "2"},"right": {"type": "number","value": "1"},"type": "*"},"type": "+"}]);
assert.deepEqual(cleanup(AdderScript._internals.Parser.parse(lexer.parseExpression("func(2,3)"))),
[{"args":[{"type":"number","value":"2"},{"type":"number","value":"3"}],"name":"func","type":"call"}]);
assert.deepEqual(cleanup(AdderScript._internals.Parser.parse(lexer.parseExpression('5 + "2"'))),
[{"type":"+","left":{"type":"number","value":"5"},"right":{"type":"string","value":'"2"'}}]);
});
// pass statement
QUnit.test("statement_pass", function( assert ) {
// check legal cases
assert.deepEqual(executeAndReturn("pass"), ["builtin.statements.pass", null]);
assert.deepEqual(executeAndReturn("pass\n"), ["builtin.statements.pass", null]);
// these are not a pass statement but evaluations..
assert.throws(function(){executeAndReturn("pass + NOT")}, AdderScript.Errors.UndefinedVariable);
assert.throws(function(){executeAndReturn("passover")}, AdderScript.Errors.UndefinedVariable);
});
// comments
QUnit.test("statement_comment", function( assert ) {
var compiler = new AdderScript._internals.Compiler();
// check legal cases
assert.deepEqual(executeAndReturn("# foo"), [null, null]);
assert.deepEqual(executeAndReturn("#foo"), [null, null]);
});
// strings
QUnit.test("strings", function( assert ) {
// quotes with "
compareExpression(assert, 'a = "test"', "test");
compareExpression(assert, 'a = "te;st"', "te;st");
compareExpression(assert, 'a = "te; \n \t ; # st"', "te; \n \t ; # st");
compareExpression(assert, 'a = "te; \n \' \' \t ; # st"', "te; \n ' ' \t ; # st");
// quotes with '
compareExpression(assert, "a = 'test'", "test");
compareExpression(assert, "a = 'te;st'", "te;st");
compareExpression(assert, "a = 'te; \n \t ; # st'", "te; \n \t ; # st");
compareExpression(assert, "a = 'te; \n \" \" \t ; # st'", "te; \n \" \" \t ; # st");
});
// compiler - compile def
QUnit.test("statement_def", function( assert ) {
// check legal cases
assert.equal(executeAndReturn("def test(): pass")[0], "builtin.statements.def");
assert.equal(executeAndReturn("def test (\t ) : pass")[0], "builtin.statements.def");
assert.equal(executeAndReturn("def test(foo2): pass")[0], "builtin.statements.def");
assert.equal(executeAndReturn("def test($foo_2): pass")[0], "builtin.statements.def");
assert.equal(executeAndReturn("def test(foo): pass")[0], "builtin.statements.def");
assert.equal(executeAndReturn("def test(__foo): pass# just a comment")[0], "builtin.statements.def");
assert.equal(executeAndReturn("def test(\tfoo ): pass")[0], "builtin.statements.def");
assert.equal(executeAndReturn('def test(\tfoo, bar ): pass')[0], "builtin.statements.def");
// check syntax errors
assert.throws(function(){executeAndReturn("def (): pass")}, AdderScript.Errors.SyntaxError);
assert.throws(function(){executeAndReturn("defoo test(): pass")}, AdderScript.Errors.SyntaxError);
assert.throws(function(){executeAndReturn("def test ()")}, AdderScript.Errors.SyntaxError);
assert.throws(function(){executeAndReturn("def test (: pass")}, AdderScript.Errors.SyntaxError);
assert.throws(function(){executeAndReturn("def test ):")}, AdderScript.Errors.SyntaxError);
assert.throws(function(){executeAndReturn("def test (#):")}, AdderScript.Errors.SyntaxError);
assert.throws(function(){executeAndReturn("def test () #:")}, AdderScript.Errors.SyntaxError);
assert.throws(function(){executeAndReturn('def test(\tfoo, "bar" ):')}, AdderScript.Errors.SyntaxError);
assert.throws(function(){executeAndReturn('def test(\tfoo, "bar" + "gaga," ):')}, AdderScript.Errors.SyntaxError);
assert.throws(function(){executeAndReturn('def test(1+2-3*4/5.0, 6 ):')}, AdderScript.Errors.SyntaxError);
assert.throws(function(){executeAndReturn('def test(foo, 5):')}, AdderScript.Errors.SyntaxError);
assert.throws(function(){executeAndReturn('def test(5):')}, AdderScript.Errors.SyntaxError);
});
// defining and using functions
QUnit.test("functions", function( assert ) {
// basic functions calls
compareExpression(assert, "def test(): pass; test()", null);
compareExpression(assert, "def test(): return 15; test()", 15);
compareExpression(assert, "a = 5; def test(): return a; test()", 5);
compareExpression(assert, "a = 5; def test(): return a+=1; test()", 6);
compareExpression(assert, "a = 5; def test(): return a+=1; test(); a", 6);
compareExpression(assert, "a = 5; b = 2; def test1(): return a; def test2(): return test1() + b; test2()", 5+2);
compareExpression(assert, "def test(a): return 15 + a; test(2)", 15 + 2);
compareExpression(assert, "def test(a): return 15 + a; test(2); test(3)", 15 + 3);
compareExpression(assert, "a = 0; def test(): return a; a", 0);
// try function with block
var code = "" +
"def func(a,b,c):\n" +
" return a + b + c\n" +
"\n" +
"func(1,2,3)\n";
compareExpression(assert, code, 1+2+3);
// function inside function!
var code = "" +
"def func(a,b,c):\n" +
" def internal(): return 5\n" +
" return a + b + c + internal()\n" +
"func(1,2,3)\n";
compareExpression(assert, code, 1+2+3+5);
// function inside function with block body!
var code = "" +
"def func(a,b,c):\n" +
" def internal():\n" +
" return 5\n" +
" return a + b + c + internal()\n" +
"func(1,2,3)\n";
compareExpression(assert, code, 1+2+3+5);
// function that calls another function and return value
var code = "" +
"def test2():\n" +
" return 5\n" +
"def test():\n" +
" return test2()\n" +
"test()\n";
compareExpression(assert, code, 5);
// function that calls another function but note that the wrapping function "forgets" to add return, so should return None
var code = "" +
"def test2():\n" +
" return 5\n" +
"def test():\n" +
" test2() # missing return statement here \n" +
"test()\n";
compareExpression(assert, code, null);
});
// basic expressions evaluation
QUnit.test("basic_expressions", function( assert ) {
// test basic expressions
compareExpression(assert, "5", 5);
compareExpression(assert, "5 + 5", 5 + 5);
compareExpression(assert, "5 + -5", 5 + -5);
compareExpression(assert, "-5 + -5", -5 + -5);
compareExpression(assert, "5 + 5 * 2", 5 + 5 * 2);
compareExpression(assert, "5 + 5 * 2 / 5 ", 5 + 5 * 2 / 5);
compareExpression(assert, "5 + 5 * 2 / 5 + (2+5/2) * 3", 5 + 5 * 2 / 5 + (2+5/2) * 3);
compareExpression(assert, "5 + -5 * -2 / -5 + (2+5/-2) * 3", 5 + -5 * -2 / -5 + (2+5/-2) * 3);
compareExpression(assert, "-5.2 + - - + - - + 5.1 * - -2.6 / (2+3.0) -+5 + (2+\t5/-2) * 3", -5.2 + - - + - - + 5.1 * - -2.6 / (2+3.0) -+5 + (2+ 5/-2) * 3);
compareExpression(assert, "5 - 3 + 52 / 2.1 * 5.2 + 2 - 4 % 23 * 21.2 - 0.23 / ( 1 -4 ) + 1.422 + Math.abs(-4) - 2", 5 - 3 + 52 / 2.1 * 5.2 + 2 - 4 % 23 * 21.2 - 0.23 / ( 1 -4 ) + 1.422 + Math.abs(-4) - 2);
compareExpression(assert, "4 + 63 | 52", 4 + 63 | 52);
compareExpression(assert, "4 + 63 & 52", 4 + 63 & 52);
compareExpression(assert, "4 + 63 % 52", 4 + 63 % 52);
compareExpression(assert, "3 | 5 & 2 * 54 % 23 * 21.2", 3 | 5 & 2 * 54 % 23 * 21.2);
compareExpression(assert, "52 / 2.1 * 5.2 + 2 - 3 | 5 & 2 * 54 % 23 * 21.2", 52 / 2.1 * 5.2 + 2 - 3 | 5 & 2 * 54 % 23 * 21.2);
compareExpression(assert, "5 - 3 + 52 / 2.1 * 5.2 + 2 - 3 | 5 & 2 * 54 % 23 * 21.2 - 0.23 / ( 1 -4 ) + 1.422 + Math.abs(-4) - 2",
5 - 3 + 52 / 2.1 * 5.2 + 2 - 3 | 5 & 2 * 54 % 23 * 21.2 - 0.23 / ( 1 -4 ) + 1.422 + Math.abs(-4) - 2);
compareExpression(assert, "a = 5 - 3 + 52 / 2.1 * 5.2 + 2 - 3 | 5 & 2 * 54 % 23 * 21.2 - 0.23 / ( 1 -4 ) + 1.422 + Math.abs(-4) - 2; a",
5 - 3 + 52 / 2.1 * 5.2 + 2 - 3 | 5 & 2 * 54 % 23 * 21.2 - 0.23 / ( 1 -4 ) + 1.422 + Math.abs(-4) - 2);
// now do expressions with functions calls
_window.__adder_script_test = function() {return 5.2;};
var _sec_test = _window.__adder_script_test;
compareExpression(assert, "_sec_test()", _sec_test());
compareExpression(assert, "5 + 5 - _sec_test()", 5 + 5 - _sec_test());
compareExpression(assert, "5 + -_sec_test()", 5 + -_sec_test());
compareExpression(assert, "-5 + -_sec_test()", -5 + -_sec_test());
compareExpression(assert, "5 + _sec_test() * 2", 5 + _sec_test() * 2);
compareExpression(assert, "5 + 5 * _sec_test() / 5 ", 5 + 5 * _sec_test() / 5);
compareExpression(assert, "5 + 5 * _sec_test() / 5 + (2+5/_sec_test()) * 3", 5 + 5 * _sec_test() / 5 + (2+5/_sec_test()) * 3);
compareExpression(assert, "5 + -5 * -2 / -5 + (2+5/-_sec_test()) * 3", 5 + -5 * -2 / -5 + (2+5/-_sec_test()) * 3);
compareExpression(assert, "-_sec_test() + - - + - - + 5.1 * - -_sec_test() / (2+3.0) -+5 + (2+\t5/-2) * 3", -_sec_test() + - - + - - + 5.1 * - -_sec_test() / (2+3.0) -+5 + (2+ 5/-2) * 3);
// with variables
compareExpression(assert, "a = 5; a + 5", 5 + 5);
compareExpression(assert, "a = 5; b = 2; a + b", 5 + 2);
var a = 5; var b = 6; var c = 2.3;
compareExpression(assert, "a = 5; b = 6; a + 5 * b / 5 + (2+a/2) * 3", a + 5 * b / 5 + (2+a/2) * 3);
compareExpression(assert, "a = 5; b = 6; a + -a * -b / -5 + (a+5/-b) * a", a + -a * -b / -5 + (a+5/-b) * a);
compareExpression(assert, "a = 5; b = 6; c = 2.3; -c + - - + - - + a * - -b / (2+a) -+c + (2+\t5/-2) * 3", -c + - - + - - + a * - -b / (2+a) -+c + (2+ 5/-2) * 3);
compareExpression(assert, "a = 5; b = 6; c = 2.3; a = a | b & c; a", a | b & c);
// now with a function that gets param
_window.__adder_script_test = function(x) {return x;};
var _sec_test = _window.__adder_script_test;
compareExpression(assert, "_sec_test(5)", _sec_test(5));
compareExpression(assert, "5 + 5 - _sec_test(5)", 5 + 5 - _sec_test(5));
});
// test all operators
QUnit.test("operators", function( assert ) {
// just a reminder:
// ["+=", "-=", "*=", "/=", "|=", "&=", "%=", "**", "==", "!=", ">", "<", ">=", "<=",
// "+", "-", "*", "/", "|", "&", "%", "=", "not", "in", "or", "and"];
// test all the following: "+=", "-=", "*=", "/=", "|=", "&=", "%="
var ops = ["+=", "-=", "*=", "/=", "|=", "&=", "%="];
for (var i = 0; i < ops.length; ++i)
{
var op = ops[i];
var expected = eval("var d = 5; d " + op + " 2; d");
compareExpression(assert, "d = 5; d " + op + " 2; d", expected);
}
// test ** operator
compareExpression(assert, "3 ** 2", Math.pow(3, 2));
// test "==", "!=", ">", "<", ">=", "<="
compareExpression(assert, "3 == 3", true);
compareExpression(assert, "3 == 2", false);
compareExpression(assert, "3 != 2", true);
compareExpression(assert, "3 != 3", false);
compareExpression(assert, "3 > 1", true);
compareExpression(assert, "3 > 3", false);
compareExpression(assert, "3 > 5", false);
compareExpression(assert, "3 < 1", false);
compareExpression(assert, "3 < 3", false);
compareExpression(assert, "3 < 5", true);
compareExpression(assert, "3 >= 3", true);
compareExpression(assert, "3 >= 5", false);
compareExpression(assert, "3 <= 1", false);
compareExpression(assert, "3 <= 3", true);
compareExpression(assert, "3 <= 5", true);
// test the basics: +, -, *, /, |, &, %
compareExpression(assert, "3 + 2", 3 + 2);
compareExpression(assert, "3 - 2", 3 - 2);
compareExpression(assert, "3 * 2", 3 * 2);
compareExpression(assert, "3 / 2", 3 / 2);
compareExpression(assert, "3 + 2.1", 3 + 2.1);
compareExpression(assert, "3 - 2.1", 3 - 2.1);
compareExpression(assert, "3 * 2.1", 3 * 2.1);
compareExpression(assert, "3 / 2.1", 3 / 2.1);
compareExpression(assert, "3 | 2", 3 | 2);
compareExpression(assert, "3 & 2", 3 & 2);
compareExpression(assert, "3 % 2", 3 % 2);
// check basic assignment
compareExpression(assert, "a = 2; a", 2);
// test 'not'
compareExpression(assert, "not True", false);
compareExpression(assert, "not False", true);
compareExpression(assert, "not 5", false);
compareExpression(assert, "not 0", true);
// test 'or'
compareExpression(assert, "0 or 5", 5);
compareExpression(assert, "5 or 0", 5);
compareExpression(assert, "True or False", true);
compareExpression(assert, "False or True", true);
compareExpression(assert, "False or None or 5 or 10", 5);
compareExpression(assert, "a = False or None or 5 or 10; a", 5);
compareExpression(assert, '"aa" or False', "aa");
compareExpression(assert, 'True or "aa" or False', true);
compareExpression(assert, '0 or False', false);
compareExpression(assert, 'a = False and None and "yay!" or True; a', true);
compareExpression(assert, 'a = False or None and "yay!" and True; a', null);
// test 'and'
compareExpression(assert, "0 and 5", 0);
compareExpression(assert, "5 and 0", 0);
compareExpression(assert, "True and False", false);
compareExpression(assert, "False and True", false);
compareExpression(assert, '"aa" and False', false);
compareExpression(assert, 'True and "aa" and False', false);
compareExpression(assert, '1 and True', true);
// test 'in'
compareExpression(assert, '1 in 100', true);
compareExpression(assert, '1 in 430', false);
compareExpression(assert, '"a" in "abcd"', true);
compareExpression(assert, '"z" in "abcd"', false);
compareExpression(assert, '"a" not in "abcd"', false);
compareExpression(assert, '"z" not in "abcd"', true);
compareExpression(assert, '"a" in list("a", "b", "c")', true);
compareExpression(assert, '"z" in list("a", "b", "c")', false);
compareExpression(assert, '1 in list(1,2,3)', true);
compareExpression(assert, '5 in list(1,2,3)', false);
compareExpression(assert, '"1" in list(1,2,3)', false);
compareExpression(assert, '"True" in dir()', true);
compareExpression(assert, '"bla bla" in dir()', false);
// test 'is'
compareExpression(assert, '1 is 1', true);
compareExpression(assert, 'True is True', true);
compareExpression(assert, 'True is False', false);
compareExpression(assert, 'None is False', false);
compareExpression(assert, 'list() is list()', false);
compareExpression(assert, 'list() is not list()', true);
compareExpression(assert, 'True is not True', false);
compareExpression(assert, 'True is not False', true);
});
// test for loops
QUnit.test("loop_while", function( assert ) {
// legal values
compareExpression(assert, 'a = ""; letters = list("t", "e", "s", "t"); while letters.len(): a += letters.shift(); a', "test");
compareExpression(assert, 'a = ""; letters = list("t", "e", "s", "t"); while False: a += letters.shift(); a', "");
compareExpression(assert, 'a = 0; while a < 10: a += 1; a', 10);
compareExpression(assert, 'a = 0; while a < 10 and False: a += 1; a', 0);
// invalid syntax
assert.throws(function(){executeAndReturn('while pass')}, AdderScript.Errors.SyntaxError);
assert.throws(function(){executeAndReturn('while True pass')}, AdderScript.Errors.SyntaxError);
assert.throws(function(){executeAndReturn('while True False: pass')}, AdderScript.Errors.SyntaxError);
});
// test for loops
QUnit.test("loop_for", function( assert ) {
// legal values
compareExpression(assert, 'a = ""; for i in "test": a += i; a', "test");
compareExpression(assert, 'a = 0; for i in range(10): a += i; a', 1+2+3+4+5+6+7+8+9);
compareExpression(assert, 'a = 0; for i in range(10): continue; a', 0);
compareExpression(assert, 'a = 0; for i in range(10): break; a', 0);
compareExpression(assert, 'a = 0; def test(): a+=1; for i in range(10): test(); a', 10);
compareExpression(assert, 'a = 1; for i in range(10): pass; a', 1);
// invalid syntax
assert.throws(function(){executeAndReturn('a = 5; for 5 in "test": pass')}, AdderScript.Errors.SyntaxError);
assert.throws(function(){executeAndReturn('a = 5; for "a" in "test": pass')}, AdderScript.Errors.SyntaxError);
assert.throws(function(){executeAndReturn('a = 5; for vg in "test" pass')}, AdderScript.Errors.SyntaxError);
assert.throws(function(){executeAndReturn('a = 5; for vg + 5 in "test": pass')}, AdderScript.Errors.SyntaxError);
});
// test loop continue
QUnit.test("loop_continue", function( assert ) {
// continue to skip odd values
var code = "" +
"a = 0;\n" +
"for i in range(10):\n" +
" if i % 2 == 0: continue\n" +
" a += 1\n" +
"a";
compareExpression(assert, code, 5);
// continue to skip all
var code = "" +
"a = 0;\n" +
"for i in range(10):\n" +
" if True: continue\n" +
" a += 1\n" +
"a";
compareExpression(assert, code, 0);
// continue to skip odd values
var code = "" +
"a = 0; i = 0;\n" +
"while i < 10:\n" +
" i += 1\n" +
" if i % 2 == 0: continue\n" +
" a += 1\n" +
"a";
compareExpression(assert, code, 5);
// continue to skip all
var code = "" +
"a = 0; i = 0;\n" +
"while i < 10:\n" +
" i += 1\n" +
" if True: continue\n" +
" a += 1\n" +
"a";
compareExpression(assert, code, 0);
// invalid syntax - using continue / break outside loops
assert.throws(function(){executeAndReturn('a = 5; continue;')}, AdderScript.Errors.RuntimeError);
});
// test loop break
QUnit.test("loop_break", function( assert ) {
// continue to skip odd values
var code = "" +
"a = 0;\n" +
"for i in range(10):\n" +
" if i == 5: break\n" +
" a += 1\n" +
"a";
compareExpression(assert, code, 5);
// continue to skip all
var code = "" +
"a = 0;\n" +
"for i in range(10):\n" +
" if i is 0: break\n" +
" a += 1\n" +
"a";
compareExpression(assert, code, 0);
// invalid syntax - using continue / break outside loops
assert.throws(function(){executeAndReturn('a = 5; break;')}, AdderScript.Errors.RuntimeError);
});
// test if
QUnit.test("if_statement", function( assert ) {
var code = "" +
"a = 0;\n" +
"if True: a = 1\n" +
"a";
compareExpression(assert, code, 1);
var code = "" +
"a = 0;\n" +
"if False: a = 1\n" +
"a";
compareExpression(assert, code, 0);
var code = "" +
"n = 1\n" +
"a = 0\n" +
"if n == 1 or n == 2: a = 1\n" +
"a";
compareExpression(assert, code, 1);
var code = "" +
"n = 1\n" +
"a = 0\n" +
"if n == 1 and n == 2: a = 1\n" +
"a";
compareExpression(assert, code, 0);
// invalid syntax
assert.throws(function(){executeAndReturn('if True a = 5')}, AdderScript.Errors.SyntaxError);
assert.throws(function(){executeAndReturn('if True')}, AdderScript.Errors.SyntaxError);
assert.throws(function(){executeAndReturn('if : a = 5')}, AdderScript.Errors.SyntaxError);
});
// test else
QUnit.test("else_statement", function( assert ) {
var code = "" +
"a = 0;\n" +
"if True: a = 1\n" +
"else: a = 2\n" +
"a";
compareExpression(assert, code, 1);
var code = "" +
"a = 0;\n" +
"if False: a = 1\n" +
"else: a = 2\n" +
"a";
compareExpression(assert, code, 2);
var code = "" +
"a = 0;\n" +
"if False: a = 1\n" +
"else: if True: a = 2\n" +
"a";
compareExpression(assert, code, 2);
var code = "" +
"a = 0;\n" +
"if False: a = 1\n" +
"else: if None: a = 2\n" +
"a";
compareExpression(assert, code, 0);
// some basic cases
compareExpression(assert, 'if False: a=5; else: a=1; a', 1);
compareExpression(assert, 'if True: a=5; else: a=1; a', 5);
compareExpression(assert, 'a=5; if False: a=1; else:; a', 5);
var code = "" +
"a=0\n"+
"if False:\n" +
" pass\n" +
"else:\n" +
" if 0:\n" +
" a = 1\n" +
" else:\n" +
" a = 5\n" +
"a"
compareExpression(assert, code, 5);
var code = "" +
"a=0\n"+
"if False:\n" +
" pass\n" +
"else:\n" +
" if True:\n" +
" a = 1\n" +
" else:\n" +
" a = 5\n" +
"a"
compareExpression(assert, code, 1);
// invalid syntax
assert.throws(function(){executeAndReturn('if True: a = 5; else foo')}, AdderScript.Errors.SyntaxError);
assert.throws(function(){executeAndReturn('else: a = 5')}, AdderScript.Errors.SyntaxError);
assert.throws(function(){executeAndReturn('a = 6; else: a = 5;')}, AdderScript.Errors.SyntaxError);
assert.throws(function(){executeAndReturn('a=0; if False: pass; else: if 0: a = 1; else: a = 5; a')}, AdderScript.Errors.SyntaxError);
});
// test elif
QUnit.test("elif_statement", function( assert ) {
var code = "" +
"a = 0;\n" +
"if True: a = 1\n" +
"elif True: a = 2\n" +
"a";
compareExpression(assert, code, 1);
var code = "" +
"a = 0;\n" +
"if False: a = 1\n" +
"elif True: a = 2\n" +
"a";
compareExpression(assert, code, 2);
var code = "" +
"a = 0;\n" +
"if False: a = 1\n" +
"elif False: a = 2\n" +
"a";
compareExpression(assert, code, 0);
var code = "" +
"a = 0;\n" +
"if False: a = 1\n" +
"elif a == 0: \n" +
" a = 2\n" +
"a";
compareExpression(assert, code, 2);
// some basic cases
compareExpression(assert, 'if False: a=5; elif True: a=1; a', 1);
compareExpression(assert, 'if True: a=5; elif 1: a=1; a', 5);
compareExpression(assert, 'a=5; if False: a=1; elif list():; a', 5);
var code = "" +
"a=0\n"+
"if False:\n" +
" pass\n" +
"elif a == 1 or True:\n" +
" if 0:\n" +
" a = 1\n" +
" else:\n" +
" a = 5\n" +
"a"
compareExpression(assert, code, 5);
var code = "" +
"a=0\n"+
"if False:\n" +
" pass\n" +
"elif a == 0 or False and True:\n" +
" if True:\n" +
" a = 1\n" +
" elif False:\n" +
" a = 5\n" +
"a"
compareExpression(assert, code, 1);
// invalid syntax
assert.throws(function(){executeAndReturn('if True: a = 5; elif foo')}, AdderScript.Errors.SyntaxError);
assert.throws(function(){executeAndReturn('elif True: a = 5')}, AdderScript.Errors.SyntaxError);
assert.throws(function(){executeAndReturn('a = 6; elif True: a = 5;')}, AdderScript.Errors.SyntaxError);
assert.throws(function(){executeAndReturn('a=0; if False: pass; elif: if 0: a = 1; else: a = 5; a')}, AdderScript.Errors.SyntaxError);
});
// basic function calls
QUnit.test("basic_function_call", function( assert ) {
// register temp test function
_window.__adder_script_test = function()
{
var args = AdderScript._internals.Utils.toArray(arguments);
if (args.length === 1) {return args[0]._value;}
return args.map(function(x) {
return x._value;
});
}
// call with simple values
var values = ["1,2,3", '"das"', "15", "__VERSION__", "15 + 5", "15 + 5 * 2 + -1", '"5+2"', '1,2 + 2,3 + "abcd"', '1, "2,3"'];
var results = [[1,2,3], 'das', 15, AdderScript._internals.version, 20, 15 + 5 * 2 + -1, '5+2', [1,2 + 2 ,3 + 'abcd'], [1, '2,3']];
for (var i = 0; i < values.length; ++i)
{
var val = values[i];
var expected = results[i];
assert.deepEqual(executeAndReturn("_sec_test(" + val + ")")[1], expected);
}
});
// test strings api
QUnit.test("strings_api", function( assert ) {
// test len()
testReturnValue(assert, '"".len()', 0);
testReturnValue(assert, '"abcd".len()', 4);
testReturnValue(assert, 'a = "abcd"; a.len()', 4);
// test split()
testReturnValue(assert, '"abc def".split()', ["abc", "def"]);
testReturnValue(assert, '"abc def".split(" ")', ["abc", "def"]);
testReturnValue(assert, '"abc def".split("-")', ["abc def"]);
testReturnValue(assert, '"abc-word-def".split("-word-")', ["abc", "def"]);
testReturnValue(assert, '"abc def".split("")', ["a", "b", "c", " ", "d", "e", "f"]);
// test replace()
testReturnValue(assert, '"abc def".replace("abc", "foo")', "foo def");
testReturnValue(assert, '"abc def abc".replace("abc", "foo")', "foo def foo");
testReturnValue(assert, 'a = "abc def abc"; a.replace("abc", "foo"); a', "abc def abc");
testReturnValue(assert, 'a = "abc def abc abc234"; a.replace("abc", "foo")', "foo def foo foo234");
// test remove()
testReturnValue(assert, '"abc def".remove("abc")', " def");
testReturnValue(assert, '"abc def abc".remove("abc")', " def ");
testReturnValue(assert, 'a = "abc def abc"; a.remove("abc"); a', "abc def abc");
testReturnValue(assert, 'a = "abc def abc abc234"; a.remove("abc")', " def 234");
// test index()
testReturnValue(assert, '"abcdefg".index("abc")', 0);
testReturnValue(assert, '"abcdefg".index("fofo")', -1);
testReturnValue(assert, '"abcdefg".index("ef")', 4);
testReturnValue(assert, '"abcdefg".index("efaaaa")', -1);
// test has()
testReturnValue(assert, '"abcdefg".has("abc")', true);
testReturnValue(assert, '"abcdefg".has("fofo")', false);
testReturnValue(assert, '"abcdefg".has("ef")', true);
testReturnValue(assert, '"abcdefg".has("efaaaa")', false);
// test count()
testReturnValue(assert, '"abcdefgabcabc".count("abc")', 3);
testReturnValue(assert, '"abcfoodefg".count("foo")', 1);
testReturnValue(assert, '"abcdefg".count("nope")', 0);
// test trim()
testReturnValue(assert, '" abcd ".trim()', "abcd");
testReturnValue(assert, '" \n abcd \t ".trim()', "abcd");
// test hash()
testReturnValue(assert, '"abcd".hash()', 2987074);
// test ends_with()
testReturnValue(assert, '"abcdefg".ends_with("efg")', true);
testReturnValue(assert, '"abcdefg".ends_with("g")', true);
testReturnValue(assert, '"abcdefg".ends_with("aaa")', false);
testReturnValue(assert, '"abcdefg".ends_with("")', false);
// test starts_with()
testReturnValue(assert, '"abcdefg".starts_with("abc")', true);
testReturnValue(assert, '"abcdefg".starts_with("a")', true);
testReturnValue(assert, '"abcdefg".starts_with("aaa")', false);
testReturnValue(assert, '"abcdefg".starts_with("")', true);
// test is_alpha()
testReturnValue(assert, '"abcdefg".is_alpha()', true);
testReturnValue(assert, '"".is_alpha()', false);
testReturnValue(assert, '"abc defg".is_alpha()', false);
testReturnValue(assert, '"abcd1efg".is_alpha()', false);
testReturnValue(assert, '"abcd?efg".is_alpha()', false);
testReturnValue(assert, '"_".is_alpha()', false);
testReturnValue(assert, '"ab\ncdefg".is_alpha()', false);
// test is_digit()
testReturnValue(assert, '"123".is_digit()', true);
testReturnValue(assert, '"".is_digit()', false);
testReturnValue(assert, '"123 456".is_digit()', false);
testReturnValue(assert, '"123a456".is_digit()', false);
testReturnValue(assert, '"123?456".is_digit()', false);
testReturnValue(assert, '"1_".is_digit()', false);
testReturnValue(assert, '"1\n2".is_digit()', false);
// test lower()
testReturnValue(assert, '"AbCdE fG12".lower()', "abcde fg12");
// test upper()
testReturnValue(assert, '"AbCdE fG12".upper()', "ABCDE FG12");
// test title()
testReturnValue(assert, '"hello there my friend! nice_to\nmeet\tyou".title()', "Hello There My Friend! Nice_to\nMeet\tYou");
// test slice()
testReturnValue(assert, '"hello world".slice(3)', "lo world");
testReturnValue(assert, '"hello world".slice(3, 2)', "lo");
});
// test some builtin functions
QUnit.test("basic_function_builtins", function( assert ) {
// test all()
testReturnValue(assert, "all()", true);
testReturnValue(assert, "all(5)", true);
testReturnValue(assert, "all(True, 1, 5 + 2)", true);
testReturnValue(assert, "all(0)", false);
testReturnValue(assert, "all(True, 1, False)", false);
testReturnValue(assert, "all(True, 1, None, 4)", false);
testReturnValue(assert, 'all(1 + 1, "fw", True, False)', false);
// test any()
testReturnValue(assert, "any()", false);
testReturnValue(assert, "any(5)", true);
testReturnValue(assert, "any(True, 1, 5 + 2)", true);
testReturnValue(assert, "any(0)", false);
testReturnValue(assert, "any(0, False, None)", false);
testReturnValue(assert, "any(True, 1, False)", true);
testReturnValue(assert, "any(True, 1, None, 4)", true);
testReturnValue(assert, 'any(1 + 1, "fw", True, False)', true);
// test bin()
testReturnValue(assert, "bin(0)", "0");
testReturnValue(assert, "bin(1)", "1");
testReturnValue(assert, "bin(5238965)", "10011111111000010110101");
// test delete
testReturnValue(assert, "a = 5; b = a; delete(a); b", 5);
assert.throws(function() {testReturnValue(assert, "a = 5; b = a; delete(a); a", 5)}, AdderScript.Errors.UndefinedVariable);
assert.throws(function() {testReturnValue(assert, "delete(Math)", 5)}, AdderScript.Errors.RuntimeError);
assert.throws(function() {testReturnValue(assert, "a = 5; def test(): delete(a); test(); a", 5)}, AdderScript.Errors.UndefinedVariable);
assert.throws(function() {testReturnValue(assert, "a = 5; def test(): a = 5 and delete(a); test(); a", 5)}, AdderScript.Errors.UndefinedVariable);
testReturnValue(assert, 'a = 5; delete(a); exist("a");', false);
// test exist
testReturnValue(assert, 'exist("Math")', true);
testReturnValue(assert, 'exist("None")', true);
testReturnValue(assert, 'exist("True")', true);
testReturnValue(assert, 'a=5; exist("a")', true);
testReturnValue(assert, 'a=5; exist("b")', false);
// test bool()
testReturnValue(assert, "bool(1)", true);
testReturnValue(assert, 'bool("5")', true);
testReturnValue(assert, "bool(1 + 3)", true);
testReturnValue(assert, "bool(True)", true);
testReturnValue(assert, "bool(0)", false);
testReturnValue(assert, "bool(None)", false);
testReturnValue(assert, "bool(False)", false);
// test callable
testReturnValue(assert, "callable(any)", true);
testReturnValue(assert, "callable(\"55\")", false);
testReturnValue(assert, "callable(435)", false);
testReturnValue(assert, "callable(False)", false);
testReturnValue(assert, "callable(None)", false);
// test chr
testReturnValue(assert, "chr(90)", 'Z');
// test cmp
testReturnValue(assert, 'cmp("a", "b")', -1);
testReturnValue(assert, 'cmp("b", "a")', 1);
testReturnValue(assert, 'cmp(5, 1)', 4);
testReturnValue(assert, 'cmp(1, 5)', -4);
// test float
testReturnValue(assert, 'float("0")', 0);
testReturnValue(assert, 'float("0.5")', 0.5);
testReturnValue(assert, 'float("bla")', NaN);
// test int
testReturnValue(assert, 'int("0")', 0);
testReturnValue(assert, 'int("5.0")', 5);
testReturnValue(assert, 'int("100", 8)', 64);
testReturnValue(assert, 'int("100")', 100);
testReturnValue(assert, 'int("bla")', NaN);
// test ord
testReturnValue(assert, 'ord("a")', 97);
// test repr
testReturnValue(assert, 'repr("foo")', '"foo"');
testReturnValue(assert, 'repr(10)', '10');
testReturnValue(assert, 'repr(list("foo"))', 'list(foo)');
testReturnValue(assert, 'repr(None)', 'None');
testReturnValue(assert, 'repr(True)', 'True');
// test range
testReturnValue(assert, 'equal(range(3), list(0,1,2))', true);
testReturnValue(assert, 'equal(range(1, 3), list(1,2))', true);
testReturnValue(assert, 'equal(range(5, 1, -1), list(5,4,3,2))', true);
testReturnValue(assert, 'equal(range(5, 1, -2), list(5,3))', true);
testReturnValue(assert, 'equal(range(1, 3, -1), list())', true);
testReturnValue(assert, 'equal(range(5, 2, 1), list())', true);
testReturnValue(assert, 'equal(range(0), list())', true);
compareExpression(assert, 'range(0)', []);
compareExpression(assert, 'range(1)', [0]);
compareExpression(assert, 'range(10)', [0,1,2,3,4,5,6,7,8,9]);
// test str
testReturnValue(assert, 'str("5")', "5");
testReturnValue(assert, 'str(None)', "");
testReturnValue(assert, 'str(True)', "True");
testReturnValue(assert, 'str(False)', "False");
testReturnValue(assert, 'str("abc")', "abc");
// test type
testReturnValue(assert, 'type(True)', "boolean");
testReturnValue(assert, 'type(False)', "boolean");
testReturnValue(assert, 'type(None)', "none");
testReturnValue(assert, 'type("a")', "string");
testReturnValue(assert, 'type(123)', "number");
testReturnValue(assert, 'type(__VERSION__)', "string");
// test list creation
testReturnValue(assert, 'type(list())', "list");
testReturnValue(assert, 'type(list(1,2,3))', "list");
// test reversed
testReturnValue(assert, 'equal(reversed(list(1,2,3)), list(3,2,1))', true);
});
// test the built-in Random module
QUnit.test("module_random", function( assert ) {
// test basic rand()
var oldRand = Math.random;
Math.random = function() {return 0.2;}
testReturnValue(assert, "Random.rand()", 0.2);
testReturnValue(assert, "Random.rand_int(5)", 1);
testReturnValue(assert, "Random.rand_int(5, 10)", 6);
testReturnValue(assert, "Random.rand_float(4.0)", 0.8);
testReturnValue(assert, "Random.rand_float(5.5, 10.0)", 6.4);
testReturnValue(assert, "Random.binary()", 0);
testReturnValue(assert, "Random.boolean()", false);
testReturnValue(assert, 'Random.select(list("foo", "bar", "goo"))', "foo");
testReturnValue(assert, 'Random.select(set("foo", "bar", "goo"))', "foo");
testReturnValue(assert, 'a = dict(); a.set("foo", 5); a.set("bar", 6); Random.select(a)', "foo");
Math.random = oldRand;
});
// test the built-in Math module
QUnit.test("module_math", function( assert ) {
// test abs()
testReturnValue(assert, "Math.abs(5)", 5);
testReturnValue(assert, "Math.abs(-5)", 5);
testReturnValue(assert, "Math.abs(5 + 2 * -1)", Math.abs(5 + 2 * -1));
testReturnValue(assert, "Math.abs(5 + 2 * -1 - 10)", Math.abs(5 + 2 * -1 - 10));
// test min
testReturnValue(assert, 'Math.min(1,2,3,1)', 1);
testReturnValue(assert, 'Math.min(1,3)', 1);
testReturnValue(assert, 'Math.min(-5,-1)', -5);
testReturnValue(assert, 'Math.min(list(2, 4, -5,-1))', -5);
testReturnValue(assert, 'Math.min(set(2, 4, -5,-1))', -5);
// test max
testReturnValue(assert, 'Math.max(1,2,3, 1)', 3);
testReturnValue(assert, 'Math.max(1,3)', 3);
testReturnValue(assert, 'Math.max(-5,-1)', -1);
testReturnValue(assert, 'Math.max(list(2, 4, -5,-1))', 4);
testReturnValue(assert, 'Math.max(set(2, 4, -5,-1))', 4);
// test pow
testReturnValue(assert, 'Math.pow(2, 2)', 4);
testReturnValue(assert, 'Math.pow(3, 2)', 9);
// test round / floor / ceil
testReturnValue(assert, 'Mat