test(PageList): add failing test for reflow grapheme OOM

pull/8277/head
Qwerasd 2025-08-18 18:39:10 -06:00
parent c105d70c73
commit 3fcfc34ef7
2 changed files with 109 additions and 1 deletions

View File

@ -7375,6 +7375,114 @@ test "PageList resize reflow exceeds hyperlink memory forcing capacity increase"
try s.resize(.{ .cols = s.cols + 1, .reflow = true });
}
test "PageList resize reflow exceeds grapheme memory forcing capacity increase" {
const testing = std.testing;
const alloc = testing.allocator;
var s = try init(alloc, 2, 10, 0);
defer s.deinit();
try testing.expectEqual(@as(usize, 1), s.totalPages());
// Grow to the capacity of the first page and add
// one more row so that we have two pages total.
{
const page = &s.pages.first.?.data;
page.pauseIntegrityChecks(true);
for (page.size.rows..page.capacity.rows) |_| {
_ = try s.grow();
}
page.pauseIntegrityChecks(false);
try testing.expectEqual(@as(usize, 1), s.totalPages());
try s.growRows(1);
try testing.expectEqual(@as(usize, 2), s.totalPages());
// We now have two pages.
try std.testing.expect(s.pages.first.? != s.pages.last.?);
try std.testing.expectEqual(s.pages.last.?, s.pages.first.?.next);
}
// We use almost all grapheme alloc capacity with a grapheme in the final
// row of the first page, and do the same on the first row of the second
// page. We also mark the row as wrapped so that when we resize with more
// cols the row unwraps and we have a single row that requires almost two
// times the base grapheme alloc capacity.
//
// This forces the reflow to increase capacity.
//
// +--+ = PAGE 0
// : :
// | X <- where X is a grapheme which uses almost all the capacity.
// +--+
// +--+ = PAGE 1
// X | <- X here also almost hits grapheme cap.
// +--+
// Almost hit grapheme alloc cap in bottom right of first page.
// Mark the final row as wrapped.
{
const page = &s.pages.first.?.data;
const rac = page.getRowAndCell(page.size.cols - 1, page.size.rows - 1);
rac.row.wrap = true;
rac.cell.* = .{
.content_tag = .codepoint,
.content = .{ .codepoint = 'X' },
};
try page.setGraphemes(
rac.row,
rac.cell,
&@as(
[@divFloor(
pagepkg.grapheme_bytes_default - 1,
@sizeOf(u21),
)]u21,
@splat('a'),
),
);
try std.testing.expectError(
error.OutOfMemory,
page.grapheme_alloc.alloc(
u21,
page.memory,
16,
),
);
}
// Almost hit grapheme alloc cap in top left of second page.
// Mark the first row as a wrap continuation.
{
const page = &s.pages.last.?.data;
const rac = page.getRowAndCell(0, 0);
rac.row.wrap = true;
rac.cell.* = .{
.content_tag = .codepoint,
.content = .{ .codepoint = 'X' },
};
try page.setGraphemes(
rac.row,
rac.cell,
&@as(
[@divFloor(
pagepkg.grapheme_bytes_default - 1,
@sizeOf(u21),
)]u21,
@splat('a'),
),
);
try std.testing.expectError(
error.OutOfMemory,
page.grapheme_alloc.alloc(
u21,
page.memory,
16,
),
);
}
// Resize to 1 column wider, unwrapping the row.
try s.resize(.{ .cols = s.cols + 1, .reflow = true });
}
test "PageList resize reflow more cols unwrap wide spacer head" {
const testing = std.testing;
const alloc = testing.allocator;

View File

@ -34,7 +34,7 @@ const grapheme_chunk_len = 4;
const grapheme_chunk = grapheme_chunk_len * @sizeOf(u21);
const GraphemeAlloc = BitmapAllocator(grapheme_chunk);
const grapheme_count_default = GraphemeAlloc.bitmap_bit_size;
const grapheme_bytes_default = grapheme_count_default * grapheme_chunk;
pub const grapheme_bytes_default = grapheme_count_default * grapheme_chunk;
const GraphemeMap = AutoOffsetHashMap(Offset(Cell), Offset(u21).Slice);
/// The allocator used for shared utf8-encoded strings within a page.