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.test.tsmodified+44-1
Changes to scripts/mobile-e2e/protocol.test.ts
@@ -1,5 +1,7 @@
11import { describe, expect, it } from 'bun:test'
2import { evaluateRun, hasTerminated, parseDriverOutput, REQUIRED_CASES, requiredCaseProblems, ZIG_REFUSED_ACTION, ZIG_TESTED_ACTIONS, zigDispatchedActions, zigRefusals } from './protocol'
2import { readFileSync } from 'node:fs'
3import { join } from 'node:path'
4import { ANDROID_DECLINE_PHRASES, androidDeclines, evaluateRun, hasTerminated, parseDriverOutput, REQUIRED_CASES, requiredCaseProblems, ZIG_REFUSED_ACTION, ZIG_TESTED_ACTIONS, zigDispatchedActions, zigRefusals } from './protocol'
35
46const ESC = String.fromCharCode(27)
57
@@ -276,3 +278,44 @@ describe('zig attribution', () => {
276278 expect(ZIG_TESTED_ACTIONS).toContain(ZIG_REFUSED_ACTION)
277279 })
278280})
281
282describe('android declines', () => {
283 // The lines the first Android runtime run actually produced, plus the two
284 // decline kinds it did not hit, so all three are read the way they are
285 // written.
286 const logcat = [
287 '09-16 11:29:24.000 2611 2611 I CraftNative: craft: libcraft.so loaded',
288 '09-16 11:29:24.001 2611 2611 I CraftNative: craft: registered 103 natives on com/craft/runtime/CraftNative',
289 '09-16 11:29:24.535 2611 2705 W System.err: java.lang.NoSuchFieldError: no "J" field "longVersionCode"',
290 '09-16 11:29:24.535 2611 2705 W CraftNative: craft: getDeviceInfo fell through to the shim (JavaException)',
291 '09-16 11:29:25.000 2611 2705 E CraftNative: craft: locationResult failed with no fallback (OutOfMemory)',
292 '09-16 11:29:26.000 2611 2705 E CraftNative: craft: reviewError could not reach the page (NoWebView)',
293 '09-16 11:29:26.100 2611 2705 D CraftBridge: CRAFT-E2E {"event":"done","passed":4,"failed":0}',
294 ].join('\n')
295
296 it('reads every kind of decline, and names the action and the error', () => {
297 expect(androidDeclines(logcat)).toEqual([
298 'getDeviceInfo: fell through to the shim (JavaException)',
299 'locationResult: failed with no fallback (OutOfMemory)',
300 'reviewError: could not reach the page (NoWebView)',
301 ])
302 })
303
304 // The case that shipped: bound, every page case passing, and Kotlin
305 // answering. Registration is not a decline and must not be read as one —
306 // and a run with nothing but registration is the clean result.
307 it('does not mistake loading or registration for a decline', () => {
308 const clean = logcat.split('\n').filter(line => !/fell through|no fallback|reach the page/.test(line)).join('\n')
309 expect(androidDeclines(clean)).toEqual([])
310 })
311
312 it('matches the wording android_dispatch.zig actually writes', () => {
313 // Read from the Zig source rather than trusted, because each side alone
314 // would let a reworded helper turn the runtime leg blind: Zig would log a
315 // phrase this suite no longer matched, and every decline would pass.
316 const dispatch = readFileSync(join(import.meta.dir, '../../packages/zig/src/android_dispatch.zig'), 'utf8')
317 for (const phrase of ANDROID_DECLINE_PHRASES)
318 expect(dispatch).toContain(`" ${phrase} ({s})"`)
319 })
320})
321