-
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
Unify and standardize behavior and exit codes when k6 is stopped #2804
Comments
There is actually a 7th way to stop a test - timeouts 😅 Specifically, both These timeouts have their own exit and |
It's also worth considering and testing that there is probably a difference between script exceptions (e.g.
|
@imiric raised a very good suggestion in #2810 (comment) - now that #2810 will add a |
#2885 was a reminder that |
As #2885 and #2893 have proven, In general, if a script error or |
Another way a k6 test (the 8th? 😱) can be stopped is when a specific output tells it to 😩 See Lines 69 to 76 in b85d09d
|
Connected to #2804 (comment), the exit code of a |
Would it be possible to pass custom exit code to We have tests that are not interested about metrics, but about specific functional failures that occur only under load. Typically we need to know what happened and provide some contextual data eg: if (res.status === 400 && res.json('error_code') === 666) {
const context = JSON.stringify({
reqBody,
resStatus: res.status,
resBody: res.body,
});
execution.test.abort(`error_code must not be 666: ${context}`);
} I would probably like to have something like: execution.test.abort({
exitCode: 1666,
message: `error_code must not be 666`,
context: whatever, // I assume this has to be serializable
});
/*
The summary would then contain:
type SummaryData = {
result: {
outcome: 'passed' | 'aborted' | ...
exitCode: number,
context: unknown,
}
// ...other props ...
}
Btw summary data type is not provided by types/k6
*/ We also have some post-test scripts that depend on what went wrong, so it's quite useful to reflect this in the exit code, without the need to inspect summary/output. There are workarounds like counter + threshold[count<1] + check with dynamic name (contextual data serialized in the check name) or looking up the specific check in the output (contextual data in tags), but it's quite a hassle for such a simple thing. I noticed discussions about "angry checks" (checks interrupting iteration), maybe there should be a "furious check" (check aborting a test) :D Please excuse me if this is not a good place to discuss this. |
After #3876 and #3923, Cloud runs with thresholds crossed are consistently being reported as Before, those were being reported as Both will be shipped in v0.54 🚀 |
k6 has multiple different ways to stop a test:
test.abort()
JS API fromk6/execution
curl -i -X PATCH -d '{"data":{"type":"status","id":"default","attributes":{"stopped":true}}}' 'http://localhost:6565/v1/status'
abortOnFail: true
And, for reference, we currently have these exit codes predefined:
k6/errext/exitcodes/codes.go
Lines 10 to 21 in 1b9c5fa
The problem is that stopping the test in different ways and at different times behaves very inconsistently and unpredictably, even when you account for the unavoidable peculiarities of k6... 😭 Here is what I've determined:
options
):1
, which is apparently the default Go interrupt signal handling behavior when it has not yet been handled by k6test.abort()
will surprisingly work as expected and cause k6 to exit withScriptAborted
ScriptException
GenericEngine
, the catch-all exit code for when k6 doesn't "know" what caused the failure... 😞Context
when initializing VUs #2800test.abort()
kind of works, k6 exits withScriptAborted
and after Pass and respect aContext
when initializing VUs #2800 it should be graceful 🤞ScriptException
stopped
, however the VU initialization won't actually stop, even after Pass and respect aContext
when initializing VUs #2800 😞 After VU initialization finishes,setup()
is executed (:facepalm:), then no iterations are executed, thenteardown()
is executed and finally, k6 exits with a 0 exit codehttp_req_duration: ['p(99)<100']
, they will happily pass with no iterations 😞setup()
execution:GenericEngine
, the catch-all exit code for when k6 doesn't "know" what caused the failure... 😞test.abort()
works as expected and exits withScriptAborted
ScriptException
andhandleSummary()
is even executed (after Wait for metrics handling to finish even when there is an error #2798)GenericEngine
code and needs more testssetup()
, which is probably the correct behavior, though it needs to be evaluated and tested 🤔ExternalAbort
test.abort()
works as expected and exits withScriptAborted
try
/catch
andtest.abort()
if they want an exception to abort the whole test, though there are certainly some UX improvements we can make by default (e.g. Emit an errors metric and have a default error rate script abort threshold #877)ThresholdsHaveFailed
teardown()
execution:teardown()
execution which, IIRC, was an intentional decision... and it's probably the correct decision - if the user interrupted the execution mid-test (i.e. section ⬆️), andsetup()
had already executed, we probably want to make sure we runteardown()
too 🤔 however, the exit code probably shouldn't be0
ExternalAbort
, so it's not a big deal that the first Ctrl+C waits for teardown to finish.test.abort()
works as expected and exits withScriptAborted
ScriptException
andhandleSummary()
is even executed (after Wait for metrics handling to finish even when there is an error #2798)teardown()
execution, which makes some sense for similar reasons to Ctrl+C not stopping it, however the exit code should probably also not be 0teardown()
execution (arguably the correct behavior, the exit code will beThresholdsHaveFailed
in the end, so it seems fine to mehandleSummary()
executionExternalAbort
test.abort()
aborts the test run and the error is logged, but the exit code is 0... k6 doesn't fall back to the default built-in end-of-test summary, which might be a good idea, but needs evaluation to be suretest.abort()
, a script exception aborts the function and is logged, and k6 even falls back and runs the default built-in end-of-test summary - all of that is completely fine, but the exit code should probably not be 0 🤔handleSummary()
is called--linger
option--linger
is waiting for, the exit code is (and should be)0
ExternalAbort
if there is some sort of a bug in k6's test run finishing logictest.abort()
can't be used at this point, no JS code is running--linger
... we should add a separate endpoint (e.g. as a part of Deprecate the REST API/v1 #995) if we want to be able to clear the lingering stateThis is connected to #2790 and #1889, but goes way beyond them... All of these behaviors should be standardized before test suites (#1342) can be implemented, for example.
In general, it makes sense for all external and internal test aborts to make k6 exit with a non-zero exit code. We could maybe have different exit codes, depending on the cause, so users can filter out expected ones. Maybe we can even make some of these configurable (#870, #680) but, by default, it makes sense to me that the default behavior should be a non-zero exit code when the test was prematurely stopped in any way.
The only way a k6 process should exit with a
0
exit code is if the test finished normally and no thresholds failed.The text was updated successfully, but these errors were encountered: