Important
This client is not officially supported by the Hegel core team.
Warning
This is far from anything that could be considered "production ready". Expect bugs, incomplete features, and most likely some breaking changes in the near future. Zig's testing story is great, but has some aspects that are tricky to work with in the context of Hegel. (Not being able to cleanly handle a panic, for instance!) See the section on "Limitations" for more info!
hegel-zig is a property-based testing library for Zig, based on Hypothesis, using the Hegel protocol.
Add to your build.zig.zon:
.dependencies = .{
.hegel = .{
.url = "git+https://github.com/nickmonad/hegel-zig#<commit>",
.hash = "...",
},
},Alternatively, you can run:
zig fetch --save git+https://github.com/nickmonad/hegel-zig.git
zig fetchwill resolve the latest commit (and calculate the hash) of this repository, which is likely what you want until we start versioning.
Then in build.zig:
const hegel = b.dependency("hegel", .{});
my_module.addImport("hegel", hegel.module("hegel"));
// alternatively, within a `createModule()` call...
// ...
.imports = &.{
.{ .name = "hegel", .module = hegel.module("hegel") },
},
// ...At runtime, hegel-zig requires a running hegel-core server component.
Currently, hegel-zig will first check if HEGEL_SERVER_COMMAND is set in the running environment.
If so, it will use that to invoke the server. If it is not set, it will invoke uv tool run to download and run the server.
Today, hegel-zig expects that at the very least, uv is installed on the host system.
Other Hegel client libraries will attempt to download uv and install it, but we have not implemented that here yet.
For more information on the uv dependency, please see the Installation reference on the Hegel website.
hegel-zig writes out some *.log files at runtime. It's recommended to add the following to your project's .gitignore.
.hegel/
hegel*.log
Here's a quick example of how to write a Hegel test! The mySort function deduplicates elements in
the sorted slice, which the std sort function does not do. Hegel will detect this difference and report a minimally failing test case.
const std = @import("std");
const hegel = @import("std");
const Test = hegel.Test;
const TestCase = hegel.TestCase;
const Int = hegel.Int;
const List = hegel.List;
fn mySort(arena: std.mem.Allocator, list: []const u64) []const u64 {
if (list.len <= 1) {
return list;
}
// Copy...
var copied: []u64 = arena.dupe(u64, list) catch unreachable;
// Sort...
std.mem.sort(u64, copied, {}, comptime std.sort.asc(u64));
// Deduplicate in-place...
var insert: usize = 0;
for (1..copied.len) |i| {
if (copied[i] != copied[insert]) {
insert += 1;
copied[insert] = copied[i];
}
}
return copied[0..(insert + 1)];
}
test "hegel:sort" {
try Test(.{ .name = "sort", .test_cases = 100 }, struct {
fn run(tc: *TestCase) anyerror!void {
const l1: []const u64 = try tc.draw(List(Int(u64, .{}), .{ .max_size = 1000 }));
const l2: []const u64 = mySort(tc.arena, l1);
std.mem.sort(u64, @constCast(l1), {}, comptime std.sort.asc(u64));
try tc.expectEqualSlices(@src(), u64, l1, l2);
}
}.run);
}This test will fail when run with zig test!
The minimally failing test case will be written to hegel.test.log in the working directory.
This report shows us that a slice of { 0, 0 } is a minimal value we can use to reproduce the failure.
cat hegel.test.log
--- starting test run (sort) with 100 cases ---
seed = "258037487401619116070047623238935554120"
passed = false, tests = 25, invalid = 0, interesting = 1
failing test case!
Draw: { 0, 0 }
main.zig:43:TestExpectedEqual
--- end test run ---
- Documentation. More to come!
- Zig does not support parallel test execution in the standard test runner. This might change in the future, but currently, tests execute sequentially. (Tracking issue.)
- Test cases that result in a panic or
assert()failure will not be reported or gracefully handled. It might be possible to solve this by implementing a custom test runner, which could run each test in a separate process. - Users MUST pass
@src()as the first argument to allTestCase.expect*functions. This is so the source location can be used when reporting interesting test cases to the Hegel server. - All tests to run in Hegel MUST begin with
"hegel:", in the name after thetestkeyword. This is a crude, but simple way to ensure we can properly cleanup the shared client and server resources after all tests have executed.