In order to use this from k6 test scripts we need to register the module by adding the following:
Note that all k6 extensions should have the k6/x/ prefix and the short name must be unique among all extensions built in the same k6 binary.
The final extension code will look like so:
We can then build a k6 binary with this extension by running xk6 build --with xk6-compare=.. In this case xk6-compare is the Go module name passed to go mod init, but in a real-world scenario this would be a URL.
Finally we can use the extension in a test script:
And run the test with ./k6 run test.js, which should output INFO true.
The k6 Go-JS bridge has a few features we should highlight:
Go method names will be converted from Pascal case to Camel case when accessed in JS, as in the example above: IsGreater becomes isGreater.
Similarly, Go field names will be converted from Pascal case to Snake case. For example, the struct field SomeField string will be accessible in JS as the some_field object property. This behavior is configurable with the js struct tag, so this can be changed with SomeField string `js:"someField"` or the field can be hidden with `js:"-"`.
Methods with a name prefixed with X will be transformed to JS constructors, and will support the new operator. For example, defining the following method on the above struct:
Would allow creating a Comparator instance in JS with new compare.Comparator(), which is a bit more idiomatic to JS.
If your extension requires access to internal k6 objects to, for example, inspect the state of the test during execution, we will need to make some slightly more complicated changes to the above example.
- lib.State: the VU state with values like the VU ID and iteration number.
- a global context.Context which contains other interesting objects like lib.ExecutionState.
Additionally there should be a root module implementation of the modules.Module interface that will serve as a factory of Compare instances for each VU. Note that this can have memory implications depending on the size of your module.
Here's how that would look like:
Currently this module isn't taking advantage of the methods provided by modules.VU because our simple example extension doesn't require it, but here is a contrived example of how that could be done:
Running a script like:
For a more extensive usage example of this API, take a look at the k6/execution module.
This also demonstrates the native constructors feature from goja, where methods with this signature will be transformed to JS constructors, and also have the benefit of receiving the goja.Runtime, which is an alternative way to access it in addition to the GetRuntime() method shown above.
- The code in the default function (or another function specified by exec) will be executed many times during a test run and possibly in parallel by thousands of VUs. As such any operation of your extension meant to run in that context needs to be performant and thread-safe.
- Any heavy initialization should be done in the init context if possible, and not as part of the default function execution.
- Custom metric emission can be done by creating new metrics using stats.New() and emitting them using stats.PushIfNotDone(). For an example of this see the xk6-remote-write extension.
The core of an Output extension is a struct that implements the output.Output interface. For example:
Notice a couple of things:
- The module initializer New() receives an instance of output.Params. With this object the extension can access the output-specific configuration, interfaces to the filesystem, synchronized stdout and stderr, and more.
- AddMetricSamples in this example simply writes to stdout. In a real-world scenario this output might have to be buffered and flushed periodically to avoid memory leaks. Below we'll discuss some helpers you can use for that.
- Output structs can optionally implement additional interfaces that allows them to receive thresholds, test run status updates or interrupt a test run.
- Because output implementations typically need to process large amounts of data that k6 produces and dispatch it to another system, we've provided a couple of helper structs you can use in your extensions: output.SampleBuffer is a thread-safe buffer for metric samples to help with memory management and output.PeriodicFlusher will periodically run a function which is useful for flushing or dispatching the buffered samples. For usage examples see the statsd output.