Skip to content

Commit

Permalink
Stabilize k6/experimental/timers into k6/timers
Browse files Browse the repository at this point in the history
Closes #3297
  • Loading branch information
mstoykov committed Feb 9, 2024
1 parent aabf8a1 commit e0f7c40
Show file tree
Hide file tree
Showing 10 changed files with 230 additions and 220 deletions.
2 changes: 1 addition & 1 deletion cmd/tests/eventloop_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ func eventLoopTest(t *testing.T, script []byte, testHandle func(logLines []strin
ts := NewGlobalTestState(t)
ts.CmdArgs = []string{"k6", "--quiet", "run", "-"}
ts.Stdin = bytes.NewBuffer(
append([]byte("import { setTimeout } from 'k6/experimental/timers';\n"), script...),
append([]byte("import { setTimeout } from 'k6/timers';\n"), script...),
)

cmd.ExecuteWithGlobalState(ts.GlobalState)
Expand Down
2 changes: 1 addition & 1 deletion examples/experimental/ws.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
clearTimeout,
setInterval,
clearInterval,
} from "k6/experimental/timers";
} from "k6/timers";

let chatRoomName = "publicRoom"; // choose your chat room name
let sessionDuration = randomIntBetween(5000, 60000); // user session between 5s and 1m
Expand Down
2 changes: 1 addition & 1 deletion examples/experimental/timers.js → examples/timers.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// based on https://developer.mozilla.org/en-US/docs/Web/API/setTimeout#reasons_for_delays_longer_than_specified
import { setTimeout } from "k6/experimental/timers";
import { setTimeout } from "k6/timers";
let last = 0;
let iterations = 10;

Expand Down
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ require (
github.com/grafana/xk6-dashboard v0.7.2
github.com/grafana/xk6-output-prometheus-remote v0.3.1
github.com/grafana/xk6-redis v0.2.0
github.com/grafana/xk6-timers v0.2.3
github.com/grafana/xk6-webcrypto v0.1.1
github.com/grafana/xk6-websockets v0.2.1
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,6 @@ github.com/grafana/xk6-output-prometheus-remote v0.3.1 h1:X23rQzlJD8dXWB31DkxR4u
github.com/grafana/xk6-output-prometheus-remote v0.3.1/go.mod h1:0JLAm4ONsNUlNoxJXAwOCfA6GtDwTPs557OplAvE+3o=
github.com/grafana/xk6-redis v0.2.0 h1:iXmAKVlAxafZ/h8ptuXTFhGu63IFsyDI8QjUgWm66BU=
github.com/grafana/xk6-redis v0.2.0/go.mod h1:B3PA9PAPJa2/WUfNJCdQwZrbb6D4e6UHIk8dssQbj7w=
github.com/grafana/xk6-timers v0.2.3 h1:uShQZ6T+9fpCc9j8AAuBPRMKNneG/TRtkM1uuwhXH4g=
github.com/grafana/xk6-timers v0.2.3/go.mod h1:QbhJwMBHm9k8ukFm1AtnsoCbeRSngk+8iFaxnKZaKdo=
github.com/grafana/xk6-webcrypto v0.1.1 h1:SSGjm3mea8V9AkbqHkzlle+6KOu87gLE2xf9kfM3jlg=
github.com/grafana/xk6-webcrypto v0.1.1/go.mod h1:2pyN4Lmf5cK6EH9xnSn2B81k9vpCdBzcklWhZ/dH+18=
github.com/grafana/xk6-websockets v0.2.1 h1:99tuI5g9UPTCpGbiEo/9E7VFKQIOvTLq231qoMVef5c=
Expand Down
45 changes: 35 additions & 10 deletions js/jsmodules.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package js

import (
"sync"

"go.k6.io/k6/ext"
"go.k6.io/k6/js/modules"
"go.k6.io/k6/js/modules/k6"
"go.k6.io/k6/js/modules/k6/crypto"
"go.k6.io/k6/js/modules/k6/crypto/x509"
Expand All @@ -14,11 +17,11 @@ import (
"go.k6.io/k6/js/modules/k6/html"
"go.k6.io/k6/js/modules/k6/http"
"go.k6.io/k6/js/modules/k6/metrics"
"go.k6.io/k6/js/modules/k6/timers"
"go.k6.io/k6/js/modules/k6/ws"

"github.com/grafana/xk6-browser/browser"
"github.com/grafana/xk6-redis/redis"
"github.com/grafana/xk6-timers/timers"
"github.com/grafana/xk6-webcrypto/webcrypto"
expws "github.com/grafana/xk6-websockets/websockets"
)
Expand All @@ -30,20 +33,23 @@ func getInternalJSModules() map[string]interface{} {
"k6/crypto/x509": x509.New(),
"k6/data": data.New(),
"k6/encoding": encoding.New(),
"k6/timers": timers.New(),
"k6/execution": execution.New(),
"k6/experimental/redis": redis.New(),
"k6/experimental/webcrypto": webcrypto.New(),
"k6/experimental/websockets": &expws.RootModule{},
"k6/experimental/grpc": grpc.NewExperimental(),
"k6/experimental/timers": timers.New(),
"k6/experimental/tracing": tracing.New(),
"k6/experimental/browser": browser.New(),
"k6/experimental/fs": fs.New(),
"k6/net/grpc": grpc.New(),
"k6/html": html.New(),
"k6/http": http.New(),
"k6/metrics": metrics.New(),
"k6/ws": ws.New(),
"k6/experimental/timers": newWarnExperimentalModule(timers.New(),
"k6/experimental/timers is now part of the k6 core, please change your imports to use k6/timers instead."+
" The k6/experimental/timers will be removed in k6 v0.52.0"),
"k6/experimental/tracing": tracing.New(),
"k6/experimental/browser": browser.New(),
"k6/experimental/fs": fs.New(),
"k6/net/grpc": grpc.New(),
"k6/html": html.New(),
"k6/http": http.New(),
"k6/metrics": metrics.New(),
"k6/ws": ws.New(),
}
}

Expand All @@ -58,3 +64,22 @@ func getJSModules() map[string]interface{} {

return result
}

type warnExperimentalModule struct {
once *sync.Once
msg string
base modules.Module
}

func newWarnExperimentalModule(base modules.Module, msg string) modules.Module {
return &warnExperimentalModule{
msg: msg,
base: base,
once: &sync.Once{},
}
}

func (w *warnExperimentalModule) NewModuleInstance(vu modules.VU) modules.Instance {
w.once.Do(func() { vu.InitEnv().Logger.Warn(w.msg) })
return w.base.NewModuleInstance(vu)
}
File renamed without changes.
192 changes: 192 additions & 0 deletions js/modules/k6/timers/timers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
package timers

import (
"context"
"testing"
"time"

"github.com/stretchr/testify/require"
"go.k6.io/k6/js/modulestest"
)

func TestSetTimeout(t *testing.T) {
t.Parallel()
runtime := modulestest.NewRuntime(t)
err := runtime.SetupModuleSystem(map[string]any{"k6/x/timers": New()}, nil, nil)
require.NoError(t, err)

rt := runtime.VU.Runtime()
var log []string
require.NoError(t, rt.Set("print", func(s string) { log = append(log, s) }))

_, err = runtime.RunOnEventLoop(`
let timers = require("k6/x/timers");
timers.setTimeout(()=> {
print("in setTimeout")
})
print("outside setTimeout")
`)
require.NoError(t, err)
require.Equal(t, []string{"outside setTimeout", "in setTimeout"}, log)
}

func TestSetInterval(t *testing.T) {
t.Parallel()
runtime := modulestest.NewRuntime(t)
err := runtime.SetupModuleSystem(map[string]any{"k6/x/timers": New()}, nil, nil)
require.NoError(t, err)

rt := runtime.VU.Runtime()
var log []string
require.NoError(t, rt.Set("print", func(s string) { log = append(log, s) }))
require.NoError(t, rt.Set("sleep10", func() { time.Sleep(10 * time.Millisecond) }))

_, err = runtime.RunOnEventLoop(`
let timers = require("k6/x/timers");
var i = 0;
let s = timers.setInterval(()=> {
sleep10();
if (i>1) {
print("in setInterval");
timers.clearInterval(s);
}
i++;
}, 1);
print("outside setInterval")
`)
require.NoError(t, err)
require.Equal(t, len(log), 2)
require.Equal(t, "outside setInterval", log[0])
for i, l := range log[1:] {
require.Equal(t, "in setInterval", l, i)
}
}

func TestSetTimeoutOrder(t *testing.T) {
t.Parallel()
runtime := modulestest.NewRuntime(t)
err := runtime.SetupModuleSystem(map[string]any{"k6/x/timers": New()}, nil, nil)
require.NoError(t, err)

rt := runtime.VU.Runtime()
var log []string
require.NoError(t, rt.Set("print", func(s string) { log = append(log, s) }))

_, err = rt.RunString(`globalThis.setTimeout = require("k6/x/timers").setTimeout;`)
require.NoError(t, err)

for i := 0; i < 100; i++ {
_, err = runtime.RunOnEventLoop(`
setTimeout((_) => print("one"), 1);
setTimeout((_) => print("two"), 1);
setTimeout((_) => print("three"), 1);
setTimeout((_) => print("last"), 10);
setTimeout((_) => print("four"), 1);
setTimeout((_) => print("five"), 1);
setTimeout((_) => print("six"), 1);
print("outside setTimeout");
`)
require.NoError(t, err)
require.Equal(t, []string{"outside setTimeout", "one", "two", "three", "four", "five", "six", "last"}, log, i)
log = log[:0]
}
}

func TestSetIntervalOrder(t *testing.T) {
t.Parallel()
runtime := modulestest.NewRuntime(t)
err := runtime.SetupModuleSystem(map[string]any{"k6/x/timers": New()}, nil, nil)
require.NoError(t, err)

rt := runtime.VU.Runtime()
var log []string
require.NoError(t, rt.Set("print", func(s string) { log = append(log, s) }))

_, err = rt.RunString(`globalThis.setInterval = require("k6/x/timers").setInterval;`)
require.NoError(t, err)

_, err = rt.RunString(`globalThis.clearInterval = require("k6/x/timers").clearInterval;`)
require.NoError(t, err)

for i := 0; i < 100; i++ {
_, err = runtime.RunOnEventLoop(`
var one = setInterval((_) => print("one"), 1);
var two = setInterval((_) => print("two"), 1);
var last = setInterval((_) => {
print("last")
clearInterval(one);
clearInterval(two);
clearInterval(three);
clearInterval(last);
}, 4);
var three = setInterval((_) => print("three"), 1);
print("outside");
`)
require.NoError(t, err)
require.GreaterOrEqual(t, len(log), 5)
require.Equal(t, log[0], "outside")
for i := 1; i < len(log)-1; i += 3 {
switch len(log) - i {
case 2:
require.Equal(t, log[i:i+1], []string{"one"})
case 3:
require.Equal(t, log[i:i+2], []string{"one", "two"})
default:
require.Equal(t, log[i:i+3], []string{"one", "two", "three"})
}
}
require.Equal(t, log[len(log)-1], "last")
log = log[:0]
}
}

func TestSetTimeoutContextCancel(t *testing.T) {
t.Parallel()
runtime := modulestest.NewRuntime(t)
err := runtime.SetupModuleSystem(map[string]any{"k6/x/timers": New()}, nil, nil)
require.NoError(t, err)

rt := runtime.VU.Runtime()
var log []string
interruptChannel := make(chan struct{})
require.NoError(t, rt.Set("print", func(s string) { log = append(log, s) }))
require.NoError(t, rt.Set("interrupt", func() {
select {
case interruptChannel <- struct{}{}:
default:
}
}))

_, err = rt.RunString(`globalThis.setTimeout = require("k6/x/timers").setTimeout;`)
require.NoError(t, err)

for i := 0; i < 2000; i++ {
ctx, cancel := context.WithCancel(context.Background())
runtime.CancelContext = cancel
runtime.VU.CtxField = ctx
runtime.VU.RuntimeField.ClearInterrupt()
const interruptMsg = "definitely an interrupt"
go func() {
<-interruptChannel
time.Sleep(time.Millisecond)
runtime.CancelContext()
runtime.VU.RuntimeField.Interrupt(interruptMsg)
}()
_, err = runtime.RunOnEventLoop(`
(async () => {
let poll = async (resolve, reject) => {
await (async () => 5);
setTimeout(poll, 1, resolve, reject);
interrupt();
}
setTimeout(async () => {
await new Promise(poll)
}, 0)
})()
`)
if err != nil {
require.ErrorContains(t, err, interruptMsg)
}
require.Empty(t, log)
}
}
Loading

0 comments on commit e0f7c40

Please sign in to comment.