Files
mim/src/v1_smoke.zig
T
2026-06-21 22:21:19 +02:00

278 lines
12 KiB
Zig

const std = @import("std");
const diagnostics = @import("diagnostics.zig");
const job = @import("job.zig");
const lsp = @import("lsp.zig");
const profile = @import("profile.zig");
const repo = @import("repo.zig");
const session_mod = @import("session.zig");
const socket = @import("socket.zig");
const syntax = @import("syntax.zig");
const tui = @import("tui.zig");
const allocator = std.testing.allocator;
test "regular: v1 smoke opens edits saves and reports local socket context" {
var client = try tui.Client.init(allocator, .{ .width = 44, .height = 10 });
defer client.deinit();
try client.handleTraceLine("open pub fn main() void {}");
try client.handleTraceLine("right");
try client.handleTraceLine("insert // mobile fix");
try client.handleTraceLine("save");
try std.testing.expect(std.mem.indexOf(u8, try client.saved(), "// mobile fix") != null);
const frame = try client.render(allocator);
defer allocator.free(frame);
try std.testing.expect(std.mem.indexOf(u8, frame, "saved") != null);
var session = session_mod.Session.init(allocator);
defer session.deinit();
try session.openFixture("pub const answer = 42;\n");
try session.openListPanel("files", &.{ "src/main.zig", "src/lib.zig" });
const path = "/tmp/mim-v1-smoke.sock";
var server = try socket.Server.listen(allocator, path, &session);
defer server.deinit();
const thread = try std.Thread.spawn(.{}, socket.Server.acceptOnce, .{&server});
const response = try socket.request(allocator, path, "context iphone_fix inspect");
defer allocator.free(response);
thread.join();
try std.testing.expect(std.mem.indexOf(u8, response, "context:v1\n") != null);
try std.testing.expect(std.mem.indexOf(u8, response, "active_panel=files") != null);
try std.testing.expect(std.mem.indexOf(u8, response, "task=iphone_fix") != null);
}
test "regular: v1 smoke covers repo panels, syntax, and LSP fixture rows" {
var client = try tui.Client.init(allocator, .{ .width = 52, .height = 12 });
defer client.deinit();
try client.handleTraceLine("repo_gitignore zig-out/");
try client.handleTraceLine("repo_file src/main.zig=pub fn main() void { add(1, 2); }");
try client.handleTraceLine("repo_file zig-out/log.txt=ignored build output");
try client.handleTraceLine("file_picker");
{
const frame = try client.render(allocator);
defer allocator.free(frame);
try std.testing.expect(std.mem.indexOf(u8, frame, "src/main.zig") != null);
try std.testing.expect(std.mem.indexOf(u8, frame, "zig-out/log.txt") == null);
}
try client.handleTraceLine("search_text add");
{
const frame = try client.render(allocator);
defer allocator.free(frame);
try std.testing.expect(std.mem.indexOf(u8, frame, "src/main.zig:1:") != null);
}
const syntax_rows = try syntax.rowsAlloc(allocator, "zig", "pub const answer = 42; // ok\n");
defer freeRows(syntax_rows);
try std.testing.expect(syntax_rows.len >= 2);
try std.testing.expect(std.mem.indexOf(u8, syntax_rows[0], "syntax:zig:keyword") != null);
const hover_rows = try lsp.hoverRowsAlloc(allocator,
\\{"jsonrpc":"2.0","id":8,"result":{"contents":{"kind":"markdown","value":"pub fn add(lhs: i32, rhs: i32) i32"}}}
, 28);
defer freeRows(hover_rows);
try std.testing.expect(hover_rows.len >= 2);
try std.testing.expect(std.mem.indexOf(u8, hover_rows[0], "lsp:hover") != null);
const param = try lsp.parameterMoveRowsAlloc(allocator, "add(1, 2)", 4, .next);
defer freeRows(param.rows);
try std.testing.expect(param.cursor > 4);
try std.testing.expect(std.mem.indexOf(u8, param.rows[0], "lsp:param:active_2") != null);
}
test "adversarial: v1 smoke catches realistic profile and boundary failures" {
try std.testing.expectError(error.NonLocalPath, socket.validateLocalSocketPath("relative.sock"));
try std.testing.expectError(error.NonLocalPath, socket.validateLocalSocketPath("/home/user/mim.sock"));
var index = repo.Index.init(allocator);
defer index.deinit();
try index.addIgnorePattern("secret/");
try index.addFile("src/main.zig", "pub fn main() void {}\n");
try index.addFile("secret/token.txt", "do-not-show\n");
const rows = try index.pickerItemsAlloc(allocator, false);
defer allocator.free(rows);
try std.testing.expectEqual(@as(usize, 1), rows.len);
try std.testing.expectEqualStrings("src/main.zig", rows[0]);
try std.testing.expectError(lsp.Error.InvalidNavigationRow, lsp.navigationLocationFromRow("not a navigation row"));
const status = try profile.statusAlloc(allocator);
defer allocator.free(status);
try std.testing.expect(std.mem.indexOf(u8, status, "profile:v1") != null);
try std.testing.expect(std.mem.indexOf(u8, status, "remote_transport=not_built") != null);
try std.testing.expect(std.mem.indexOf(u8, status, "plugin_host=not_built") != null);
}
test "regular: v1 smoke exposes bounded search, job, LSP, and save diagnostics" {
var client = try tui.Client.init(allocator, .{ .width = 72, .height = 12 });
defer client.deinit();
try client.handleTraceLine("open ready");
var line_buf: [64]u8 = undefined;
var i: usize = 0;
while (i < diagnostics.max_search_rows + 3) : (i += 1) {
const line = try std.fmt.bufPrint(&line_buf, "repo_file src/file{d}.zig=needle_{d}", .{ i, i });
try client.handleTraceLine(line);
}
try client.handleTraceLine("search_text needle");
{
const frame = try client.render(allocator);
defer allocator.free(frame);
try std.testing.expect(std.mem.indexOf(u8, frame, "diagnostic:search_truncated") != null);
}
const job_rows = try job.runRowsAlloc(allocator, std.Io.failing, ".", &.{"definitely-missing-mim-command"});
defer freeRows(job_rows);
try std.testing.expect(rowsContain(job_rows, "diagnostic:job_limits"));
try std.testing.expect(rowsContain(job_rows, "job:status:spawn_error"));
const lsp_rows = try lsp.runDocumentSyncRowsAlloc(allocator, std.Io.failing, ".", &.{"definitely-missing-lsp"}, .{
.uri = "file:///repo/src/main.zig",
.language_id = "zig",
.text = "pub fn main() void {}",
});
defer freeRows(lsp_rows);
try std.testing.expect(rowsContain(lsp_rows, "diagnostic:lsp_limits"));
try std.testing.expect(rowsContain(lsp_rows, "lsp:status:spawn_error"));
}
test "adversarial: v1 smoke surfaces large or unsupported file and save failures" {
var client = try tui.Client.init(allocator, .{ .width = 72, .height = 6 });
defer client.deinit();
try std.testing.expectError(repo.Error.InvalidPath, client.handleTraceLine("repo_file ../secret=hidden"));
{
const frame = try client.render(allocator);
defer allocator.free(frame);
try std.testing.expect(std.mem.indexOf(u8, frame, "diagnostic:unsupported_file") != null);
}
const large_bytes = try allocator.alloc(u8, diagnostics.max_file_bytes + 1);
defer allocator.free(large_bytes);
@memset(large_bytes, 'x');
const large_command = try std.fmt.allocPrint(allocator, "repo_file src/large.txt={s}", .{large_bytes});
defer allocator.free(large_command);
try std.testing.expectError(tui.Error.ProtocolRejected, client.handleTraceLine(large_command));
{
const frame = try client.render(allocator);
defer allocator.free(frame);
try std.testing.expect(std.mem.indexOf(u8, frame, "diagnostic:file_too_large") != null);
}
try client.handleTraceLine("open ok");
const insert_command = try std.fmt.allocPrint(allocator, "insert {s}", .{large_bytes});
defer allocator.free(insert_command);
try client.handleTraceLine(insert_command);
try std.testing.expectError(tui.Error.ProtocolRejected, client.handleTraceLine("save"));
{
const frame = try client.render(allocator);
defer allocator.free(frame);
try std.testing.expect(std.mem.indexOf(u8, frame, "diagnostic:save_failed:file_too_large") != null);
}
}
fn rowsContain(rows: []const []const u8, needle: []const u8) bool {
for (rows) |row| {
if (std.mem.indexOf(u8, row, needle) != null) return true;
}
return false;
}
fn freeRows(rows: [][]const u8) void {
for (rows) |row| allocator.free(row);
allocator.free(rows);
}
test "regular: ns8 local production smoke covers editor workflow before real-device dogfood" {
var client = try tui.Client.initWithIo(allocator, .{ .width = 39, .height = 8 }, std.testing.io);
defer client.deinit();
try client.handleTraceLine("repo_file src/main.zig=pub fn main() void { needle(); }\n");
try client.handleTraceLine("repo_file README.md=needle docs\n");
try client.handleTraceLine("file_picker");
try client.handleTraceLine("list_filter src/main");
try client.handleTraceLine("file_open_selected");
try client.handleTraceLine("key end");
try client.handleInput("i");
try client.handleInput("X");
try client.handleTraceLine("key escape");
try client.handleTraceLine("save_without_format");
try std.testing.expect(std.mem.indexOf(u8, try client.saved(), "X") != null);
try client.handleTraceLine("search_text needle");
{
const frame = try client.render(allocator);
defer allocator.free(frame);
try std.testing.expect(std.mem.indexOf(u8, frame, "src/main.zig") != null);
try std.testing.expect(frameLinesFit(frame, 39));
}
try client.handleTraceLine("panel_close");
try client.handleTraceLine("diagnostic_fixture zls|2|src/main.zig|0|3|warning|demo");
try client.handleInput(" ");
try client.handleInput("d");
{
const frame = try client.render(allocator);
defer allocator.free(frame);
try std.testing.expect(std.mem.indexOf(u8, frame, "diag:fresh:zls") != null);
try std.testing.expect(frameLinesFit(frame, 39));
}
try client.handleTraceLine("panel_close");
try client.handleTraceLine("key escape");
try client.handleInput(" ");
try client.handleInput("t");
try client.handleInput("x");
{
const frame = try client.render(allocator);
defer allocator.free(frame);
try std.testing.expect(std.mem.indexOf(u8, frame, "job:status:cancelled:user") != null);
try std.testing.expect(frameLinesFit(frame, 39));
}
var guarded = try tui.Client.init(allocator, .{ .width = 39, .height = 8 });
defer guarded.deinit();
try guarded.handleTraceLine("open abc");
try guarded.handleTraceLine("language_edit zls|format|1|0|3|ZLS");
try guarded.handleTraceLine("language_edit prettier|format|1|0|3|PRETTY");
try guarded.handleTraceLine("save");
try std.testing.expectError(tui.Error.NothingSaved, guarded.saved());
const guarded_frame = try guarded.render(allocator);
defer allocator.free(guarded_frame);
try std.testing.expect(std.mem.indexOf(u8, guarded_frame, "action:format:provider") != null);
}
fn frameLinesFit(frame: []const u8, width: usize) bool {
var lines = std.mem.splitScalar(u8, frame, '\n');
while (lines.next()) |line| if (visibleCells(line) > width) return false;
return true;
}
fn visibleCells(line: []const u8) usize {
var cells: usize = 0;
var i: usize = 0;
while (i < line.len) {
if (line[i] == 0x1b and i + 1 < line.len and line[i + 1] == '[') {
i += 2;
while (i < line.len) : (i += 1) {
const byte = line[i];
if ((byte >= 'A' and byte <= 'Z') or (byte >= 'a' and byte <= 'z')) {
i += 1;
break;
}
}
continue;
}
const len = std.unicode.utf8ByteSequenceLength(line[i]) catch 1;
cells += 1;
i += @min(len, line.len - i);
}
return cells;
}