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; } };