diff --git a/js/modules/k6/experimental/tracing/client_test.go b/js/modules/k6/experimental/tracing/client_test.go index 946f942ef31..1932f787e63 100644 --- a/js/modules/k6/experimental/tracing/client_test.go +++ b/js/modules/k6/experimental/tracing/client_test.go @@ -253,15 +253,11 @@ func TestCallingInstrumentedRequestEmitsTraceIdMetadata(t *testing.T) { // Assert there is no trace_id key in vu metadata before calling an instrumented // function, and that it's cleaned up after the call. t.Cleanup(testCase.TestRuntime.EventLoop.WaitOnRegistered) - err = testCase.TestRuntime.EventLoop.Start(func() error { - _, err = rt.RunString(httpBin.Replacer.Replace(` - assert_has_trace_id_metadata(false) - http.request("GET", "HTTPBIN_URL") - assert_has_trace_id_metadata(false) - `)) - - return err - }) + _, err = testCase.TestRuntime.RunOnEventLoop(httpBin.Replacer.Replace(` + assert_has_trace_id_metadata(false) + http.request("GET", "HTTPBIN_URL") + assert_has_trace_id_metadata(false) + `)) require.NoError(t, err) close(samples) diff --git a/js/modules/k6/http/async_request_test.go b/js/modules/k6/http/async_request_test.go index a251be18beb..15680489cb8 100644 --- a/js/modules/k6/http/async_request_test.go +++ b/js/modules/k6/http/async_request_test.go @@ -7,7 +7,6 @@ import ( "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "go.k6.io/k6/js/modulestest" ) func wrapInAsyncLambda(input string) string { @@ -15,16 +14,6 @@ func wrapInAsyncLambda(input string) string { return "(async () => {\n " + input + "\n })()" } -func runOnEventLoop(runtime *modulestest.Runtime, code string) error { - // TODO move this in modulestest.Runtime and extend it - err := runtime.EventLoop.Start(func() error { - _, err := runtime.VU.Runtime().RunString(wrapInAsyncLambda(code)) - return err - }) - runtime.EventLoop.WaitOnRegistered() - return err -} - func TestAsyncRequest(t *testing.T) { t.Parallel() t.Run("EmptyBody", func(t *testing.T) { @@ -32,7 +21,7 @@ func TestAsyncRequest(t *testing.T) { ts := newTestCase(t) sr := ts.tb.Replacer.Replace - err := runOnEventLoop(ts.runtime, sr(` + _, err := ts.runtime.RunOnEventLoop(wrapInAsyncLambda(sr(` var reqUrl = "HTTPBIN_URL/cookies" var res = await http.asyncRequest("GET", reqUrl); var jar = new http.CookieJar(); @@ -50,7 +39,7 @@ func TestAsyncRequest(t *testing.T) { } if (res.request["cookies"]["key2"][0].name != "key2") { throw new Error("wrong http request cookies: " + JSON.stringify(JSON.stringify(res.request["cookies"]["key2"]))) } if (res.request["headers"]["User-Agent"][0] != "TestUserAgent") { throw new Error("wrong http request headers: " + JSON.stringify(res.request)) } - `)) + `))) assert.NoError(t, err) }) t.Run("NonEmptyBody", func(t *testing.T) { @@ -58,18 +47,18 @@ func TestAsyncRequest(t *testing.T) { ts := newTestCase(t) sr := ts.tb.Replacer.Replace - err := runOnEventLoop(ts.runtime, sr(` + _, err := ts.runtime.RunOnEventLoop(wrapInAsyncLambda(sr(` var res = await http.asyncRequest("POST", "HTTPBIN_URL/post", {a: "a", b: 2}, {headers: {"Content-Type": "application/x-www-form-urlencoded; charset=utf-8"}}); if (res.status != 200) { throw new Error("wrong status: " + res.status); } if (res.request["body"] != "a=a&b=2") { throw new Error("http request body was not set properly: " + JSON.stringify(res.request))} - `)) + `))) assert.NoError(t, err) }) t.Run("Concurrent", func(t *testing.T) { t.Parallel() ts := newTestCase(t) sr := ts.tb.Replacer.Replace - err := runOnEventLoop(ts.runtime, sr(` + _, err := ts.runtime.RunOnEventLoop(wrapInAsyncLambda(sr(` let start = Date.now() let p1 = http.asyncRequest("GET", "HTTPBIN_URL/delay/200ms").then(() => { return Date.now() - start}) let p2 = http.asyncRequest("GET", "HTTPBIN_URL/delay/100ms").then(() => { return Date.now() - start}) @@ -78,8 +67,7 @@ func TestAsyncRequest(t *testing.T) { if (time1 < time2) { throw("request that should've taken 200ms took less time then one that should take 100ms " + time1 +">" + time2 ) } - - `)) + `))) assert.NoError(t, err) }) } @@ -102,7 +90,7 @@ func TestAsyncRequestResponseCallbackRace(t *testing.T) { // t.Log(s) // uncomment for debugging }) require.NoError(t, err) - err = runOnEventLoop(ts.runtime, ts.tb.Replacer.Replace(` + _, err = ts.runtime.RunOnEventLoop(wrapInAsyncLambda(ts.tb.Replacer.Replace(` let call = (i) => { log("s"+i) if (i > 200) { return null; } @@ -113,7 +101,7 @@ func TestAsyncRequestResponseCallbackRace(t *testing.T) { call(0) await http.asyncRequest("GET", "HTTPBIN_URL/redirect/20").then(() => log("!!!!!!!!!!!!!!!"+j)) } - `)) + `))) require.NoError(t, err) } @@ -126,13 +114,13 @@ func TestAsyncRequestErrors(t *testing.T) { t.Parallel() ts := newTestCase(t) - err := runOnEventLoop(ts.runtime, ` + _, err := ts.runtime.RunOnEventLoop(wrapInAsyncLambda(` try { http.asyncRequest("", "").catch((e) => globalThis.promiseRejected = e ) } catch (e) { globalThis.exceptionThrown = e } - `) + `)) require.NoError(t, err) promiseRejected := ts.runtime.VU.Runtime().Get("promiseRejected") exceptionThrown := ts.runtime.VU.Runtime().Get("exceptionThrown") @@ -148,13 +136,13 @@ func TestAsyncRequestErrors(t *testing.T) { t.Run("throw=false", func(t *testing.T) { t.Parallel() ts := newTestCase(t) - err := runOnEventLoop(ts.runtime, ` + _, err := ts.runtime.RunOnEventLoop(wrapInAsyncLambda(` var res = await http.asyncRequest("GET", "some://example.com", null, { throw: false }); if (res.error.search('unsupported protocol scheme "some"') == -1) { throw new Error("wrong error:" + res.error); } throw new Error("another error"); - `) + `)) require.ErrorContains(t, err, "another error") logEntry := ts.hook.LastEntry() @@ -181,7 +169,7 @@ func TestAsyncRequestErrors(t *testing.T) { globalThis.exceptionThrown = e } ` - err := runOnEventLoop(ts.runtime, js) + _, err := ts.runtime.RunOnEventLoop(wrapInAsyncLambda(js)) require.NoError(t, err) promiseRejected := ts.runtime.VU.Runtime().Get("promiseRejected") exceptionThrown := ts.runtime.VU.Runtime().Get("exceptionThrown") @@ -203,7 +191,7 @@ func TestAsyncRequestErrors(t *testing.T) { var r = await http.asyncRequest("GET", "https:// test.k6.io"); globalThis.ret = {error: r.error, error_code: r.error_code}; ` - err := runOnEventLoop(ts.runtime, js) + _, err := ts.runtime.RunOnEventLoop(wrapInAsyncLambda(js)) require.NoError(t, err) ret := rt.GlobalObject().Get("ret") var retobj map[string]interface{} @@ -237,7 +225,7 @@ func TestAsyncRequestErrors(t *testing.T) { r.json(); globalThis.ret = r.error_code; // not reached because of json() ` - err := runOnEventLoop(ts.runtime, js) + _, err := ts.runtime.RunOnEventLoop(wrapInAsyncLambda(js)) ret := rt.GlobalObject().Get("ret") require.Error(t, err) assert.Nil(t, ret) @@ -256,12 +244,12 @@ func TestAsyncRequestErrors(t *testing.T) { t.Run("Unroutable", func(t *testing.T) { t.Parallel() ts := newTestCase(t) - err := runOnEventLoop(ts.runtime, ` + _, err := ts.runtime.RunOnEventLoop(wrapInAsyncLambda(` try { http.asyncRequest("GET", "http://sdafsgdhfjg/").catch((e) => globalThis.promiseRejected = e ) } catch (e) { globalThis.exceptionThrown = e - }`) + }`)) expErr := "lookup sdafsgdhfjg" require.NoError(t, err) promiseRejected := ts.runtime.VU.Runtime().Get("promiseRejected") diff --git a/js/modules/k6/http/response_callback_test.go b/js/modules/k6/http/response_callback_test.go index c333173262a..d6e7939c57a 100644 --- a/js/modules/k6/http/response_callback_test.go +++ b/js/modules/k6/http/response_callback_test.go @@ -262,11 +262,7 @@ func TestResponseCallbackInAction(t *testing.T) { t.Helper() ts.instance.defaultClient.responseCallback = defaultExpectedStatuses.match - err := ts.runtime.EventLoop.Start(func() error { - _, err := ts.runtime.VU.Runtime().RunString(sr(code)) - return err - }) - ts.runtime.EventLoop.WaitOnRegistered() + _, err := ts.runtime.RunOnEventLoop(sr(code)) assert.NoError(t, err) bufSamples := metrics.GetBufferedSamples(samples) diff --git a/js/modules/k6/k6_test.go b/js/modules/k6/k6_test.go index 23aac46904e..74432901088 100644 --- a/js/modules/k6/k6_test.go +++ b/js/modules/k6/k6_test.go @@ -5,7 +5,6 @@ import ( "testing" "time" - "github.com/dop251/goja" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -18,7 +17,7 @@ func TestFail(t *testing.T) { t.Parallel() tc := testCaseRuntime(t) - _, err := tc.run(`k6.fail("blah")`) + _, err := tc.testRuntime.RunOnEventLoop(`k6.fail("blah")`) assert.Contains(t, err.Error(), "blah") } @@ -36,7 +35,7 @@ func TestSleep(t *testing.T) { t.Parallel() tc := testCaseRuntime(t) startTime := time.Now() - _, err := tc.run(`k6.sleep(1)`) + _, err := tc.testRuntime.RunOnEventLoop(`k6.sleep(1)`) endTime := time.Now() assert.NoError(t, err) assert.True(t, endTime.Sub(startTime) > d, "did not sleep long enough") @@ -51,7 +50,7 @@ func TestSleep(t *testing.T) { dch := make(chan time.Duration) go func() { startTime := time.Now() - _, err := tc.run(`k6.sleep(10)`) + _, err := tc.testRuntime.RunOnEventLoop(`k6.sleep(10)`) endTime := time.Now() assert.NoError(t, err) dch <- endTime.Sub(startTime) @@ -72,13 +71,13 @@ func TestRandSeed(t *testing.T) { tc := testCaseRuntime(t) rand := 0.8487305991992138 - _, err := tc.run(fmt.Sprintf(` + _, err := tc.testRuntime.RunOnEventLoop(fmt.Sprintf(` var rnd = Math.random(); if (rnd == %.16f) { throw new Error("wrong random: " + rnd); } `, rand)) assert.NoError(t, err) - _, err = tc.run(fmt.Sprintf(` + _, err = tc.testRuntime.RunOnEventLoop(fmt.Sprintf(` k6.randomSeed(12345) var rnd = Math.random(); if (rnd != %.16f) { throw new Error("wrong random: " + rnd); } @@ -101,7 +100,7 @@ func TestGroup(t *testing.T) { assert.Equal(t, state.Group.Name, "my group") assert.Equal(t, state.Group.Parent, root) })) - _, err := tc.run(`k6.group("my group", fn)`) + _, err := tc.testRuntime.RunOnEventLoop(`k6.group("my group", fn)`) assert.NoError(t, err) assert.Equal(t, state.Group, root) groupTag, ok := state.Tags.GetCurrentValues().Tags.Get("group") @@ -112,21 +111,21 @@ func TestGroup(t *testing.T) { t.Run("Invalid", func(t *testing.T) { t.Parallel() tc := testCaseRuntime(t) - _, err := tc.run(`k6.group("::", function() { throw new Error("nooo") })`) + _, err := tc.testRuntime.RunOnEventLoop(`k6.group("::", function() { throw new Error("nooo") })`) assert.Contains(t, err.Error(), "group and check names may not contain '::'") }) t.Run("async function", func(t *testing.T) { t.Parallel() tc := testCaseRuntime(t) - _, err := tc.run(`k6.group("something", async function() { })`) + _, err := tc.testRuntime.RunOnEventLoop(`k6.group("something", async function() { })`) assert.ErrorContains(t, err, "group() does not support async functions as arguments") }) t.Run("async lambda", func(t *testing.T) { t.Parallel() tc := testCaseRuntime(t) - _, err := tc.run(`k6.group("something", async () => { })`) + _, err := tc.testRuntime.RunOnEventLoop(`k6.group("something", async () => { })`) assert.ErrorContains(t, err, "group() does not support async functions as arguments") }) } @@ -137,7 +136,7 @@ func TestCheckObject(t *testing.T) { t.Parallel() tc := testCaseRuntime(t) - _, err := tc.run(`k6.check(null, { "check": true })`) + _, err := tc.testRuntime.RunOnEventLoop(`k6.check(null, { "check": true })`) assert.NoError(t, err) bufSamples := metrics.GetBufferedSamples(tc.samples) @@ -158,7 +157,7 @@ func TestCheckObject(t *testing.T) { t.Parallel() tc := testCaseRuntime(t) - _, err := tc.run(`k6.check(null, { "a": true, "b": false })`) + _, err := tc.testRuntime.RunOnEventLoop(`k6.check(null, { "a": true, "b": false })`) assert.NoError(t, err) bufSamples := metrics.GetBufferedSamples(tc.samples) @@ -187,21 +186,21 @@ func TestCheckObject(t *testing.T) { t.Run("Invalid", func(t *testing.T) { t.Parallel() tc := testCaseRuntime(t) - _, err := tc.run(`k6.check(null, { "::": true })`) + _, err := tc.testRuntime.RunOnEventLoop(`k6.check(null, { "::": true })`) assert.Contains(t, err.Error(), "group and check names may not contain '::'") }) t.Run("async function", func(t *testing.T) { t.Parallel() tc := testCaseRuntime(t) - _, err := tc.run(`k6.check("something", {"async": async function() { }})`) + _, err := tc.testRuntime.RunOnEventLoop(`k6.check("something", {"async": async function() { }})`) assert.ErrorContains(t, err, "check() does not support async functions as arguments") }) t.Run("async lambda", func(t *testing.T) { t.Parallel() tc := testCaseRuntime(t) - _, err := tc.run(`k6.check("something", {"async": async () =>{ }})`) + _, err := tc.testRuntime.RunOnEventLoop(`k6.check("something", {"async": async () =>{ }})`) assert.ErrorContains(t, err, "check() does not support async functions as arguments") }) } @@ -210,7 +209,7 @@ func TestCheckArray(t *testing.T) { t.Parallel() tc := testCaseRuntime(t) - _, err := tc.run(`k6.check(null, [ true ])`) + _, err := tc.testRuntime.RunOnEventLoop(`k6.check(null, [ true ])`) assert.NoError(t, err) bufSamples := metrics.GetBufferedSamples(tc.samples) @@ -234,7 +233,7 @@ func TestCheckContextDone(t *testing.T) { tc := testCaseRuntime(t) tc.testRuntime.CancelContext() - v, err := tc.run(` k6.check(null, {"name": ()=>{ return true }})`) + v, err := tc.testRuntime.RunOnEventLoop(` k6.check(null, {"name": ()=>{ return true }})`) assert.NoError(t, err) assert.Len(t, metrics.GetBufferedSamples(tc.samples), 0) assert.True(t, v.ToBoolean()) @@ -244,7 +243,7 @@ func TestCheckContextDone(t *testing.T) { tc := testCaseRuntime(t) tc.testRuntime.CancelContext() - v, err := tc.run(`k6.check(null, {"name": ()=>{ return false }})`) + v, err := tc.testRuntime.RunOnEventLoop(`k6.check(null, {"name": ()=>{ return false }})`) assert.NoError(t, err) assert.Len(t, metrics.GetBufferedSamples(tc.samples), 0) assert.False(t, v.ToBoolean()) @@ -255,7 +254,7 @@ func TestCheckLiteral(t *testing.T) { t.Parallel() tc := testCaseRuntime(t) - _, err := tc.run(`k6.check(null, 12345)`) + _, err := tc.testRuntime.RunOnEventLoop(`k6.check(null, 12345)`) assert.NoError(t, err) assert.Len(t, metrics.GetBufferedSamples(tc.samples), 0) } @@ -264,7 +263,7 @@ func TestCheckNull(t *testing.T) { t.Parallel() tc := testCaseRuntime(t) - _, err := tc.run(`k6.check(5)`) + _, err := tc.testRuntime.RunOnEventLoop(`k6.check(5)`) require.Error(t, err) assert.Contains(t, err.Error(), "no checks provided") assert.Len(t, metrics.GetBufferedSamples(tc.samples), 0) @@ -273,7 +272,7 @@ func TestCheckNull(t *testing.T) { func TestCheckThrows(t *testing.T) { t.Parallel() tc := testCaseRuntime(t) - _, err := tc.run(` + _, err := tc.testRuntime.RunOnEventLoop(` k6.check(null, { "a": function() { throw new Error("error A") }, "b": function() { throw new Error("error B") }, @@ -324,7 +323,7 @@ func TestCheckTypes(t *testing.T) { t.Parallel() tc := testCaseRuntime(t) - v, err := tc.run(fmt.Sprintf(tpl, value)) + v, err := tc.testRuntime.RunOnEventLoop(fmt.Sprintf(tpl, value)) require.NoError(t, err) assert.Equal(t, succ, v.Export()) @@ -355,7 +354,7 @@ func TestCheckContextExpiry(t *testing.T) { tc := testCaseRuntime(t) - v, err := tc.run(`value = k6.check(null, { "check": true })`) + v, err := tc.testRuntime.RunOnEventLoop(`value = k6.check(null, { "check": true })`) require.NoError(t, err) assert.Equal(t, true, v.Export()) @@ -365,7 +364,7 @@ func TestCheckContextExpiry(t *testing.T) { tc.testRuntime.CancelContext() - v, err = tc.run(`k6.check(null, { "check": true })`) + v, err = tc.testRuntime.RunOnEventLoop(`k6.check(null, { "check": true })`) require.NoError(t, err) assert.Equal(t, true, v.Export()) @@ -377,7 +376,7 @@ func TestCheckTags(t *testing.T) { t.Parallel() tc := testCaseRuntime(t) - v, err := tc.run(`k6.check(null, {"check": true}, {a: 1, b: "2"})`) + v, err := tc.testRuntime.RunOnEventLoop(`k6.check(null, {"check": true}, {a: 1, b: "2"})`) require.NoError(t, err) assert.Equal(t, true, v.Export()) @@ -428,12 +427,3 @@ func testCaseRuntime(t testing.TB) *testCase { testRuntime: testRuntime, } } - -func (t *testCase) run(script string) (v goja.Value, err error) { - defer t.testRuntime.EventLoop.WaitOnRegistered() - err = t.testRuntime.EventLoop.Start(func() error { - v, err = t.testRuntime.VU.Runtime().RunString(script) - return err - }) - return v, err -} diff --git a/js/modulestest/runtime.go b/js/modulestest/runtime.go index 0cfbedba5c0..5b607013a3b 100644 --- a/js/modulestest/runtime.go +++ b/js/modulestest/runtime.go @@ -74,6 +74,34 @@ func (r *Runtime) SetupModuleSystemFromAnother(another *Runtime) error { return r.innerSetupModuleSystem() } +// RunOnEventLoop will run the given code on the event loop. +// +// It is meant as a helper to test code that is expected to be run on the event loop, such +// as code that returns a promise. +// +// A typical usage is to facilitate writing testsĀ for asynchrounous code: +// +// func TestSomething(t *testing.T) { +// runtime := modulestest.NewRuntime(t) +// +// err := runtime.RunOnEventLoop(` +// doSomethingAsync().then(() => { +// // do some assertions +// }); +// `) +// require.NoError(t, err) +// } +func (r *Runtime) RunOnEventLoop(code string) (value goja.Value, err error) { + defer r.EventLoop.WaitOnRegistered() + + err = r.EventLoop.Start(func() error { + value, err = r.VU.Runtime().RunString(code) + return err + }) + + return value, err +} + func (r *Runtime) innerSetupModuleSystem() error { ms := modules.NewModuleSystem(r.mr, r.VU) impl := modules.NewLegacyRequireImpl(r.VU, ms, url.URL{})