package goja import ( "sync" "testing" ) type testDynObject struct { r *Runtime m map[string]Value } func (t *testDynObject) Get(key string) Value { return t.m[key] } func (t *testDynObject) Set(key string, val Value) bool { t.m[key] = val return true } func (t *testDynObject) Has(key string) bool { _, exists := t.m[key] return exists } func (t *testDynObject) Delete(key string) bool { delete(t.m, key) return true } func (t *testDynObject) Keys() []string { keys := make([]string, 0, len(t.m)) for k := range t.m { keys = append(keys, k) } return keys } type testDynArray struct { r *Runtime a []Value } func (t *testDynArray) Len() int { return len(t.a) } func (t *testDynArray) Get(idx int) Value { if idx < 0 { idx += len(t.a) } if idx >= 0 && idx < len(t.a) { return t.a[idx] } return nil } func (t *testDynArray) expand(newLen int) { if newLen > cap(t.a) { a := make([]Value, newLen) copy(a, t.a) t.a = a } else { t.a = t.a[:newLen] } } func (t *testDynArray) Set(idx int, val Value) bool { if idx < 0 { idx += len(t.a) } if idx < 0 { return false } if idx >= len(t.a) { t.expand(idx + 1) } t.a[idx] = val return true } func (t *testDynArray) SetLen(i int) bool { if i > len(t.a) { t.expand(i) return true } if i < 0 { return false } if i < len(t.a) { tail := t.a[i:len(t.a)] for j := range tail { tail[j] = nil } t.a = t.a[:i] } return true } func TestDynamicObject(t *testing.T) { vm := New() dynObj := &testDynObject{ r: vm, m: make(map[string]Value), } o := vm.NewDynamicObject(dynObj) vm.Set("o", o) vm.testScriptWithTestLibX(` assert(o instanceof Object, "instanceof Object"); assert(o === o, "self equality"); assert(o !== {}, "non-equality"); o.test = 42; assert("test" in o, "'test' in o"); assert(deepEqual(Object.getOwnPropertyDescriptor(o, "test"), {value: 42, writable: true, enumerable: true, configurable: true}), "prop desc"); assert.throws(TypeError, function() { "use strict"; Object.defineProperty(o, "test1", {value: 0, writable: false, enumerable: false, configurable: true}); }, "define prop"); var keys = []; for (var key in o) { keys.push(key); } assert(compareArray(keys, ["test"]), "for-in"); assert(delete o.test, "delete"); assert(!("test" in o), "'test' in o after delete"); assert("__proto__" in o, "__proto__ in o"); assert.sameValue(o.__proto__, Object.prototype, "__proto__"); o.__proto__ = null; assert(!("__proto__" in o), "__proto__ in o after setting to null"); `, _undefined, t) } func TestDynamicObjectCustomProto(t *testing.T) { vm := New() m := make(map[string]Value) dynObj := &testDynObject{ r: vm, m: m, } o := vm.NewDynamicObject(dynObj) vm.Set("o", o) vm.testScriptWithTestLib(` var proto = { valueOf: function() { return this.num; } }; proto[Symbol.toStringTag] = "GoObject"; Object.setPrototypeOf(o, proto); o.num = 41; assert(o instanceof Object, "instanceof"); assert.sameValue(o+1, 42); assert.sameValue(o.toString(), "[object GoObject]"); `, _undefined, t) if v := m["num"]; v.Export() != int64(41) { t.Fatal(v) } } func TestDynamicArray(t *testing.T) { vm := New() dynObj := &testDynArray{ r: vm, } a := vm.NewDynamicArray(dynObj) vm.Set("a", a) vm.testScriptWithTestLibX(` assert(a instanceof Array, "instanceof Array"); assert(a instanceof Object, "instanceof Object"); assert(a === a, "self equality"); assert(a !== [], "non-equality"); assert(Array.isArray(a), "isArray()"); assert("length" in a, "length in a"); assert.sameValue(a.length, 0, "len == 0"); assert.sameValue(a[0], undefined, "a[0] (1)"); a[0] = 0; assert.sameValue(a[0], 0, "a[0] (2)"); assert.sameValue(a.length, 1, "length"); assert(deepEqual(Object.getOwnPropertyDescriptor(a, 0), {value: 0, writable: true, enumerable: true, configurable: true}), "prop desc"); assert(deepEqual(Object.getOwnPropertyDescriptor(a, "length"), {value: 1, writable: true, enumerable: false, configurable: false}), "length prop desc"); assert("__proto__" in a, "__proto__ in a"); assert.sameValue(a.__proto__, Array.prototype, "__proto__"); assert(compareArray(Object.keys(a), ["0"]), "Object.keys()"); assert(compareArray(Reflect.ownKeys(a), ["0", "length"]), "Reflect.ownKeys()"); a.length = 2; assert.sameValue(a.length, 2, "length after grow"); assert.sameValue(a[1], undefined, "a[1]"); a[1] = 1; assert.sameValue(a[1], 1, "a[1] after set"); a.length = 1; assert.sameValue(a.length, 1, "length after shrink"); assert.sameValue(a[1], undefined, "a[1] after shrink"); a.length = 2; assert.sameValue(a.length, 2, "length after shrink and grow"); assert.sameValue(a[1], undefined, "a[1] after grow"); a[0] = 3; a[1] = 1; a[2] = 2; assert.sameValue(a.length, 3); var keys = []; for (var key in a) { keys.push(key); } assert(compareArray(keys, ["0","1","2"]), "for-in"); var vals = []; for (var val of a) { vals.push(val); } assert(compareArray(vals, [3,1,2]), "for-of"); a.sort(); assert(compareArray(a, [1,2,3]), "sort: "+a); assert.sameValue(a[-1], 3); assert.sameValue(a[-4], undefined); assert.throws(TypeError, function() { "use strict"; delete a.length; }, "delete length"); assert.throws(TypeError, function() { "use strict"; a.test = true; }, "set string prop"); assert.throws(TypeError, function() { "use strict"; Object.defineProperty(a, 0, {value: 0, writable: false, enumerable: false, configurable: true}); }, "define prop"); `, _undefined, t) } type testSharedDynObject struct { sync.RWMutex m map[string]Value } func (t *testSharedDynObject) Get(key string) Value { t.RLock() val := t.m[key] t.RUnlock() return val } func (t *testSharedDynObject) Set(key string, val Value) bool { t.Lock() t.m[key] = val t.Unlock() return true } func (t *testSharedDynObject) Has(key string) bool { t.RLock() _, exists := t.m[key] t.RUnlock() return exists } func (t *testSharedDynObject) Delete(key string) bool { t.Lock() delete(t.m, key) t.Unlock() return true } func (t *testSharedDynObject) Keys() []string { t.RLock() keys := make([]string, 0, len(t.m)) for k := range t.m { keys = append(keys, k) } t.RUnlock() return keys } func TestSharedDynamicObject(t *testing.T) { dynObj := &testSharedDynObject{m: make(map[string]Value, 10000)} o := NewSharedDynamicObject(dynObj) ch := make(chan error, 1) go func() { vm := New() vm.Set("o", o) _, err := vm.RunString(` for (let i = 0; i < 10000; i++) { o[i] = i; } `) ch <- err }() vm := New() vm.Set("o", o) _, err := vm.RunString(` for (let i = 0; i < 10000; i++) { o[i] = i+1; } `) if err != nil { t.Fatal(err) } err = <-ch if err != nil { t.Fatal(err) } } type testSharedDynArray struct { sync.RWMutex a []Value } func (t *testSharedDynArray) Len() int { t.RLock() l := len(t.a) t.RUnlock() return l } func (t *testSharedDynArray) Get(idx int) Value { t.RLock() defer t.RUnlock() if idx < 0 { idx += len(t.a) } if idx >= 0 && idx < len(t.a) { return t.a[idx] } return nil } func (t *testSharedDynArray) expand(newLen int) { if newLen > cap(t.a) { a := make([]Value, newLen) copy(a, t.a) t.a = a } else { t.a = t.a[:newLen] } } func (t *testSharedDynArray) Set(idx int, val Value) bool { t.Lock() defer t.Unlock() if idx < 0 { idx += len(t.a) } if idx < 0 { return false } if idx >= len(t.a) { t.expand(idx + 1) } t.a[idx] = val return true } func (t *testSharedDynArray) SetLen(i int) bool { t.Lock() defer t.Unlock() if i > len(t.a) { t.expand(i) return true } if i < 0 { return false } if i < len(t.a) { tail := t.a[i:len(t.a)] for j := range tail { tail[j] = nil } t.a = t.a[:i] } return true } func TestSharedDynamicArray(t *testing.T) { dynObj := &testSharedDynArray{a: make([]Value, 10000)} o := NewSharedDynamicArray(dynObj) ch := make(chan error, 1) go func() { vm := New() vm.Set("o", o) _, err := vm.RunString(` for (let i = 0; i < 10000; i++) { o[i] = i; } `) ch <- err }() vm := New() vm.Set("o", o) _, err := vm.RunString(` for (let i = 0; i < 10000; i++) { o[i] = i+1; } `) if err != nil { t.Fatal(err) } err = <-ch if err != nil { t.Fatal(err) } }