Conversation
The instrumented tests under java/android/nnstreamer/src/androidTest never run in CI: the Android jobs only build the library, and running them needs a device. So a regression in the JNI wrapper or in the C-API behavior it relies on is not caught before it is merged. The JNI wrapper is the same code on Ubuntu (meson -Djava-home builds it, and build-nnstreamer-ubuntu.sh builds the Java API), and most of the tests do not need Android. test-nnstreamer-ubuntu.sh compiles the Java API the same way as build-nnstreamer-ubuntu.sh, compiles the tests against small stubs of the Android classes they use (java/host-test/stub), and runs them on a desktop JVM against the JNI wrapper from the meson build. If the JVM crashes, the native stack from the crash report is printed. HostTestRunner runs the test classes with JUnit 4 and skips the tests listed in java/host-test/exclude.txt: the few tests that need an Android element (amcsrc), the Android application context, or the test assets copied from the Android test package. An entry that matches no test fails the run, so the list cannot silently go stale. ml-service is not built, so APITestMLService is left out. The new workflow installs nnstreamer from the PPA, builds ml-api with -Djava-home and runs the script for pull requests that change c/ or java/. On this tree 168 tests pass and 6 are excluded, in about two minutes of test time. Signed-off-by: MyungJoo Ham <myungjoo.ham@samsung.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Nothing builds the instrumented tests, so a change in the Java API or in a test that breaks their compilation goes unnoticed, including the tests that the Ubuntu job has to exclude. Add --build_test to build-nnstreamer-android.sh, which runs assembleDebugAndroidTest after the library is built, and enable it for the x86_64 Android build in CI. Other ABIs build the same Java code, so one ABI is enough. Signed-off-by: MyungJoo Ham <myungjoo.ham@samsung.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The GitHub Ubuntu 22.04 image ships libunwind-14-dev, which conflicts with libunwind-dev required by libgstreamer1.0-dev, so apt refuses to install the GStreamer development packages. Request libunwind-dev explicitly as the nnstreamer workflows do, which lets apt replace the preinstalled package. Signed-off-by: MyungJoo Ham <myungjoo.ham@samsung.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
testSetNullSurface returns early where glimagesink is not installed, which is why it passed locally. The GitHub runner has glimagesink but no display, so the pipeline cannot start and the test fails. The video sink of the Android API renders to an Android native window, which the host cannot provide, so exclude the test with the other Android-only ones. Signed-off-by: MyungJoo Ham <myungjoo.ham@samsung.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
SummaryReviewed the diff (5 files changed, 1 new workflow, host-test harness + stubs, Does it solve the stated problem? Yes. The instrumented tests under Regression risk to other modules: Low. Size vs. topic: Reasonable. Every changed/added file is either the new CI job, the host-test harness (runner + minimal Android stubs, ~250 lines total) needed to make that job possible, or the two small hooks ( Findings[Medium] CI green does not currently mean "blocks merge." I checked [Medium, please double-check] Possible vacuous pass in three sibling surface tests. [Low] Workflow path filters miss top-level build files. [Low / Nit] Self-disclosed cache churn, confirmed real. Editing [Nit] Local-test discoverability. Other checks
VerdictVerdict: ok to merge No Critical or High-severity issues found in the changed code itself; CI is green and the added coverage is demonstrably real (the PR's own mutation-testing table shows genuine failures being caught, and I independently traced every exclusion to a legitimate host limitation). The Medium items above are both non-blocking follow-ups: one is a repo-setting gap (branch protection) outside this PR's file set, the other is a "please double check locally" question about three specific tests rather than a confirmed bug. |
The three negative surface cases wrap the whole scenario in a try block and treat any exception as the expected one. On a host with glimagesink but no display, ml_pipeline_start() already fails, so they would pass on that failure instead of on the setSurface() call they are written for, the same reason testSetNullSurface fails there. Exclude them as well. Also trigger the workflow on the root meson files, as the JNI wrapper is built from them, and describe the scripts in java/README.md, which was empty. Signed-off-by: MyungJoo Ham <myungjoo.ham@samsung.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Addressing the review in #697 (comment). Three of the five items are fixed in
|
ScopeRe-review of Does it solve the stated problem? Yes, unchanged from the previous round: the instrumented tests now compile and run on a desktop JVM against the real JNI wrapper, with a self-enforcing exclude list. Verification of the three "fixed" items
The two items left unchanged (branch protection not requiring this check; the one-time GStreamer-Android cache-key churn) are correctly reasoned: branch protection is a repo setting outside this PR's file set, and the cache churn was already self-disclosed and is a one-time cost, not a correctness issue. New findings (this round)[Low] The new job's check shows up simply as "test", not by a descriptive name. [Informational, not a defect] One check is still pending at review time. Other checks
VerdictVerdict: ok to merge No Critical/High issues. The previous round's two Medium findings are resolved (vacuous pass) or correctly deferred as out-of-scope (branch protection is a repo setting, not this PR's to fix). The one new item (Low) is cosmetic and non-blocking — it doesn't affect current CI behavior, only the discoverability of the check's name. |
The job appears as "test" in the check list, while every other workflow gives its job a descriptive name. Name it, so the check can be told apart from any other job named test, for example when choosing required status checks. Signed-off-by: MyungJoo Ham <myungjoo.ham@samsung.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Addressing the review in #697 (comment).
The three items fixed in |
add-apt-repository asks the Launchpad API about the PPA before writing the source list, and that call returned HTTP 504 in a run, failing the job before anything was built. Retry it a few times. Signed-off-by: MyungJoo Ham <myungjoo.ham@samsung.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
java/android/nnstreamer/src/androidTest) never run in CI and are not even compiled, so a regression in the JNI wrapper, or in the C-API behavior it relies on, reachesmainunnoticed. This came up in the review of [Android] release native handles before their callback data #696 (H5/H6 of [C-Api] Memory-safety audit of C sources: 47 verified issues (6 HIGH / 15 MEDIUM / 26 LOW) — tracking checklist #690), whose regression tests are instrumented tests.meson -Djava-home), so a new job builds it, compiles the tests against small stubs of the Android classes they use, and runs them on a desktop JVM, skipping the tests that need a device (java/host-test/exclude.txt). Separately, the x86_64 Android job now also compiles the instrumented tests (assembleDebugAndroidTest).nns_sink_data_cb) and fail the job, so the bug class is now caught before merge.What it looks like
The new check is
Android JNI test on Ubuntu. It installs nnstreamer fromppa:nnstreamer/ppa, builds ml-api with-Djava-home, and runsjava/test-nnstreamer-ubuntu.sh. The script prints the JUnit result, and the native stack from the crash report if the JVM crashes. It runs on pull requests that changec/**,java/**, or the workflow file, and can also be run locally:The 10 excluded tests need an Android-only element (
amcsrc), an Android native window for the video sink, the Android application context check, or the test assets shipped in the Android test package (png, video, tflite model). The four video-sink cases are excluded together: withglimagesinkpresent but no display the pipeline does not start, so the three negative ones would pass on that failure rather than on thesetSurface()call they are written for.APITestMLServiceis not built, as the host build has no ml-service.Limitations: the host job does not cover the
__ANDROID__code paths, ART's stricter JNI checks, or GStreamer 1.24 (Ubuntu 22.04 ships 1.20); an emulator job would, and was deliberately not chosen for per-PR CI (runtime, flakiness). The job depends on the nnstreamer PPA daily build staying installable.Details for reviewers
Files.
.github/workflows/android-jni-test-ubuntu.yml(new job),java/test-nnstreamer-ubuntu.sh(build and run),java/host-test/stub/**(Android stubs:Context,AssetManager,Build,InstrumentationRegistry,AndroidJUnit4,Surface*),java/host-test/src/.../HostTestRunner.java(JUnit runner honoring the exclude list),java/host-test/exclude.txt, and for the Android sidejava/build-nnstreamer-android.sh(--build_test) with.github/actions/android-build/action.yml.Exclude list. An entry that matches no test fails the run, so the list cannot silently go stale when a test is renamed or removed.
Verification. Besides the green CI run, each failure mode was checked locally (WSL, Ubuntu 22.04, GStreamer 1.20.3, OpenJDK 11):
nns_sink_data_cb←cb_sink_event, native frames printedshellcheck reports nothing on the new script, and the Android build script's warning count is unchanged.
Two runner-specific fixes found by CI:
libunwind-devmust be requested explicitly, as the nnstreamer workflows do (a082c31);testSetNullSurfacefails whereglimagesinkexists but there is no display, so it is excluded (a5d0edd).Not enabled:
-Xcheck:jni. It reports about 33k "JNI call made without checking exceptions" warnings from existing code (CallObjectMethod/CallIntMethodwithout anExceptionCheck, e.g. innns_parse_tensors_data). That is existing JNI hygiene debt and deserves its own issue.Side effect: editing
.github/actions/android-build/action.ymlchanges the GStreamer-Android cache key, so the next runs download that package again untilmainsaves a new cache.🤖 Generated with Claude Code