also looking at this
Implement the web UI server with Zig I/O
#26
8 files
+513
-114
| @@ -1,10 +1,10 @@ | ||
| 1 | 1 | const std = ; |
| 2 | 2 | const builtin = ; |
| 3 | 3 | const lib = ; |
| 4 | const compat = lib.compat; | |
| 5 | 4 | |
| 6 | // Global signal handler state | |
| 7 | var shutdown_requested = std.atomic.Value(bool).init(false); | |
| 5 | // Global signal handler state. Watch mode shares this flag so Ctrl+C can | |
| 6 | // unwind through normal cleanup instead of bypassing the UI server shutdown. | |
| 7 | var keep_running = std.atomic.Value(bool).init(true); | |
| 8 | 8 | |
| 9 | 9 | /// Install signal handlers |
| 10 | 10 | fn installSignalHandlers() !void { |
| @@ -16,7 +16,7 @@ fn installSignalHandlers() !void { | ||
| 16 | 16 | const Handler = struct { |
| 17 | 17 | fn handle(sig: std.posix.SIG) callconv(.c) void { |
| 18 | 18 | _ = sig; |
| 19 | shutdown_requested.store(true, .monotonic); | |
| 19 | keep_running.store(false, .monotonic); | |
| 20 | 20 | std.debug.print("\n\nShutdown requested... cleaning up\n", .{}); |
| 21 | 21 | } |
| 22 | 22 | }; |
| @@ -107,20 +107,14 @@ pub fn main(init: std.process.Init.Minimal) !void { | ||
| 107 | 107 | |
| 108 | 108 | // Start UI server if requested |
| 109 | 109 | var ui_server: ?lib.UIServer = null; |
| 110 | var ui_thread: ?std.Thread = null; | |
| 111 | var server_running = std.atomic.Value(bool).init(false); | |
| 112 | 110 | |
| 113 | 111 | defer { |
| 114 | if (shutdown_requested.load(.monotonic)) { | |
| 112 | if (!keep_running.load(.monotonic)) { | |
| 115 | 113 | std.debug.print("Cleanup complete.\n", .{}); |
| 116 | 114 | } |
| 117 | 115 | if (ui_server) |*server| { |
| 118 | server_running.store(false, .monotonic); | |
| 119 | 116 | server.deinit(); |
| 120 | 117 | } |
| 121 | if (ui_thread) |thread| { | |
| 122 | thread.detach(); | |
| 123 | } | |
| 124 | 118 | } |
| 125 | 119 | |
| 126 | 120 | if (cli_parser.options.ui) { |
| @@ -133,38 +127,8 @@ pub fn main(init: std.process.Init.Minimal) !void { | ||
| 133 | 127 | ui_server = lib.UIServer.init(allocator, ui_options); |
| 134 | 128 | try ui_server.?.start(); |
| 135 | 129 | |
| 136 | std.debug.print("UI Server started at http://{s}:{d}\n", .{ ui_options.host, ui_options.port }); | |
| 130 | std.debug.print("UI Server started at http://{s}:{d}\n", .{ ui_options.host, ui_server.?.port() }); | |
| 137 | 131 | std.debug.print("Open this URL in your browser to view test results.\n\n", .{}); |
| 138 | ||
| 139 | // Start server in background thread | |
| 140 | const ServerContext = struct { | |
| 141 | server: *lib.UIServer, | |
| 142 | running: *std.atomic.Value(bool), | |
| 143 | verbose: bool, | |
| 144 | ||
| 145 | fn run(ctx: @This()) void { | |
| 146 | ctx.running.store(true, .monotonic); | |
| 147 | while (ctx.running.load(.monotonic)) { | |
| 148 | ctx.server.acceptClient() catch |err| { | |
| 149 | if (ctx.verbose) { | |
| 150 | std.debug.print("UI Server error: {any}\n", .{err}); | |
| 151 | } | |
| 152 | // Small delay to avoid tight loop on errors | |
| 153 | compat.sleep(100 * std.time.ns_per_ms); | |
| 154 | }; | |
| 155 | } | |
| 156 | } | |
| 157 | }; | |
| 158 | ||
| 159 | const server_ctx = ServerContext{ | |
| 160 | .server = &ui_server.?, | |
| 161 | .running = &server_running, | |
| 162 | .verbose = cli_parser.options.verbose, | |
| 163 | }; | |
| 164 | ui_thread = try std.Thread.spawn(.{}, ServerContext.run, .{server_ctx}); | |
| 165 | ||
| 166 | // Give server time to start | |
| 167 | compat.sleep(100 * std.time.ns_per_ms); | |
| 168 | 132 | } |
| 169 | 133 | |
| 170 | 134 | var all_passed: bool = undefined; |
| @@ -197,8 +161,7 @@ pub fn main(init: std.process.Init.Minimal) !void { | ||
| 197 | 161 | .verbose = cli_parser.options.verbose, |
| 198 | 162 | }; |
| 199 | 163 | |
| 200 | var watch_running = std.atomic.Value(bool).init(true); | |
| 201 | var watcher = lib.TestWatcher.init(allocator, watch_options, &watch_running); | |
| 164 | var watcher = lib.TestWatcher.init(allocator, watch_options, &keep_running); | |
| 202 | 165 | |
| 203 | 166 | // Create coverage options if coverage is enabled |
| 204 | 167 | const cov_opts = if (cli_parser.options.coverage) lib.CoverageOptions{ |
| @@ -303,9 +266,11 @@ pub fn main(init: std.process.Init.Minimal) !void { | ||
| 303 | 266 | } |
| 304 | 267 | |
| 305 | 268 | // Exit with appropriate code |
| 306 | if (all_passed) { | |
| 307 | std.process.exit(0); | |
| 308 | } else { | |
| 309 | std.process.exit(1); | |
| 310 | } | |
| 269 | if (all_passed) return; | |
| 270 | ||
| 271 | // `std.process.exit` does not run defers, so explicitly stop the server | |
| 272 | // before returning the unsuccessful process status. | |
| 273 | if (ui_server) |*server| server.deinit(); | |
| 274 | ui_server = null; | |
| 275 | std.process.exit(1); | |
| 311 | 276 | } |