ai_old/goja/runtime_test.go

3141 lines
59 KiB
Go
Raw Normal View History

2024-09-20 16:50:35 +08:00
package goja
import (
"errors"
"fmt"
"math"
"reflect"
"runtime"
"strconv"
"strings"
"testing"
"time"
"apigo.cc/ai/ai/goja/parser"
2024-09-20 16:50:35 +08:00
)
func TestGlobalObjectProto(t *testing.T) {
const SCRIPT = `
this instanceof Object
`
testScript(SCRIPT, valueTrue, t)
}
func TestUnicodeString(t *testing.T) {
const SCRIPT = `
var s = "Тест";
s.length === 4 && s[1] === "е";
`
testScript(SCRIPT, valueTrue, t)
}
func Test2TierHierarchyProp(t *testing.T) {
const SCRIPT = `
var a = {};
Object.defineProperty(a, "test", {
value: 42,
writable: false,
enumerable: false,
configurable: true
});
var b = Object.create(a);
var c = Object.create(b);
c.test = 43;
c.test === 42 && !b.hasOwnProperty("test");
`
testScript(SCRIPT, valueTrue, t)
}
func TestConstStringIter(t *testing.T) {
const SCRIPT = `
var count = 0;
for (var i in "1234") {
for (var j in "1234567") {
count++
}
}
count;
`
testScript(SCRIPT, intToValue(28), t)
}
func TestUnicodeConcat(t *testing.T) {
const SCRIPT = `
var s = "тест";
var s1 = "test";
var s2 = "абвгд";
s.concat(s1) === естtest" && s.concat(s1, s2) === естtestабвгд" && s1.concat(s, s2) === "testтестабвгд"
&& s.concat(s2) === "тестабвгд";
`
testScript(SCRIPT, valueTrue, t)
}
func TestIndexOf(t *testing.T) {
const SCRIPT = `
"abc".indexOf("", 4)
`
testScript(SCRIPT, intToValue(3), t)
}
func TestUnicodeIndexOf(t *testing.T) {
const SCRIPT = `
"абвгд".indexOf("вг", 1) === 2 && '中国'.indexOf('国') === 1
`
testScript(SCRIPT, valueTrue, t)
}
func TestLastIndexOf(t *testing.T) {
const SCRIPT = `
"abcabab".lastIndexOf("ab", 3)
`
testScript(SCRIPT, intToValue(3), t)
}
func TestUnicodeLastIndexOf(t *testing.T) {
const SCRIPT = `
"абвабаб".lastIndexOf("аб", 3)
`
testScript(SCRIPT, intToValue(3), t)
}
func TestUnicodeLastIndexOf1(t *testing.T) {
const SCRIPT = `
"abꞐcde".lastIndexOf("cd");
`
testScript(SCRIPT, intToValue(3), t)
}
func TestNumber(t *testing.T) {
const SCRIPT = `
(new Number(100111122133144155)).toString()
`
testScript(SCRIPT, asciiString("100111122133144160"), t)
}
func TestFractionalNumberToStringRadix(t *testing.T) {
const SCRIPT = `
(new Number(123.456)).toString(36)
`
testScript(SCRIPT, asciiString("3f.gez4w97ry"), t)
}
func TestNumberFormatRounding(t *testing.T) {
const SCRIPT = `
assert.sameValue((123.456).toExponential(undefined), "1.23456e+2", "undefined");
assert.sameValue((0.000001).toPrecision(2), "0.0000010")
assert.sameValue((-7).toPrecision(1), "-7");
assert.sameValue((-42).toPrecision(1), "-4e+1");
assert.sameValue((0.000001).toPrecision(1), "0.000001");
assert.sameValue((123.456).toPrecision(1), "1e+2", "1");
assert.sameValue((123.456).toPrecision(2), "1.2e+2", "2");
var n = new Number("0.000000000000000000001"); // 1e-21
assert.sameValue((n).toPrecision(1), "1e-21");
assert.sameValue((25).toExponential(0), "3e+1");
assert.sameValue((-25).toExponential(0), "-3e+1");
assert.sameValue((12345).toExponential(3), "1.235e+4");
assert.sameValue((25.5).toFixed(0), "26");
assert.sameValue((-25.5).toFixed(0), "-26");
assert.sameValue((99.9).toFixed(0), "100");
assert.sameValue((99.99).toFixed(1), "100.0");
`
testScriptWithTestLib(SCRIPT, _undefined, t)
}
func TestBinOctalNumbers(t *testing.T) {
const SCRIPT = `
0b111;
`
testScript(SCRIPT, valueInt(7), t)
}
func TestSetFunc(t *testing.T) {
const SCRIPT = `
sum(40, 2);
`
r := New()
err := r.Set("sum", func(call FunctionCall) Value {
return r.ToValue(call.Argument(0).ToInteger() + call.Argument(1).ToInteger())
})
if err != nil {
t.Fatal(err)
}
v, err := r.RunString(SCRIPT)
if err != nil {
t.Fatal(err)
}
if i := v.ToInteger(); i != 42 {
t.Fatalf("Expected 42, got: %d", i)
}
}
func ExampleRuntime_Set_lexical() {
r := New()
_, err := r.RunString("let x")
if err != nil {
panic(err)
}
err = r.Set("x", 1)
if err != nil {
panic(err)
}
fmt.Print(r.Get("x"), r.GlobalObject().Get("x"))
// Output: 1 <nil>
}
func TestRecursiveRun(t *testing.T) {
// Make sure that a recursive call to Run*() correctly sets the environment and no stash or stack
// corruptions occur.
vm := New()
vm.Set("f", func() (Value, error) {
return vm.RunString("let x = 1; { let z = 100, z1 = 200, z2 = 300, z3 = 400; x = x + z3} x;")
})
res, err := vm.RunString(`
function f1() {
let x = 2;
eval('');
{
let y = 3;
let res = f();
if (x !== 2) { // check for stash corruption
throw new Error("x="+x);
}
if (y !== 3) { // check for stack corruption
throw new Error("y="+y);
}
return res;
}
};
f1();
`)
if err != nil {
t.Fatal(err)
}
if !res.SameAs(valueInt(401)) {
t.Fatal(res)
}
}
func TestRecursiveRunWithNArgs(t *testing.T) {
vm := New()
vm.Set("f", func() (Value, error) {
return vm.RunString(`
{
let a = 0;
let b = 1;
a = 2; // this used to to corrupt b, because its location on the stack was off by vm.args (1 in our case)
b;
}
`)
})
_, err := vm.RunString(`
(function(arg) { // need an ES function call with an argument to set vm.args
let res = f();
if (res !== 1) {
throw new Error(res);
}
})(123);
`)
if err != nil {
t.Fatal(err)
}
}
func TestRecursiveRunCallee(t *testing.T) {
// Make sure that a recursive call to Run*() correctly sets the callee (i.e. stack[sb-1])
vm := New()
vm.Set("f", func() (Value, error) {
return vm.RunString("this; (() => 1)()")
})
res, err := vm.RunString(`
f(123, 123);
`)
if err != nil {
t.Fatal(err)
}
if !res.SameAs(valueInt(1)) {
t.Fatal(res)
}
}
func TestObjectGetSet(t *testing.T) {
const SCRIPT = `
input.test++;
input;
`
r := New()
o := r.NewObject()
o.Set("test", 42)
r.Set("input", o)
v, err := r.RunString(SCRIPT)
if err != nil {
t.Fatal(err)
}
if o1, ok := v.(*Object); ok {
if v1 := o1.Get("test"); v1.Export() != int64(43) {
t.Fatalf("Unexpected test value: %v (%T)", v1, v1.Export())
}
}
}
func TestThrowFromNativeFunc(t *testing.T) {
const SCRIPT = `
var thrown;
try {
f();
} catch (e) {
thrown = e;
}
thrown;
`
r := New()
r.Set("f", func(call FunctionCall) Value {
panic(r.ToValue("testError"))
})
v, err := r.RunString(SCRIPT)
if err != nil {
t.Fatal(err)
}
if !v.Equals(asciiString("testError")) {
t.Fatalf("Unexpected result: %v", v)
}
}
func TestSetGoFunc(t *testing.T) {
const SCRIPT = `
f(40, 2)
`
r := New()
r.Set("f", func(a, b int) int {
return a + b
})
v, err := r.RunString(SCRIPT)
if err != nil {
t.Fatal(err)
}
if v.ToInteger() != 42 {
t.Fatalf("Unexpected result: %v", v)
}
}
func TestSetFuncVariadic(t *testing.T) {
vm := New()
vm.Set("f", func(s string, g ...Value) {
something := g[0].ToObject(vm).Get(s).ToInteger()
if something != 5 {
t.Fatal()
}
})
_, err := vm.RunString(`
f("something", {something: 5})
`)
if err != nil {
t.Fatal(err)
}
}
func TestSetFuncVariadicFuncArg(t *testing.T) {
vm := New()
vm.Set("f", func(s string, g ...Value) {
if f, ok := AssertFunction(g[0]); ok {
v, err := f(nil)
if err != nil {
t.Fatal(err)
}
if v != valueTrue {
t.Fatal(v)
}
}
})
_, err := vm.RunString(`
f("something", () => true)
`)
if err != nil {
t.Fatal(err)
}
}
func TestArgsKeys(t *testing.T) {
const SCRIPT = `
function testArgs2(x, y, z) {
// Properties of the arguments object are enumerable.
return Object.keys(arguments);
}
testArgs2(1,2).length
`
testScript(SCRIPT, intToValue(2), t)
}
func TestIPowOverflow(t *testing.T) {
const SCRIPT = `
assert.sameValue(Math.pow(65536, 6), 7.922816251426434e+28);
assert.sameValue(Math.pow(10, 19), 1e19);
assert.sameValue(Math.pow(2097151, 3), 9223358842721534000);
assert.sameValue(Math.pow(2097152, 3), 9223372036854776000);
assert.sameValue(Math.pow(-2097151, 3), -9223358842721534000);
assert.sameValue(Math.pow(-2097152, 3), -9223372036854776000);
assert.sameValue(Math.pow(9007199254740992, 0), 1);
assert.sameValue(Math.pow(-9007199254740992, 0), 1);
assert.sameValue(Math.pow(0, 0), 1);
`
testScriptWithTestLib(SCRIPT, _undefined, t)
}
func TestIPow(t *testing.T) {
if res := ipow(-9223372036854775808, 1); res != -9223372036854775808 {
t.Fatal(res)
}
if res := ipow(9223372036854775807, 1); res != 9223372036854775807 {
t.Fatal(res)
}
if res := ipow(-9223372036854775807, 1); res != -9223372036854775807 {
t.Fatal(res)
}
if res := ipow(9223372036854775807, 0); res != 1 {
t.Fatal(res)
}
if res := ipow(-9223372036854775807, 0); res != 1 {
t.Fatal(res)
}
if res := ipow(-9223372036854775808, 0); res != 1 {
t.Fatal(res)
}
}
func TestInterrupt(t *testing.T) {
const SCRIPT = `
var i = 0;
for (;;) {
i++;
}
`
vm := New()
time.AfterFunc(200*time.Millisecond, func() {
vm.Interrupt("halt")
})
_, err := vm.RunString(SCRIPT)
if err == nil {
t.Fatal("Err is nil")
}
}
func TestRuntime_ExportToNumbers(t *testing.T) {
vm := New()
t.Run("int8/no overflow", func(t *testing.T) {
var i8 int8
err := vm.ExportTo(vm.ToValue(-123), &i8)
if err != nil {
t.Fatal(err)
}
if i8 != -123 {
t.Fatalf("i8: %d", i8)
}
})
t.Run("int8/overflow", func(t *testing.T) {
var i8 int8
err := vm.ExportTo(vm.ToValue(333), &i8)
if err != nil {
t.Fatal(err)
}
if i8 != 77 {
t.Fatalf("i8: %d", i8)
}
})
t.Run("int64/uint64", func(t *testing.T) {
var ui64 uint64
err := vm.ExportTo(vm.ToValue(-1), &ui64)
if err != nil {
t.Fatal(err)
}
if ui64 != math.MaxUint64 {
t.Fatalf("ui64: %d", ui64)
}
})
t.Run("int8/float", func(t *testing.T) {
var i8 int8
err := vm.ExportTo(vm.ToValue(333.9234), &i8)
if err != nil {
t.Fatal(err)
}
if i8 != 77 {
t.Fatalf("i8: %d", i8)
}
})
t.Run("int8/object", func(t *testing.T) {
var i8 int8
err := vm.ExportTo(vm.NewObject(), &i8)
if err != nil {
t.Fatal(err)
}
if i8 != 0 {
t.Fatalf("i8: %d", i8)
}
})
t.Run("int/object_cust_valueOf", func(t *testing.T) {
var i int
obj, err := vm.RunString(`
({
valueOf: function() { return 42; }
})
`)
if err != nil {
t.Fatal(err)
}
err = vm.ExportTo(obj, &i)
if err != nil {
t.Fatal(err)
}
if i != 42 {
t.Fatalf("i: %d", i)
}
})
t.Run("float32/no_trunc", func(t *testing.T) {
var f float32
err := vm.ExportTo(vm.ToValue(1.234567), &f)
if err != nil {
t.Fatal(err)
}
if f != 1.234567 {
t.Fatalf("f: %f", f)
}
})
t.Run("float32/trunc", func(t *testing.T) {
var f float32
err := vm.ExportTo(vm.ToValue(1.234567890), &f)
if err != nil {
t.Fatal(err)
}
if f != float32(1.234567890) {
t.Fatalf("f: %f", f)
}
})
t.Run("float64", func(t *testing.T) {
var f float64
err := vm.ExportTo(vm.ToValue(1.234567), &f)
if err != nil {
t.Fatal(err)
}
if f != 1.234567 {
t.Fatalf("f: %f", f)
}
})
t.Run("float32/object", func(t *testing.T) {
var f float32
err := vm.ExportTo(vm.NewObject(), &f)
if err != nil {
t.Fatal(err)
}
if f == f { // expecting NaN
t.Fatalf("f: %f", f)
}
})
t.Run("float64/object", func(t *testing.T) {
var f float64
err := vm.ExportTo(vm.NewObject(), &f)
if err != nil {
t.Fatal(err)
}
if f == f { // expecting NaN
t.Fatalf("f: %f", f)
}
})
}
func TestRuntime_ExportToSlice(t *testing.T) {
const SCRIPT = `
var a = [1, 2, 3];
a;
`
vm := New()
v, err := vm.RunString(SCRIPT)
if err != nil {
t.Fatal(err)
}
var a []string
err = vm.ExportTo(v, &a)
if err != nil {
t.Fatal(err)
}
if l := len(a); l != 3 {
t.Fatalf("Unexpected len: %d", l)
}
if a[0] != "1" || a[1] != "2" || a[2] != "3" {
t.Fatalf("Unexpected value: %+v", a)
}
}
func TestRuntime_ExportToMap(t *testing.T) {
const SCRIPT = `
var m = {
"0": 1,
"1": 2,
"2": 3,
}
m;
`
vm := New()
v, err := vm.RunString(SCRIPT)
if err != nil {
t.Fatal(err)
}
var m map[int]string
err = vm.ExportTo(v, &m)
if err != nil {
t.Fatal(err)
}
if l := len(m); l != 3 {
t.Fatalf("Unexpected len: %d", l)
}
if m[0] != "1" || m[1] != "2" || m[2] != "3" {
t.Fatalf("Unexpected value: %+v", m)
}
}
func TestRuntime_ExportToMap1(t *testing.T) {
const SCRIPT = `
var m = {
"0": 1,
"1": 2,
"2": 3,
}
m;
`
vm := New()
v, err := vm.RunString(SCRIPT)
if err != nil {
t.Fatal(err)
}
var m map[string]string
err = vm.ExportTo(v, &m)
if err != nil {
t.Fatal(err)
}
if l := len(m); l != 3 {
t.Fatalf("Unexpected len: %d", l)
}
if m["0"] != "1" || m["1"] != "2" || m["2"] != "3" {
t.Fatalf("Unexpected value: %+v", m)
}
}
func TestRuntime_ExportToStruct(t *testing.T) {
const SCRIPT = `
var m = {
Test: 1,
}
m;
`
vm := New()
v, err := vm.RunString(SCRIPT)
if err != nil {
t.Fatal(err)
}
var o testGoReflectMethod_O
err = vm.ExportTo(v, &o)
if err != nil {
t.Fatal(err)
}
if o.Test != "1" {
t.Fatalf("Unexpected value: '%s'", o.Test)
}
}
func TestRuntime_ExportToStructPtr(t *testing.T) {
const SCRIPT = `
var m = {
Test: 1,
}
m;
`
vm := New()
v, err := vm.RunString(SCRIPT)
if err != nil {
t.Fatal(err)
}
var o *testGoReflectMethod_O
err = vm.ExportTo(v, &o)
if err != nil {
t.Fatal(err)
}
if o.Test != "1" {
t.Fatalf("Unexpected value: '%s'", o.Test)
}
}
func TestRuntime_ExportToStructAnonymous(t *testing.T) {
type BaseTestStruct struct {
A int64
B int64
}
type TestStruct struct {
BaseTestStruct
C string
}
const SCRIPT = `
var m = {
A: 1,
B: 2,
C: "testC"
}
m;
`
vm := New()
v, err := vm.RunString(SCRIPT)
if err != nil {
t.Fatal(err)
}
test := &TestStruct{}
err = vm.ExportTo(v, test)
if err != nil {
t.Fatal(err)
}
if test.A != 1 {
t.Fatalf("Unexpected value: '%d'", test.A)
}
if test.B != 2 {
t.Fatalf("Unexpected value: '%d'", test.B)
}
if test.C != "testC" {
t.Fatalf("Unexpected value: '%s'", test.C)
}
}
func TestRuntime_ExportToStructFromPtr(t *testing.T) {
vm := New()
v := vm.ToValue(&testGoReflectMethod_O{
field: "5",
Test: "12",
})
var o testGoReflectMethod_O
err := vm.ExportTo(v, &o)
if err != nil {
t.Fatal(err)
}
if o.Test != "12" {
t.Fatalf("Unexpected value: '%s'", o.Test)
}
if o.field != "5" {
t.Fatalf("Unexpected value for field: '%s'", o.field)
}
}
func TestRuntime_ExportToStructWithPtrValues(t *testing.T) {
type BaseTestStruct struct {
A int64
B *int64
}
type TestStruct2 struct {
E string
}
type TestStruct struct {
BaseTestStruct
C *string
D *TestStruct2
}
const SCRIPT = `
var m = {
A: 1,
B: 2,
C: "testC",
D: {
E: "testE",
}
}
m;
`
vm := New()
v, err := vm.RunString(SCRIPT)
if err != nil {
t.Fatal(err)
}
test := &TestStruct{}
err = vm.ExportTo(v, test)
if err != nil {
t.Fatal(err)
}
if test.A != 1 {
t.Fatalf("Unexpected value: '%d'", test.A)
}
if test.B == nil || *test.B != 2 {
t.Fatalf("Unexpected value: '%v'", test.B)
}
if test.C == nil || *test.C != "testC" {
t.Fatalf("Unexpected value: '%v'", test.C)
}
if test.D == nil || test.D.E != "testE" {
t.Fatalf("Unexpected value: '%s'", test.D.E)
}
}
func TestRuntime_ExportToTime(t *testing.T) {
const SCRIPT = `
var dateStr = "2018-08-13T15:02:13+02:00";
var str = "test123";
`
vm := New()
_, err := vm.RunString(SCRIPT)
if err != nil {
t.Fatal(err)
}
var ti time.Time
err = vm.ExportTo(vm.Get("dateStr"), &ti)
if err != nil {
t.Fatal(err)
}
if ti.Format(time.RFC3339) != "2018-08-13T15:02:13+02:00" {
t.Fatalf("Unexpected value: '%s'", ti.Format(time.RFC3339))
}
err = vm.ExportTo(vm.Get("str"), &ti)
if err == nil {
t.Fatal("Expected err to not be nil")
}
var str string
err = vm.ExportTo(vm.Get("dateStr"), &str)
if err != nil {
t.Fatal(err)
}
if str != "2018-08-13T15:02:13+02:00" {
t.Fatalf("Unexpected value: '%s'", str)
}
d, err := vm.RunString(`new Date(1000)`)
if err != nil {
t.Fatal(err)
}
ti = time.Time{}
err = vm.ExportTo(d, &ti)
if err != nil {
t.Fatal(err)
}
if ti.UnixNano() != 1000*1e6 {
t.Fatal(ti)
}
if ti.Location() != time.Local {
t.Fatalf("Wrong location: %v", ti)
}
}
func ExampleRuntime_ExportTo_func() {
const SCRIPT = `
function f(param) {
return +param + 2;
}
`
vm := New()
_, err := vm.RunString(SCRIPT)
if err != nil {
panic(err)
}
var fn func(string) string
err = vm.ExportTo(vm.Get("f"), &fn)
if err != nil {
panic(err)
}
fmt.Println(fn("40")) // note, _this_ value in the function will be undefined.
// Output: 42
}
func ExampleRuntime_ExportTo_funcThrow() {
const SCRIPT = `
function f(param) {
throw new Error("testing");
}
`
vm := New()
_, err := vm.RunString(SCRIPT)
if err != nil {
panic(err)
}
var fn func(string) (string, error)
err = vm.ExportTo(vm.Get("f"), &fn)
if err != nil {
panic(err)
}
_, err = fn("")
fmt.Println(err)
// Output: Error: testing at f (<eval>:3:9(3))
}
func ExampleRuntime_ExportTo_funcVariadic() {
const SCRIPT = `
function f(...args) {
return args.join("#");
}
`
vm := New()
_, err := vm.RunString(SCRIPT)
if err != nil {
panic(err)
}
var fn func(args ...interface{}) string
err = vm.ExportTo(vm.Get("f"), &fn)
if err != nil {
panic(err)
}
fmt.Println(fn("a", "b", 42))
// Output: a#b#42
}
func TestRuntime_ExportTo_funcVariadic(t *testing.T) {
const SCRIPT = `
function f(...args) {
return args.join("#");
}
`
vm := New()
_, err := vm.RunString(SCRIPT)
if err != nil {
panic(err)
}
t.Run("no args", func(t *testing.T) {
var fn func(args ...any) string
err = vm.ExportTo(vm.Get("f"), &fn)
if err != nil {
panic(err)
}
res := fn()
if res != "" {
t.Fatal(res)
}
})
t.Run("non-variadic args", func(t *testing.T) {
var fn func(firstArg any, args ...any) string
err = vm.ExportTo(vm.Get("f"), &fn)
if err != nil {
panic(err)
}
res := fn("first")
if res != "first" {
t.Fatal(res)
}
})
t.Run("non-variadic and variadic args", func(t *testing.T) {
var fn func(firstArg any, args ...any) string
err = vm.ExportTo(vm.Get("f"), &fn)
if err != nil {
panic(err)
}
res := fn("first", "second")
if res != "first#second" {
t.Fatal(res)
}
})
}
func TestRuntime_ExportToFuncFail(t *testing.T) {
const SCRIPT = `
function f(param) {
return +param + 2;
}
`
type T struct {
Field1 int
}
var fn func(string) (T, error)
vm := New()
_, err := vm.RunString(SCRIPT)
if err != nil {
t.Fatal(err)
}
err = vm.ExportTo(vm.Get("f"), &fn)
if err != nil {
t.Fatal(err)
}
if _, err := fn("40"); err == nil {
t.Fatal("Expected error")
}
}
func TestRuntime_ExportToCallable(t *testing.T) {
const SCRIPT = `
function f(param) {
return +param + 2;
}
`
vm := New()
_, err := vm.RunString(SCRIPT)
if err != nil {
t.Fatal(err)
}
var c Callable
err = vm.ExportTo(vm.Get("f"), &c)
if err != nil {
t.Fatal(err)
}
res, err := c(Undefined(), vm.ToValue("40"))
if err != nil {
t.Fatal(err)
} else if !res.StrictEquals(vm.ToValue(42)) {
t.Fatalf("Unexpected value: %v", res)
}
}
func TestRuntime_ExportToObject(t *testing.T) {
const SCRIPT = `
var o = {"test": 42};
o;
`
vm := New()
_, err := vm.RunString(SCRIPT)
if err != nil {
t.Fatal(err)
}
var o *Object
err = vm.ExportTo(vm.Get("o"), &o)
if err != nil {
t.Fatal(err)
}
if v := o.Get("test"); !v.StrictEquals(vm.ToValue(42)) {
t.Fatalf("Unexpected value: %v", v)
}
}
func ExampleAssertFunction() {
vm := New()
_, err := vm.RunString(`
function sum(a, b) {
return a+b;
}
`)
if err != nil {
panic(err)
}
sum, ok := AssertFunction(vm.Get("sum"))
if !ok {
panic("Not a function")
}
res, err := sum(Undefined(), vm.ToValue(40), vm.ToValue(2))
if err != nil {
panic(err)
}
fmt.Println(res)
// Output: 42
}
func TestGoFuncError(t *testing.T) {
const SCRIPT = `
try {
f();
} catch (e) {
if (!(e instanceof GoError)) {
throw(e);
}
if (e.value.Error() !== "Test") {
throw("Unexpected value: " + e.value.Error());
}
}
`
f := func() error {
return errors.New("Test")
}
vm := New()
vm.Set("f", f)
_, err := vm.RunString(SCRIPT)
if err != nil {
t.Fatal(err)
}
}
func TestToValueNil(t *testing.T) {
type T struct{}
var a *T
vm := New()
if v := vm.ToValue(nil); !IsNull(v) {
t.Fatalf("nil: %v", v)
}
if v := vm.ToValue(a); !IsNull(v) {
t.Fatalf("struct ptr: %v", v)
}
var m map[string]interface{}
if v := vm.ToValue(m); !IsNull(v) {
t.Fatalf("map[string]interface{}: %v", v)
}
var ar []interface{}
if v := vm.ToValue(ar); IsNull(v) {
t.Fatalf("[]interface{}: %v", v)
}
var arptr *[]interface{}
if v := vm.ToValue(arptr); !IsNull(v) {
t.Fatalf("*[]interface{}: %v", v)
}
}
func TestToValueFloat(t *testing.T) {
vm := New()
vm.Set("f64", float64(123))
vm.Set("f32", float32(321))
v, err := vm.RunString("f64 === 123 && f32 === 321")
if err != nil {
t.Fatal(err)
}
if v.Export().(bool) != true {
t.Fatalf("StrictEquals for golang float failed")
}
}
func TestToValueInterface(t *testing.T) {
f := func(i interface{}) bool {
return i == t
}
vm := New()
vm.Set("f", f)
vm.Set("t", t)
v, err := vm.RunString(`f(t)`)
if err != nil {
t.Fatal(err)
}
if v != valueTrue {
t.Fatalf("v: %v", v)
}
}
func TestJSONEscape(t *testing.T) {
const SCRIPT = `
var a = "\\+1";
JSON.stringify(a);
`
testScript(SCRIPT, asciiString(`"\\+1"`), t)
}
func TestJSONObjectInArray(t *testing.T) {
const SCRIPT = `
var a = "[{\"a\":1},{\"a\":2}]";
JSON.stringify(JSON.parse(a)) == a;
`
testScript(SCRIPT, valueTrue, t)
}
func TestJSONQuirkyNumbers(t *testing.T) {
const SCRIPT = `
var s;
s = JSON.stringify(NaN);
if (s != "null") {
throw new Error("NaN: " + s);
}
s = JSON.stringify(Infinity);
if (s != "null") {
throw new Error("Infinity: " + s);
}
s = JSON.stringify(-Infinity);
if (s != "null") {
throw new Error("-Infinity: " + s);
}
`
testScript(SCRIPT, _undefined, t)
}
func TestJSONNil(t *testing.T) {
const SCRIPT = `
JSON.stringify(i);
`
vm := New()
var i interface{}
vm.Set("i", i)
ret, err := vm.RunString(SCRIPT)
if err != nil {
t.Fatal(err)
}
if ret.String() != "null" {
t.Fatalf("Expected 'null', got: %v", ret)
}
}
type customJsonEncodable struct{}
func (*customJsonEncodable) JsonEncodable() interface{} {
return "Test"
}
func TestJsonEncodable(t *testing.T) {
var s customJsonEncodable
vm := New()
vm.Set("s", &s)
ret, err := vm.RunString("JSON.stringify(s)")
if err != nil {
t.Fatal(err)
}
if !ret.StrictEquals(vm.ToValue("\"Test\"")) {
t.Fatalf("Expected \"Test\", got: %v", ret)
}
}
func TestSortComparatorReturnValues(t *testing.T) {
const SCRIPT = `
var a = [];
for (var i = 0; i < 12; i++) {
a[i] = i;
}
a.sort(function(x, y) { return y - x });
for (var i = 0; i < 12; i++) {
if (a[i] !== 11-i) {
throw new Error("Value at index " + i + " is incorrect: " + a[i]);
}
}
`
testScript(SCRIPT, _undefined, t)
}
func TestSortComparatorReturnValueFloats(t *testing.T) {
const SCRIPT = `
var a = [
5.97,
9.91,
4.13,
9.28,
3.29,
];
a.sort( function(a, b) { return a - b; } );
for (var i = 1; i < a.length; i++) {
if (a[i] < a[i-1]) {
throw new Error("Array is not sorted: " + a);
}
}
`
testScript(SCRIPT, _undefined, t)
}
func TestSortComparatorReturnValueNegZero(t *testing.T) {
const SCRIPT = `
var a = [2, 1];
a.sort( function(a, b) { return a > b ? 0 : -0; } );
for (var i = 1; i < a.length; i++) {
if (a[i] < a[i-1]) {
throw new Error("Array is not sorted: " + a);
}
}
`
testScript(SCRIPT, _undefined, t)
}
func TestNilApplyArg(t *testing.T) {
const SCRIPT = `
(function x(a, b) {
return a === undefined && b === 1;
}).apply(this, [,1])
`
testScript(SCRIPT, valueTrue, t)
}
func TestNilCallArg(t *testing.T) {
const SCRIPT = `
"use strict";
function f(a) {
return this === undefined && a === undefined;
}
`
vm := New()
prg := MustCompile("test.js", SCRIPT, false)
vm.RunProgram(prg)
if f, ok := AssertFunction(vm.Get("f")); ok {
v, err := f(nil, nil)
if err != nil {
t.Fatal(err)
}
if !v.StrictEquals(valueTrue) {
t.Fatalf("Unexpected result: %v", v)
}
}
}
func TestNullCallArg(t *testing.T) {
const SCRIPT = `
f(null);
`
vm := New()
prg := MustCompile("test.js", SCRIPT, false)
vm.Set("f", func(x *int) bool {
return x == nil
})
v, err := vm.RunProgram(prg)
if err != nil {
t.Fatal(err)
}
if !v.StrictEquals(valueTrue) {
t.Fatalf("Unexpected result: %v", v)
}
}
func TestObjectKeys(t *testing.T) {
const SCRIPT = `
var o = { a: 1, b: 2, c: 3, d: 4 };
o;
`
vm := New()
prg := MustCompile("test.js", SCRIPT, false)
res, err := vm.RunProgram(prg)
if err != nil {
t.Fatal(err)
}
if o, ok := res.(*Object); ok {
keys := o.Keys()
if !reflect.DeepEqual(keys, []string{"a", "b", "c", "d"}) {
t.Fatalf("Unexpected keys: %v", keys)
}
}
}
func TestReflectCallExtraArgs(t *testing.T) {
const SCRIPT = `
f(41, "extra")
`
f := func(x int) int {
return x + 1
}
vm := New()
vm.Set("f", f)
prg := MustCompile("test.js", SCRIPT, false)
res, err := vm.RunProgram(prg)
if err != nil {
t.Fatal(err)
}
if !res.StrictEquals(intToValue(42)) {
t.Fatalf("Unexpected result: %v", res)
}
}
func TestReflectCallNotEnoughArgs(t *testing.T) {
const SCRIPT = `
f(42)
`
vm := New()
f := func(x, y int, z *int, s string) (int, error) {
if z != nil {
return 0, fmt.Errorf("z is not nil")
}
if s != "" {
return 0, fmt.Errorf("s is not \"\"")
}
return x + y, nil
}
vm.Set("f", f)
prg := MustCompile("test.js", SCRIPT, false)
res, err := vm.RunProgram(prg)
if err != nil {
t.Fatal(err)
}
if !res.StrictEquals(intToValue(42)) {
t.Fatalf("Unexpected result: %v", res)
}
}
func TestReflectCallVariadic(t *testing.T) {
const SCRIPT = `
var r = f("Hello %s, %d", "test", 42);
if (r !== "Hello test, 42") {
throw new Error("test 1 has failed: " + r);
}
r = f("Hello %s, %s", "test");
if (r !== "Hello test, %!s(MISSING)") {
throw new Error("test 2 has failed: " + r);
}
r = f();
if (r !== "") {
throw new Error("test 3 has failed: " + r);
}
`
vm := New()
vm.Set("f", fmt.Sprintf)
prg := MustCompile("test.js", SCRIPT, false)
_, err := vm.RunProgram(prg)
if err != nil {
t.Fatal(err)
}
}
func TestReflectNullValueArgument(t *testing.T) {
rt := New()
rt.Set("fn", func(v Value) {
if v == nil {
t.Error("null becomes nil")
}
if !IsNull(v) {
t.Error("null is not null")
}
})
rt.RunString(`fn(null);`)
}
type testNativeConstructHelper struct {
rt *Runtime
base int64
// any other state
}
func (t *testNativeConstructHelper) calc(call FunctionCall) Value {
return t.rt.ToValue(t.base + call.Argument(0).ToInteger())
}
func TestNativeConstruct(t *testing.T) {
const SCRIPT = `
var f = new F(40);
f instanceof F && f.method() === 42 && f.calc(2) === 42;
`
rt := New()
method := func(call FunctionCall) Value {
return rt.ToValue(42)
}
rt.Set("F", func(call ConstructorCall) *Object { // constructor signature (as opposed to 'func(FunctionCall) Value')
h := &testNativeConstructHelper{
rt: rt,
base: call.Argument(0).ToInteger(),
}
call.This.Set("method", method)
call.This.Set("calc", h.calc)
return nil // or any other *Object which will be used instead of call.This
})
prg := MustCompile("test.js", SCRIPT, false)
res, err := rt.RunProgram(prg)
if err != nil {
t.Fatal(err)
}
if !res.StrictEquals(valueTrue) {
t.Fatalf("Unexpected result: %v", res)
}
if fn, ok := AssertFunction(rt.Get("F")); ok {
v, err := fn(nil, rt.ToValue(42))
if err != nil {
t.Fatal(err)
}
if o, ok := v.(*Object); ok {
if o.Get("method") == nil {
t.Fatal("No method")
}
} else {
t.Fatal("Not an object")
}
} else {
t.Fatal("Not a function")
}
resp := &testNativeConstructHelper{}
value := rt.ToValue(resp)
if value.Export() != resp {
t.Fatal("no")
}
}
func TestCreateObject(t *testing.T) {
const SCRIPT = `
inst instanceof C;
`
r := New()
c := r.ToValue(func(call ConstructorCall) *Object {
return nil
})
proto := c.(*Object).Get("prototype").(*Object)
inst := r.CreateObject(proto)
r.Set("C", c)
r.Set("inst", inst)
prg := MustCompile("test.js", SCRIPT, false)
res, err := r.RunProgram(prg)
if err != nil {
t.Fatal(err)
}
if !res.StrictEquals(valueTrue) {
t.Fatalf("Unexpected result: %v", res)
}
}
func TestInterruptInWrappedFunction(t *testing.T) {
rt := New()
v, err := rt.RunString(`
var fn = function() {
while (true) {}
};
fn;
`)
if err != nil {
t.Fatal(err)
}
fn, ok := AssertFunction(v)
if !ok {
t.Fatal("Not a function")
}
go func() {
<-time.After(10 * time.Millisecond)
rt.Interrupt(errors.New("hi"))
}()
_, err = fn(nil)
if err == nil {
t.Fatal("expected error")
}
if _, ok := err.(*InterruptedError); !ok {
t.Fatalf("Wrong error type: %T", err)
}
}
func TestInterruptInWrappedFunction2(t *testing.T) {
rt := New()
// this test panics as otherwise goja will recover and possibly loop
var called bool
rt.Set("v", rt.ToValue(func() {
if called {
go func() {
panic("this should never get called twice")
}()
}
called = true
rt.Interrupt("here is the error")
}))
rt.Set("s", rt.ToValue(func(a Callable) (Value, error) {
return a(nil)
}))
rt.Set("k", rt.ToValue(func(e Value) {
go func() {
panic("this should never get called actually")
}()
}))
_, err := rt.RunString(`
Promise.resolve().then(()=>k()); // this should never resolve
while(true) {
try{
s(() =>{
v();
})
break;
} catch (e) {
k(e);
}
}
`)
if err == nil {
t.Fatal("expected error but got no error")
}
intErr := new(InterruptedError)
if !errors.As(err, &intErr) {
t.Fatalf("Wrong error type: %T", err)
}
if !strings.Contains(intErr.Error(), "here is the error") {
t.Fatalf("Wrong error message: %q", intErr.Error())
}
_, err = rt.RunString(`Promise.resolve().then(()=>globalThis.S=5)`)
if err != nil {
t.Fatal(err)
}
s := rt.Get("S")
if s == nil || s.ToInteger() != 5 {
t.Fatalf("Wrong value for S %v", s)
}
}
func TestInterruptInWrappedFunction2Recover(t *testing.T) {
rt := New()
// this test panics as otherwise goja will recover and possibly loop
var vCalled int
rt.Set("v", rt.ToValue(func() {
if vCalled == 0 {
rt.Interrupt("here is the error")
}
vCalled++
}))
rt.Set("s", rt.ToValue(func(a Callable) (Value, error) {
v, err := a(nil)
if err != nil {
intErr := new(InterruptedError)
if errors.As(err, &intErr) {
rt.ClearInterrupt()
return nil, errors.New("oops we got interrupted let's not that")
}
}
return v, err
}))
var kCalled int
rt.Set("k", rt.ToValue(func(e Value) {
kCalled++
}))
_, err := rt.RunString(`
Promise.resolve().then(()=>k());
while(true) {
try{
s(() => {
v();
})
break;
} catch (e) {
k(e);
}
}
`)
if err != nil {
t.Fatal(err)
}
if vCalled != 2 {
t.Fatalf("v was not called exactly twice but %d times", vCalled)
}
if kCalled != 2 {
t.Fatalf("k was not called exactly twice but %d times", kCalled)
}
_, err = rt.RunString(`Promise.resolve().then(()=>globalThis.S=5)`)
if err != nil {
t.Fatal(err)
}
s := rt.Get("S")
if s == nil || s.ToInteger() != 5 {
t.Fatalf("Wrong value for S %v", s)
}
}
func TestInterruptInWrappedFunctionExpectInteruptError(t *testing.T) {
rt := New()
// this test panics as otherwise goja will recover and possibly loop
rt.Set("v", rt.ToValue(func() {
rt.Interrupt("here is the error")
}))
rt.Set("s", rt.ToValue(func(a Callable) (Value, error) {
return a(nil)
}))
_, err := rt.RunString(`
s(() =>{
v();
})
`)
if err == nil {
t.Fatal("expected error but got no error")
}
var intErr *InterruptedError
if !errors.As(err, &intErr) {
t.Fatalf("Wrong error type: %T", err)
}
if !strings.Contains(intErr.Error(), "here is the error") {
t.Fatalf("Wrong error message: %q", intErr.Error())
}
}
func TestInterruptInWrappedFunctionExpectStackOverflowError(t *testing.T) {
rt := New()
rt.SetMaxCallStackSize(5)
// this test panics as otherwise goja will recover and possibly loop
rt.Set("v", rt.ToValue(func() {
_, err := rt.RunString(`
(function loop() { loop() })();
`)
if err != nil {
panic(err)
}
}))
rt.Set("s", rt.ToValue(func(a Callable) (Value, error) {
return a(nil)
}))
_, err := rt.RunString(`
s(() =>{
v();
})
`)
if err == nil {
t.Fatal("expected error but got no error")
}
var soErr *StackOverflowError
if !errors.As(err, &soErr) {
t.Fatalf("Wrong error type: %T", err)
}
}
func TestRunLoopPreempt(t *testing.T) {
vm := New()
v, err := vm.RunString("(function() {for (;;) {}})")
if err != nil {
t.Fatal(err)
}
fn, ok := AssertFunction(v)
if !ok {
t.Fatal("Not a function")
}
go func() {
<-time.After(100 * time.Millisecond)
runtime.GC() // this hangs if the vm loop does not have any preemption points
vm.Interrupt(errors.New("hi"))
}()
_, err = fn(nil)
if err == nil {
t.Fatal("expected error")
}
if _, ok := err.(*InterruptedError); !ok {
t.Fatalf("Wrong error type: %T", err)
}
}
func TestNaN(t *testing.T) {
if !IsNaN(_NaN) {
t.Fatal("IsNaN() doesn't detect NaN")
}
if IsNaN(Undefined()) {
t.Fatal("IsNaN() says undefined is a NaN")
}
if !IsNaN(NaN()) {
t.Fatal("NaN() doesn't return NaN")
}
}
func TestInf(t *testing.T) {
if !IsInfinity(_positiveInf) {
t.Fatal("IsInfinity() doesn't detect +Inf")
}
if !IsInfinity(_negativeInf) {
t.Fatal("IsInfinity() doesn't detect -Inf")
}
if IsInfinity(Undefined()) {
t.Fatal("IsInfinity() says undefined is a Infinity")
}
if !IsInfinity(PositiveInf()) {
t.Fatal("PositiveInfinity() doesn't return Inf")
}
if !IsInfinity(NegativeInf()) {
t.Fatal("NegativeInfinity() doesn't return Inf")
}
}
func TestRuntimeNew(t *testing.T) {
vm := New()
v, err := vm.New(vm.Get("Number"), vm.ToValue("12345"))
if err != nil {
t.Fatal(err)
}
if n, ok := v.Export().(int64); ok {
if n != 12345 {
t.Fatalf("n: %v", n)
}
} else {
t.Fatalf("v: %T", v)
}
}
func TestAutoBoxing(t *testing.T) {
const SCRIPT = `
function f() {
'use strict';
var a = 1;
var thrown1 = false;
var thrown2 = false;
try {
a.test = 42;
} catch (e) {
thrown1 = e instanceof TypeError;
}
try {
a["test1"] = 42;
} catch (e) {
thrown2 = e instanceof TypeError;
}
return thrown1 && thrown2;
}
var a = 1;
a.test = 42; // should not throw
a["test1"] = 42; // should not throw
a.test === undefined && a.test1 === undefined && f();
`
testScript(SCRIPT, valueTrue, t)
}
func TestProtoGetter(t *testing.T) {
const SCRIPT = `
({}).__proto__ === Object.prototype && [].__proto__ === Array.prototype;
`
testScript(SCRIPT, valueTrue, t)
}
func TestSymbol1(t *testing.T) {
const SCRIPT = `
Symbol.toPrimitive[Symbol.toPrimitive]() === Symbol.toPrimitive;
`
testScript(SCRIPT, valueTrue, t)
}
func TestFreezeSymbol(t *testing.T) {
const SCRIPT = `
var s = Symbol(1);
var o = {};
o[s] = 42;
Object.freeze(o);
o[s] = 43;
o[s] === 42 && Object.isFrozen(o);
`
testScript(SCRIPT, valueTrue, t)
}
func TestToPropertyKey(t *testing.T) {
const SCRIPT = `
var sym = Symbol(42);
var callCount = 0;
var wrapper = {
toString: function() {
callCount += 1;
return sym;
},
valueOf: function() {
$ERROR("valueOf() called");
}
};
var o = {};
o[wrapper] = function() { return "test" };
assert.sameValue(o[wrapper], o[sym], "o[wrapper] === o[sym]");
assert.sameValue(o[wrapper](), "test", "o[wrapper]()");
assert.sameValue(o[sym](), "test", "o[sym]()");
var wrapper1 = {};
wrapper1[Symbol.toPrimitive] = function(hint) {
if (hint === "string" || hint === "default") {
return "1";
}
if (hint === "number") {
return 2;
}
$ERROR("Unknown hint value "+hint);
};
var a = [];
a[wrapper1] = 42;
assert.sameValue(a[1], 42, "a[1]");
assert.sameValue(a[1], a[wrapper1], "a[1] === a[wrapper1]");
`
testScriptWithTestLib(SCRIPT, _undefined, t)
}
func TestPrimThisValue(t *testing.T) {
const SCRIPT = `
function t() {
'use strict';
Boolean.prototype.toString = function() {
return typeof this;
};
assert.sameValue(true.toLocaleString(), "boolean");
Boolean.prototype[Symbol.iterator] = function() {
return [typeof this][Symbol.iterator]();
}
var s = new Set(true);
assert.sameValue(s.size, 1, "size");
assert.sameValue(s.has("boolean"), true, "s.has('boolean')");
}
t();
`
testScriptWithTestLib(SCRIPT, _undefined, t)
}
func TestPrimThisValueGetter(t *testing.T) {
const SCRIPT = `
function t() {
'use strict';
Object.defineProperty(Boolean.prototype, "toString", {
get: function() {
var v = typeof this;
return function() {
return v;
};
}
});
assert.sameValue(true.toLocaleString(), "boolean");
}
t();
`
testScriptWithTestLib(SCRIPT, _undefined, t)
}
func TestObjSetSym(t *testing.T) {
const SCRIPT = `
'use strict';
var sym = Symbol(true);
var p1 = Object.create(null);
var p2 = Object.create(p1);
Object.defineProperty(p1, sym, {
value: 42
});
Object.defineProperty(p2, sym, {
value: 43,
writable: true,
});
var o = Object.create(p2);
o[sym] = 44;
o[sym];
`
testScript(SCRIPT, intToValue(44), t)
}
func TestObjSet(t *testing.T) {
const SCRIPT = `
'use strict';
var p1 = Object.create(null);
var p2 = Object.create(p1);
Object.defineProperty(p1, "test", {
value: 42
});
Object.defineProperty(p2, "test", {
value: 43,
writable: true,
});
var o = Object.create(p2);
o.test = 44;
o.test;
`
testScript(SCRIPT, intToValue(44), t)
}
func TestToValueNilValue(t *testing.T) {
r := New()
var a Value
r.Set("a", a)
ret, err := r.RunString(`
""+a;
`)
if err != nil {
t.Fatal(err)
}
if !asciiString("null").SameAs(ret) {
t.Fatalf("ret: %v", ret)
}
}
func TestDateConversion(t *testing.T) {
now := time.Now()
vm := New()
val, err := vm.New(vm.Get("Date").ToObject(vm), vm.ToValue(now.UnixNano()/1e6))
if err != nil {
t.Fatal(err)
}
vm.Set("d", val)
res, err := vm.RunString(`+d`)
if err != nil {
t.Fatal(err)
}
if exp := res.Export(); exp != now.UnixNano()/1e6 {
t.Fatalf("Value does not match: %v", exp)
}
vm.Set("goval", now)
res, err = vm.RunString(`+(new Date(goval.UnixNano()/1e6))`)
if err != nil {
t.Fatal(err)
}
if exp := res.Export(); exp != now.UnixNano()/1e6 {
t.Fatalf("Value does not match: %v", exp)
}
}
func TestNativeCtorNewTarget(t *testing.T) {
const SCRIPT = `
function NewTarget() {
}
var o = Reflect.construct(Number, [1], NewTarget);
o.__proto__ === NewTarget.prototype && o.toString() === "[object Number]";
`
testScript(SCRIPT, valueTrue, t)
}
func TestNativeCtorNonNewCall(t *testing.T) {
vm := New()
vm.Set(`Animal`, func(call ConstructorCall) *Object {
obj := call.This
obj.Set(`name`, call.Argument(0).String())
obj.Set(`eat`, func(call FunctionCall) Value {
self := call.This.(*Object)
return vm.ToValue(fmt.Sprintf("%s eat", self.Get(`name`)))
})
return nil
})
v, err := vm.RunString(`
function __extends(d, b){
function __() {
this.constructor = d;
}
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
}
var Cat = (function (_super) {
__extends(Cat, _super);
function Cat() {
return _super.call(this, "cat") || this;
}
return Cat;
}(Animal));
var cat = new Cat();
cat instanceof Cat && cat.eat() === "cat eat";
`)
if err != nil {
t.Fatal(err)
}
if v != valueTrue {
t.Fatal(v)
}
}
func ExampleNewSymbol() {
sym1 := NewSymbol("66")
sym2 := NewSymbol("66")
fmt.Printf("%s %s %v", sym1, sym2, sym1.Equals(sym2))
// Output: 66 66 false
}
func ExampleObject_SetSymbol() {
type IterResult struct {
Done bool
Value Value
}
vm := New()
vm.SetFieldNameMapper(UncapFieldNameMapper()) // to use IterResult
o := vm.NewObject()
o.SetSymbol(SymIterator, func() *Object {
count := 0
iter := vm.NewObject()
iter.Set("next", func() IterResult {
if count < 10 {
count++
return IterResult{
Value: vm.ToValue(count),
}
}
return IterResult{
Done: true,
}
})
return iter
})
vm.Set("o", o)
res, err := vm.RunString(`
var acc = "";
for (var v of o) {
acc += v + " ";
}
acc;
`)
if err != nil {
panic(err)
}
fmt.Println(res)
// Output: 1 2 3 4 5 6 7 8 9 10
}
func ExampleRuntime_NewArray() {
vm := New()
array := vm.NewArray(1, 2, true)
vm.Set("array", array)
res, err := vm.RunString(`
var acc = "";
for (var v of array) {
acc += v + " ";
}
acc;
`)
if err != nil {
panic(err)
}
fmt.Println(res)
// Output: 1 2 true
}
func ExampleRuntime_SetParserOptions() {
vm := New()
vm.SetParserOptions(parser.WithDisableSourceMaps)
res, err := vm.RunString(`
"I did not hang!";
//# sourceMappingURL=/dev/zero`)
if err != nil {
panic(err)
}
fmt.Println(res.String())
// Output: I did not hang!
}
func TestRuntime_SetParserOptions_Eval(t *testing.T) {
vm := New()
vm.SetParserOptions(parser.WithDisableSourceMaps)
_, err := vm.RunString(`
eval("//# sourceMappingURL=/dev/zero");
`)
if err != nil {
t.Fatal(err)
}
}
func TestNativeCallWithRuntimeParameter(t *testing.T) {
vm := New()
vm.Set("f", func(_ FunctionCall, r *Runtime) Value {
if r == vm {
return valueTrue
}
return valueFalse
})
ret, err := vm.RunString(`f()`)
if err != nil {
t.Fatal(err)
}
if ret != valueTrue {
t.Fatal(ret)
}
}
func TestNestedEnumerate(t *testing.T) {
const SCRIPT = `
var o = {baz: true, foo: true, bar: true};
var res = "";
for (var i in o) {
delete o.baz;
Object.defineProperty(o, "hidden", {value: true, configurable: true});
for (var j in o) {
Object.defineProperty(o, "0", {value: true, configurable: true});
Object.defineProperty(o, "1", {value: true, configurable: true});
for (var k in o) {}
res += i + "-" + j + " ";
}
}
assert(compareArray(Reflect.ownKeys(o), ["0","1","foo","bar","hidden"]), "keys");
res;
`
testScriptWithTestLib(SCRIPT, asciiString("baz-foo baz-bar foo-foo foo-bar bar-foo bar-bar "), t)
}
func TestAbandonedEnumerate(t *testing.T) {
const SCRIPT = `
var o = {baz: true, foo: true, bar: true};
var res = "";
for (var i in o) {
delete o.baz;
for (var j in o) {
res += i + "-" + j + " ";
break;
}
}
res;
`
testScript(SCRIPT, asciiString("baz-foo foo-foo bar-foo "), t)
}
func TestIterCloseThrows(t *testing.T) {
const SCRIPT = `
var returnCount = 0;
var iterable = {};
var iterator = {
next: function() {
return { value: true };
},
return: function() {
returnCount += 1;
throw new Error();
}
};
iterable[Symbol.iterator] = function() {
return iterator;
};
try {
for (var i of iterable) {
break;
}
} catch (e) {};
returnCount;
`
testScript(SCRIPT, valueInt(1), t)
}
func TestDeclareGlobalFunc(t *testing.T) {
const SCRIPT = `
var initial;
Object.defineProperty(this, 'f', {
enumerable: true,
writable: true,
configurable: false
});
(0,eval)('initial = f; function f() { return 2222; }');
var desc = Object.getOwnPropertyDescriptor(this, "f");
assert(desc.enumerable, "enumerable");
assert(desc.writable, "writable");
assert(!desc.configurable, "configurable");
assert.sameValue(initial(), 2222);
`
testScriptWithTestLib(SCRIPT, _undefined, t)
}
func TestStackOverflowError(t *testing.T) {
vm := New()
vm.SetMaxCallStackSize(3)
_, err := vm.RunString(`
function f() {
f();
}
f();
`)
if _, ok := err.(*StackOverflowError); !ok {
t.Fatal(err)
}
}
func TestStacktraceLocationThrowFromCatch(t *testing.T) {
vm := New()
_, err := vm.RunString(`
function main(arg) {
try {
if (arg === 1) {
return f1();
}
if (arg === 2) {
return f2();
}
if (arg === 3) {
return f3();
}
} catch (e) {
throw e;
}
}
function f1() {}
function f2() {
throw new Error();
}
function f3() {}
main(2);
`)
if err == nil {
t.Fatal("Expected error")
}
stack := err.(*Exception).stack
if len(stack) != 3 {
t.Fatalf("Unexpected stack len: %v", stack)
}
if frame := stack[0]; frame.funcName != "f2" || frame.pc != 2 {
t.Fatalf("Unexpected stack frame 0: %#v", frame)
}
if frame := stack[1]; frame.funcName != "main" || frame.pc != 17 {
t.Fatalf("Unexpected stack frame 1: %#v", frame)
}
if frame := stack[2]; frame.funcName != "" || frame.pc != 7 {
t.Fatalf("Unexpected stack frame 2: %#v", frame)
}
}
func TestErrorStackRethrow(t *testing.T) {
const SCRIPT = `
function f(e) {
throw e;
}
try {
f(new Error());
} catch(e) {
assertStack(e, [["test.js", "", 6, 5]]);
}
`
testScriptWithTestLibX(SCRIPT, _undefined, t)
}
func TestStacktraceLocationThrowFromGo(t *testing.T) {
vm := New()
f := func() {
panic(vm.ToValue("Test"))
}
vm.Set("f", f)
_, err := vm.RunString(`
function main() {
(function noop() {})();
return callee();
}
function callee() {
return f();
}
main();
`)
if err == nil {
t.Fatal("Expected error")
}
stack := err.(*Exception).stack
if len(stack) != 4 {
t.Fatalf("Unexpected stack len: %v", stack)
}
if frame := stack[0]; !strings.HasSuffix(frame.funcName.String(), "TestStacktraceLocationThrowFromGo.func1") {
t.Fatalf("Unexpected stack frame 0: %#v", frame)
}
if frame := stack[1]; frame.funcName != "callee" || frame.pc != 2 {
t.Fatalf("Unexpected stack frame 1: %#v", frame)
}
if frame := stack[2]; frame.funcName != "main" || frame.pc != 6 {
t.Fatalf("Unexpected stack frame 2: %#v", frame)
}
if frame := stack[3]; frame.funcName != "" || frame.pc != 4 {
t.Fatalf("Unexpected stack frame 3: %#v", frame)
}
}
func TestStacktraceLocationThrowNativeInTheMiddle(t *testing.T) {
vm := New()
v, err := vm.RunString(`(function f1() {
throw new Error("test")
})`)
if err != nil {
t.Fatal(err)
}
var f1 func()
err = vm.ExportTo(v, &f1)
if err != nil {
t.Fatal(err)
}
f := func() {
f1()
}
vm.Set("f", f)
_, err = vm.RunString(`
function main() {
(function noop() {})();
return callee();
}
function callee() {
return f();
}
main();
`)
if err == nil {
t.Fatal("Expected error")
}
stack := err.(*Exception).stack
if len(stack) != 5 {
t.Fatalf("Unexpected stack len: %v", stack)
}
if frame := stack[0]; frame.funcName != "f1" || frame.pc != 7 {
t.Fatalf("Unexpected stack frame 0: %#v", frame)
}
if frame := stack[1]; !strings.HasSuffix(frame.funcName.String(), "TestStacktraceLocationThrowNativeInTheMiddle.func1") {
t.Fatalf("Unexpected stack frame 1: %#v", frame)
}
if frame := stack[2]; frame.funcName != "callee" || frame.pc != 2 {
t.Fatalf("Unexpected stack frame 2: %#v", frame)
}
if frame := stack[3]; frame.funcName != "main" || frame.pc != 6 {
t.Fatalf("Unexpected stack frame 3: %#v", frame)
}
if frame := stack[4]; frame.funcName != "" || frame.pc != 4 {
t.Fatalf("Unexpected stack frame 4: %#v", frame)
}
}
func TestStrToInt64(t *testing.T) {
if _, ok := strToInt64(""); ok {
t.Fatal("<empty>")
}
if n, ok := strToInt64("0"); !ok || n != 0 {
t.Fatal("0", n, ok)
}
if n, ok := strToInt64("-0"); ok {
t.Fatal("-0", n, ok)
}
if n, ok := strToInt64("-1"); !ok || n != -1 {
t.Fatal("-1", n, ok)
}
if n, ok := strToInt64("9223372036854775808"); ok {
t.Fatal("max+1", n, ok)
}
if n, ok := strToInt64("9223372036854775817"); ok {
t.Fatal("9223372036854775817", n, ok)
}
if n, ok := strToInt64("-9223372036854775818"); ok {
t.Fatal("-9223372036854775818", n, ok)
}
if n, ok := strToInt64("9223372036854775807"); !ok || n != 9223372036854775807 {
t.Fatal("max", n, ok)
}
if n, ok := strToInt64("-9223372036854775809"); ok {
t.Fatal("min-1", n, ok)
}
if n, ok := strToInt64("-9223372036854775808"); !ok || n != -9223372036854775808 {
t.Fatal("min", n, ok)
}
if n, ok := strToInt64("-00"); ok {
t.Fatal("-00", n, ok)
}
if n, ok := strToInt64("-01"); ok {
t.Fatal("-01", n, ok)
}
}
func TestStrToInt32(t *testing.T) {
if _, ok := strToInt32(""); ok {
t.Fatal("<empty>")
}
if n, ok := strToInt32("0"); !ok || n != 0 {
t.Fatal("0", n, ok)
}
if n, ok := strToInt32("-0"); ok {
t.Fatal("-0", n, ok)
}
if n, ok := strToInt32("-1"); !ok || n != -1 {
t.Fatal("-1", n, ok)
}
if n, ok := strToInt32("2147483648"); ok {
t.Fatal("max+1", n, ok)
}
if n, ok := strToInt32("2147483657"); ok {
t.Fatal("2147483657", n, ok)
}
if n, ok := strToInt32("-2147483658"); ok {
t.Fatal("-2147483658", n, ok)
}
if n, ok := strToInt32("2147483647"); !ok || n != 2147483647 {
t.Fatal("max", n, ok)
}
if n, ok := strToInt32("-2147483649"); ok {
t.Fatal("min-1", n, ok)
}
if n, ok := strToInt32("-2147483648"); !ok || n != -2147483648 {
t.Fatal("min", n, ok)
}
if n, ok := strToInt32("-00"); ok {
t.Fatal("-00", n, ok)
}
if n, ok := strToInt32("-01"); ok {
t.Fatal("-01", n, ok)
}
}
func TestDestructSymbol(t *testing.T) {
const SCRIPT = `
var S = Symbol("S");
var s, rest;
({[S]: s, ...rest} = {[S]: true, test: 1});
assert.sameValue(s, true, "S");
assert(deepEqual(rest, {test: 1}), "rest");
`
testScriptWithTestLibX(SCRIPT, _undefined, t)
}
func TestAccessorFuncName(t *testing.T) {
const SCRIPT = `
const namedSym = Symbol('test262');
const emptyStrSym = Symbol("");
const anonSym = Symbol();
const o = {
get id() {},
get [anonSym]() {},
get [namedSym]() {},
get [emptyStrSym]() {},
set id(v) {},
set [anonSym](v) {},
set [namedSym](v) {},
set [emptyStrSym](v) {}
};
let prop;
prop = Object.getOwnPropertyDescriptor(o, 'id');
assert.sameValue(prop.get.name, 'get id');
assert.sameValue(prop.set.name, 'set id');
prop = Object.getOwnPropertyDescriptor(o, anonSym);
assert.sameValue(prop.get.name, 'get ');
assert.sameValue(prop.set.name, 'set ');
prop = Object.getOwnPropertyDescriptor(o, emptyStrSym);
assert.sameValue(prop.get.name, 'get []');
assert.sameValue(prop.set.name, 'set []');
prop = Object.getOwnPropertyDescriptor(o, namedSym);
assert.sameValue(prop.get.name, 'get [test262]');
assert.sameValue(prop.set.name, 'set [test262]');
`
testScriptWithTestLib(SCRIPT, _undefined, t)
}
func TestCoverFuncName(t *testing.T) {
const SCRIPT = `
var namedSym = Symbol('');
var anonSym = Symbol();
var o;
o = {
xId: (0, function() {}),
id: (function() {}),
id1: function x() {},
[anonSym]: (function() {}),
[namedSym]: (function() {})
};
assert(o.xId.name !== 'xId');
assert.sameValue(o.id1.name, 'x');
assert.sameValue(o.id.name, 'id', 'via IdentifierName');
assert.sameValue(o[anonSym].name, '', 'via anonymous Symbol');
assert.sameValue(o[namedSym].name, '[]', 'via Symbol');
`
testScriptWithTestLib(SCRIPT, _undefined, t)
}
func TestAnonFuncName(t *testing.T) {
const SCRIPT = `
const d = Object.getOwnPropertyDescriptor((function() {}), 'name');
d !== undefined && d.value === '';
`
testScript(SCRIPT, valueTrue, t)
}
func TestStringToBytesConversion(t *testing.T) {
vm := New()
v := vm.ToValue("Test")
var b []byte
err := vm.ExportTo(v, &b)
if err != nil {
t.Fatal(err)
}
if string(b) != "Test" {
t.Fatal(b)
}
}
func TestPromiseAll(t *testing.T) {
const SCRIPT = `
var p1 = new Promise(function() {});
var p2 = new Promise(function() {});
var p3 = new Promise(function() {});
var callCount = 0;
var currentThis = p1;
var nextThis = p2;
var afterNextThis = p3;
p1.then = p2.then = p3.then = function(a, b) {
assert.sameValue(typeof a, 'function', 'type of first argument');
assert.sameValue(
a.length,
1,
'ES6 25.4.1.3.2: The length property of a promise resolve function is 1.'
);
assert.sameValue(typeof b, 'function', 'type of second argument');
assert.sameValue(
b.length,
1,
'ES6 25.4.1.3.1: The length property of a promise reject function is 1.'
);
assert.sameValue(arguments.length, 2, '"then"" invoked with two arguments');
assert.sameValue(this, currentThis, '"this" value');
currentThis = nextThis;
nextThis = afterNextThis;
afterNextThis = null;
callCount += 1;
};
Promise.all([p1, p2, p3]);
assert.sameValue(callCount, 3, '"then"" invoked once for every iterated value');
`
testScriptWithTestLib(SCRIPT, _undefined, t)
}
func TestPromiseExport(t *testing.T) {
vm := New()
p, _, _ := vm.NewPromise()
pv := vm.ToValue(p)
if actual := pv.ExportType(); actual != reflect.TypeOf((*Promise)(nil)) {
t.Fatalf("Export type: %v", actual)
}
if ev := pv.Export(); ev != p {
t.Fatalf("Export value: %v", ev)
}
}
func TestErrorStack(t *testing.T) {
const SCRIPT = `
const err = new Error("test");
if (!("stack" in err)) {
throw new Error("in");
}
if (Reflect.ownKeys(err)[0] !== "stack") {
throw new Error("property order");
}
const stack = err.stack;
if (stack !== "Error: test\n\tat test.js:2:14(3)\n") {
throw new Error(stack);
}
delete err.stack;
if ("stack" in err) {
throw new Error("stack still in err after delete");
}
`
testScript(SCRIPT, _undefined, t)
}
func TestErrorFormatSymbols(t *testing.T) {
vm := New()
vm.Set("a", func() (Value, error) { return nil, errors.New("something %s %f") })
_, err := vm.RunString("a()")
if !strings.Contains(err.Error(), "something %s %f") {
t.Fatalf("Wrong value %q", err.Error())
}
}
func TestPanicPassthrough(t *testing.T) {
const panicString = "Test panic"
r := New()
r.Set("f", func() {
panic(panicString)
})
defer func() {
if x := recover(); x != nil {
if x != panicString {
t.Fatalf("Wrong panic value: %v", x)
}
if len(r.vm.callStack) > 0 {
t.Fatal("vm.callStack is not empty")
}
} else {
t.Fatal("No panic")
}
}()
_, _ = r.RunString("f()")
t.Fatal("Should not reach here")
}
func TestSuspendResumeRelStackLen(t *testing.T) {
const SCRIPT = `
async function f2() {
throw new Error("test");
}
async function f1() {
let a = [1];
for (let i of a) {
try {
await f2();
} catch {
return true;
}
}
}
async function f() {
let a = [1];
for (let i of a) {
return await f1();
}
}
return f();
`
testAsyncFunc(SCRIPT, valueTrue, t)
}
func TestSuspendResumeStacks(t *testing.T) {
const SCRIPT = `
async function f1() {
throw new Error();
}
async function f() {
try {
await f1();
} catch {}
}
result = await f();
`
testAsyncFunc(SCRIPT, _undefined, t)
}
func TestNestedTopLevelConstructorCall(t *testing.T) {
r := New()
c := func(call ConstructorCall, rt *Runtime) *Object {
if _, err := rt.RunString("(5)"); err != nil {
panic(err)
}
return nil
}
if err := r.Set("C", c); err != nil {
panic(err)
}
if _, err := r.RunString("new C()"); err != nil {
panic(err)
}
}
func TestNestedTopLevelConstructorPanicAsync(t *testing.T) {
r := New()
c := func(call ConstructorCall, rt *Runtime) *Object {
c, ok := AssertFunction(rt.ToValue(func() {}))
if !ok {
panic("wat")
}
if _, err := c(Undefined()); err != nil {
panic(err)
}
return nil
}
if err := r.Set("C", c); err != nil {
panic(err)
}
if _, err := r.RunString("new C()"); err != nil {
panic(err)
}
}
func TestAsyncFuncThrow(t *testing.T) {
const SCRIPT = `
class TestError extends Error {
}
async function f() {
throw new TestError();
}
async function f1() {
try {
await f();
} catch (e) {
assert.sameValue(e.constructor.name, TestError.name);
return;
}
throw new Error("No exception was thrown");
}
await f1();
return undefined;
`
testAsyncFuncWithTestLib(SCRIPT, _undefined, t)
}
func TestAsyncStacktrace(t *testing.T) {
// Do not reformat, assertions depend on the line and column numbers
const SCRIPT = `
let ex;
async function foo(x) {
await bar(x);
}
async function bar(x) {
await x;
throw new Error("Let's have a look...");
}
try {
await foo(1);
} catch (e) {
assertStack(e, [
["test.js", "bar", 9, 10],
["test.js", "foo", 4, 13],
["test.js", "test", 13, 12],
]);
}
`
testAsyncFuncWithTestLibX(SCRIPT, _undefined, t)
}
func TestPanicPropagation(t *testing.T) {
r := New()
r.Set("doPanic", func() {
panic(true)
})
v, err := r.RunString(`(function() {
doPanic();
})`)
if err != nil {
t.Fatal(err)
}
f, ok := AssertFunction(v)
if !ok {
t.Fatal("not a function")
}
defer func() {
if x := recover(); x != nil {
if x != true {
t.Fatal("Invalid panic value")
}
}
}()
_, _ = f(nil)
t.Fatal("Expected panic")
}
func TestAwaitInParameters(t *testing.T) {
_, err := Compile("", `
async function g() {
async function inner(a = 1 + await 1) {
}
}
`, false)
if err == nil {
t.Fatal("Expected error")
}
}
func ExampleRuntime_ForOf() {
r := New()
v, err := r.RunString(`
new Map().set("a", 1).set("b", 2);
`)
if err != nil {
panic(err)
}
var sb strings.Builder
ex := r.Try(func() {
r.ForOf(v, func(v Value) bool {
o := v.ToObject(r)
key := o.Get("0")
value := o.Get("1")
sb.WriteString(key.String())
sb.WriteString("=")
sb.WriteString(value.String())
sb.WriteString(",")
return true
})
})
if ex != nil {
panic(ex)
}
fmt.Println(sb.String())
// Output: a=1,b=2,
}
func TestDestructAssignToSymbol(t *testing.T) {
const SCRIPT = `
const s = Symbol('s');
const target = {};
({a: target[s]} = {a: 42});
assert.sameValue(target[s], 42);
`
testScriptWithTestLib(SCRIPT, _undefined, t)
}
func TestToNumber(t *testing.T) {
const SCRIPT = `
assert(isNaN(Number("+")));
assert(isNaN(Number("++")));
assert(isNaN(Number("-")));
assert(isNaN(Number("0xfp1")));
assert(isNaN(Number("0Xfp1")));
assert(isNaN(Number("+0xfp1")));
assert(isNaN(Number(" +0xfp1")));
assert(isNaN(Number(" + 0xfp1")));
assert(isNaN(Number(" 0xfp1")));
assert(isNaN(Number("-0xfp1")));
assert(isNaN(Number("- 0xfp1")));
assert(isNaN(Number(" - 0xfp1")));
assert.sameValue(Number("0."), 0);
assert.sameValue(Number(" "), 0);
assert.sameValue(Number(" Infinity"), Infinity);
let a = [1];
assert.sameValue(1, a.at("0xfp1"));
assert.sameValue(1, a.at(" 0xfp1"));
`
testScriptWithTestLib(SCRIPT, _undefined, t)
}
/*
func TestArrayConcatSparse(t *testing.T) {
function foo(a,b,c)
{
arguments[0] = 1; arguments[1] = 'str'; arguments[2] = 2.1;
if(1 === a && 'str' === b && 2.1 === c)
return true;
}
const SCRIPT = `
var a1 = [];
var a2 = [];
a1[500000] = 1;
a2[1000000] = 2;
var a3 = a1.concat(a2);
a3.length === 1500002 && a3[500000] === 1 && a3[1500001] == 2;
`
testScript(SCRIPT, valueTrue, t)
}
*/
func BenchmarkCallReflect(b *testing.B) {
vm := New()
vm.Set("f", func(v Value) {
})
prg := MustCompile("test.js", "f(null)", true)
b.ResetTimer()
for i := 0; i < b.N; i++ {
vm.RunProgram(prg)
}
}
func BenchmarkCallNative(b *testing.B) {
vm := New()
vm.Set("f", func(call FunctionCall) (ret Value) {
return
})
prg := MustCompile("test.js", "f(null)", true)
b.ResetTimer()
for i := 0; i < b.N; i++ {
vm.RunProgram(prg)
}
}
func BenchmarkCallJS(b *testing.B) {
vm := New()
_, err := vm.RunString(`
function f() {
return 42;
}
`)
if err != nil {
b.Fatal(err)
}
prg := MustCompile("test.js", "f(null)", true)
b.ResetTimer()
for i := 0; i < b.N; i++ {
vm.RunProgram(prg)
}
}
func BenchmarkMainLoop(b *testing.B) {
vm := New()
const SCRIPT = `
for (var i=0; i<100000; i++) {
}
`
prg := MustCompile("test.js", SCRIPT, true)
b.ResetTimer()
for i := 0; i < b.N; i++ {
vm.RunProgram(prg)
}
}
func BenchmarkStringMapGet(b *testing.B) {
m := make(map[string]Value)
for i := 0; i < 100; i++ {
m[strconv.Itoa(i)] = intToValue(int64(i))
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
if m["50"] == nil {
b.Fatal()
}
}
}
func BenchmarkValueStringMapGet(b *testing.B) {
m := make(map[String]Value)
for i := 0; i < 100; i++ {
m[asciiString(strconv.Itoa(i))] = intToValue(int64(i))
}
b.ResetTimer()
var key String = asciiString("50")
for i := 0; i < b.N; i++ {
if m[key] == nil {
b.Fatal()
}
}
}
func BenchmarkAsciiStringMapGet(b *testing.B) {
m := make(map[asciiString]Value)
for i := 0; i < 100; i++ {
m[asciiString(strconv.Itoa(i))] = intToValue(int64(i))
}
b.ResetTimer()
var key = asciiString("50")
for i := 0; i < b.N; i++ {
if m[key] == nil {
b.Fatal()
}
}
}
func BenchmarkNew(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
New()
}
}