diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-15 18:52:46 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:14 -0300 |
| commit | 3f4f26e0e40d06427e72f72e760d01adcdc0326c (patch) | |
| tree | d4bd56c6bc97f27e7ca11f3ee646513a9e4b116d /test | |
| parent | 02ea4db25f9802e76112f3b4e69aa75e5a9464dc (diff) | |
| download | pardes-3f4f26e0e40d06427e72f72e760d01adcdc0326c.tar.gz pardes-3f4f26e0e40d06427e72f72e760d01adcdc0326c.zip | |
Reuse bounded source analysis and speed up result context traversal
Diffstat (limited to 'test')
| -rw-r--r-- | test/locations_bench.zig | 61 | ||||
| -rw-r--r-- | test/output.zig | 139 |
2 files changed, 200 insertions, 0 deletions
diff --git a/test/locations_bench.zig b/test/locations_bench.zig new file mode 100644 index 00000000..54cebb59 --- /dev/null +++ b/test/locations_bench.zig @@ -0,0 +1,61 @@ +const std = @import("std"); +const pds = @import("pardes"); +const loc = pds.locations_config; +fn now() u64 { + var ts: std.c.timespec = undefined; + _ = std.c.clock_gettime(.MONOTONIC, &ts); + return @as(u64, @intCast(ts.sec)) * 1_000_000_000 + @as(u64, @intCast(ts.nsec)); +} +// Run: zig build locations-bench -Doptimize=ReleaseFast +// Timed work is the real Output.fillResults pipeline. Fixtures are open unsaved +// sources; source generation, input duplication, rendering and subprocesses are +// excluded. Each layout/config starts with an empty source-analysis cache. +// Two repeats retain that cache and existing output. No latency threshold applies. +test "locations benchmark cold and warm formatting" { + if (!pds.syntax.supportsPath("large.zig")) return error.ZigGrammarRequired; + std.debug.print("layout,context,matches,source_bytes,phase,repeat,elapsed_ns\n", .{}); + const gpa = std.heap.smp_allocator; + for ([_]usize{ 100, 500, 1500 }) |functions| { + var source: std.Io.Writer.Allocating = .init(gpa); + defer source.deinit(); + var input: std.Io.Writer.Allocating = .init(gpa); + defer input.deinit(); + try source.writer.writeAll("pub const Module = struct {\n"); + var row: usize = 2; + for (0..functions) |i| { + try source.writer.print(" pub fn f{d}(x: u32) u32 {{\n", .{i}); + row += 1; + for (0..20) |j| { + try source.writer.print(" const v{d} = x + {d}; // explanatory source comment\n", .{ j, j }); + row += 1; + } + const line = " return x; // BENCH_MATCH"; + try source.writer.print("{s}\n }}\n", .{line}); + try input.writer.print("large.zig:{d}:1 {s}\n", .{ row, line }); + row += 2; + } + try source.writer.writeAll("};\n"); + const p = try pds.Pardes.init(gpa, .{ .tty_only = true, .cols = 120, .rows = 40 }); + defer p.deinit(); + const pane = try p.setTestFile(source.written()); + gpa.free(pane.file.?.path); + pane.file.?.path = try gpa.dupe(u8, "/bench/large.zig"); + for ([_]loc.Config.Layout{ .@"inline", .stacked }) |layout| { + for ([_]loc.Config{ .{ .context = 0, .tscontext = false }, .{ .context = 2, .tscontext = false }, .{ .context = 0, .tscontext = true }, .{ .context = 2, .tscontext = true } }, 0..) |config, mode| { + p.locations_config = config; + p.locations_cache.deinit(p.tree_sitter_gpa); + p.locations_cache = .{}; + p.locations_config.layout = layout; + for (0..3) |rep| { + _ = p.scratch.reset(.retain_capacity); + const data = try gpa.dupe(u8, input.written()); + const t = now(); + try pds.panes.Output.fillResults(p, 0, "/bench", .{ .cmd = .Grep }, "BENCH_MATCH", data, null); + const elapsed = now() - t; + const modes = [_][]const u8{ "off", "ordinary", "tree_sitter", "both" }; + std.debug.print("{s},{s},{d},{d},{s},{d},{d}\n", .{ @tagName(layout), modes[mode], functions, source.written().len, if (rep == 0) "cold" else "warm", rep, elapsed }); + } + } + } + } +} diff --git a/test/output.zig b/test/output.zig index c738a661..21e9f04c 100644 --- a/test/output.zig +++ b/test/output.zig @@ -1585,3 +1585,142 @@ test "stacked search and LSP results retain source tint navigation and dump owne } } } + +test "location analysis cache refreshes unsaved ancestry and query context" { + if (!pardes.syntax.enabled) return error.SkipZigTest; + const before = "const Outer = struct {\n" ++ + " fn first() void {\n" ++ + " marker();\n" ++ + " }\n" ++ + " fn second() void {\n" ++ + " marker();\n" ++ + " }\n" ++ + "};\n"; + const after = "pub fn revised() void {\n // new neighbor\n marker();\n}\n"; + const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true }); + defer p.deinit(); + const source = try p.setTestFile(before); + p.gpa.free(source.file.?.path); + source.file.?.path = try p.gpa.dupe(u8, "/analysis-cache.zig"); + p.locations_config = .{ .context = 0, .tscontext = true, .tslocations = false }; + const Runner = struct { + fn fill(editor: *Pardes, line: usize) !void { + const path = editor.panes[0].?.file.?.path; + const input = try std.fmt.allocPrint(editor.gpa, "{s}:{d}:9-14 marker();\n", .{ path, line }); + try panes.Output.fillResults(editor, 0, "/", .search, "marker", input, 0); + } + }; + try Runner.fill(p, 3); + const id = source.search_pane.?; + const file = &p.panes[id].?.file.?; + try std.testing.expect(std.mem.indexOf(u8, file.content, "const Outer") != null); + try std.testing.expect(std.mem.indexOf(u8, file.content, "fn first") != null); + try std.testing.expect(std.mem.indexOf(u8, file.content, "fn second") == null); + const original = file.content.ptr; + const revision = file.revision; + const analyses = p.locations_cache.analyses; + const hits = p.locations_cache.hits; + try Runner.fill(p, 3); + try std.testing.expectEqual(id, source.search_pane.?); + try std.testing.expectEqual(original, file.content.ptr); + try std.testing.expectEqual(revision, file.revision); + try std.testing.expectEqual(analyses, p.locations_cache.analyses); + try std.testing.expect(p.locations_cache.hits > hits); + + // Reusing analysis must still choose the ancestry of the current match. + try Runner.fill(p, 6); + try std.testing.expectEqual(analyses, p.locations_cache.analyses); + try std.testing.expect(std.mem.indexOf(u8, file.content, "const Outer") != null); + try std.testing.expect(std.mem.indexOf(u8, file.content, "fn first") == null); + try std.testing.expect(std.mem.indexOf(u8, file.content, "fn second") != null); + const match = file.location_rows[source.search_row.?]; + try std.testing.expectEqual(.match, match.kind); + try std.testing.expectEqual(@as(usize, 6), match.at.line); + + // The path stays identical, but an unsaved replacement changes its tree. + panes.File.setContent(p, &source.file.?, try p.gpa.dupe(u8, after)); + try Runner.fill(p, 3); + try std.testing.expect(p.locations_cache.analyses > analyses); + try std.testing.expect(std.mem.indexOf(u8, file.content, "fn revised") != null); + try std.testing.expect(std.mem.indexOf(u8, file.content, "const Outer") == null); + try std.testing.expect(std.mem.indexOf(u8, file.content, "fn second") == null); + try std.testing.expect(std.mem.indexOf(u8, file.content, "new neighbor") == null); + + p.locations_config.context = 1; + try Runner.fill(p, 3); + try std.testing.expect(std.mem.indexOf(u8, file.content, "fn revised") != null); + try std.testing.expect(std.mem.indexOf(u8, file.content, "new neighbor") != null); + const expanded = try p.gpa.dupe(u8, file.content); + defer p.gpa.free(expanded); + const expanded_analyses = p.locations_cache.analyses; + p.locations_config.context = 0; + try Runner.fill(p, 3); + try std.testing.expect(std.mem.indexOf(u8, file.content, "new neighbor") == null); + p.locations_config.context = 1; + try Runner.fill(p, 3); + try std.testing.expectEqualStrings(expanded, file.content); + try std.testing.expectEqual(expanded_analyses, p.locations_cache.analyses); + p.locations_config.tscontext = false; + try Runner.fill(p, 3); + try std.testing.expect(std.mem.indexOf(u8, file.content, "fn revised") == null); + try std.testing.expect(std.mem.indexOf(u8, file.content, "new neighbor") != null); + try std.testing.expectEqualStrings(after, source.file.?.content); +} + +test "location cache eviction retains early colors and honors disk and unsaved replacements" { + if (!pardes.syntax.enabled) return error.SkipZigTest; + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + var dir_buf: [4096]u8 = undefined; + const dir = dir_buf[0..try tmp.dir.realPath(std.testing.io, &dir_buf)]; + const comment = "/* open\nconst int value = 42;\n*/\nneedle();\n"; + const code = "// open\nconst int value = 42;\n//\nneedle();\n"; + try std.testing.expectEqual(comment.len, code.len); + const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true }); + defer p.deinit(); + const source = try p.setTestFile("notes\n"); + p.locations_config = .{ .context = 3, .tscontext = false }; + var input: std.Io.Writer.Allocating = .init(p.gpa); + defer input.deinit(); + for (0..65) |index| { + var name_buf: [32]u8 = undefined; + const name = try std.fmt.bufPrint(&name_buf, "f{d:0>2}.c", .{index}); + try tmp.dir.writeFile(std.testing.io, .{ .sub_path = name, .data = if (index == 0) comment else code }); + try input.writer.print("{s}:4:1-6 needle();\n", .{name}); + } + try panes.Output.fillResults(p, 0, dir, .search, "eviction", try p.gpa.dupe(u8, input.written()), null); + const id = source.search_pane.?; + const file = &p.panes[id].?.file.?; + try std.testing.expectEqual(@as(usize, 64), p.locations_cache.len); + try std.testing.expectEqual(@as(usize, 65), p.locations_cache.analyses); + const Color = struct { + fn valueColor(result: *const panes.File.State) !u8 { + for (result.location_rows, 0..) |row, index| { + if (row.kind != .context or row.at.line != 2 or !std.mem.endsWith(u8, row.path, "f00.c")) continue; + const line = modal.lineSlice(result.content, index); + const number = std.mem.indexOf(u8, line[row.code_start..], "42").?; + try std.testing.expectEqualStrings("const int value = 42;", line[row.code_start..]); + try std.testing.expectEqual(line.len - row.code_start, row.colors.len); + return row.colors[number]; + } + return error.MissingEarlyContext; + } + }; + try std.testing.expectEqual(@intFromEnum(pardes.syntax.Syn.comment), try Color.valueColor(file)); + const single = "f00.c:4:1-6 needle();\n"; + // Put the evicted file back before a same-size edit tests exact-byte keys. + try panes.Output.fillResults(p, 0, dir, .search, "eviction", try p.gpa.dupe(u8, single), null); + const analyses = p.locations_cache.analyses; + try tmp.dir.writeFile(std.testing.io, .{ .sub_path = "f00.c", .data = code }); + try panes.Output.fillResults(p, 0, dir, .search, "eviction", try p.gpa.dupe(u8, single), null); + try std.testing.expect(p.locations_cache.analyses > analyses); + try std.testing.expectEqual(@intFromEnum(pardes.syntax.Syn.number), try Color.valueColor(file)); + + // An open buffer has priority over that disk revision, even with equal size. + p.gpa.free(source.file.?.path); + source.file.?.path = try std.fs.path.join(p.gpa, &.{ dir, "f00.c" }); + panes.File.setContent(p, &source.file.?, try p.gpa.dupe(u8, comment)); + try panes.Output.fillResults(p, 0, dir, .search, "eviction", try p.gpa.dupe(u8, single), null); + try std.testing.expectEqual(@intFromEnum(pardes.syntax.Syn.comment), try Color.valueColor(file)); + try std.testing.expectEqualStrings(comment, source.file.?.content); +} |
