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 }