nats.zig/tests/util.zig
torque 74bbe30d0a
all: update for zig-0.13
This is mainly updates to the build system, but there were a couple of
stdlib changes for the tests. The build system does include handling
more properly now as well, I think. It has fewer hacks, at least.
2024-06-18 20:44:26 -07:00

157 lines
5.7 KiB
Zig

// This file is licensed under the CC0 1.0 license.
// See: https://creativecommons.org/publicdomain/zero/1.0/legalcode
const std = @import("std");
const KeyCert = struct { key: [:0]const u8, cert: [:0]const u8 };
const server_rsa: KeyCert = .{
.key = @embedFile("./data/server-rsa.key"),
.cert = @embedFile("./data/server-rsa.cert"),
};
const server_ecc: KeyCert = .{
.key = @embedFile("./data/server-ecc.key"),
.cert = @embedFile("./data/server-ecc.cert"),
};
const TestLaunchError = error{
NoLaunchStringFound,
};
pub const TestServer = struct {
allocator: std.mem.Allocator,
process: std.process.Child,
key_dir: ?std.testing.TmpDir,
url: [:0]u8,
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,
tls: enum {
none,
rsa,
ecc,
} = .none,
allocator: std.mem.Allocator = std.testing.allocator,
pub fn connectionString(self: LaunchOptions) ![:0]u8 {
const authstr: []u8 = switch (self.auth) {
// dupe this so we don't have to handle an edge case where it
// does not need to be freed
.none => try self.allocator.dupe(u8, ""),
.token => |tok| try std.fmt.allocPrint(
self.allocator,
"{s}@",
.{tok},
),
.password => |auth| try std.fmt.allocPrint(
self.allocator,
"{s}:{s}@",
.{ auth.user, auth.pass },
),
};
defer self.allocator.free(authstr);
return try std.fmt.allocPrintZ(self.allocator, "nats://{s}127.0.0.1:{d}", .{ authstr, self.port });
}
};
pub fn launch(options: LaunchOptions) !TestServer {
var portbuf = [_]u8{0} ** 5;
const strport = try std.fmt.bufPrint(&portbuf, "{d}", .{options.port});
var key_dir: ?std.testing.TmpDir = null;
var key_path: ?[]const u8 = null;
var cert_path: ?[]const u8 = null;
errdefer if (key_dir) |*kd| kd.cleanup();
// ChildProcess copies these, so we can free them before the process has
// closed.
defer {
if (key_path) |kp| options.allocator.free(kp);
if (cert_path) |cp| options.allocator.free(cp);
}
const args: [][]const u8 = blk: {
const executable: []const []const u8 = &.{options.executable};
const listen: []const []const u8 = &.{ "-a", "127.0.0.1" };
const port: []const []const u8 = &.{ "-p", strport };
const auth: []const []const u8 = switch (options.auth) {
.none => &[_][]const u8{},
.token => |tok| &[_][]const u8{ "--auth", tok },
.password => |auth| &[_][]const u8{ "--user", auth.user, "--pass", auth.pass },
};
const tls: []const []const u8 = switch (options.tls) {
.none => &[_][]const u8{},
.rsa, .ecc => |keytype| keyb: {
const pair = switch (keytype) {
.rsa => server_rsa,
.ecc => server_ecc,
else => unreachable,
};
const out_dir = std.testing.tmpDir(.{});
key_dir = out_dir;
try out_dir.dir.writeFile(.{ .sub_path = "server.key", .data = pair.key });
try out_dir.dir.writeFile(.{ .sub_path = "server.cert", .data = pair.cert });
// since testing.tmpDir will actually bury itself in zig-cache/tmp,
// there's not an easy way to extract files from within the temp
// directory except through using realPath, as far as I can tell
// (or reproducing the path naming logic, but that seems fragile).
const out_key = try out_dir.dir.realpathAlloc(options.allocator, "server.key");
const out_cert = try out_dir.dir.realpathAlloc(options.allocator, "server.cert");
key_path = out_key;
cert_path = out_cert;
break :keyb &[_][]const u8{ "--tls", "--tlscert", out_cert, "--tlskey", out_key };
},
};
break :blk try std.mem.concat(
options.allocator,
[]const u8,
&.{ executable, listen, port, auth, tls },
);
};
defer options.allocator.free(args);
var child = std.process.Child.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 .{
.allocator = options.allocator,
.process = child,
.key_dir = key_dir,
.url = try options.connectionString(),
};
}
}
_ = try child.kill();
std.debug.print("output: {s}\n", .{poller.fifo(.stderr).buf});
return error.NoLaunchStringFound;
}
pub fn stop(self: *TestServer) void {
if (self.key_dir) |*key_dir| key_dir.cleanup();
self.allocator.free(self.url);
_ = self.process.kill() catch return;
}
};