ReviewOS

also looking at this

craft-native/craft

fix(android): make every Zig decline speak, and prove Zig answers

#205
Merged glennmichael123 wants to merge fix/android-zig-serves into main
8 files +616 -255
scripts/mobile-e2e/protocol.tsmodified+37-0
Changes to scripts/mobile-e2e/protocol.ts
@@ -357,3 +357,40 @@ export const ZIG_TESTED_ACTIONS = [
357357
358358/** The one action the suite configures off, so Zig must refuse it by name. */
359359export const ZIG_REFUSED_ACTION = 'getCurrentPosition'
360
361/**
362 * The three ways an Android native says it gave up, as `android_dispatch.zig`
363 * writes them.
364 *
365 * - fell through to the shim: Kotlin served the action instead of Zig.
366 * - failed with no fallback: a callback into Zig gave up, and nothing will.
367 * - could not reach the page: Zig had the answer and could not deliver it.
368 *
369 * `test/android_declines_test.zig` keeps every decline routed through one of
370 * these, and `protocol.test.ts` reads the Zig source to check the wording has
371 * not drifted from what is matched here. Either half alone would let the suite
372 * stop seeing declines and keep passing.
373 */
374export const ANDROID_DECLINE_PHRASES = [
375 'fell through to the shim',
376 'failed with no fallback',
377 'could not reach the page',
378]
379
380/**
381 * Every Android decline line in a logcat dump, as `action: phrase (error)`.
382 *
383 * Why the runtime leg needs this rather than the registration line alone: the
384 * first run in which the Zig library loaded reported "registered 103 natives"
385 * and passed every case — while `getDeviceInfo` threw `NoSuchFieldError` on
386 * every call and Kotlin quietly answered instead. Registration proves the
387 * library bound. Only the absence of declines, with every decline made to
388 * speak, proves Zig answered.
389 */
390export function androidDeclines(text: string): string[] {
391 const plain = text.replace(ANSI, '')
392 const phrases = ANDROID_DECLINE_PHRASES.map(phrase => phrase.replace(/ /g, '\\s')).join('|')
393 const pattern = new RegExp(`craft: (\\w+) (${phrases}) \\(([^)]*)\\)`, 'g')
394 return [...plain.matchAll(pattern)].map(match => `${match[1]}: ${match[2]} (${match[3]})`)
395}
396