From 1c3e5ff5387d72764a4f277dc3cec8e99cd5abf4 Mon Sep 17 00:00:00 2001 From: torque Date: Sun, 27 Aug 2023 18:10:53 -0700 Subject: [PATCH] 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. --- tests/connection.zig | 18 +++++++++++ tests/main.zig | 1 + tests/util.zig | 73 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+) create mode 100644 tests/connection.zig create mode 100644 tests/util.zig diff --git a/tests/connection.zig b/tests/connection.zig new file mode 100644 index 0000000..6c45ac9 --- /dev/null +++ b/tests/connection.zig @@ -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(); + } +} diff --git a/tests/main.zig b/tests/main.zig index bdcd393..a219ae8 100644 --- a/tests/main.zig +++ b/tests/main.zig @@ -1,4 +1,5 @@ test { _ = @import("./nats.zig"); + _ = @import("./connection.zig"); _ = @import("./message.zig"); } diff --git a/tests/util.zig b/tests/util.zig new file mode 100644 index 0000000..bf3e92b --- /dev/null +++ b/tests/util.zig @@ -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; + } +};