lib/pdf/src/filter/test.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const flate = std.compress.flate;
  3 
  4 const filter = @import("root.zig");
  5 
  6 const Storage = filter.Storage;
  7 const bytesFromStream = filter.bytesFromStream;
  8 
  9 fn deflateAlloc(allocator: std.mem.Allocator, raw: []const u8) ![]u8 {
 10     var output = try std.Io.Writer.Allocating.initCapacity(allocator, 64);
 11     errdefer output.deinit();
 12     const window = try allocator.alloc(u8, flate.max_window_len);
 13     defer allocator.free(window);
 14     var compressor = try flate.Compress.init(
 15         &output.writer,
 16         window,
 17         .zlib,
 18         flate.Compress.Options.level_1,
 19     );
 20     try compressor.writer.writeAll(raw);
 21     try compressor.finish();
 22     return try output.toOwnedSlice();
 23 }
 24 
 25 test "PDF filter decodes Flate and PNG predictor streams" {
 26     const raw = [_]u8{ 2, 1, 2, 3, 2, 1, 1, 1 };
 27     const compressed = try deflateAlloc(std.testing.allocator, &raw);
 28     defer std.testing.allocator.free(compressed);
 29     var storage = try Storage.init(std.testing.allocator, .{ .bounds = .{
 30         .input_bytes = compressed.len,
 31         .decoded_bytes = raw.len,
 32     } });
 33     defer storage.deinit(std.testing.allocator);
 34     storage.activate();
 35     const decoded = try bytesFromStream(
 36         &storage,
 37         "FlateDecode",
 38         .{ .predictor = 12, .columns = 3 },
 39         compressed,
 40     );
 41     defer storage.reset();
 42     try std.testing.expectEqualSlices(u8, &.{ 1, 2, 3, 2, 3, 4 }, decoded);
 43 }
 44 
 45 test "PDF filter storage accepts exact bounds and rejects max plus one" {
 46     comptime {
 47         @stardustClaim(
 48             @import("alloc_phase").capacity.witness(@import("./root.zig").Storage, "pdf_filter_boundaries"),
 49             null,
 50             null,
 51             null,
 52             null,
 53             null,
 54             null,
 55         );
 56     }
 57 
 58     var storage = try Storage.init(std.testing.allocator, .{ .bounds = .{
 59         .input_bytes = 5,
 60         .decoded_bytes = 4,
 61     } });
 62     defer storage.deinit(std.testing.allocator);
 63     storage.activate();
 64     @memset(storage.output, 0xa5);
 65     const hash = std.hash.Wyhash.hash(0, storage.output);
 66     try std.testing.expectError(
 67         error.InputByteCapacityExceeded,
 68         bytesFromStream(&storage, null, .{}, "123456"),
 69     );
 70     try std.testing.expectEqual(hash, std.hash.Wyhash.hash(0, storage.output));
 71     const exact = try bytesFromStream(&storage, null, .{}, "1234");
 72     try std.testing.expectEqualStrings("1234", exact);
 73     storage.reset();
 74     try std.testing.expectError(
 75         error.DecodedByteCapacityExceeded,
 76         bytesFromStream(&storage, null, .{}, "12345"),
 77     );
 78 }
 79 
 80 test "PDF filter storage rejects concurrent use and supports reuse" {
 81     comptime {
 82         @stardustClaim(
 83             @import("alloc_phase").capacity.witness(@import("./root.zig").Storage, "pdf_filter_reuse"),
 84             null,
 85             null,
 86             null,
 87             null,
 88             null,
 89             null,
 90         );
 91     }
 92 
 93     var storage = try Storage.init(std.testing.allocator, .{ .bounds = .{
 94         .input_bytes = 4,
 95         .decoded_bytes = 4,
 96     } });
 97     defer storage.deinit(std.testing.allocator);
 98     storage.activate();
 99     _ = try bytesFromStream(&storage, null, .{}, "one");
100     try std.testing.expectError(
101         error.DecodeStorageInUse,
102         bytesFromStream(&storage, null, .{}, "two"),
103     );
104     storage.reset();
105     const second = try bytesFromStream(&storage, null, .{}, "two");
106     defer storage.reset();
107     try std.testing.expectEqualStrings("two", second);
108 }
109 
110 test "Activated PDF filter storage performs no backing allocation" {
111     comptime {
112         @stardustClaim(
113             @import("alloc_phase").capacity.witness(@import("./root.zig").Storage, "pdf_filter_sealed"),
114             null,
115             null,
116             null,
117             null,
118             null,
119             null,
120         );
121     }
122 
123     var counting = std.testing.FailingAllocator.init(std.testing.allocator, .{});
124     var storage = try Storage.init(counting.allocator(), .{ .bounds = .{
125         .input_bytes = 4,
126         .decoded_bytes = 4,
127     } });
128     defer storage.deinit(counting.allocator());
129     storage.activate();
130     const decoded = try bytesFromStream(&storage, null, .{}, "data");
131     defer storage.reset();
132     try std.testing.expectEqualStrings("data", decoded);
133     try std.testing.expectEqual(@as(usize, 1), counting.alloc_index);
134     try std.testing.expectEqual(@as(usize, 0), counting.resize_index);
135 }
136 
137 test "PDF filter decode errors release storage before retry" {
138     var storage = try Storage.init(std.testing.allocator, .{ .bounds = .{
139         .input_bytes = 4,
140         .decoded_bytes = 4,
141     } });
142     defer storage.deinit(std.testing.allocator);
143     storage.activate();
144     try std.testing.expectError(
145         error.DecodeFailed,
146         bytesFromStream(&storage, "FlateDecode", .{}, "bad"),
147     );
148     try std.testing.expect(!storage.status().in_use);
149     const decoded = try bytesFromStream(&storage, null, .{}, "ok");
150     defer storage.reset();
151     try std.testing.expectEqualStrings("ok", decoded);
152 }
153 
154 test "PDF filter DEFLATE overflow stays private and releases storage" {
155     comptime {
156         @stardustClaim(
157             @import("alloc_phase").capacity.witness(@import("./root.zig").Storage, "pdf_filter_decode_overflow"),
158             null,
159             null,
160             null,
161             null,
162             null,
163             null,
164         );
165     }
166 
167     const compressed = try deflateAlloc(std.testing.allocator, "12345");
168     defer std.testing.allocator.free(compressed);
169     var storage = try Storage.init(std.testing.allocator, .{ .bounds = .{
170         .input_bytes = compressed.len,
171         .decoded_bytes = 4,
172     } });
173     defer storage.deinit(std.testing.allocator);
174     storage.activate();
175     try std.testing.expectError(
176         error.DecodedByteCapacityExceeded,
177         bytesFromStream(&storage, "FlateDecode", .{}, compressed),
178     );
179     try std.testing.expect(!storage.status().in_use);
180     const decoded = try bytesFromStream(&storage, null, .{}, "next");
181     defer storage.reset();
182     try std.testing.expectEqualStrings("next", decoded);
183 }
184 
185 test "PDF filter rejects unsupported filters" {
186     var storage = try Storage.init(std.testing.allocator, .{ .bounds = .{
187         .input_bytes = 1,
188         .decoded_bytes = 1,
189     } });
190     defer storage.deinit(std.testing.allocator);
191     storage.activate();
192     try std.testing.expectError(
193         error.UnsupportedFilter,
194         bytesFromStream(&storage, "LZWDecode", .{}, ""),
195     );
196 }