ai_old/goja_nodejs/eventloop/eventloop_test.go
Star 767d87ac3e update throw exception
add util.load and util.save
2024-09-24 13:34:32 +08:00

642 lines
12 KiB
Go

package eventloop
import (
"fmt"
"sync/atomic"
"testing"
"time"
"apigo.cc/ai/ai/goja"
"go.uber.org/goleak"
)
func TestRun(t *testing.T) {
t.Parallel()
const SCRIPT = `
var calledAt;
setTimeout(function() {
calledAt = now();
}, 1000);
`
loop := NewEventLoop()
prg, err := goja.Compile("main.js", SCRIPT, false)
if err != nil {
t.Fatal(err)
}
startTime := time.Now()
loop.Run(func(vm *goja.Runtime) {
vm.Set("now", time.Now)
_, err = vm.RunProgram(prg)
})
if err != nil {
t.Fatal(err)
}
var calledAt time.Time
loop.Run(func(vm *goja.Runtime) {
err = vm.ExportTo(vm.Get("calledAt"), &calledAt)
})
if err != nil {
t.Fatal(err)
}
if calledAt.IsZero() {
t.Fatal("Not called")
}
if dur := calledAt.Sub(startTime); dur < time.Second {
t.Fatal(dur)
}
}
func TestStart(t *testing.T) {
t.Parallel()
const SCRIPT = `
var calledAt;
setTimeout(function() {
calledAt = now();
}, 1000);
`
prg, err := goja.Compile("main.js", SCRIPT, false)
if err != nil {
t.Fatal(err)
}
loop := NewEventLoop()
startTime := time.Now()
loop.Start()
loop.RunOnLoop(func(vm *goja.Runtime) {
vm.Set("now", time.Now)
vm.RunProgram(prg)
})
time.Sleep(2 * time.Second)
if remainingJobs := loop.Stop(); remainingJobs != 0 {
t.Fatal(remainingJobs)
}
var calledAt time.Time
loop.Run(func(vm *goja.Runtime) {
err = vm.ExportTo(vm.Get("calledAt"), &calledAt)
})
if err != nil {
t.Fatal(err)
}
if calledAt.IsZero() {
t.Fatal("Not called")
}
if dur := calledAt.Sub(startTime); dur < time.Second {
t.Fatal(dur)
}
}
func TestStartInForeground(t *testing.T) {
t.Parallel()
const SCRIPT = `
var calledAt;
setTimeout(function() {
calledAt = now();
}, 1000);
`
prg, err := goja.Compile("main.js", SCRIPT, false)
if err != nil {
t.Fatal(err)
}
loop := NewEventLoop()
startTime := time.Now()
go loop.StartInForeground()
loop.RunOnLoop(func(vm *goja.Runtime) {
vm.Set("now", time.Now)
vm.RunProgram(prg)
})
time.Sleep(2 * time.Second)
if remainingJobs := loop.Stop(); remainingJobs != 0 {
t.Fatal(remainingJobs)
}
var calledAt time.Time
loop.Run(func(vm *goja.Runtime) {
err = vm.ExportTo(vm.Get("calledAt"), &calledAt)
})
if err != nil {
t.Fatal(err)
}
if calledAt.IsZero() {
t.Fatal("Not called")
}
if dur := calledAt.Sub(startTime); dur < time.Second {
t.Fatal(dur)
}
}
func TestInterval(t *testing.T) {
t.Parallel()
const SCRIPT = `
var count = 0;
var t = setInterval(function(times) {
console.log("tick");
if (++count > times) {
clearInterval(t);
}
}, 1000, 2);
console.log("Started");
`
loop := NewEventLoop()
prg, err := goja.Compile("main.js", SCRIPT, false)
if err != nil {
t.Fatal(err)
}
loop.Run(func(vm *goja.Runtime) {
_, err = vm.RunProgram(prg)
})
if err != nil {
t.Fatal(err)
}
var count int64
loop.Run(func(vm *goja.Runtime) {
count = vm.Get("count").ToInteger()
})
if count != 3 {
t.Fatal(count)
}
}
func TestImmediate(t *testing.T) {
t.Parallel()
const SCRIPT = `
let log = [];
function cb(arg) {
log.push(arg);
}
var i;
var t = setImmediate(function() {
cb("tick");
setImmediate(cb, "tick 2");
i = setImmediate(cb, "should not run")
});
setImmediate(function() {
clearImmediate(i);
});
cb("Started");
`
loop := NewEventLoop()
prg, err := goja.Compile("main.js", SCRIPT, false)
if err != nil {
t.Fatal(err)
}
loop.Run(func(vm *goja.Runtime) {
_, err = vm.RunProgram(prg)
})
if err != nil {
t.Fatal(err)
}
loop.Run(func(vm *goja.Runtime) {
_, err = vm.RunString(`
if (log.length != 3) {
throw new Error("Invalid log length: " + log);
}
if (log[0] !== "Started" || log[1] !== "tick" || log[2] !== "tick 2") {
throw new Error("Invalid log: " + log);
}
`)
})
if err != nil {
t.Fatal(err)
}
}
func TestRunNoSchedule(t *testing.T) {
loop := NewEventLoop()
fired := false
loop.Run(func(vm *goja.Runtime) { // should not hang
fired = true
// do not schedule anything
})
if !fired {
t.Fatal("Not fired")
}
}
func TestRunWithConsole(t *testing.T) {
const SCRIPT = `
console.log("Started");
`
loop := NewEventLoop()
prg, err := goja.Compile("main.js", SCRIPT, false)
if err != nil {
t.Fatal(err)
}
loop.Run(func(vm *goja.Runtime) {
_, err = vm.RunProgram(prg)
})
if err != nil {
t.Fatal("Call to console.log generated an error", err)
}
loop = NewEventLoop(EnableConsole(true))
prg, err = goja.Compile("main.js", SCRIPT, false)
if err != nil {
t.Fatal(err)
}
loop.Run(func(vm *goja.Runtime) {
_, err = vm.RunProgram(prg)
})
if err != nil {
t.Fatal("Call to console.log generated an error", err)
}
}
func TestRunNoConsole(t *testing.T) {
const SCRIPT = `
console.log("Started");
`
loop := NewEventLoop(EnableConsole(false))
prg, err := goja.Compile("main.js", SCRIPT, false)
if err != nil {
t.Fatal(err)
}
loop.Run(func(vm *goja.Runtime) {
_, err = vm.RunProgram(prg)
})
if err == nil {
t.Fatal("Call to console.log did not generate an error", err)
}
}
func TestClearIntervalRace(t *testing.T) {
t.Parallel()
const SCRIPT = `
console.log("calling setInterval");
var t = setInterval(function() {
console.log("tick");
}, 500);
console.log("calling sleep");
sleep(2000);
console.log("calling clearInterval");
clearInterval(t);
`
loop := NewEventLoop()
prg, err := goja.Compile("main.js", SCRIPT, false)
if err != nil {
t.Fatal(err)
}
// Should not hang
loop.Run(func(vm *goja.Runtime) {
vm.Set("sleep", func(ms int) {
<-time.After(time.Duration(ms) * time.Millisecond)
})
vm.RunProgram(prg)
})
}
func TestNativeTimeout(t *testing.T) {
t.Parallel()
fired := false
loop := NewEventLoop()
loop.SetTimeout(func(*goja.Runtime) {
fired = true
}, 1*time.Second)
loop.Run(func(*goja.Runtime) {
// do not schedule anything
})
if !fired {
t.Fatal("Not fired")
}
}
func TestNativeClearTimeout(t *testing.T) {
t.Parallel()
fired := false
loop := NewEventLoop()
timer := loop.SetTimeout(func(*goja.Runtime) {
fired = true
}, 2*time.Second)
loop.SetTimeout(func(*goja.Runtime) {
loop.ClearTimeout(timer)
}, 1*time.Second)
loop.Run(func(*goja.Runtime) {
// do not schedule anything
})
if fired {
t.Fatal("Cancelled timer fired!")
}
}
func TestNativeInterval(t *testing.T) {
t.Parallel()
count := 0
loop := NewEventLoop()
var i *Interval
i = loop.SetInterval(func(*goja.Runtime) {
t.Log("tick")
count++
if count > 2 {
loop.ClearInterval(i)
}
}, 1*time.Second)
loop.Run(func(*goja.Runtime) {
// do not schedule anything
})
if count != 3 {
t.Fatal("Expected interval to fire 3 times, got", count)
}
}
func TestNativeClearInterval(t *testing.T) {
t.Parallel()
count := 0
loop := NewEventLoop()
loop.Run(func(*goja.Runtime) {
i := loop.SetInterval(func(*goja.Runtime) {
t.Log("tick")
count++
}, 500*time.Millisecond)
<-time.After(2 * time.Second)
loop.ClearInterval(i)
})
if count != 0 {
t.Fatal("Expected interval to fire 0 times, got", count)
}
}
func TestSetAndClearOnStoppedLoop(t *testing.T) {
t.Parallel()
loop := NewEventLoop()
timeout := loop.SetTimeout(func(runtime *goja.Runtime) {
panic("must not run")
}, 1*time.Millisecond)
loop.ClearTimeout(timeout)
loop.Start()
time.Sleep(10 * time.Millisecond)
loop.Terminate()
}
func TestSetTimeoutConcurrent(t *testing.T) {
t.Parallel()
loop := NewEventLoop()
loop.Start()
ch := make(chan struct{}, 1)
loop.SetTimeout(func(*goja.Runtime) {
ch <- struct{}{}
}, 100*time.Millisecond)
<-ch
loop.Stop()
}
func TestClearTimeoutConcurrent(t *testing.T) {
t.Parallel()
loop := NewEventLoop()
loop.Start()
timer := loop.SetTimeout(func(*goja.Runtime) {
}, 100*time.Millisecond)
loop.ClearTimeout(timer)
loop.Stop()
if c := loop.jobCount; c != 0 {
t.Fatalf("jobCount: %d", c)
}
}
func TestClearIntervalConcurrent(t *testing.T) {
t.Parallel()
loop := NewEventLoop()
loop.Start()
ch := make(chan struct{}, 1)
i := loop.SetInterval(func(*goja.Runtime) {
ch <- struct{}{}
}, 500*time.Millisecond)
<-ch
loop.ClearInterval(i)
loop.Stop()
if c := loop.jobCount; c != 0 {
t.Fatalf("jobCount: %d", c)
}
}
func TestRunOnStoppedLoop(t *testing.T) {
t.Parallel()
loop := NewEventLoop()
var failed int32
done := make(chan struct{})
go func() {
for atomic.LoadInt32(&failed) == 0 {
loop.Start()
time.Sleep(10 * time.Millisecond)
loop.Stop()
}
}()
go func() {
for atomic.LoadInt32(&failed) == 0 {
loop.RunOnLoop(func(*goja.Runtime) {
if !loop.running {
atomic.StoreInt32(&failed, 1)
close(done)
return
}
})
time.Sleep(10 * time.Millisecond)
}
}()
select {
case <-done:
case <-time.After(5 * time.Second):
}
if atomic.LoadInt32(&failed) != 0 {
t.Fatal("running job on stopped loop")
}
}
func TestPromise(t *testing.T) {
t.Parallel()
const SCRIPT = `
let result;
const p = new Promise((resolve, reject) => {
setTimeout(() => {resolve("passed")}, 500);
});
p.then(value => {
result = value;
});
`
loop := NewEventLoop()
prg, err := goja.Compile("main.js", SCRIPT, false)
if err != nil {
t.Fatal(err)
}
loop.Run(func(vm *goja.Runtime) {
_, err = vm.RunProgram(prg)
})
if err != nil {
t.Fatal(err)
}
loop.Run(func(vm *goja.Runtime) {
result := vm.Get("result")
if !result.SameAs(vm.ToValue("passed")) {
err = fmt.Errorf("unexpected result: %v", result)
}
})
if err != nil {
t.Fatal(err)
}
}
func TestPromiseNative(t *testing.T) {
t.Parallel()
const SCRIPT = `
let result;
p.then(value => {
result = value;
done();
});
`
loop := NewEventLoop()
prg, err := goja.Compile("main.js", SCRIPT, false)
if err != nil {
t.Fatal(err)
}
ch := make(chan error)
loop.Start()
defer loop.Stop()
loop.RunOnLoop(func(vm *goja.Runtime) {
vm.Set("done", func() {
ch <- nil
})
p, resolve, _ := vm.NewPromise()
vm.Set("p", p)
_, err = vm.RunProgram(prg)
if err != nil {
ch <- err
return
}
go func() {
time.Sleep(500 * time.Millisecond)
loop.RunOnLoop(func(*goja.Runtime) {
resolve("passed")
})
}()
})
err = <-ch
if err != nil {
t.Fatal(err)
}
loop.RunOnLoop(func(vm *goja.Runtime) {
result := vm.Get("result")
if !result.SameAs(vm.ToValue("passed")) {
ch <- fmt.Errorf("unexpected result: %v", result)
} else {
ch <- nil
}
})
err = <-ch
if err != nil {
t.Fatal(err)
}
}
func TestEventLoop_StopNoWait(t *testing.T) {
t.Parallel()
loop := NewEventLoop()
var ran int32
loop.Run(func(runtime *goja.Runtime) {
loop.SetTimeout(func(*goja.Runtime) {
atomic.StoreInt32(&ran, 1)
}, 5*time.Second)
loop.SetTimeout(func(*goja.Runtime) {
loop.StopNoWait()
}, 500*time.Millisecond)
})
if atomic.LoadInt32(&ran) != 0 {
t.Fatal("ran != 0")
}
}
func TestEventLoop_ClearRunningTimeout(t *testing.T) {
t.Parallel()
const SCRIPT = `
var called = 0;
let aTimer;
function a() {
if (++called > 5) {
return;
}
if (aTimer) {
clearTimeout(aTimer);
}
console.log("ok");
aTimer = setTimeout(a, 500);
}
a();`
prg, err := goja.Compile("main.js", SCRIPT, false)
if err != nil {
t.Fatal(err)
}
loop := NewEventLoop()
loop.Run(func(vm *goja.Runtime) {
_, err = vm.RunProgram(prg)
})
if err != nil {
t.Fatal(err)
}
var called int64
loop.Run(func(vm *goja.Runtime) {
called = vm.Get("called").ToInteger()
})
if called != 6 {
t.Fatal(called)
}
}
func TestEventLoop_Terminate(t *testing.T) {
defer goleak.VerifyNone(t)
loop := NewEventLoop()
loop.Start()
interval := loop.SetInterval(func(vm *goja.Runtime) {}, 10*time.Millisecond)
time.Sleep(500 * time.Millisecond)
loop.ClearInterval(interval)
loop.Terminate()
if loop.SetTimeout(func(*goja.Runtime) {}, time.Millisecond) != nil {
t.Fatal("was able to SetTimeout()")
}
if loop.SetInterval(func(*goja.Runtime) {}, time.Millisecond) != nil {
t.Fatal("was able to SetInterval()")
}
if loop.RunOnLoop(func(*goja.Runtime) {}) {
t.Fatal("was able to RunOnLoop()")
}
ch := make(chan struct{})
loop.Start()
if !loop.RunOnLoop(func(runtime *goja.Runtime) {
close(ch)
}) {
t.Fatal("RunOnLoop() has failed after restart")
}
<-ch
loop.Terminate()
}