-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How to use imported async functions in test script? #779
Comments
On the second topic, API wrappers. If you have them properly specified you can use tools such as Swagger Codegen to generate a javascript client wrapper for it. If that's how you actually want to build your tests it works fine. The you will have to adjust the implementations to use the k6 request APIs rather than plain js but if it's something that happens a lot you might find it worth while to implement in Codegen. |
k6 doesn't currently support promises and As briefly described here, the current synchronous k6 execution model mostly works because k6 runs multiple JS goja runtimes in parallel. The current implementation is simpler and it's sufficient for most load testing purposes, but it locks us out of the broader JS ecosystem of tools and libraries, and makes code reuse somewhat difficult as you've found out. As mentioned here, we have a localized mini-event-loop in the case of websocket connections, but it's very bounded and much more manageable than a global one. When we add an event loop, we'd probably be able to somewhat reuse the simple event loop in the No idea what the best solution for that would be, probably some configurable timeout that would specify how long k6 would wait for events to finish after the end of an iteration before it clears any stragglers and starts the next iteration. Not sure if that would be enough or if other restrictions would be necessary as well. We'd likely open a separate issue in the future to discuss the actual implementation details and different design trade-offs of an event loop, but any comments on that are welcome here as well. Connected/duplicate issue: #706 |
I struggled with this as well and found a workaround - we need webpack to polyfill Promises Now we only need to polyfill global.setTimeout = function(cb, ms = 0) {
sleep(ms/1000)
if(typeof cb === "function") {
cb()
}
}; note that I did not implement const timeout = async (ms: number): Promise<void> => {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms)
})
}
export default async (): Promise<void> => {
const now = Date.now()
await timeout(1000)
console.log(Date.now() - now) // returns 1000
} Note that If I would now use promises like so: export default async (): Promise<void> => {
const now = Date.now()
timeout(1000).then(res => console.log("foo"))
console.log(Date.now() - now)
} Would return:
in that order. Since everything is synchronous now. This should work for most cases but we still cant use |
For the record in v0.31.0 we dropped some of the babel plugins and core-js polyfills (Promise) that were helping the above workaround. In our opinion, while this workaround "worked" it is much better for most users to get better error messages for what k6 doesn't support. Also whoever is using the workaround, already needs a specific setup so requiring to polyfill Promise and possibly some more babel plugins shouldn't be |
Using #2228 (which is a WIP and will likely change) and https://github.com/grafana/k6-template-es6 you can now transpile
and you will get
🎉 This still though uses babel to transpile the You can see that the code is ... not great. Basically, any async function will get to be a switch wrapped in endless loop where on each The same effect can be enabled through enabling the following two(1, 2) babel plugins in k6 (by uncommenting the lines). This though (from my not so in-depth testing) seems to not pass most tests, actually the only ones it seems to pass is the one that expect an error, which isn't really ... useful and some very basic ones (actually a lot of Promise ones that currently just get's disabled due to being marked as `async. This again still requires the import though. Given that and:
I do think that these plugins should not be enabled for at least some time (v0.36.0 for example) and instead we should try to make progress on the actual event loop. tangent: given that generators are functions that can return(yield) and get reinvoked from the place they last returned from/yieled and that |
@mstoykov I am not able to make the I am using typescript and my babel config looks like this:
My Webpack config is configured to transpile both typescript and javascript as I have some imported modules that I am transpiling that are not ES5. it's basically a regex in the finally my test script looks like this: import "core-js/stable";
import "regenerator-runtime/runtime";
import { sleep, check, group } from "k6";
import { Options } from "k6/options";
import { Rate } from "k6/metrics";
import http from "k6/http";
import { ProxyBaseURL } from "proxy-client-ts";
import { SSMClient, GetParametersCommand } from "@aws-sdk/client-ssm";
export let options: Options = {
vus: 1,
duration: "1s",
thresholds: {
// 90% of requests must finish within 3s.
http_req_duration: ["p(95) < 3000"],
// http errors should be less than 1% for health checks
health_req_failed: ["rate<0.01"],
},
};
const ssmPath = "/auth/asymmetric/private-key/tenant-service";
const client = new SSMClient({ region: "us-west-2" });
const command = new GetParametersCommand({
Names: [ssmPath],
WithDecryption: true,
});
// creating a custom rate metrics
const health_failed = new Rate("health_req_failed");
export default async () => {
console.log("before");
const keyPairs = await client.send(command);
console.log("after", keyPairs);
group("hitting myself endpoint with authentication", function () {
console.log("inside");
console.log(keyPairs);
const url = `${ProxyBaseURL.DEV_US}/proxy-service/rest/api/2/proxy/rest/api/2/myself`;
const res = http.get(url);
check(res, {
"status is 401": () => res.status === 401,
});
sleep(1);
});
}; The code above transpiles well and doesn't throw any errors when running the test. I had to add a few dependencies for that to happen: with that said the test runs but doesn't execute any code after the
but it doesn't execute anything after that and the test results are empty
so my question is. how can I make this work and what am I missing here? |
@mstoykov Also to add to this I have tried the exact example provided and the code after the await doesn't execute. so this: import "regenerator-runtime/runtime";
const timeout = async (ms) => {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms);
});
};
export default async () => {
console.log("start");
const now = Date.now();
console.log("before wait");
await timeout(1000);
console.log(Date.now() - now); // returns 1000
console.log("end");
}; Will output this:
|
Hi @gimyboya, First, I would like to point out that this isn't officially supported, it just happens to "work"(for some definition of this word) in some cases. From what you are providing, I would guess there is an exception later on down the line inside a Promise which just aborts the whole iteration. Unfortunately, at the time Promises were added we didn't really have time to integrate them (and it will probably be at least one more release before this happens) so nobody(me specifically) noticed that if a Promise gets an exception inside it and there is no When you have some code like let result = await something():
// more code this loosely translates to something().then((result)={
// some code
}) Which means that if So far, there are two "solutions":
Hope this helps you at least somewhat, and hopefully this year we will get native async/await support and nobody will need to try to make this work. p.s. I am also really surprised |
@gimyboya
As noted in there this example is ran with the event loop WIP PR which implements |
With the merge of #2830 k6 has support for the async/await syntax. That and the fact that an event loop has been implemented for some time now means that we can close this issue. The async/await syntax merged is supposed to be released with v0.43.0. |
Wanted to post another possible solution here for keeping things DRY by ferreting away asynchronous code into external methods. Because This works because k6 runs all JavaScript it reads synchronously and will wait for asynchronous code to complete before continuing execution, but it won't delay (nor does it really "know") the assignment of an externally called method which, if doing something async, will return undefined (or some other value) immediately. An example: // your_external_methods.js
import ws from 'k6/ws';
let result = {};
/**
* Return the results object that the webSocketPayment helper writes to
*/
export function getResult() {
return result;
}
/**
* Reset the result object
*/
export function clearResult() {
result = {};
}
/**
* Do something async, i.e. web socket stuff
*/
export function asyncStuff() {
const wsUrl = 'wss://some.thing.dev/ws';
const maxEvents = 5;
const maxDuration = 120 * 1000;
const events = [];
try {
result.response = ws.connect(wsUrl, function (socket) {
socket.on('open', () => {
socket.send(JSON.stringify({ event: 'start-your-engines'));
});
socket.on('message', (data) => {
events.push(JSON.parse(data));
//
if (events.length >= maxEvents) {
close(socket, 'Got enough events, closing the connection');
}
});
socket.on('close', () => {
result.events = events;
});
socket.on('error', (e) => {
result.error = e;
});
// set timeout in case server doesn't close connection and so tests don't hang
socket.setTimeout(() => {
close(socket, 'Timeout reached, closing the connection');
}, maxDuration);
});
} catch (e) {
result.error = e;
}
} Then from inside your test file: // your_k6_script.js
import { asyncStuff, getResult } from './graphql_payment_helper.js';
export default function () {
asyncStuff();
let result = getResult();
console.log(result); // profit
} It's a bit messy but just pretend your external module is a class with getters and setters - the external async code acts as the setter - and you only assign the result after calling the set logic - which will wait before assigning the result as expected. |
It seem that async code is not supported in k6, so how imported async methods should be used? I demonstrate the problem by two examples.
For context initialization I would simply like to code something like:
The reason being that login procedure is quite complex and for that we have some utility functions that are asynchronous. I could re-write the login functions in synchronous way, but that would be mostly implementing some thing twice because testing framework (k6) doesn't support it. So it doesn't sound such a good idea.
Also, in the actual test code (default function) I would like to call our microservice via HTTP API wrapper, not HTTP directly, such as
await serviceClient.createTree(/*some parameters*/);
. That would be more realistic, a bit closer to end-to-end testing. Again, duplicating the code and making it synchronous because k6 doesn't support async code seems a bad idea. Are there any better workarounds?The text was updated successfully, but these errors were encountered: