Compare commits

...

4 Commits

Author SHA1 Message Date
60632f62ad
tests.message: clean up a little bit 2023-08-27 18:11:16 -07:00
e2bf69eb31
tests: add basic connection test
I created a utility class to spawn a NATS server. This assumes it is
installed and in PATH, which should be easy enough to accomplish for a
CI environment. The current approach will not work with parallel
tests, but that's not a practical reality, so this route should be
fine for the time being. It might be nice to spawn a default server at
the beginning of testing and tear it down at the end, to save waiting
for the startup/shutdown time in many individual tests. This makes me
wonder: if I initialize a server in the beginning of the `test` block
in main that imports the other modules, does that scope stay live
while the "child" tests are running? My default guess would be
probably not, but that would be very convenient, so I might try it out
and see.
2023-08-27 18:10:53 -07:00
3ec8ab1b9c
build test: install unit tests binary
I've gotten tired of trying to find this in the cache dir, since the
build system does not rerun it once it is cached. Also sometimes the
build runner does weird things to the output.
2023-08-27 18:05:33 -07:00
dc97e44c9e
fixup! tests: add top level function tests 2023-08-27 18:04:21 -07:00
6 changed files with 110 additions and 10 deletions

View File

@ -19,6 +19,7 @@ pub fn build(b: *std.Build) void {
});
const tests = b.addTest(.{
.name = "nats-zig-unit-tests",
.root_source_file = .{ .path = "tests/main.zig" },
.target = target,
.optimize = optimize,
@ -29,6 +30,7 @@ pub fn build(b: *std.Build) void {
const run_main_tests = b.addRunArtifact(tests);
const test_step = b.step("test", "Run tests");
test_step.dependOn(&b.addInstallArtifact(tests, .{}).step);
test_step.dependOn(&run_main_tests.step);
add_examples(b, .{

View File

@ -114,7 +114,8 @@ pub fn sign(encoded_seed: [:0]const u8, input: [:0]const u8) Error![]const u8 {
// Note: an "Inbox" is actually just a string. This API creates a random (unique)
// string suitable for passing as the `reply` field to Message.create or
// Connection.publishRequest.
// Connection.publishRequest. The string is owned by the caller and should be freed
// using `destroyInbox`.
pub fn createInbox() Error![:0]u8 {
var self: [*c]u8 = undefined;
const status = Status.fromInt(nats_c.natsInbox_Create(@ptrCast(&self)));
@ -122,12 +123,19 @@ pub fn createInbox() Error![:0]u8 {
return status.toError() orelse std.mem.sliceTo(self, 0);
}
pub fn destroyInbox(inbox: [:0]const u8) void {
nats_c.natsInbox_Destroy(@constCast(@ptrCast(inbox.ptr)));
pub fn destroyInbox(inbox: [:0]u8) void {
nats_c.natsInbox_Destroy(@ptrCast(inbox.ptr));
}
// I think this is also a jetstream API. This function sure does not seem at all useful
// by itself.
// by itself. Note: for some reason, most of the jetstream data structures are all
// public, instead of following the opaque handle style that the rest of the library
// does.
// typedef struct natsMsgList {
// natsMsg **Msgs;
// int Count;
// } natsMsgList;
pub const MessageList = opaque {
pub fn destroy(self: *MessageList) void {
nats_c.natsMsgList_Destroy(@ptrCast(self));

18
tests/connection.zig Normal file
View File

@ -0,0 +1,18 @@
const std = @import("std");
const nats = @import("nats");
const util = @import("./util.zig");
test "nats.Connection.connectTo" {
var server = try util.TestServer.launch(.{});
defer server.stop();
{
try nats.init(nats.default_spin_count);
defer nats.deinit();
const connection = try nats.Connection.connectTo(nats.default_server_url);
defer connection.destroy();
}
}

View File

@ -1,4 +1,5 @@
test {
_ = @import("./nats.zig");
_ = @import("./connection.zig");
_ = @import("./message.zig");
}

View File

@ -1,8 +1,6 @@
const std = @import("std");
const nats = @import("nats");
// const nats = @import("../src/nats.zig");
// const message = @import("../src/message.zig");
const nats = @import("nats");
test "message: create message" {
const subject = "hello";
@ -11,7 +9,7 @@ test "message: create message" {
// have to initialize the library so the reference counter can correctly destroy
// objects, otherwise we segfault on trying to free the memory.
try nats.init(-1);
try nats.init(nats.default_spin_count);
defer nats.deinit();
const message = try nats.Message.create(subject, reply, data);
@ -28,7 +26,7 @@ test "message: create message" {
}
test "message: get subject" {
try nats.init(-1);
try nats.init(nats.default_spin_count);
defer nats.deinit();
const subject = "hello";
@ -40,7 +38,7 @@ test "message: get subject" {
}
test "message: get reply" {
try nats.init(-1);
try nats.init(nats.default_spin_count);
defer nats.deinit();
const subject = "hello";

73
tests/util.zig Normal file
View File

@ -0,0 +1,73 @@
const std = @import("std");
const TestLaunchError = error{
NoLaunchStringFound,
};
pub const TestServer = struct {
process: std.ChildProcess,
pub const LaunchOptions = struct {
executable: []const u8 = "nats-server",
port: u16 = 4222,
auth: union(enum) {
none: void,
token: []const u8,
password: struct { user: []const u8, pass: []const u8 },
} = .none,
allocator: std.mem.Allocator = std.testing.allocator,
fn argLen(self: LaunchOptions) usize {
// executable, -a, 127.0.0.1, -p, 4222
const base_len: usize = 5;
return base_len + switch (self.auth) {
.none => @as(usize, 0),
.token => @as(usize, 2),
.password => @as(usize, 4),
};
}
};
pub fn launch(options: LaunchOptions) !TestServer {
// const allocator = std.testing.allocator;
var portbuf = [_]u8{0} ** 5;
const strport = try std.fmt.bufPrint(&portbuf, "{d}", .{options.port});
const argsbuf: [9][]const u8 = blk: {
const executable: [1][]const u8 = .{options.executable};
const listen: [2][]const u8 = .{ "-a", "127.0.0.1" };
const port: [2][]const u8 = .{ "-p", strport };
const auth: [4][]const u8 = switch (options.auth) {
.none => .{""} ** 4,
.token => |tok| .{ "--auth", tok, "", "" },
.password => |auth| .{ "--user", auth.user, "--password", auth.pass },
};
break :blk executable ++ listen ++ port ++ auth;
};
const args = argsbuf[0..options.argLen()];
var child = std.ChildProcess.init(args, options.allocator);
child.stdin_behavior = .Ignore;
child.stdout_behavior = .Pipe;
child.stderr_behavior = .Pipe;
try child.spawn();
var poller = std.io.poll(options.allocator, enum { stderr }, .{ .stderr = child.stderr.? });
defer poller.deinit();
while (try poller.poll()) {
if (std.mem.indexOf(u8, poller.fifo(.stderr).buf, "[INF] Server is ready")) |_| {
return .{ .process = child };
}
}
_ = try child.kill();
return error.NoLaunchStringFound;
}
pub fn stop(self: *TestServer) void {
_ = self.process.kill() catch return;
}
};