Document JNI host checks and their platform requirements

This commit is contained in:
evgeny committed 2026-10-28 12:28:28 +01:00
1 parent 0931eeedad
commit 269d09815b
2 files changed
+38 -3

No files matched your search

@@ -0,0 +1,34 @@
# Проверка JNI-доставки логов
Linux host, без телефона и запущенного ядра. Нужны Python 3, C99-компилятор,
заголовки OpenSSL и JDK с `include/jni.h`. Используется настоящий
`jni_bridge/android_jni_bridge.c`: неиспользуемые функции отсекает ELF linker.
JNI function tables моделируют JVM, локальные ссылки и принудительные отказы;
Android logcat заменён только проверяющим приёмником диагностики.
Из корня репозитория:
```sh
python3 tools/chatgui-android/tests/jni_log/run.py
# Явный JDK / другой компилятор:
CC=clang python3 tools/chatgui-android/tests/jni_log/run.py --java-home /path/to/jdk
```
В Android Gradle на Linux задача `:app:testJniLog` запускается перед JVM-тестами
приложения, включая `:app:testDebugUnitTest`. На других ОС эта host-задача пропускается.
Android `AttachCurrentThread` принимает `JNIEnv**`, JDK — `void**` с тем же ABI;
только это различие типов допускается host-компилятором. Настоящая NDK-сборка
проверяет Android-заголовки отдельно. Host-модели не включаются в APK.
22 сценария проверяют GetEnv/attach/detach, получение callback/class/method,
создание/копирование массивов, исходный UTF-8 с кириллицей/emoji, освобождение
всех локальных ссылок и продолжение после ошибки. Отдельно проверяются
Java-исключение callback, сохранение чужого pending exception, подавление
рекурсии и удержание local ref при замене global callback.
Для операций, допускающих `NULL` без Java-исключения, проверяются оба отказа.
Тест не запускает ART и не проверяет сборщик мусора Android; JVM-тест
`NativeLogContractTest` дополнительно сопоставляет C lookup с дескриптором
скомпилированного Kotlin `onNativeLog(Int, ByteArray?, ByteArray?)`.
Правила JNI: [Android JNI tips](https://developer.android.com/ndk/guides/jni-tips),
[JNI function specification](https://docs.oracle.com/en/java/javase/17/docs/specs/jni/functions.html).
+4 -3
View File
@@ -3,6 +3,7 @@
import argparse
import os
from pathlib import Path
import shlex
import shutil
import subprocess
import tempfile
@@ -18,19 +19,19 @@ def main():
java_home = args.java_home or (Path(javac).resolve().parents[1] if javac else None)
if java_home is None or not (java_home / "include/jni.h").is_file():
parser.error("JDK JNI headers missing; pass --java-home")
compiler = os.environ.get("CC", "cc")
compiler = shlex.split(os.environ.get("CC", "cc"))
includes = [directory, java_home / "include", java_home / "include/linux",
root / "src", root / "src/transport_layer", root / "src/routing_layer",
root / "src/chat", root / "src/media_async", root / "lib"]
with tempfile.TemporaryDirectory(prefix="utun-jni-log-") as output:
binary = Path(output) / "test_jni_log"
command = [compiler, "-std=c99", "-D_DEFAULT_SOURCE", "-D__ANDROID__", "-DUSE_OPENSSL",
command = compiler + ["-std=c99", "-D_DEFAULT_SOURCE", "-D__ANDROID__", "-DUSE_OPENSSL",
"-ffunction-sections", "-fdata-sections", "-Wl,--gc-sections", "-pthread",
# Android JNI принимает JNIEnv**, JDK — void**; ABI одинаков, чужие callbacks здесь не вызываются.
"-Wno-incompatible-pointer-types"]
command += [f"-I{path}" for path in includes]
command += [str(directory / "test_jni_log.c"), "-o", str(binary)]
subprocess.run(command, check=True)
subprocess.run(command, check=True, timeout=90)
subprocess.run([str(binary)], check=True, timeout=15)