ReviewOS

also looking at this

craft-native/craft

test(android): the deferral table, and the no-op it was needed for

#149
Merged glennmichael123 wants to merge test/android-deferral-table into main
1 file +70 -0
packages/zig/test/android_conformance_test.zigmodified+70-0
Changes to packages/zig/test/android_conformance_test.zig
@@ -124,6 +124,76 @@ fn collectZigActions(allocator: std.mem.Allocator) !std.StringHashMap(void) {
124124 return set;
125125}
126126
127const Deferral = struct {
128 action: []const u8,
129 reason: []const u8,
130};
131
132/// Actions deliberately left on the Kotlin, with the reason each is left.
133///
134/// The iOS table this mirrors exists because three of its reasons had expired
135/// and nothing re-checked them — a migration is where prose goes stale fastest,
136/// since the thing a reason describes is exactly what the next commit changes.
137/// So the table arrives here with the first row that needs it rather than after
138/// the tenth, and the two guards below are the whole point: a row Zig starts
139/// serving fails, and so does a row naming an action the Kotlin no longer has.
140///
141/// Deliberately non-exhaustive. Ninety-one actions are unmigrated and almost
142/// all of them are simply not reached yet, which is an honest state that needs
143/// no row. A reason is owed only where someone would otherwise try and find
144/// out the hard way — and demanding one for the rest would invite an invented
145/// reason, which is worse than silence.
146const deliberate_deferrals = [_]Deferral{
147 // Not a capability gap: the Kotlin builds a `NotificationManagerCompat`,
148 // discards it, never reads `count`, and returns. The real work is left as
149 // two comments. Migrating it would mean porting a no-op — the same reason
150 // the iOS table refuses `startVideoRecording`, whose success path has never
151 // run.
152 //
153 // Android has no first-party badge API and the Kotlin's comment is right
154 // about that; launchers implement it through vendor broadcasts. But the
155 // page cannot tell, because the same call works on iOS. Tracked in #148.
156 .{ .action = "setBadge", .reason = "the Kotlin discards the manager it builds and never reads count; there is no working contract to port" },
157 .{ .action = "clearBadge", .reason = "delegates to setBadge, which does nothing" },
158};
159
160test "every recorded deferral is real, and still a deferral" {
161 // The anti-rot property. A deferral that has been migrated must fail here
162 // rather than sit in a list telling the next reader not to bother.
163 var spec = try collectSpecActions(testing.allocator);
164 defer spec.deinit();
165
166 var zig = try collectZigActions(testing.allocator);
167 defer zig.deinit();
168
169 for (deliberate_deferrals) |d| {
170 if (!spec.contains(d.action)) {
171 std.debug.print(
172 "the deferral table names `{s}`, which CraftBridge.kt does not expose — " ++
173 "typo, or the method was renamed.\n",
174 .{d.action},
175 );
176 return error.DeferralNamesNoSuchAction;
177 }
178 if (zig.contains(d.action)) {
179 std.debug.print(
180 "the deferral table still lists `{s}`, but Zig serves it now — " ++
181 "delete the row, its reason is spent.\n",
182 .{d.action},
183 );
184 return error.DeferralAlreadyMigrated;
185 }
186 // A reason is the entire point of the row.
187 try testing.expect(d.reason.len > 0);
188 }
189
190 // Non-vacuity: the loop above is satisfied by an empty table.
191 try testing.expect(deliberate_deferrals.len >= 2);
192
193 // And the table cannot claim more than remain unmigrated.
194 try testing.expect(deliberate_deferrals.len <= max_not_yet_migrated);
195}
196
127197test "the spec scan finds the bridge, and finds methods in it" {
128198 // Non-vacuity, first. Every assertion below is a membership check, and a
129199 // scan that silently matched nothing would satisfy all of them.