a73x

98db6e5d

main: let runScenarios defers run before process exit

a73x   2026-04-19 13:36

Commit message
main: let runScenarios defers run before process exit

Switches runScenarios return type from !void to !u8 and
replaces every std.process.exit call with `return N`. main()
exits with the returned code after control returns.

Fixes a leak of Failure.label strings (via rc.deinit) on the
success path and every mismatch path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

src/main.zig
Old New
@@ -180,7 +180,8 @@ pub fn main() !void {
180 } 180 }
181 181
182 if (args.len >= 2 and std.mem.eql(u8, args[1], "--scenario")) { 182 if (args.len >= 2 and std.mem.eql(u8, args[1], "--scenario")) {
183 return runScenarios(alloc, args[1..]); 183 const code = try runScenarios(alloc, args[1..]);
184 std.process.exit(code);
184 } 185 }
185 186
186 return runTerminal(alloc, null); 187 return runTerminal(alloc, null);
@@ -205,23 +206,23 @@ fn basenameNoExt(path: []const u8) []const u8 {
205 /// 4 — one or more captures mismatched (unless WAYSTTY_SCENARIO_UPDATE=1) 206 /// 4 — one or more captures mismatched (unless WAYSTTY_SCENARIO_UPDATE=1)
206 /// 5 — Vulkan-side timeout (mapped from CallbackFailed conservatively) 207 /// 5 — Vulkan-side timeout (mapped from CallbackFailed conservatively)
207 /// 6 — other runtime error 208 /// 6 — other runtime error
208 pub fn runScenarios(alloc: std.mem.Allocator, argv: []const [:0]const u8) !void { 209 pub fn runScenarios(alloc: std.mem.Allocator, argv: []const [:0]const u8) !u8 {
209 if (argv.len < 2) { 210 if (argv.len < 2) {
210 std.debug.print("usage: waystty --scenario <path>\n", .{}); 211 std.debug.print("usage: waystty --scenario <path>\n", .{});
211 std.process.exit(@intFromEnum(scenario_runtime.ExitCode.parse_error)); 212 return @intFromEnum(scenario_runtime.ExitCode.parse_error);
212 } 213 }
213 const scenario_path = argv[1]; 214 const scenario_path = argv[1];
214 215
215 const source = std.fs.cwd().readFileAlloc(alloc, scenario_path, 1 * 1024 * 1024) catch |err| { 216 const source = std.fs.cwd().readFileAlloc(alloc, scenario_path, 1 * 1024 * 1024) catch |err| {
216 std.debug.print("scenario: cannot read {s}: {s}\n", .{ scenario_path, @errorName(err) }); 217 std.debug.print("scenario: cannot read {s}: {s}\n", .{ scenario_path, @errorName(err) });
217 std.process.exit(@intFromEnum(scenario_runtime.ExitCode.parse_error)); 218 return @intFromEnum(scenario_runtime.ExitCode.parse_error);
218 }; 219 };
219 defer alloc.free(source); 220 defer alloc.free(source);
220 221
221 var diag: scenario.Diagnostic = .{}; 222 var diag: scenario.Diagnostic = .{};
222 var parsed = scenario.parse(alloc, source, &diag) catch { 223 var parsed = scenario.parse(alloc, source, &diag) catch {
223 std.debug.print("scenario: {s}:{d}: {s}\n", .{ scenario_path, diag.line, diag.message }); 224 std.debug.print("scenario: {s}:{d}: {s}\n", .{ scenario_path, diag.line, diag.message });
224 std.process.exit(@intFromEnum(scenario_runtime.ExitCode.parse_error)); 225 return @intFromEnum(scenario_runtime.ExitCode.parse_error);
225 }; 226 };
226 defer parsed.deinit(); 227 defer parsed.deinit();
227 228
@@ -238,13 +239,13 @@ pub fn runScenarios(alloc: std.mem.Allocator, argv: []const [:0]const u8) !void
238 239
239 runTerminal(alloc, &rc) catch |err| { 240 runTerminal(alloc, &rc) catch |err| {
240 std.debug.print("scenario {s}: runtime error: {s}\n", .{ scenario_name, @errorName(err) }); 241 std.debug.print("scenario {s}: runtime error: {s}\n", .{ scenario_name, @errorName(err) });
241 std.process.exit(@intFromEnum(scenario_runtime.ExitCode.other_error)); 242 return @intFromEnum(scenario_runtime.ExitCode.other_error);
242 }; 243 };
243 244
244 if (rc.tick_fatal) |err| { 245 if (rc.tick_fatal) |err| {
245 const code = scenario_runtime.mapTickError(err); 246 const code = scenario_runtime.mapTickError(err);
246 std.debug.print("scenario {s}: {s}\n", .{ scenario_name, @errorName(err) }); 247 std.debug.print("scenario {s}: {s}\n", .{ scenario_name, @errorName(err) });
247 std.process.exit(@intFromEnum(code)); 248 return @intFromEnum(code);
248 } 249 }
249 250
250 if (rc.failures.items.len > 0) { 251 if (rc.failures.items.len > 0) {
@@ -263,11 +264,11 @@ pub fn runScenarios(alloc: std.mem.Allocator, argv: []const [:0]const u8) !void
263 .{m.label}, 264 .{m.label},
264 ), 265 ),
265 }; 266 };
266 if (!update) std.process.exit(@intFromEnum(scenario_runtime.ExitCode.assertion_mismatch)); 267 if (!update) return @intFromEnum(scenario_runtime.ExitCode.assertion_mismatch);
267 } 268 }
268 269
269 std.debug.print("scenario {s}: OK\n", .{scenario_name}); 270 std.debug.print("scenario {s}: OK\n", .{scenario_name});
270 std.process.exit(0); 271 return 0;
271 } 272 }
272 273
273 fn runTerminal(alloc: std.mem.Allocator, tick_ctx: ?*scenario_runtime.RunContext) !void { 274 fn runTerminal(alloc: std.mem.Allocator, tick_ctx: ?*scenario_runtime.RunContext) !void {