A Zig test framework for xtp / Extism plugins.
const std = @import("std");
const Test = @import("xtp-test").Test;
const CountVowel = struct {
total: u32,
count: u32,
vowels: []const u8,
};
export fn @"test"() i32 {
const xtp_test = Test.init(std.heap.wasm_allocator);
const output = xtp_test.call("count_vowels", "this is a test") catch unreachable;
const cv = fromJson(output);
xtp_test.assertEq("count_vowels returns expected count", cv.count, 4);
// create a group of tests inside a new scope, use defer to close the group at the end of the scope
{
const maintain_state_group = xtp_test.newGroup("plugin should maintain state");
defer maintain_state_group.close();
var accumTotal: u32 = 0;
for (0..10) |_| {
const loop_output = xtp_test.call("count_vowels", "this is a test") catch unreachable;
const loop_cv = fromJson(loop_output);
accumTotal += cv.count;
const msg = std.fmt.allocPrint(std.heap.wasm_allocator, "count_vowels returns expected incremented total: {}", .{accumTotal}) catch unreachable;
xtp_test.assertEq(msg, loop_cv.total, accumTotal);
}
}
// create a group without a scope, and close it manually at the end of your tests
const simple_group = xtp_test.newGroup("simple timing tests");
const sec = xtp_test.timeSec("count_vowels", "this is a test");
xtp_test.assertLt("it should be fast", sec, 0.5);
const ns = xtp_test.timeNs("count_vowels", "this is a test");
xtp_test.assertLt("it should be really fast", ns, 1e5);
simple_group.close();
return 0;
}
fn fromJson(json: []const u8) CountVowel {
const cv = std.json.parseFromSlice(CountVowel, std.heap.wasm_allocator, json, .{}) catch unreachable;
return cv.value;
}
See the main.zig
file for the public API of this library.
1. Create a Zig project using the XTP Test library
mkdir zig-xtp-test
cd zig-xtp-test
zig init
zig fetch --save https://github.com/dylibso/xtp-test-zig/archive/v0.1.0.tar.gz
# see the `build.zig` in this repo for examples on how to configure it
2. Write your test in Zig
const std = @import("std");
const Test = @import("xtp-test").Test;
const CountVowel = struct {
total: u32,
count: u32,
vowels: []const u8,
};
// you _must_ export a single `test` function (in Zig, "test" is a keyword, so use this raw literal syntax)
export fn @"test"() i32 {
// initialize your test to run functions in a target plugin
const xtp_test = Test.init(std.heap.wasm_allocator);
xtp_test.assert("this is a test", true, "Expect true == true");
// run the "count_vowels" function in the target plugin and assert the output is as expected
const output = xtp_test.call("count_vowels", "this is a test") catch unreachable;
const cv = fromJson(output);
xtp_test.assertEq("count_vowels returns expected count", cv.count, 4);
...
3. Compile your test to .wasm:
Ensure your build.zig
is set up properly to compile to wasm32 freestanding
or wasi
. See the
Extism zig-pdk
examples or the
build.zig
in this repository for more details.
zig build
# which should output a .wasm into zig-out/bin/
4. Run the test against your plugin: Once you have your test code as a
.wasm
module, you can run the test against your plugin using the xtp
CLI:
curl https://static.dylibso.com/cli/install.sh | sudo sh
xtp plugin test ./plugin-*.wasm --with test.wasm --mock-host host.wasm
# ^^^^^^^^^^^^^^^ ^^^^^^^^^ ^^^^^^^^^
# your plugin(s) test to run optional mock host functions
Note: The optional mock host functions must be implemented as Extism plugins, whose exported functions match the host function signature imported by the plugins being tested.
Please reach out via the
#xtp
channel on Discord.