Skip to content

build(android): Run Robolectric unit tests in their own forked task - #6470

Draft
sentry-junior[bot] wants to merge 3 commits into
mainfrom
ref/split-robolectric-unit-tests
Draft

sentry-junior[bot] wants to merge 3 commits into
mainfrom
ref/split-robolectric-unit-tests

Conversation

@sentry-junior

@sentry-junior sentry-junior Bot commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

📜 Description

Adds an io.sentry.robolectric-test-split build-logic plugin and applies it to sentry-android-core and sentry-android-replay. It splits each module's unit tests into two tasks:

  • testReleaseUnitTest (the AGP task) runs only the Robolectric tests. It gets maxHeapSize = "2g" and maxParallelForks = availableProcessors / 2 (at least 1). The plugin now sets core's old unitTests.all { maxHeapSize = "2g" }.
  • testReleaseJvmUnitTest (new) runs the other tests with Gradle's defaults: one fork and a 512m heap. It uses the same test classes and classpath as the AGP task, and test/check depend on it.

A Robolectric test is a class whose own bytecode has @RunWith(AndroidJUnit4 | RobolectricTestRunner | ParameterizedRobolectricTestRunner). Both modules use only @RunWith(AndroidJUnit4::class) and put it directly on the test class. The bytecode is read with ASM when the task runs, so the split is config-cache safe and needs no per-file list.

When you pass --tests to testReleaseUnitTest, the split is skipped, so running a plain JVM test from Android Studio still works. Gradle has no public getter for the --tests patterns, so this reads DefaultTestFilter.commandLineIncludePatterns, which is an internal API.

💡 Motivation and Context

Robolectric tests take most of the unit-test time, and the build scans show that each module's unit tests run in a single executor (maxParallelForks was never set). This tries giving only the Robolectric tests more forks and heap, while the plain JVM tests run separately with default settings. Discussed in #team-sdk-java-android.

Caching note: both tasks share the same classpath (all test classes), so changing any test class still reruns both. This PR doesn't make caching more granular; that would take separate source sets.

💚 How did you test it?

Locally (JDK 21, 4 CPUs):

  • :sentry-android-replay:testReleaseJvmUnitTest + :sentry-android-replay:testReleaseUnitTest: 79 tests in 4 classes + 210 tests in 18 classes, no failures. There's no overlap, and the split matches the @RunWith(AndroidJUnit4::class) source files exactly. Two Robolectric executors ran with -Xmx2g and the JVM executor with -Xmx512m. A second run reused the configuration cache.
  • :sentry-android-core:testReleaseJvmUnitTest: 364 tests in 41 classes, no failures. The only non-Robolectric file with @Test it doesn't run is the abstract ApplicationExitIntegrationTestBase, whose tests run through its Robolectric subclasses.
  • :sentry-android-replay:testReleaseUnitTest --tests DefaultReplayBreadcrumbConverterTest (a JVM test) runs it.
  • I did not run core's Robolectric task locally; CI covers it.

Timing in my sandbox doesn't tell us much. Compare the CI build scan for this PR with main.

📝 Checklist

  • I added GH Issue ID & Linear ID
  • I added tests to verify the changes.
  • No new PII added or SDK only sends newly added PII if sendDefaultPII is enabled.
  • I updated the docs if needed.
  • I updated the wizard if needed.
  • Review from the native team if needed.
  • No breaking change or entry added to the changelog.
  • No breaking change for hybrid SDKs or communicated to hybrid SDKs.
  • Public API changes reviewed by another Mobile SDK team member or implemented according to the develop docs spec.

🔮 Next steps

  • Compare the sentry-android-core / sentry-android-replay test task durations and CI memory against main, and tune maxParallelForks (CI runners have 4 CPUs, so this gives 2 forks).
  • If it helps, apply the plugin to the other Robolectric modules (navigation, navigation3, distribution).
  • Open question: is the internal DefaultTestFilter use for the --tests passthrough acceptable, or would we rather drop IDE support for JVM tests through testReleaseUnitTest?

#skip-changelog

via roman.

--

View Session [Sentry]

Split the unit tests of sentry-android-core and sentry-android-replay into
two tasks. The AGP test task keeps the Robolectric tests and gets a 2g heap
and one fork per two CPUs. A new testReleaseJvmUnitTest task runs the other
tests with the Gradle defaults.

Co-Authored-By: Roman Zavarnitsyn <roman.zavarnitsyn@sentry.io>
@sentry

sentry Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

📲 Install Builds

Android

🔗 App Name App ID Version Configuration
SDK Size io.sentry.tests.size 8.62.0 (1) release

⚙️ sentry-android Build Distribution Settings

sentry-junior Bot and others added 2 commits November 5, 2026 12:15
With more forks, the test order changes, and a class that ran before
this one in the same JVM can leave cached app info without the DSN
metadata. Reset it before each test, like the other tests that mock
manifest metadata.
… API

Use androidComponents.onVariants and the unit test component's
configureTestTask instead of afterEvaluate and a task name match.

Co-Authored-By: Nelson Osacky <nelson.osacky@sentry.io>
Co-Authored-By: Roman Zavarnitsyn <roman.zavarnitsyn@sentry.io>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants