Adding upstream version 0.0~git20250409.f7acab6.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
b9b5d88025
commit
21b930d007
51 changed files with 11229 additions and 0 deletions
641
eventloop/eventloop_test.go
Normal file
641
eventloop/eventloop_test.go
Normal file
|
@ -0,0 +1,641 @@
|
|||
package eventloop
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/dop251/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()
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue