diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e12638e..8b1064d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,5 +22,6 @@ jobs: - name: Test example run: | # this is configured by the `xtp.toml` in the root - xtp plugin test + xtp plugin test --path examples/basic + xtp plugin test --path examples/json diff --git a/build.zig b/build.zig index 34b1f37..93f36b3 100644 --- a/build.zig +++ b/build.zig @@ -23,15 +23,27 @@ pub fn build(b: *std.Build) void { var basic_test = b.addExecutable(.{ .name = "basic-test", - .root_source_file = b.path("examples/basic.zig"), + .root_source_file = b.path("examples/basic/basic.zig"), .target = target, .optimize = optimize, }); basic_test.rdynamic = true; basic_test.entry = .disabled; // or, add an empty `pub fn main() void {}` in your code basic_test.root_module.addImport("xtp-test", xtp_test_module); - b.installArtifact(basic_test); const basic_test_step = b.step("basic_test", "Build basic_test"); basic_test_step.dependOn(b.getInstallStep()); + + var json_test = b.addExecutable(.{ + .name = "json-test", + .root_source_file = b.path("examples/json/json.zig"), + .target = target, + .optimize = optimize, + }); + json_test.rdynamic = true; + json_test.entry = .disabled; // or, add an empty `pub fn main() void {}` in your code + json_test.root_module.addImport("xtp-test", xtp_test_module); + b.installArtifact(json_test); + const json_test_step = b.step("json_test", "Build json_test"); + json_test_step.dependOn(b.getInstallStep()); } diff --git a/build.zig.zon b/build.zig.zon index 3f1973a..d71bec8 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -2,7 +2,7 @@ .name = "xtp-test", // This is a [Semantic Version](https://semver.org/). // In a future version of Zig it will be used for package deduplication. - .version = "0.0.0", + .version = "0.1.0", // This field is optional. // This is currently advisory only; Zig does not yet do anything diff --git a/examples/basic.zig b/examples/basic/basic.zig similarity index 100% rename from examples/basic.zig rename to examples/basic/basic.zig diff --git a/xtp.toml b/examples/basic/xtp.toml similarity index 86% rename from xtp.toml rename to examples/basic/xtp.toml index df0c10b..b3362a6 100644 --- a/xtp.toml +++ b/examples/basic/xtp.toml @@ -10,4 +10,4 @@ mock_input = { data = "this is my mock input data" } name = "basic - file input" build = "zig build basic_test" with = "zig-out/bin/basic-test.wasm" -mock_input = { file = "examples/basic.zig" } +mock_input = { file = "examples/basic/basic.zig" } diff --git a/examples/json/json.zig b/examples/json/json.zig new file mode 100644 index 0000000..71ca1b0 --- /dev/null +++ b/examples/json/json.zig @@ -0,0 +1,18 @@ +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 input = xtp_test.mockInputJson(CountVowel, null) catch unreachable; + const example = CountVowel{ .total = 2, .count = 1, .vowels = "aeiouAEIOU" }; + xtp_test.assertEq("json works (consistent .count)", input.count, example.count); + xtp_test.assertEq("json works (consistent .total)", input.total, example.total); + xtp_test.assert("json works (consistent .vowels)", std.mem.eql(u8, input.vowels, example.vowels), "expected count and vowels fields to match after conversion"); + return 0; +} diff --git a/examples/json/xtp.toml b/examples/json/xtp.toml new file mode 100644 index 0000000..8918636 --- /dev/null +++ b/examples/json/xtp.toml @@ -0,0 +1,7 @@ +bin = "https://raw.githubusercontent.com/extism/extism/main/wasm/code.wasm" + +[[test]] +name = "json" +build = "zig build json_test" +with = "zig-out/bin/json-test.wasm" +mock_input = { data = '{ "count": 1, "total": 2, "vowels": "aeiouAEIOU" }' } diff --git a/src/main.zig b/src/main.zig index 513e2a4..feaca50 100644 --- a/src/main.zig +++ b/src/main.zig @@ -6,6 +6,21 @@ const harness = @import("harness.zig"); const FORMAT_FAILED = "-- test runner failed to format reason --"; +pub fn Json(comptime T: type) type { + return struct { + parsed: std.json.Parsed(T), + slice: []const u8, + + pub fn value(self: @This()) T { + return self.parsed.value; + } + + pub fn deinit(self: @This()) void { + self.parsed.deinit(); + } + }; +} + pub const Test = struct { plugin: Plugin, @@ -29,6 +44,15 @@ pub const Test = struct { } } + pub fn mockInputJson(self: Test, comptime T: type, options: ?std.json.ParseOptions) !T { + const default_opts = .{ .allocate = .alloc_always, .ignore_unknown_fields = true }; + const bytes = self.mockInput() orelse return error.NoMockInput; + const out = try std.json.parseFromSlice(T, self.plugin.allocator, bytes, options orelse default_opts); + const FromJson = Json(T); + const input = FromJson{ .parsed = out, .slice = bytes }; + return input.parsed.value; + } + // Call a function from the Extism plugin being tested, passing input and returning its output. pub fn call(self: Test, func_name: []const u8, input: []const u8) ![]const u8 { const func_mem = self.plugin.allocateBytes(func_name);