ReviewOS

also looking at this

craft-native/craft

test(android): compare each JNI descriptor with its Kotlin external fun

#235
Merged glennmichael123 wants to merge test/android-jni-descriptors into main
1 file +192 -3
9 days ago

Closes #230.

What was missing

android_conformance_test.zig compared the two sides of the JNI binding by name only, in both directions. The descriptor — the (Landroid/app/Activity;Z)Z half — had nothing checking it.

That is the quietest of the three failures. RegisterNatives refuses the whole table when one entry's descriptor does not match the class, so the library still loads, no native binds at all, and every action falls back to the Kotlin shim. The shim E2E leg passes, because the shim is what it tests. Only the runtime leg notices, as a missing registered N natives line, after a full emulator run.

#215 changed one descriptor, nativeDispatchDeepLink to (Ljava/lang/String;Z)Z, and could only pin that one string in a Zig test. Whether it matched the Kotlin was still CI's job.

What it checks now

Each external fun in CraftNative.kt is turned into the descriptor JNI expects for it, and compared with the signature the table registers under that name.

Types resolve in two steps:

  • Kotlin builtins from a small map: Boolean → Z, Int → I, Long → J, Float → F, Double → D, ByteArray → [B, String → Ljava/lang/String;, and a missing return type → V.
  • Everything else from the holder's own import lines. Activity finds import android.app.Activity and becomes Landroid/app/Activity;. So the five classes the file passes today — Activity, WebView, IntentSender, SharedPreferences, SQLiteDatabase — need no table, and neither will the next one.

A type that resolves as neither fails the test by name, rather than being skipped:

CraftNative.kt uses the type 'NotAThing', which this test cannot turn into a
  JNI descriptor: it is neither a Kotlin builtin nor a class the file imports.
  Add it to builtinDescriptor, or import it in CraftNative.kt.

String? and String are one descriptor: nullability is Kotlin's, not the JVM's. The parser reads across newlines, so the declarations that wrap — nativeLocationResult and its seven parameters — are covered like the rest.

Mutations run against it

mutationresult
nativeDispatchDeepLink registered as (Ljava/lang/String;)Z, its shape before #215DescriptorDoesNotMatchKotlin
Kotlin changes nativeStartListening's available: Boolean to IntDescriptorDoesNotMatchKotlin
Kotlin adds a parameter of an imported type the table does not carryDescriptorDoesNotMatchKotlin
Kotlin adds a parameter of a type that is neither builtin nor importedUnknownKotlinType, with the message above

Each prints both descriptors, so the failure says which side to change.

The test also asserts both scans found at least 100 entries, so a file that stops matching cannot leave it comparing empty maps — the same non-vacuity guard the two name tests carry.

Verification

zig build test and zig build test:android, clean before and after each mutation.

1 changed file on the files tab, with 0 review threads.

Sign in to comment on this pull request.