zeno-mobile-runner 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/FEATURES.md +1 -1
- package/README.md +1 -1
- package/build.zig.zon +2 -2
- package/clients/kotlin/README.md +1 -1
- package/clients/kotlin/build.gradle.kts +1 -1
- package/clients/python/pyproject.toml +1 -1
- package/clients/rust/Cargo.lock +1 -1
- package/clients/rust/Cargo.toml +1 -1
- package/clients/typescript/package.json +1 -1
- package/docs/protocol-fixtures/core-session.responses.jsonl +1 -1
- package/docs/protocol.md +10 -10
- package/package.json +3 -1
- package/prebuilds/darwin-arm64/zmr +0 -0
- package/prebuilds/darwin-x64/zmr +0 -0
- package/prebuilds/linux-arm64/zmr +0 -0
- package/prebuilds/linux-x64/zmr +0 -0
- package/scripts/create-react-native-expo-demo-app.sh +11 -13
- package/shims/ios/ZMRShim.swift +40 -12
- package/shims/ios/ZMRShimUITestCase.swift +135 -15
- package/src/android.zig +10 -9
- package/src/android_emulator.zig +22 -11
- package/src/android_screen_recording.zig +11 -7
- package/src/bundle.zig +10 -9
- package/src/bundle_redaction.zig +29 -28
- package/src/bundle_tar.zig +15 -12
- package/src/cli_devices.zig +7 -3
- package/src/cli_discover.zig +7 -3
- package/src/cli_doctor.zig +7 -3
- package/src/cli_draft.zig +51 -47
- package/src/cli_explore.zig +7 -3
- package/src/cli_import.zig +8 -4
- package/src/cli_info.zig +13 -6
- package/src/cli_init.zig +9 -5
- package/src/cli_inspect.zig +8 -4
- package/src/cli_run.zig +22 -16
- package/src/cli_serve.zig +3 -3
- package/src/cli_trace.zig +25 -12
- package/src/cli_validate.zig +8 -4
- package/src/command.zig +81 -99
- package/src/config.zig +2 -1
- package/src/config_diagnostics.zig +2 -1
- package/src/config_paths.zig +2 -1
- package/src/doctor.zig +8 -7
- package/src/doctor_hints.zig +1 -1
- package/src/errors.zig +5 -5
- package/src/importer.zig +8 -7
- package/src/ios.zig +19 -18
- package/src/ios_devices.zig +6 -5
- package/src/ios_lifecycle.zig +4 -4
- package/src/json_rpc.zig +39 -40
- package/src/json_rpc_methods.zig +8 -8
- package/src/json_rpc_observation.zig +9 -8
- package/src/json_rpc_params.zig +1 -1
- package/src/json_rpc_trace.zig +22 -21
- package/src/main.zig +19 -10
- package/src/mcp.zig +28 -19
- package/src/mcp_trace.zig +30 -29
- package/src/report.zig +39 -36
- package/src/report_html.zig +5 -4
- package/src/runner.zig +2 -1
- package/src/runner_actions.zig +20 -17
- package/src/runner_diagnostics.zig +4 -4
- package/src/runner_events.zig +55 -51
- package/src/runner_native.zig +21 -19
- package/src/runner_waits.zig +46 -41
- package/src/scaffold.zig +25 -24
- package/src/scenario.zig +4 -3
- package/src/stdio.zig +129 -0
- package/src/trace.zig +34 -26
- package/src/trace_summary.zig +3 -2
- package/src/trace_summary_diagnostic.zig +15 -13
- package/src/validation.zig +5 -4
- package/src/version.zig +1 -1
package/src/ios.zig
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const std = @import("std");
|
|
2
|
+
const stdio = @import("stdio.zig");
|
|
2
3
|
const command = @import("command.zig");
|
|
3
4
|
const ios_devices = @import("ios_devices.zig");
|
|
4
5
|
const ios_lifecycle = @import("ios_lifecycle.zig");
|
|
@@ -190,11 +191,11 @@ pub const IosDevice = struct {
|
|
|
190
191
|
.duration_ms = @as(u32, @intCast(@min(timeout_ms, std.math.maxInt(u32)))),
|
|
191
192
|
});
|
|
192
193
|
}
|
|
193
|
-
|
|
194
|
+
stdio.sleepNs(timeout_ms * std.time.ns_per_ms);
|
|
194
195
|
}
|
|
195
196
|
|
|
196
197
|
pub fn snapshot(self: *IosDevice, writer: ?*trace.TraceWriter) !types.ObservationSnapshot {
|
|
197
|
-
const id = if (writer) |tw| try tw.nextSnapshotId() else try std.fmt.allocPrint(self.allocator, "snapshot-{d}", .{
|
|
198
|
+
const id = if (writer) |tw| try tw.nextSnapshotId() else try std.fmt.allocPrint(self.allocator, "snapshot-{d}", .{stdio.nowMs()});
|
|
198
199
|
errdefer self.allocator.free(id);
|
|
199
200
|
|
|
200
201
|
var screenshot_artifact: ?[]const u8 = null;
|
|
@@ -239,7 +240,7 @@ pub const IosDevice = struct {
|
|
|
239
240
|
|
|
240
241
|
return .{
|
|
241
242
|
.id = id,
|
|
242
|
-
.timestamp_ms =
|
|
243
|
+
.timestamp_ms = stdio.nowMs(),
|
|
243
244
|
.viewport = viewport,
|
|
244
245
|
.active_package = active_package,
|
|
245
246
|
.active_activity = null,
|
|
@@ -257,21 +258,21 @@ pub const IosDevice = struct {
|
|
|
257
258
|
defer self.allocator.free(response);
|
|
258
259
|
return try ios_shim.parseScreenshotPng(self.allocator, response);
|
|
259
260
|
}
|
|
260
|
-
const path = try std.fmt.allocPrint(self.allocator, "/tmp/zmr-ios-screenshot-{d}.png", .{
|
|
261
|
+
const path = try std.fmt.allocPrint(self.allocator, "/tmp/zmr-ios-screenshot-{d}.png", .{stdio.nowNs()});
|
|
261
262
|
defer self.allocator.free(path);
|
|
262
|
-
defer std.
|
|
263
|
+
defer std.Io.Dir.cwd().deleteFile(stdio.io(), path) catch {};
|
|
263
264
|
|
|
264
265
|
const result = try self.runSimctl(&.{ "io", self.target(), "screenshot", path }, default_max_output);
|
|
265
266
|
defer result.deinit(self.allocator);
|
|
266
267
|
try result.ensureSuccess();
|
|
267
|
-
return try
|
|
268
|
+
return try stdio.readFileAlloc(self.allocator, path, default_max_output);
|
|
268
269
|
}
|
|
269
270
|
|
|
270
271
|
fn logDelta(self: *IosDevice) !?[]const u8 {
|
|
271
272
|
if (self.target_kind == .physical) return null;
|
|
272
273
|
const result = try self.runSimctl(&.{ "spawn", self.target(), "log", "show", "--style", "compact", "--last", "30s" }, 1024 * 1024);
|
|
273
274
|
defer result.deinit(self.allocator);
|
|
274
|
-
if (result.term != .
|
|
275
|
+
if (result.term != .exited or result.term.exited != 0) return null;
|
|
275
276
|
return try self.allocator.dupe(u8, result.stdout);
|
|
276
277
|
}
|
|
277
278
|
|
|
@@ -282,15 +283,15 @@ pub const IosDevice = struct {
|
|
|
282
283
|
}
|
|
283
284
|
|
|
284
285
|
fn recordSnapshotSemanticFailure(self: *IosDevice, writer: *trace.TraceWriter, screenshot_artifact: []const u8, err: anyerror) !void {
|
|
285
|
-
var payload =
|
|
286
|
-
defer payload.deinit(
|
|
287
|
-
const out = payload.writer
|
|
286
|
+
var payload: std.Io.Writer.Allocating = .init(writer.allocator);
|
|
287
|
+
defer payload.deinit();
|
|
288
|
+
const out = &payload.writer;
|
|
288
289
|
try out.writeAll("{\"status\":\"failed\",\"artifactStatus\":\"captured\",\"semanticStatus\":\"failed\",\"error\":");
|
|
289
290
|
try trace.writeJsonString(out, @errorName(err));
|
|
290
291
|
try out.writeAll(",\"screenshotArtifact\":");
|
|
291
292
|
try trace.writeJsonString(out, screenshot_artifact);
|
|
292
293
|
try out.writeAll(",\"source\":\"ios-xctest-shim\"}");
|
|
293
|
-
try writer.recordEvent("observe.snapshot.semanticExtraction",
|
|
294
|
+
try writer.recordEvent("observe.snapshot.semanticExtraction", out.buffered());
|
|
294
295
|
_ = self;
|
|
295
296
|
}
|
|
296
297
|
|
|
@@ -340,19 +341,19 @@ pub const IosDevice = struct {
|
|
|
340
341
|
fn runShimWithTimeout(self: *IosDevice, shim_command: ios_shim.Command, timeout_ms: u64) ![]u8 {
|
|
341
342
|
const path = self.shim_path orelse return error.IosXCTestShimRequired;
|
|
342
343
|
|
|
343
|
-
var input =
|
|
344
|
-
defer input.deinit(
|
|
345
|
-
try ios_shim.writeCommandJson(input.writer
|
|
344
|
+
var input: std.Io.Writer.Allocating = .init(self.allocator);
|
|
345
|
+
defer input.deinit();
|
|
346
|
+
try ios_shim.writeCommandJson(&input.writer, shim_command);
|
|
346
347
|
|
|
347
348
|
var attempt: usize = 0;
|
|
348
349
|
while (attempt < shim_command_attempts) {
|
|
349
350
|
attempt += 1;
|
|
350
|
-
const result = try command.runWithInputTimeout(self.allocator, &.{path}, input.
|
|
351
|
+
const result = try command.runWithInputTimeout(self.allocator, &.{path}, input.writer.buffered(), 4 * 1024 * 1024, timeout_ms);
|
|
351
352
|
defer result.deinit(self.allocator);
|
|
352
353
|
|
|
353
354
|
result.ensureSuccess() catch |err| {
|
|
354
355
|
if (attempt < shim_command_attempts and err == error.CommandFailed and isTransientShimBootstrapFailure(result)) {
|
|
355
|
-
|
|
356
|
+
stdio.sleepNs(shim_bootstrap_retry_delay_ms * std.time.ns_per_ms);
|
|
356
357
|
continue;
|
|
357
358
|
}
|
|
358
359
|
return err;
|
|
@@ -391,7 +392,7 @@ pub const IosDevice = struct {
|
|
|
391
392
|
fn isTransientShimBootstrapFailure(result: command.ExecResult) bool {
|
|
392
393
|
if (result.timed_out) return false;
|
|
393
394
|
switch (result.term) {
|
|
394
|
-
.
|
|
395
|
+
.exited => |code| if (code == 0) return false,
|
|
395
396
|
else => return false,
|
|
396
397
|
}
|
|
397
398
|
return std.mem.indexOf(u8, result.stderr, "iOS shim server exited before it became ready") != null or
|
|
@@ -400,7 +401,7 @@ fn isTransientShimBootstrapFailure(result: command.ExecResult) bool {
|
|
|
400
401
|
}
|
|
401
402
|
|
|
402
403
|
fn shimTimeoutMs() u64 {
|
|
403
|
-
return parseShimTimeoutMs(
|
|
404
|
+
return parseShimTimeoutMs(stdio.getenv(shim_timeout_env));
|
|
404
405
|
}
|
|
405
406
|
|
|
406
407
|
fn parseShimTimeoutMs(raw: ?[]const u8) u64 {
|
package/src/ios_devices.zig
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const std = @import("std");
|
|
2
|
+
const stdio = @import("stdio.zig");
|
|
2
3
|
const command = @import("command.zig");
|
|
3
4
|
const types = @import("types.zig");
|
|
4
5
|
|
|
@@ -39,7 +40,7 @@ pub fn runSimctlCommand(
|
|
|
39
40
|
}
|
|
40
41
|
result.deinit(allocator);
|
|
41
42
|
attempt += 1;
|
|
42
|
-
|
|
43
|
+
stdio.sleepNs(simctl_retry_delay_ms * std.time.ns_per_ms);
|
|
43
44
|
}
|
|
44
45
|
}
|
|
45
46
|
|
|
@@ -62,9 +63,9 @@ pub fn runDevicectlJsonCommand(
|
|
|
62
63
|
xcrun_path: []const u8,
|
|
63
64
|
extra: []const []const u8,
|
|
64
65
|
) ![]u8 {
|
|
65
|
-
const path = try std.fmt.allocPrint(allocator, "/tmp/zmr-devicectl-{d}.json", .{
|
|
66
|
+
const path = try std.fmt.allocPrint(allocator, "/tmp/zmr-devicectl-{d}.json", .{stdio.nowNs()});
|
|
66
67
|
defer allocator.free(path);
|
|
67
|
-
defer std.
|
|
68
|
+
defer std.Io.Dir.deleteFileAbsolute(stdio.io(), path) catch {};
|
|
68
69
|
|
|
69
70
|
var argv = std.ArrayList([]const u8).empty;
|
|
70
71
|
defer argv.deinit(allocator);
|
|
@@ -76,7 +77,7 @@ pub fn runDevicectlJsonCommand(
|
|
|
76
77
|
const result = try command.run(allocator, argv.items, default_max_output);
|
|
77
78
|
defer result.deinit(allocator);
|
|
78
79
|
try result.ensureSuccess();
|
|
79
|
-
return try std.
|
|
80
|
+
return try std.Io.Dir.cwd().readFileAlloc(stdio.io(), path, allocator, .limited(default_max_output));
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
pub fn parseSimulatorsJson(allocator: std.mem.Allocator, content: []const u8) ![]types.DeviceInfo {
|
|
@@ -158,7 +159,7 @@ pub fn findPidForBundleId(allocator: std.mem.Allocator, content: []const u8, app
|
|
|
158
159
|
fn isRetriableSimctlFailure(result: command.ExecResult) bool {
|
|
159
160
|
if (result.timed_out) return false;
|
|
160
161
|
switch (result.term) {
|
|
161
|
-
.
|
|
162
|
+
.exited => |code| if (code == 0) return false,
|
|
162
163
|
else => return false,
|
|
163
164
|
}
|
|
164
165
|
return std.mem.indexOf(u8, result.stderr, "CoreSimulatorService connection became invalid") != null or
|
package/src/ios_lifecycle.zig
CHANGED
|
@@ -65,7 +65,7 @@ pub fn uninstallPhysicalBestEffort(
|
|
|
65
65
|
|
|
66
66
|
pub fn isMissingInstalledApp(result: command.ExecResult) bool {
|
|
67
67
|
switch (result.term) {
|
|
68
|
-
.
|
|
68
|
+
.exited => |code| if (code == 0) return false,
|
|
69
69
|
else => return false,
|
|
70
70
|
}
|
|
71
71
|
return std.mem.indexOf(u8, result.stderr, "No installed application with bundle identifier") != null;
|
|
@@ -73,7 +73,7 @@ pub fn isMissingInstalledApp(result: command.ExecResult) bool {
|
|
|
73
73
|
|
|
74
74
|
pub fn isAppNotRunning(result: command.ExecResult) bool {
|
|
75
75
|
switch (result.term) {
|
|
76
|
-
.
|
|
76
|
+
.exited => |code| if (code == 0) return false,
|
|
77
77
|
else => return false,
|
|
78
78
|
}
|
|
79
79
|
return std.mem.indexOf(u8, result.stderr, "found nothing to terminate") != null;
|
|
@@ -89,7 +89,7 @@ test "simctl terminate missing running app is best-effort" {
|
|
|
89
89
|
try std.testing.expect(isAppNotRunning(.{
|
|
90
90
|
.stdout = stdout,
|
|
91
91
|
.stderr = stderr,
|
|
92
|
-
.term = .{ .
|
|
92
|
+
.term = .{ .exited = 3 },
|
|
93
93
|
}));
|
|
94
94
|
}
|
|
95
95
|
|
|
@@ -103,6 +103,6 @@ test "simctl terminate success is not classified as already stopped" {
|
|
|
103
103
|
try std.testing.expect(!isAppNotRunning(.{
|
|
104
104
|
.stdout = stdout,
|
|
105
105
|
.stderr = stderr,
|
|
106
|
-
.term = .{ .
|
|
106
|
+
.term = .{ .exited = 0 },
|
|
107
107
|
}));
|
|
108
108
|
}
|
package/src/json_rpc.zig
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
const std = @import("std");
|
|
2
|
+
const stdio = @import("stdio.zig");
|
|
2
3
|
const errors = @import("errors.zig");
|
|
3
4
|
const methods = @import("json_rpc_methods.zig");
|
|
4
5
|
const protocol = @import("json_rpc_protocol.zig");
|
|
5
6
|
const trace = @import("trace.zig");
|
|
6
7
|
|
|
8
|
+
const net = std.Io.net;
|
|
9
|
+
|
|
7
10
|
pub const ServeOptions = struct {
|
|
8
11
|
transport: []const u8 = "stdio",
|
|
9
12
|
};
|
|
@@ -13,12 +16,19 @@ pub fn serveStdio(allocator: std.mem.Allocator, device: anytype) !void {
|
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
pub fn serveStdioWithTrace(allocator: std.mem.Allocator, device: anytype, live_trace: ?*trace.TraceWriter) !void {
|
|
16
|
-
var
|
|
17
|
-
|
|
19
|
+
var stdin_io: stdio.Input = .{};
|
|
20
|
+
stdin_io.init(.stdin());
|
|
21
|
+
const stdin = stdin_io.reader();
|
|
22
|
+
|
|
23
|
+
var stdout_io: stdio.Output = .{};
|
|
24
|
+
stdout_io.init(.stdout());
|
|
25
|
+
defer stdout_io.deinit();
|
|
26
|
+
const stdout = stdout_io.writer();
|
|
18
27
|
|
|
19
28
|
while (true) {
|
|
20
|
-
const line =
|
|
29
|
+
const line = stdio.readLineAlloc(stdin, allocator, 16 * 1024 * 1024) catch |err| {
|
|
21
30
|
try protocol.writeError(stdout, null, -32700, @errorName(err));
|
|
31
|
+
try stdout_io.flush();
|
|
22
32
|
continue;
|
|
23
33
|
};
|
|
24
34
|
const owned_line = line orelse break;
|
|
@@ -26,6 +36,7 @@ pub fn serveStdioWithTrace(allocator: std.mem.Allocator, device: anytype, live_t
|
|
|
26
36
|
const trimmed = std.mem.trim(u8, owned_line, " \t\r\n");
|
|
27
37
|
if (trimmed.len == 0) continue;
|
|
28
38
|
try dispatchLineWithTrace(allocator, device, trimmed, stdout, live_trace);
|
|
39
|
+
try stdout_io.flush();
|
|
29
40
|
}
|
|
30
41
|
}
|
|
31
42
|
|
|
@@ -34,45 +45,33 @@ pub fn serveTcp(allocator: std.mem.Allocator, device: anytype, port: u16) !void
|
|
|
34
45
|
}
|
|
35
46
|
|
|
36
47
|
pub fn serveTcpWithTrace(allocator: std.mem.Allocator, device: anytype, port: u16, live_trace: ?*trace.TraceWriter) !void {
|
|
37
|
-
const address = try
|
|
38
|
-
var server = try address.listen(.{ .reuse_address = true });
|
|
39
|
-
defer server.deinit();
|
|
48
|
+
const address = try net.IpAddress.parse("127.0.0.1", port);
|
|
49
|
+
var server = try address.listen(stdio.io(), .{ .reuse_address = true });
|
|
50
|
+
defer server.deinit(stdio.io());
|
|
40
51
|
|
|
41
52
|
while (true) {
|
|
42
|
-
|
|
43
|
-
defer connection.
|
|
44
|
-
try serveTcpConnection(allocator, device, connection
|
|
53
|
+
const connection = try server.accept(stdio.io());
|
|
54
|
+
defer connection.close(stdio.io());
|
|
55
|
+
try serveTcpConnection(allocator, device, connection, live_trace);
|
|
45
56
|
}
|
|
46
57
|
}
|
|
47
58
|
|
|
48
|
-
fn serveTcpConnection(allocator: std.mem.Allocator, device: anytype, stream:
|
|
59
|
+
fn serveTcpConnection(allocator: std.mem.Allocator, device: anytype, stream: net.Stream, live_trace: ?*trace.TraceWriter) !void {
|
|
49
60
|
var write_buffer: [8192]u8 = undefined;
|
|
50
|
-
var stream_writer = stream.writer(&write_buffer);
|
|
61
|
+
var stream_writer = stream.writer(stdio.io(), &write_buffer);
|
|
51
62
|
const writer = &stream_writer.interface;
|
|
52
63
|
|
|
53
|
-
var line = std.ArrayList(u8).empty;
|
|
54
|
-
defer line.deinit(allocator);
|
|
55
|
-
|
|
56
64
|
var read_buffer: [4096]u8 = undefined;
|
|
65
|
+
var stream_reader = stream.reader(stdio.io(), &read_buffer);
|
|
57
66
|
while (true) {
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
line.clearRetainingCapacity();
|
|
68
|
-
} else {
|
|
69
|
-
try line.append(allocator, ch);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (line.items.len != 0) {
|
|
75
|
-
const trimmed = std.mem.trim(u8, line.items, " \t\r\n");
|
|
67
|
+
const line = stdio.readLineAlloc(&stream_reader.interface, allocator, 16 * 1024 * 1024) catch |err| {
|
|
68
|
+
try protocol.writeError(writer, null, -32700, @errorName(err));
|
|
69
|
+
try writer.flush();
|
|
70
|
+
continue;
|
|
71
|
+
};
|
|
72
|
+
const owned_line = line orelse break;
|
|
73
|
+
defer allocator.free(owned_line);
|
|
74
|
+
const trimmed = std.mem.trim(u8, owned_line, " \t\r\n");
|
|
76
75
|
if (trimmed.len != 0) {
|
|
77
76
|
try dispatchLineWithTrace(allocator, device, trimmed, writer, live_trace);
|
|
78
77
|
try writer.flush();
|
|
@@ -124,21 +123,21 @@ pub fn dispatchLineWithTrace(
|
|
|
124
123
|
}
|
|
125
124
|
|
|
126
125
|
fn recordRpcEvent(tw: *trace.TraceWriter, kind: []const u8, method: []const u8, id: ?std.json.Value) !void {
|
|
127
|
-
var payload =
|
|
128
|
-
defer payload.deinit(
|
|
129
|
-
const writer = payload.writer
|
|
126
|
+
var payload: std.Io.Writer.Allocating = .init(tw.allocator);
|
|
127
|
+
defer payload.deinit();
|
|
128
|
+
const writer = &payload.writer;
|
|
130
129
|
try writer.writeAll("{\"method\":");
|
|
131
130
|
try trace.writeJsonString(writer, method);
|
|
132
131
|
try writer.writeAll(",\"id\":");
|
|
133
132
|
try protocol.writeId(writer, id);
|
|
134
133
|
try writer.writeAll("}");
|
|
135
|
-
try tw.recordEvent(kind,
|
|
134
|
+
try tw.recordEvent(kind, writer.buffered());
|
|
136
135
|
}
|
|
137
136
|
|
|
138
137
|
fn recordRpcErrorEvent(tw: *trace.TraceWriter, method: []const u8, id: ?std.json.Value, err: anyerror) !void {
|
|
139
|
-
var payload =
|
|
140
|
-
defer payload.deinit(
|
|
141
|
-
const writer = payload.writer
|
|
138
|
+
var payload: std.Io.Writer.Allocating = .init(tw.allocator);
|
|
139
|
+
defer payload.deinit();
|
|
140
|
+
const writer = &payload.writer;
|
|
142
141
|
try writer.writeAll("{\"method\":");
|
|
143
142
|
try trace.writeJsonString(writer, method);
|
|
144
143
|
try writer.writeAll(",\"id\":");
|
|
@@ -146,5 +145,5 @@ fn recordRpcErrorEvent(tw: *trace.TraceWriter, method: []const u8, id: ?std.json
|
|
|
146
145
|
try writer.writeAll(",\"error\":");
|
|
147
146
|
try trace.writeJsonString(writer, @errorName(err));
|
|
148
147
|
try writer.writeAll("}");
|
|
149
|
-
try tw.recordEvent("rpc.error",
|
|
148
|
+
try tw.recordEvent("rpc.error", writer.buffered());
|
|
150
149
|
}
|
package/src/json_rpc_methods.zig
CHANGED
|
@@ -107,13 +107,13 @@ fn dispatchAppMethod(
|
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
fn recordAppOpenLink(tw: *trace.TraceWriter, url: []const u8) !void {
|
|
110
|
-
var payload =
|
|
111
|
-
defer payload.deinit(
|
|
112
|
-
const writer = payload.writer
|
|
110
|
+
var payload: std.Io.Writer.Allocating = .init(tw.allocator);
|
|
111
|
+
defer payload.deinit();
|
|
112
|
+
const writer = &payload.writer;
|
|
113
113
|
try writer.writeAll("{\"status\":\"ok\",\"url\":");
|
|
114
114
|
try trace.writeJsonString(writer, url);
|
|
115
115
|
try writer.writeAll("}");
|
|
116
|
-
try tw.recordEvent("app.openLink",
|
|
116
|
+
try tw.recordEvent("app.openLink", writer.buffered());
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
fn dispatchObserveMethod(
|
|
@@ -312,10 +312,10 @@ fn dispatchScenarioMethod(
|
|
|
312
312
|
const path = try params_parser.requiredString(params, "path");
|
|
313
313
|
var result = try validation.validateFile(allocator, path);
|
|
314
314
|
defer result.deinit(allocator);
|
|
315
|
-
var payload =
|
|
316
|
-
defer payload.deinit(
|
|
317
|
-
try cli_output.writeValidationJson(payload.writer
|
|
318
|
-
try protocol.writeResultRaw(writer, id, std.mem.
|
|
315
|
+
var payload: std.Io.Writer.Allocating = .init(allocator);
|
|
316
|
+
defer payload.deinit();
|
|
317
|
+
try cli_output.writeValidationJson(&payload.writer, path, result);
|
|
318
|
+
try protocol.writeResultRaw(writer, id, std.mem.trimEnd(u8, payload.writer.buffered(), " \t\r\n"));
|
|
319
319
|
return true;
|
|
320
320
|
}
|
|
321
321
|
return false;
|
|
@@ -20,12 +20,13 @@ pub fn writeResult(writer: anytype, id: ?std.json.Value, snap: types.Observation
|
|
|
20
20
|
pub fn recordArtifact(tw: *trace.TraceWriter, kind: []const u8, snap: types.ObservationSnapshot) !void {
|
|
21
21
|
const path = try tw.writeSnapshot(snap);
|
|
22
22
|
defer tw.allocator.free(path);
|
|
23
|
-
var payload =
|
|
24
|
-
defer payload.deinit(
|
|
25
|
-
|
|
26
|
-
try
|
|
27
|
-
try
|
|
28
|
-
try
|
|
29
|
-
try
|
|
30
|
-
try
|
|
23
|
+
var payload: std.Io.Writer.Allocating = .init(tw.allocator);
|
|
24
|
+
defer payload.deinit();
|
|
25
|
+
const out = &payload.writer;
|
|
26
|
+
try out.writeAll("{\"path\":");
|
|
27
|
+
try trace.writeJsonString(out, path);
|
|
28
|
+
try out.writeAll(",\"snapshotId\":");
|
|
29
|
+
try trace.writeJsonString(out, snap.id);
|
|
30
|
+
try out.writeAll("}");
|
|
31
|
+
try tw.recordEvent(kind, out.buffered());
|
|
31
32
|
}
|
package/src/json_rpc_params.zig
CHANGED
|
@@ -56,5 +56,5 @@ pub fn optionalDirection(params: ?std.json.Value, key: []const u8, default_value
|
|
|
56
56
|
if (value != .string) return error.ParamMustBeString;
|
|
57
57
|
if (std.mem.eql(u8, value.string, "down")) return .down;
|
|
58
58
|
if (std.mem.eql(u8, value.string, "up")) return .up;
|
|
59
|
-
return error.
|
|
59
|
+
return error.unknownScrollDirection;
|
|
60
60
|
}
|
package/src/json_rpc_trace.zig
CHANGED
|
@@ -4,6 +4,7 @@ const cli_explore = @import("cli_explore.zig");
|
|
|
4
4
|
const protocol = @import("json_rpc_protocol.zig");
|
|
5
5
|
const report = @import("report.zig");
|
|
6
6
|
const runner_events = @import("runner_events.zig");
|
|
7
|
+
const stdio = @import("stdio.zig");
|
|
7
8
|
const trace = @import("trace.zig");
|
|
8
9
|
|
|
9
10
|
pub fn writeEventsResult(
|
|
@@ -23,7 +24,7 @@ pub fn writeEventsResult(
|
|
|
23
24
|
|
|
24
25
|
const events_path = try std.fs.path.join(allocator, &.{ tw.root_dir, "events.jsonl" });
|
|
25
26
|
defer allocator.free(events_path);
|
|
26
|
-
const content =
|
|
27
|
+
const content = stdio.readFileAlloc(allocator, events_path, 64 * 1024 * 1024) catch |err| switch (err) {
|
|
27
28
|
error.FileNotFound => try allocator.dupe(u8, ""),
|
|
28
29
|
else => return err,
|
|
29
30
|
};
|
|
@@ -35,9 +36,9 @@ pub fn writeEventsResult(
|
|
|
35
36
|
try trace.writeJsonString(writer, tw.root_dir);
|
|
36
37
|
try writer.print(",\"afterSeq\":{d},\"nextSeq\":", .{after_seq});
|
|
37
38
|
|
|
38
|
-
var events_json =
|
|
39
|
-
defer events_json.deinit(
|
|
40
|
-
|
|
39
|
+
var events_json: std.Io.Writer.Allocating = .init(allocator);
|
|
40
|
+
defer events_json.deinit();
|
|
41
|
+
const events_writer = &events_json.writer;
|
|
41
42
|
var next_seq = after_seq;
|
|
42
43
|
var emitted: u64 = 0;
|
|
43
44
|
|
|
@@ -60,20 +61,20 @@ pub fn writeEventsResult(
|
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
try writer.print("{d},\"latestSeq\":{d},\"events\":[", .{ next_seq, tw.event_count });
|
|
63
|
-
try writer.writeAll(
|
|
64
|
+
try writer.writeAll(events_writer.buffered());
|
|
64
65
|
try writer.writeAll("]}}\n");
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
pub fn recordSimplePayload(tw: *trace.TraceWriter, kind: []const u8, key: []const u8, value: []const u8) !void {
|
|
68
|
-
var payload =
|
|
69
|
-
defer payload.deinit(
|
|
70
|
-
const writer = payload.writer
|
|
69
|
+
var payload: std.Io.Writer.Allocating = .init(tw.allocator);
|
|
70
|
+
defer payload.deinit();
|
|
71
|
+
const writer = &payload.writer;
|
|
71
72
|
try writer.writeAll("{");
|
|
72
73
|
try trace.writeJsonString(writer, key);
|
|
73
74
|
try writer.writeAll(":");
|
|
74
75
|
try trace.writeJsonString(writer, value);
|
|
75
76
|
try writer.writeAll("}");
|
|
76
|
-
try tw.recordEvent(kind,
|
|
77
|
+
try tw.recordEvent(kind, writer.buffered());
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
pub fn writeDiscoverResult(
|
|
@@ -113,10 +114,10 @@ pub fn writeDiscoverResult(
|
|
|
113
114
|
discovered.summary.validated,
|
|
114
115
|
);
|
|
115
116
|
|
|
116
|
-
var payload =
|
|
117
|
-
defer payload.deinit(
|
|
118
|
-
try cli_discover.writeJson(payload.writer
|
|
119
|
-
try protocol.writeResultRaw(writer, id, std.mem.
|
|
117
|
+
var payload: std.Io.Writer.Allocating = .init(allocator);
|
|
118
|
+
defer payload.deinit();
|
|
119
|
+
try cli_discover.writeJson(&payload.writer, discovered.summary, discovered.validation);
|
|
120
|
+
try protocol.writeResultRaw(writer, id, std.mem.trimEnd(u8, payload.writer.buffered(), " \t\r\n"));
|
|
120
121
|
}
|
|
121
122
|
|
|
122
123
|
pub fn writeExploreResult(
|
|
@@ -159,10 +160,10 @@ pub fn writeExploreResult(
|
|
|
159
160
|
explored.discovered.summary.validated,
|
|
160
161
|
);
|
|
161
162
|
|
|
162
|
-
var payload =
|
|
163
|
-
defer payload.deinit(
|
|
164
|
-
try cli_explore.writeJson(payload.writer
|
|
165
|
-
try protocol.writeResultRaw(writer, id, std.mem.
|
|
163
|
+
var payload: std.Io.Writer.Allocating = .init(allocator);
|
|
164
|
+
defer payload.deinit();
|
|
165
|
+
try cli_explore.writeJson(&payload.writer, explored.summary, explored.discovered.summary, explored.discovered.validation);
|
|
166
|
+
try protocol.writeResultRaw(writer, id, std.mem.trimEnd(u8, payload.writer.buffered(), " \t\r\n"));
|
|
166
167
|
}
|
|
167
168
|
|
|
168
169
|
pub fn writeExplainResult(
|
|
@@ -177,9 +178,9 @@ pub fn writeExplainResult(
|
|
|
177
178
|
};
|
|
178
179
|
|
|
179
180
|
try tw.flushManifest();
|
|
180
|
-
var payload =
|
|
181
|
-
defer payload.deinit(
|
|
182
|
-
try report.writeTraceExplanationJson(allocator, tw.root_dir, payload.writer
|
|
183
|
-
try protocol.writeResultRaw(writer, id, std.mem.
|
|
181
|
+
var payload: std.Io.Writer.Allocating = .init(allocator);
|
|
182
|
+
defer payload.deinit();
|
|
183
|
+
try report.writeTraceExplanationJson(allocator, tw.root_dir, &payload.writer);
|
|
184
|
+
try protocol.writeResultRaw(writer, id, std.mem.trimEnd(u8, payload.writer.buffered(), " \t\r\n"));
|
|
184
185
|
try tw.recordEvent("trace.explain", "{\"status\":\"ok\"}");
|
|
185
186
|
}
|
package/src/main.zig
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const std = @import("std");
|
|
2
|
+
const stdio = @import("stdio.zig");
|
|
2
3
|
const cli_devices = @import("cli_devices.zig");
|
|
3
4
|
const cli_discover = @import("cli_discover.zig");
|
|
4
5
|
const cli_doctor = @import("cli_doctor.zig");
|
|
@@ -14,8 +15,8 @@ const cli_trace = @import("cli_trace.zig");
|
|
|
14
15
|
const cli_validate = @import("cli_validate.zig");
|
|
15
16
|
const errors = @import("errors.zig");
|
|
16
17
|
|
|
17
|
-
pub fn main() void {
|
|
18
|
-
mainInner() catch |err| {
|
|
18
|
+
pub fn main(init: std.process.Init.Minimal) void {
|
|
19
|
+
mainInner(init) catch |err| {
|
|
19
20
|
// stdout's consumer went away (e.g. `zmr ... | head`); exit quietly
|
|
20
21
|
// with the conventional SIGPIPE status instead of reporting an error.
|
|
21
22
|
if (err == error.BrokenPipe) std.process.exit(141);
|
|
@@ -24,7 +25,7 @@ pub fn main() void {
|
|
|
24
25
|
};
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
fn mainInner() !void {
|
|
28
|
+
fn mainInner(init: std.process.Init.Minimal) !void {
|
|
28
29
|
const GeneralAllocator = if (@hasDecl(std.heap, "GeneralPurposeAllocator"))
|
|
29
30
|
std.heap.GeneralPurposeAllocator
|
|
30
31
|
else
|
|
@@ -32,8 +33,10 @@ fn mainInner() !void {
|
|
|
32
33
|
var gpa = GeneralAllocator(.{}){};
|
|
33
34
|
defer _ = gpa.deinit();
|
|
34
35
|
const allocator = gpa.allocator();
|
|
36
|
+
stdio.initProcess(init, allocator);
|
|
37
|
+
defer stdio.deinitProcess();
|
|
35
38
|
|
|
36
|
-
var args = try std.process.
|
|
39
|
+
var args = try std.process.Args.Iterator.initAllocator(init.args, allocator);
|
|
37
40
|
defer args.deinit();
|
|
38
41
|
_ = args.next();
|
|
39
42
|
const command_name = args.next() orelse {
|
|
@@ -80,26 +83,29 @@ fn mainInner() !void {
|
|
|
80
83
|
} else {
|
|
81
84
|
std.debug.print("unknown command: {s}\n\n", .{command_name});
|
|
82
85
|
try usage();
|
|
83
|
-
return error.
|
|
86
|
+
return error.unknownCommand;
|
|
84
87
|
}
|
|
85
88
|
}
|
|
86
89
|
|
|
87
90
|
fn writeTopLevelError(err: anyerror) void {
|
|
88
91
|
const public = errors.classify(err);
|
|
89
|
-
|
|
92
|
+
var stderr_io: stdio.Output = .{};
|
|
93
|
+
stderr_io.init(.stderr());
|
|
94
|
+
defer stderr_io.deinit();
|
|
95
|
+
const stderr = stderr_io.writer();
|
|
90
96
|
stderr.print("error[{s}]: {s}\n", .{ public.code, public.message }) catch {};
|
|
91
97
|
if (err == error.CommandFailed) {
|
|
92
98
|
stderr.writeAll("hint: run `zmr doctor --json` for setup diagnostics.\n") catch {};
|
|
93
99
|
}
|
|
94
|
-
if (err == error.
|
|
100
|
+
if (err == error.unknownFlag) {
|
|
95
101
|
stderr.writeAll("hint: run `zmr help` for each command's flags and arguments.\n") catch {};
|
|
96
102
|
}
|
|
97
103
|
}
|
|
98
104
|
|
|
99
105
|
fn exitCodeForError(err: anyerror) u8 {
|
|
100
106
|
return switch (err) {
|
|
101
|
-
error.
|
|
102
|
-
error.
|
|
107
|
+
error.unknownCommand,
|
|
108
|
+
error.unknownFlag,
|
|
103
109
|
error.MissingScenarioPath,
|
|
104
110
|
error.MissingDeviceSerial,
|
|
105
111
|
error.MissingTraceDir,
|
|
@@ -125,7 +131,10 @@ fn exitCodeForError(err: anyerror) u8 {
|
|
|
125
131
|
}
|
|
126
132
|
|
|
127
133
|
fn usage() !void {
|
|
128
|
-
|
|
134
|
+
var stdout_io: stdio.Output = .{};
|
|
135
|
+
stdout_io.init(.stdout());
|
|
136
|
+
defer stdout_io.deinit();
|
|
137
|
+
const stdout = stdout_io.writer();
|
|
129
138
|
try stdout.writeAll(
|
|
130
139
|
\\zmr - Zeno Mobile Runner
|
|
131
140
|
\\
|