also looking at this
test(android): the deferral table, and the no-op it was needed for
#149
1 file
+70
-0
| @@ -124,6 +124,76 @@ fn collectZigActions(allocator: std.mem.Allocator) !std.StringHashMap(void) { | ||
| 124 | 124 | return set; |
| 125 | 125 | } |
| 126 | 126 | |
| 127 | const 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. | |
| 146 | const 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 | ||
| 160 | test "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 | ||
| 127 | 197 | test "the spec scan finds the bridge, and finds methods in it" { |
| 128 | 198 | // Non-vacuity, first. Every assertion below is a membership check, and a |
| 129 | 199 | // scan that silently matched nothing would satisfy all of them. |