Describe the bug
#6444 surfaced an issue in the code coverage workflow.
Removing the test_file_not_required exemptions for the two DragDropSortInput providers in #6444 exposes a coverage build failure. Coverage for DragDropSortInputHasElementXBeforeElementYClassifierProviderTest fails while instrumenting a transitive RatioInput dependency, before tests execute.
The reported CI log contains:
Caused by: java.lang.IllegalStateException: Cannot process instrumented class
org/oppia/android/domain/classify/rules/ratioinput/RatioInputHasSpecificTermEqualToRuleClassifierProvider$createRuleClassifier$$inlined$createDoubleInputClassifier$1.
Please supply original non-instrumented classes.
The stack passes through JacocoInstrumentationKt.createCoverageInstrumentedJar and InstrSupport.assertNotInstrumented. JaCoCo version: 0.8.9.202303300400/461ebf3. The failed build target is //domain/src/main/java/org/oppia/android/domain/classify/rules/ratioinput:ratio_input_providers_kt; its output JAR is consequently missing.
Steps To Reproduce
On the develp branch, use the repository's normal build configuration and run:
bazel coverage \
//domain:src/test/java/org/oppia/android/domain/classify/rules/dragAndDropSortInput/DragDropSortInputHasElementXBeforeElementYClassifierProviderTest \
--instrumentation_filter=//domain/... \
--test_output=errors
To exercise the exemption trigger, run //scripts:run_coverage for domain/src/main/java/org/oppia/android/domain/classify/rules/dragAndDropSortInput/DragDropSortInputHasElementXBeforeElementYClassifierProvider.kt before and after removing its test_file_not_required exemption. Before removal, the script skips this source's coverage. After removal, it attempts coverage for its test.
Expected Behavior
Removing a stale test-file exemption should allow coverage to run successfully. Kotlin compilation should consume original bytecode and coverage instrumentation should not process classes that already contain JaCoCo instrumentation.
Screenshots/Videos
No response
What device/emulator are you using?
No response
Which Android version is your device/emulator running?
No response
Which version of the Oppia Android app are you using?
No response
Additional Context
I put this through Codex to analyze the stacktrace. This is what we came up with:
The coverage failure is caused by JaCoCo double-instrumenting Kotlin-generated inline classes across Bazel target boundaries.
BazelClient.runCoverageForTestTarget() applies --instrumentation_filter=//domain/..., making all eligible dependencies within the domain module subject to instrumentation, not just the source under test. Although RatioInputHasSpecificTermEqualToRuleClassifierProvider.kt is marked source_file_is_incompatible_with_code_coverage: true, this exemption only prevents coverage from being run directly for that source. It does not exclude the class from Bazel instrumentation when it is a dependency of another test.
I double-checked this through bytecode inspection. GenericRuleClassifier.Factory.createDoubleInputClassifier() is an inline function defined in a separate Bazel target. It calls another inline function, InteractionObjectTypeExtractorRepository.getExtractor(), which generates a lambda. The generic classifier's compile/ABI JAR already contains JaCoCo-instrumented versions of these generated classes, confirmed by the presence of $jacocoData, $jacocoInit(), and Offline.getProbes. When Kotlin inlines the factory into consuming classifiers such as RatioInput and FractionInput, it copies this already-instrumented bytecode. The consumer's coverage step then attempts to instrument it again, causing JaCoCo to fail with Cannot process instrumented class.
- Running the test normally: PASS.
- Running coverage with
--instrumentation_filter=//domain/...: FAIL, during compilation, before any tests execute.
- Running coverage with the generic classifier's Bazel package excluded: PASS, successfully generating
coverage.dat.
'--instrumentation_filter=//domain/...,-//domain/src/main/java/org/oppia/android/domain/classify/rules:'
The trailing colon limits the exclusion to the factory's exact Bazel package, allowing classifier subpackages to remain instrumented. This package exclusion is just a diagnostic workaround but reduces coverage for the generic classifier package.
A suggested fix:
https://github.com/bazel-contrib/rules_kotlin/blob/master/docs/kotlin.md
Address the rules_kotlin compile/ABI JAR pipeline ensuring that consumers receive uninstrumented bytecode for Kotlin inlining while instrumented bytecode remains available for runtime coverage collection.
Set experimental_use_abi_jars = True in define_kt_toolchain(name = "kotlin_16_jdk9_toolchain", ...) in tools/kotlin/BUILD.bazel.
Verification
- Both affected DragDropSortInput tests and representative RatioInput/FractionInput tests.
- Run normal tests and an app build to check annotation processing and compiler-plugin compatibility.
- Add a CI coverage regression check using the original unrestricted filter.
Describe the bug
#6444 surfaced an issue in the code coverage workflow.
Removing the
test_file_not_requiredexemptions for the two DragDropSortInput providers in #6444 exposes a coverage build failure. Coverage forDragDropSortInputHasElementXBeforeElementYClassifierProviderTestfails while instrumenting a transitive RatioInput dependency, before tests execute.The reported CI log contains:
The stack passes through
JacocoInstrumentationKt.createCoverageInstrumentedJarandInstrSupport.assertNotInstrumented. JaCoCo version:0.8.9.202303300400/461ebf3. The failed build target is//domain/src/main/java/org/oppia/android/domain/classify/rules/ratioinput:ratio_input_providers_kt; its output JAR is consequently missing.Steps To Reproduce
On the develp branch, use the repository's normal build configuration and run:
To exercise the exemption trigger, run
//scripts:run_coveragefordomain/src/main/java/org/oppia/android/domain/classify/rules/dragAndDropSortInput/DragDropSortInputHasElementXBeforeElementYClassifierProvider.ktbefore and after removing itstest_file_not_requiredexemption. Before removal, the script skips this source's coverage. After removal, it attempts coverage for its test.Expected Behavior
Removing a stale test-file exemption should allow coverage to run successfully. Kotlin compilation should consume original bytecode and coverage instrumentation should not process classes that already contain JaCoCo instrumentation.
Screenshots/Videos
No response
What device/emulator are you using?
No response
Which Android version is your device/emulator running?
No response
Which version of the Oppia Android app are you using?
No response
Additional Context
I put this through Codex to analyze the stacktrace. This is what we came up with:
The coverage failure is caused by JaCoCo double-instrumenting Kotlin-generated inline classes across Bazel target boundaries.
BazelClient.runCoverageForTestTarget()applies--instrumentation_filter=//domain/..., making all eligible dependencies within the domain module subject to instrumentation, not just the source under test. AlthoughRatioInputHasSpecificTermEqualToRuleClassifierProvider.ktis markedsource_file_is_incompatible_with_code_coverage: true, this exemption only prevents coverage from being run directly for that source. It does not exclude the class from Bazel instrumentation when it is a dependency of another test.I double-checked this through bytecode inspection.
GenericRuleClassifier.Factory.createDoubleInputClassifier()is an inline function defined in a separate Bazel target. It calls another inline function,InteractionObjectTypeExtractorRepository.getExtractor(), which generates a lambda. The generic classifier's compile/ABI JAR already contains JaCoCo-instrumented versions of these generated classes, confirmed by the presence of$jacocoData,$jacocoInit(), andOffline.getProbes. When Kotlin inlines the factory into consuming classifiers such as RatioInput and FractionInput, it copies this already-instrumented bytecode. The consumer's coverage step then attempts to instrument it again, causing JaCoCo to fail withCannot process instrumented class.--instrumentation_filter=//domain/...: FAIL, during compilation, before any tests execute.coverage.dat.'--instrumentation_filter=//domain/...,-//domain/src/main/java/org/oppia/android/domain/classify/rules:'The trailing colon limits the exclusion to the factory's exact Bazel package, allowing classifier subpackages to remain instrumented. This package exclusion is just a diagnostic workaround but reduces coverage for the generic classifier package.
A suggested fix:
https://github.com/bazel-contrib/rules_kotlin/blob/master/docs/kotlin.md
Address the
rules_kotlincompile/ABI JAR pipeline ensuring that consumers receive uninstrumented bytecode for Kotlin inlining while instrumented bytecode remains available for runtime coverage collection.Set
experimental_use_abi_jars = Trueindefine_kt_toolchain(name = "kotlin_16_jdk9_toolchain", ...)intools/kotlin/BUILD.bazel.Verification