ReviewOS

also looking at this

craft-native/craft

test(ios): require a deadline per parked call, or a written reason for none

#249
Merged glennmichael123 wants to merge test/ios-deadline-per-claim into main
2 files +71 -24
packages/zig/src/bridge_mobile_notifications.zigmodified+2-2
Changes to packages/zig/src/bridge_mobile_notifications.zig
@@ -285,8 +285,8 @@ pub const NotificationsBridge = struct {
285285 return poolFull();
286286 };
287287
288 // No deadline yet. Stage one is the authorization prompt, and there is
289 // a person reading it; `authorizationFired` starts the clock on the
288 // no-deadline: stage one is the authorization prompt, and there is a
289 // person reading it. `authorizationFired` starts the clock on the
290290 // grant, over the write that nobody is waiting in front of.
291291 const Fn = *const fn (Id, Id, c_ulong, *anyopaque) callconv(.c) void;
292292 const func: Fn = @ptrCast(&objc.objc_msgSend);
packages/zig/test/ios_conformance_test.zigmodified+69-22
Changes to packages/zig/test/ios_conformance_test.zig
@@ -2013,32 +2013,79 @@ test "no case the page waits on casts its argument without answering the other w
20132013 }
20142014}
20152015
2016test "a module that parks a call on a completion block also gives it a deadline" {
2017 // #223: a call parked on `ios_pending` is one whose only answer comes from
2018 // a framework completion. The table makes a late completion harmless; it
2019 // does not make one that never arrives answer at all. Both halves or
2020 // neither — parking without a deadline is a promise that can wait for
2021 // ever, which is what #211 found on CI twice.
2022 var parked: usize = 0;
2016/// Whether `at` sits on a line that starts a function, the boundary a claim's
2017/// enclosing function is read between.
2018fn startsFunction(source: []const u8, at: usize) bool {
2019 const line = source[at..];
2020 return std.mem.startsWith(u8, line, "fn ") or std.mem.startsWith(u8, line, "pub fn ") or
2021 std.mem.startsWith(u8, line, " fn ") or std.mem.startsWith(u8, line, " pub fn ");
2022}
2023
2024/// The function a `claim(` at `at` sits inside: from the nearest line above it
2025/// that starts one to the next line below that does.
2026fn enclosingFunction(source: []const u8, at: usize) []const u8 {
2027 var begin = at;
2028 while (begin > 0) {
2029 const line_start = if (std.mem.lastIndexOfScalar(u8, source[0..begin], '\n')) |nl| nl + 1 else 0;
2030 if (startsFunction(source, line_start)) {
2031 begin = line_start;
2032 break;
2033 }
2034 if (line_start == 0) {
2035 begin = 0;
2036 break;
2037 }
2038 begin = line_start - 1;
2039 }
2040 var finish = at;
2041 while (std.mem.indexOfScalarPos(u8, source, finish, '\n')) |nl| {
2042 if (startsFunction(source, nl + 1)) return source[begin .. nl + 1];
2043 finish = nl + 1;
2044 }
2045 return source[begin..];
2046}
2047
2048test "every place that parks a call gives it a deadline, or says why not" {
2049 // #223, and a hole the first version of this check had. It asked only
2050 // that a *module* parking on `ios_pending` call `scheduleDeadline`
2051 // somewhere — so once one action in a file had a deadline, a second could
2052 // be added without one and nothing noticed. #248 demonstrated it rather
2053 // than predicted it: deleting that action's deadline passed.
2054 //
2055 // The unit is now the function that claims a block. It has to arm a
2056 // deadline, or carry a `// no-deadline:` line saying why it does not.
2057 // Some calls rightly have none — a prompt with a person in front of it,
2058 // a later stage that an earlier deadline already covers — and the point
2059 // is that leaving one out is a statement someone wrote, not an omission.
2060 const needle = ".claim(";
2061 var claims: usize = 0;
20232062 for (zig_sources) |source| {
20242063 if (std.mem.indexOf(u8, source, "ios_pending.Table(") == null) continue;
2025 parked += 1;
2026 if (std.mem.indexOf(u8, source, "scheduleDeadline(") != null) continue;
2064 // Tests claim blocks to drive the table; they are not dispatch.
2065 const code_end = std.mem.indexOf(u8, source, "const testing = std.testing;") orelse source.len;
20272066
2028 // Named by the first action it declares, which is how these files are
2029 // recognisable without embedding their paths.
2030 const at = std.mem.indexOf(u8, source, "pub const") orelse 0;
2031 const line_end = std.mem.indexOfScalarPos(u8, source, at, '\n') orelse at;
2032 std.debug.print(
2033 "a module parks calls on ios_pending.Table and never calls scheduleDeadline.\n" ++
2034 " Near: {s}\n" ++
2035 " A parked call whose completion never comes settles nothing at all.\n",
2036 .{source[at..line_end]},
2037 );
2038 return error.ParkedCallHasNoDeadline;
2067 var search: usize = 0;
2068 while (std.mem.indexOfPos(u8, source[0..code_end], search, needle)) |at| {
2069 search = at + needle.len;
2070 claims += 1;
2071 const function = enclosingFunction(source, at);
2072 if (std.mem.indexOf(u8, function, "scheduleDeadline(") != null) continue;
2073 if (std.mem.indexOf(u8, function, "// no-deadline:") != null) continue;
2074
2075 const name_end = std.mem.indexOfScalar(u8, function, '(') orelse @min(function.len, 60);
2076 std.debug.print(
2077 "`{s}` parks a call on ios_pending and neither arms a deadline nor says why not.\n" ++
2078 " A parked call whose completion never comes settles nothing at all. Arm\n" ++
2079 " one with ios_async.scheduleDeadline, or write `// no-deadline: <reason>`\n" ++
2080 " in the function if a person is in front of it or an earlier deadline\n" ++
2081 " already covers it.\n",
2082 .{std.mem.trim(u8, function[0..name_end], " \n")},
2083 );
2084 return error.ParkedCallHasNoDeadline;
2085 }
20392086 }
2040 // Non-vacuity: the scan has to be finding the modules that do park.
2041 try testing.expect(parked >= 2);
2087 // Non-vacuity: the scan has to be finding the claims that exist.
2088 try testing.expect(claims >= 3);
20422089}
20432090
20442091test "every call the page waits on has a case that can resolve it" {