also looking at this
test(ios): require a deadline per parked call, or a written reason for none
#249
2 files
+71
-24
| @@ -285,8 +285,8 @@ pub const NotificationsBridge = struct { | ||
| 285 | 285 | return poolFull(); |
| 286 | 286 | }; |
| 287 | 287 | |
| 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 | |
| 290 | 290 | // grant, over the write that nobody is waiting in front of. |
| 291 | 291 | const Fn = *const fn (Id, Id, c_ulong, *anyopaque) callconv(.c) void; |
| 292 | 292 | const func: Fn = @ptrCast(&objc.objc_msgSend); |
| @@ -2013,32 +2013,79 @@ test "no case the page waits on casts its argument without answering the other w | ||
| 2013 | 2013 | } |
| 2014 | 2014 | } |
| 2015 | 2015 | |
| 2016 | test "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. | |
| 2018 | fn 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. | |
| 2026 | fn 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 | ||
| 2048 | test "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; | |
| 2023 | 2062 | for (zig_sources) |source| { |
| 2024 | 2063 | 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; | |
| 2027 | 2066 | |
| 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 | } | |
| 2039 | 2086 | } |
| 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); | |
| 2042 | 2089 | } |
| 2043 | 2090 | |
| 2044 | 2091 | test "every call the page waits on has a case that can resolve it" { |