| ... | ... | @@ -245,10 +245,11 @@ pub fn writeSplatHeaderLimit( |
| 245 | 245 | |
| 246 | 246 | if (remaining == 0) break :remaining_zero; |
| 247 | 247 | const pattern = data[data.len - 1]; |
| 248 | | for (0..splat) |_| { |
| 248 | for (0..splat) |i| { |
| 249 | 249 | const copy_len = @min(pattern.len, remaining); |
| 250 | 250 | if (w.buffer.len - w.end < copy_len) { |
| 251 | | const n = try writeSplatHeaderLimitFinish(w, &.{}, data[data.len - 1 ..][0..1], splat, remaining); |
| 251 | const remaining_splat = splat - i; |
| 252 | const n = try writeSplatHeaderLimitFinish(w, &.{}, data[data.len - 1 ..][0..1], remaining_splat, remaining); |
| 252 | 253 | return @backingInt(limit) - remaining + n; |
| 253 | 254 | } |
| 254 | 255 | @memcpy(w.buffer[w.end..][0..copy_len], pattern[0..copy_len]); |
| ... | ... | @@ -300,7 +301,10 @@ fn writeSplatHeaderLimitFinish( |
| 300 | 301 | return w.vtable.drain(w, (&vecs)[0..i], 1); |
| 301 | 302 | } |
| 302 | 303 | |
| 303 | | const FixedSplatHeaderTestCase = struct { |
| 304 | const SplatHeaderTestCase = struct { |
| 305 | writer_type: enum { fixed, allocating }, |
| 306 | /// When writer_type is .fixed, determines the buffer size. |
| 307 | /// When writer_type is .allocating, determines the initial capacity. |
| 304 | 308 | buf_len: usize = 100, |
| 305 | 309 | header: []const u8, |
| 306 | 310 | data: []const []const u8, |
| ... | ... | @@ -310,9 +314,18 @@ const FixedSplatHeaderTestCase = struct { |
| 310 | 314 | expected_buf_content: []const u8, |
| 311 | 315 | }; |
| 312 | 316 | |
| 313 | | fn testFixedWriteSplatHeaderLimit(comptime test_case: FixedSplatHeaderTestCase) !void { |
| 317 | fn testWriteSplatHeaderLimit(comptime test_case: SplatHeaderTestCase) !void { |
| 314 | 318 | var buf: [test_case.buf_len]u8 = @splat(0); |
| 315 | | var w: std.Io.Writer = .fixed(&buf); |
| 319 | var aw: Allocating = if (test_case.writer_type == .allocating) |
| 320 | try Allocating.initCapacity(testing.allocator, test_case.buf_len) |
| 321 | else |
| 322 | undefined; |
| 323 | defer if (test_case.writer_type == .allocating) aw.deinit(); |
| 324 | var fw: Writer = if (test_case.writer_type == .fixed) .fixed(&buf) else undefined; |
| 325 | var w: *Writer = switch (test_case.writer_type) { |
| 326 | .allocating => &aw.writer, |
| 327 | .fixed => &fw, |
| 328 | }; |
| 316 | 329 | const n_or_error = w.writeSplatHeaderLimit(test_case.header, test_case.data, test_case.splat, .limited(test_case.limit)); |
| 317 | 330 | switch (test_case.expected_res) { |
| 318 | 331 | .written => |expected_len| { |
| ... | ... | @@ -327,20 +340,23 @@ fn testFixedWriteSplatHeaderLimit(comptime test_case: FixedSplatHeaderTestCase) |
| 327 | 340 | } |
| 328 | 341 | |
| 329 | 342 | test "fixed writer writeSplatHeaderLimit" { |
| 330 | | // buffer is large |
| 331 | | try testFixedWriteSplatHeaderLimit(.{ .header = "header is longer", .data = &.{""}, .splat = 1, .limit = 6, .expected_res = .{ .written = 6 }, .expected_buf_content = "header" }); |
| 332 | | try testFixedWriteSplatHeaderLimit(.{ .header = "head", .data = &.{"123456"}, .splat = 1, .limit = 5, .expected_res = .{ .written = 5 }, .expected_buf_content = "head1" }); |
| 333 | | try testFixedWriteSplatHeaderLimit(.{ .header = "head", .data = &.{"123"}, .splat = 1, .limit = 10, .expected_res = .{ .written = 7 }, .expected_buf_content = "head123" }); |
| 334 | | try testFixedWriteSplatHeaderLimit(.{ .header = "head", .data = &.{ "1", "abcdefg" }, .splat = 1, .limit = 6, .expected_res = .{ .written = 6 }, .expected_buf_content = "head1a" }); |
| 335 | | try testFixedWriteSplatHeaderLimit(.{ .header = "head", .data = &.{ "123", "abc" }, .splat = 2, .limit = 6, .expected_res = .{ .written = 6 }, .expected_buf_content = "head12" }); |
| 336 | | try testFixedWriteSplatHeaderLimit(.{ .header = "head", .data = &.{ "123", "abc" }, .splat = 2, .limit = 11, .expected_res = .{ .written = 11 }, .expected_buf_content = "head123abca" }); |
| 337 | | try testFixedWriteSplatHeaderLimit(.{ .header = "head", .data = &.{ "123", "a" }, .splat = 2, .limit = 10, .expected_res = .{ .written = 9 }, .expected_buf_content = "head123aa" }); |
| 338 | | try testFixedWriteSplatHeaderLimit(.{ .header = "head", .data = &.{ "123", "abc" }, .splat = 2, .limit = 100, .expected_res = .{ .written = 13 }, .expected_buf_content = "head123abcabc" }); |
| 339 | | |
| 340 | | // buffer is small |
| 341 | | try testFixedWriteSplatHeaderLimit(.{ .header = "header is longer", .data = &.{""}, .splat = 1, .limit = 6, .expected_res = .write_failed, .expected_buf_content = "head", .buf_len = 4 }); |
| 342 | | try testFixedWriteSplatHeaderLimit(.{ .header = "head", .data = &.{"123456"}, .splat = 1, .limit = 8, .expected_res = .write_failed, .expected_buf_content = "head1", .buf_len = 5 }); |
| 343 | | try testFixedWriteSplatHeaderLimit(.{ .header = "head", .data = &.{ "123", "ab" }, .splat = 2, .limit = 100, .expected_res = .write_failed, .expected_buf_content = "head123aba", .buf_len = 10 }); |
| 343 | // fixed writer with buffer larger than the full data size |
| 344 | try testWriteSplatHeaderLimit(.{ .writer_type = .fixed, .header = "header is longer", .data = &.{""}, .splat = 1, .limit = 6, .expected_res = .{ .written = 6 }, .expected_buf_content = "header" }); |
| 345 | try testWriteSplatHeaderLimit(.{ .writer_type = .fixed, .header = "head", .data = &.{"123456"}, .splat = 1, .limit = 5, .expected_res = .{ .written = 5 }, .expected_buf_content = "head1" }); |
| 346 | try testWriteSplatHeaderLimit(.{ .writer_type = .fixed, .header = "head", .data = &.{"123"}, .splat = 1, .limit = 10, .expected_res = .{ .written = 7 }, .expected_buf_content = "head123" }); |
| 347 | try testWriteSplatHeaderLimit(.{ .writer_type = .fixed, .header = "head", .data = &.{ "1", "abcdefg" }, .splat = 1, .limit = 6, .expected_res = .{ .written = 6 }, .expected_buf_content = "head1a" }); |
| 348 | try testWriteSplatHeaderLimit(.{ .writer_type = .fixed, .header = "head", .data = &.{ "123", "abc" }, .splat = 2, .limit = 6, .expected_res = .{ .written = 6 }, .expected_buf_content = "head12" }); |
| 349 | try testWriteSplatHeaderLimit(.{ .writer_type = .fixed, .header = "head", .data = &.{ "123", "abc" }, .splat = 2, .limit = 11, .expected_res = .{ .written = 11 }, .expected_buf_content = "head123abca" }); |
| 350 | try testWriteSplatHeaderLimit(.{ .writer_type = .fixed, .header = "head", .data = &.{ "123", "a" }, .splat = 2, .limit = 10, .expected_res = .{ .written = 9 }, .expected_buf_content = "head123aa" }); |
| 351 | try testWriteSplatHeaderLimit(.{ .writer_type = .fixed, .header = "head", .data = &.{ "123", "abc" }, .splat = 2, .limit = 100, .expected_res = .{ .written = 13 }, .expected_buf_content = "head123abcabc" }); |
| 352 | |
| 353 | // fixed writer with buffer smaller than the full data size |
| 354 | try testWriteSplatHeaderLimit(.{ .writer_type = .fixed, .header = "header is longer", .data = &.{""}, .splat = 1, .limit = 6, .expected_res = .write_failed, .expected_buf_content = "head", .buf_len = 4 }); |
| 355 | try testWriteSplatHeaderLimit(.{ .writer_type = .fixed, .header = "head", .data = &.{"123456"}, .splat = 1, .limit = 8, .expected_res = .write_failed, .expected_buf_content = "head1", .buf_len = 5 }); |
| 356 | try testWriteSplatHeaderLimit(.{ .writer_type = .fixed, .header = "head", .data = &.{ "123", "ab" }, .splat = 2, .limit = 100, .expected_res = .write_failed, .expected_buf_content = "head123aba", .buf_len = 10 }); |
| 357 | |
| 358 | // allocating writer that needs to expand capacity during splat |
| 359 | try testWriteSplatHeaderLimit(.{ .writer_type = .allocating, .buf_len = 8, .header = "hhhh", .data = &.{"PP"}, .splat = 3, .limit = 100, .expected_res = .{ .written = 10 }, .expected_buf_content = "hhhhPPPPPP" }); |
| 344 | 360 | } |
| 345 | 361 | |
| 346 | 362 | test "writeSplatHeader splatting avoids buffer aliasing temptation" { |