lib/sys/src/font/test.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const subject = @import("root.zig");
3
4 const Catalog = subject.Catalog;
5 const Format = subject.Format;
6 const RootSet = subject.RootSet;
7 const Stats = subject.Stats;
8 const fixture = subject.fixture;
9
10 const debug_io = std.Options.debug_io;
11 const storage_bytes = 64 * 16 + 32 * 24 + 8192;
12
13 const limits: Catalog.Limits = .{
14 .entries = 64,
15 .directories = 32,
16 .path_bytes = 8192,
17 };
18
19 const Harness = struct {
20 tmp: std.testing.TmpDir,
21 layout: fixture.Layout,
22 set: RootSet,
23 storage: [storage_bytes]u8 align(Catalog.storage_alignment),
24 path_buffer: [std.Io.Dir.max_path_bytes]u8,
25
26 fn init(self: *Harness) !void {
27 self.tmp = std.testing.tmpDir(.{});
28 errdefer self.tmp.cleanup();
29 self.layout = try fixture.create(debug_io, self.tmp.dir);
30 const used = try self.tmp.dir.realPath(debug_io, &self.path_buffer);
31 self.set = .{};
32 try std.testing.expect(self.set.append(self.path_buffer[0..used]));
33 }
34
35 fn deinit(self: *Harness) void {
36 fixture.release(debug_io, self.tmp.dir);
37 self.tmp.cleanup();
38 }
39
40 fn walk(self: *Harness, catalog: *Catalog, depth_max: u8, stats: *Stats) !void {
41 try subject.enumerate(debug_io, catalog, &self.set, .{ .depth_max = depth_max }, stats);
42 }
43 };
44
45 fn find(catalog: *const Catalog, suffix: []const u8) ?subject.Entry {
46 for (catalog.entries()) |entry| {
47 if (std.mem.endsWith(u8, catalog.entryPath(entry), suffix)) return entry;
48 }
49 return null;
50 }
51
52 fn totalBytes(catalog: *const Catalog) u64 {
53 var total: u64 = 0;
54 for (catalog.entries()) |entry| total += entry.size_bytes;
55 return total;
56 }
57
58 test "font enumeration admits every font below the depth bound" {
59 var harness: Harness = undefined;
60 try harness.init();
61 defer harness.deinit();
62 var catalog = try Catalog.init(&harness.storage, limits);
63 catalog.activate();
64 defer _ = catalog.deinit();
65 var stats: Stats = .{};
66 try harness.walk(&catalog, fixture.visible_depth, &stats);
67
68 try std.testing.expectEqual(harness.layout.fonts, catalog.entryCount());
69 try std.testing.expectEqual(harness.layout.bytes, totalBytes(&catalog));
70 try std.testing.expectEqual(@as(u32, 1), stats.roots_visited);
71 try std.testing.expectEqual(@as(u32, 1), stats.directories_cycle_skipped);
72 try std.testing.expectEqual(@as(u32, 1), stats.directories_depth_skipped);
73 try std.testing.expectEqual(@as(u32, 0), stats.paths_too_long);
74 const unreadable: u32 = if (harness.layout.locked_readable) 0 else 1;
75 try std.testing.expectEqual(unreadable, stats.directories_unreadable);
76 try std.testing.expectEqual(@as(?subject.Entry, null), find(&catalog, "/Notes.txt"));
77 try std.testing.expectEqual(@as(?subject.Entry, null), find(&catalog, "/TooDeep.ttf"));
78 }
79
80 test "font enumeration reports format, size, and root for every admitted file" {
81 var harness: Harness = undefined;
82 try harness.init();
83 defer harness.deinit();
84 var catalog = try Catalog.init(&harness.storage, limits);
85 catalog.activate();
86 defer _ = catalog.deinit();
87 var stats: Stats = .{};
88 try harness.walk(&catalog, fixture.visible_depth, &stats);
89
90 const regular = find(&catalog, "/Regular.ttf").?;
91 try std.testing.expectEqual(Format.truetype, regular.format);
92 try std.testing.expectEqual(@as(u64, 64), regular.size_bytes);
93 try std.testing.expectEqual(@as(u8, 0), regular.root);
94 try std.testing.expectEqual(Format.opentype, find(&catalog, "/Italic.OTF").?.format);
95 try std.testing.expectEqual(@as(u64, 64), find(&catalog, "/link.ttf").?.size_bytes);
96 const bold = find(&catalog, "/nested/Bold.otf").?;
97 try std.testing.expectEqual(@as(u64, 16), bold.size_bytes);
98 const deep = find(&catalog, "/nested/deep/Deep.ttc").?;
99 try std.testing.expectEqual(Format.truetype_collection, deep.format);
100 try std.testing.expectEqual(Format.type1, find(&catalog, "/loop/Loop.pfb").?.format);
101 try std.testing.expect(std.mem.startsWith(u8, catalog.entryPath(regular), "/"));
102 }
103
104 test "font enumeration at depth zero reads roots only" {
105 var harness: Harness = undefined;
106 try harness.init();
107 defer harness.deinit();
108 var catalog = try Catalog.init(&harness.storage, limits);
109 catalog.activate();
110 defer _ = catalog.deinit();
111 var stats: Stats = .{};
112 try harness.walk(&catalog, 0, &stats);
113
114 try std.testing.expectEqual(@as(u32, 3), catalog.entryCount());
115 try std.testing.expectEqual(@as(u32, 3), stats.directories_depth_skipped);
116 try std.testing.expectEqual(@as(u32, 0), stats.directories_cycle_skipped);
117 try std.testing.expectEqual(@as(u32, 1), stats.directories_visited);
118 }
119
120 test "font enumeration rejects at the entry bound and keeps the accepted prefix" {
121 var harness: Harness = undefined;
122 try harness.init();
123 defer harness.deinit();
124 var catalog = try Catalog.init(&harness.storage, .{
125 .entries = 3,
126 .directories = 32,
127 .path_bytes = 8192,
128 });
129 catalog.activate();
130 defer _ = catalog.deinit();
131 var stats: Stats = .{};
132 try std.testing.expectError(
133 error.FontEntriesExhausted,
134 harness.walk(&catalog, fixture.visible_depth, &stats),
135 );
136 try std.testing.expectEqual(@as(u32, 3), catalog.entryCount());
137 for (catalog.entries()) |entry| {
138 try std.testing.expect(Format.fromName(catalog.entryPath(entry)) != null);
139 }
140 }
141
142 test "font enumeration rejects at the directory bound" {
143 var harness: Harness = undefined;
144 try harness.init();
145 defer harness.deinit();
146 var catalog = try Catalog.init(&harness.storage, .{
147 .entries = 64,
148 .directories = 2,
149 .path_bytes = 8192,
150 });
151 catalog.activate();
152 defer _ = catalog.deinit();
153 var stats: Stats = .{};
154 try std.testing.expectError(
155 error.FontDirectoriesExhausted,
156 harness.walk(&catalog, fixture.visible_depth, &stats),
157 );
158 try std.testing.expectEqual(@as(u32, 2), catalog.directoryCount());
159 }
160
161 test "font enumeration over an unsupported platform walks nothing" {
162 var set: RootSet = .{};
163 const support = subject.rootsFor(&set, .windows, .{ .home = "C:/Users/u" });
164 try std.testing.expectEqual(subject.Unsupported.platform, support.unsupported);
165 var storage: [storage_bytes]u8 align(Catalog.storage_alignment) = undefined;
166 var catalog = try Catalog.init(&storage, limits);
167 catalog.activate();
168 defer _ = catalog.deinit();
169 var stats: Stats = .{};
170 try subject.enumerate(debug_io, &catalog, &set, .{}, &stats);
171 try std.testing.expectEqual(@as(u32, 0), catalog.entryCount());
172 try std.testing.expectEqual(@as(u32, 0), stats.roots_visited);
173 try std.testing.expectEqual(@as(u32, 0), stats.directories_visited);
174 }
175
176 test "host font enumeration terminates over the installed roots" {
177 var set: RootSet = .{};
178 const support = subject.defaultRoots(&set, subject.hostEnvironment());
179 switch (support) {
180 .unsupported => return error.SkipZigTest,
181 .supported => |count| if (count == 0) return error.SkipZigTest,
182 }
183 const host_limits: Catalog.Limits = .{
184 .entries = 16384,
185 .directories = 8192,
186 .path_bytes = 4 << 20,
187 };
188 const derived = try Catalog.Capacity.derive(host_limits);
189 const storage = try std.testing.allocator.alignedAlloc(
190 u8,
191 .fromByteUnits(Catalog.storage_alignment),
192 derived.storage_bytes,
193 );
194 defer std.testing.allocator.free(storage);
195 var catalog = try Catalog.init(storage, host_limits);
196 catalog.activate();
197 defer _ = catalog.deinit();
198 var stats: Stats = .{};
199 subject.enumerate(debug_io, &catalog, &set, .{}, &stats) catch |err| switch (err) {
200 error.Canceled => return err,
201 else => return error.SkipZigTest,
202 };
203 if (stats.roots_visited == 0) return error.SkipZigTest;
204 try std.testing.expectEqual(stats.roots_visited + stats.roots_missing, set.count);
205 try std.testing.expect(stats.directories_visited >= stats.roots_visited);
206 try std.testing.expect(catalog.pathBytesUsed() <= host_limits.path_bytes);
207 for (catalog.entries()) |entry| {
208 const path = catalog.entryPath(entry);
209 try std.testing.expect(path.len != 0 and path[0] == '/');
210 try std.testing.expectEqual(entry.format, Format.fromName(path).?);
211 }
212 }