Skip to content

revert(deps): back out CMP 1.12.0-rc01 until its test regressions are fixed - #6664

Merged
jamesarich merged 3 commits into
mainfrom
revert-6662-cmp-1120-rc01
Aug 13, 2026
Merged

revert(deps): back out CMP 1.12.0-rc01 until its test regressions are fixed#6664
jamesarich merged 3 commits into
mainfrom
revert-6662-cmp-1120-rc01

Conversation

@jamesarich

@jamesarich jamesarich commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Why

main is red. CMP 1.12.0-rc01 (#6662) breaks 8 tests deterministically in :core:service and :feature:connections, and destabilises :core:ui and :feature:settings intermittently. It is a release candidate we do not need to be on. Reverting restores a green baseline now; adapting to the new CMP behaviour can happen on its own schedule instead of blocking every other PR.

It reached main through two independent gate failures, which is why nobody caught it:

  1. chore(deps): bump the Compose stack to CMP 1.12.0-rc01 in lockstep #6662 merged with its test shards cancelled, not passed. Its PR head 39fa5379 shows all three test-shards checks as cancelled. The post-merge run at bc4e9da3a executed them for the first time and they failed.
  2. The next PR's green tick was a build-cache replay. fix(database): bound withDb execution so one wedged callback can't stall every write #6661's merge-queue shard-feature job (94269148979) reported success while serving every affected test task FROM-CACHE:core:service:jvmTest, :core:service:testAndroidHostTest, :feature:connections:jvmTest, :feature:connections:testAndroidHostTest, :feature:node:jvmTest, :feature:node:testAndroidHostTest. It executed none of them.

Both gate holes are tracked separately; this PR only restores green.

Root cause

Not "wrong resource values" — the values are null, because resource reads stopped being synchronous.

compose-resources AsyncCache.getOrLoad:

1.11.1:      coroutineScope { async(start = LAZY) { load() } }.await()   // caller's context
1.12.0-rc01: private val cacheScope = CoroutineScope(SupervisorJob())    // no dispatcher => Dispatchers.Default
             cached = SharedRequest(cacheScope.async { load() })

Every cold resource load now runs on a real Dispatchers.Default thread while the caller awaits it. advanceUntilIdle() cannot drain a real-thread deferred, so:

  • tests that read state on the next line observe null;
  • a viewModelScope coroutine still awaiting a load when Dispatchers.resetMain() runs surfaces as Module with the Main dispatcher is missing, blamed on the next test to start;
  • because the cache is process-wide, whichever test touches a given string first pays the cost — so the same class can pass or fail depending on execution order.

This change is not in the CMP changelog.

Bisect

commit those test tasks result
28dd35ef9 (pre-bump) genuinely executed, no FROM-CACHE BUILD SUCCESSFUL, 7m05
bc4e9da3a (#6662) genuinely executed 9 distinct failures (24 raw entries)
d5848ad5e (#6661) all FROM-CACHE no signal

Real green, real red, then silence.

What was failing

Distinct failing test methods in the bc4e9da3a run. Parse the JUnit XML with an XML parser and count <testcase> elements that have a <failure>/<error> child — raw entry counts are ~3x higher because Develocity retries each test twice.

module test method entries
:feature:connections ScannerViewModelTest [jvm] bluetooth-disabled failure allows an immediate retry once the user re-enables it x3
location-services-disabled failure allows an immediate retry x3
scan quota failure honors retry-after cooldown x3
scan startup failure clears scanning state disables auto-scan and surfaces error x1
:core:service MeshNotificationManagerImplTest service state rendering is deferred from the caller x3
service state seeds local stats before the local node row is available x3
:core:service MeshNotificationManagerImplConversationTest notification ids are namespaced per type so a node num cannot clobber the service notification x3
:feature:connections AndroidScannerViewModelBondingTest security exception does not arm the transport and surfaces an error x3

8 distinct methods, 22 entries. Every one is a test that reads a string resource and asserts on it — consistent with the AsyncCache change and nothing else.

Two further classes are broken by the same change, bringing the total to 10 distinct CMP-caused failures: :core:ui ConnectionsViewModelTest and :feature:settings RadioConfigViewModelTest.

They look intermittent in shard runs, but that is a shard artefact, not a property of the tests — the AsyncCache is process-wide, so whether a given test finds it warm depends on what ran before it. Under a class-filtered cold run the behaviour is fully deterministic. d5848ad5e already pins rc01 and git diff --stat d5848ad5e 0eb95531b -- core/ui/src feature/settings/src is empty, so CMP is the only variable:

CMP runs of :core:ui:jvmTest --tests "*ConnectionsViewModelTest*" --rerun-tasks result
1.11.1 (this branch) 5 5/5 clean, 10 tests, entries == methods
1.12.0-rc01 (d5848ad5e) 5 5/5 failed, same method every run

The failing method is connected older known node exposes Android firmware update noticeexpected: <1> but was: <0> at ConnectionsViewModelTest.kt:201, which asserts a notification was dispatched. ConnectionsViewModel resolves that notification's title via getStringSuspend(Res.string.firmware_update_available) (ConnectionsViewModel.kt:299), so the dispatch never lands before the assertion. Same defect as the 8 above.

RadioConfigViewModelTest is the resetMain variant: a viewModelScope coroutine still awaiting a resource load when Dispatchers.resetMain() runs. It genuinely executed and passed on this branch (:feature:settings:jvmTest, five invocations, no FROM-CACHE).

Relationship to #6666

#6666 ("test(compose): settle asynchronous resource loads after the CMP 1.12 bump") fixes 8 of the 10 by making the affected tests robust to asynchronous resource resolution. It does not touch core/ui or feature/settings, so it does not green main on its own while the bump is in place.

Agreed plan is to land this revert first — it fixes all 10 in one diff and unblocks #6658 today, depending on no unpushed work — and then land #6666 on top. #6666's changes are test-only and correct under both 1.11.1 and 1.12.x, so they are not wasted: they become the groundwork that makes a future re-land safe. Whoever re-lands the bump removes the Renovate rule added here, and should also carry a fix for the two classes above, which #6666 does not cover.

🛠️ Changes

  • Revert bc4e9da3a: compose-multiplatform 1.12.0-rc01 → 1.11.1, compose-multiplatform-material3 1.12.0-alpha03 → 1.11.0-alpha07, androidx-compose-bom-aligned 1.12.0 → 1.11.4.
  • Block Renovate from re-proposing 1.12.0-rc01, following the existing Gradle 9.7.0 precedent in .github/renovate.json — without it the next Renovate pass walks the bump straight back in. The rule records the AsyncCache mechanism and the lift condition.

Testing Performed

CI on this branch is the primary evidence:

  • shard-core: SUCCESS — includes :core:ui:allTests, so ConnectionsViewModelTest passes.
  • shard-feature: fails on exactly one test, NodeDetailCompassLifecycleTest (see below) — all 8 CMP-caused failures are gone, including :feature:settings' RadioConfigViewModelTest.
  • android-check, screenshot-check: SUCCESS.

Local, with --rerun-tasks so nothing came from the build cache:

target before after
:feature:connections:jvmTest --tests "*ScannerViewModelTest*" 4 distinct failures 28/28 pass
:core:service:testAndroidHostTest --tests "*MeshNotificationManagerImpl*" 3 distinct failures 10/10 pass

Not covered here

:feature:node NodeDetailCompassLifecycleTest is a pre-existing flake and it fails on this PR too. Codecov reports a 66.67% failure rate on main (passed 2, failed 4) for compassSelectionFollowsScreenLifecycleAndDismissal, with ComposeTimeoutException: Condition still not satisfied after 1000 ms at NodeDetailCompassLifecycleTest.kt:140. It fails on 1.11.1 and 1.12.0-rc01 alike, so it is independent of the version reverted here, and it is owned by a separate effort. No fix is pushed for it here: folding an unrelated flake fix into a one-line dependency revert would make the revert harder to reason about or roll back.

Because that test is flaky rather than deterministic, re-running the job may turn it green. If this merges that way it merged on a retry that flipped a known flake, not on a clean signal — worth recording as such.

Corrections to earlier revisions of this description

Stated openly since earlier versions were used as evidence:

  • I first counted NodeDetailCompassLifecycleTest among chore(deps): bump the Compose stack to CMP 1.12.0-rc01 in lockstep #6662's regressions. Wrong — it is the pre-existing flake above. A single local pass I got on this branch was luck at a 66.67% failure rate, not verification.
  • I reported the regression as 18, then 17, distinct test methods. Both were wrong: it is 8 (plus the compass flake). My extraction used a regex over <testcase ...>(.*?)</testcase>, which mis-associates failures across self-closing <testcase/> elements and so credited passing tests with a later test's <failure>. Names I published that never failed include initChannels removes legacy categories and creates canonical channels, startBleScan succeeds while Connecting, bleDevicesForUi shows bonded devices only once they are visible via scan, startBleScan cancels active network scan, startBleAutoScan skips when device already selected and selecting BLE stops active network scan. Corrected with xml.etree; a peer session caught it.
  • I then described ConnectionsViewModelTest and RadioConfigViewModelTest as pre-existing failures unrelated to chore(deps): bump the Compose stack to CMP 1.12.0-rc01 in lockstep #6662, on the basis of runs against a "clean checkout" of d5848ad5e. That baseline was invalid: bc4e9da3a is an ancestor of d5848ad5e, so that checkout still contained CMP 1.12.0-rc01. Both classes are in fact casualties of the AsyncCache change and both pass on this branch.

🤖 Generated with Claude Code

… fixed

Reverts #6662 (bc4e9da). The bump broke 18 tests across :core:service,
:feature:connections and :feature:node: compose-resources lookups return
wrong/null values under test, and compose.uiTest idling times out.

It reached main because its own test shards were cancelled rather than run,
and the next PR's green shard replayed all six affected test tasks from the
build cache, so nothing caught it until main was already red.

Renovate is blocked from re-proposing 1.12.0-rc01, following the Gradle
9.7.0 precedent, so the revert holds until the regressions are addressed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 3485cef6-b1e8-4fc4-a9b3-e7d70975f85c

📥 Commits

Reviewing files that changed from the base of the PR and between 1b89b63 and 45e70b0.

📒 Files selected for processing (2)
  • .github/renovate.json
  • gradle/libs.versions.toml

📝 Walkthrough

Walkthrough

Changes

Compose version alignment

Layer / File(s) Summary
Compose dependency version updates
gradle/libs.versions.toml
Compose Multiplatform, Material 3, and the AndroidX Compose BOM now use aligned 1.11.x versions.
Renovate version exclusion
.github/renovate.json
Renovate now excludes Compose Multiplatform 1.12.0-rc01 for org.jetbrains.compose packages.

Estimated code review effort: 1 (Trivial) | ~5 minutes

Mergeability Score: 🔵 Low · up to 45e70

This change restores the prior Compose dependency set and prevents the known regression from being reintroduced automatically. It is mergeable with owner awareness, but the required release and full verification checks should be run to confirm build and packaging compatibility.

Possibly related PRs

Suggested labels: chore

Suggested reviewers: thebentern

🚥 Pre-merge checks | ✅ 6
✅ Passed checks (6 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Sibling Call Sites And Presence Semantics ✅ Passed The diff changes only Renovate configuration and Compose dependency versions; it adds no fields, nullable/presence semantics, zero-guards, or call-site changes in NodeItem or related models.
Tests Prove The Path, Not The End State ✅ Passed The diff changes only Renovate configuration and dependency versions; it adds or changes no test, so the custom test-path conditions do not apply.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the dependency revert and its reason, matching the primary changes in the pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the repo Repository maintenance label Aug 12, 2026
@codecov

codecov Bot commented Aug 12, 2026

Copy link
Copy Markdown

❌ 1 Tests Failed:

Tests completed Failed Passed Skipped
6013 1 6012 0
View the full list of 1 ❄️ flaky test(s)
org.meshtastic.feature.node.detail.NodeDetailCompassLifecycleTest::compassSelectionFollowsScreenLifecycleAndDismissal()[jvm]

Flake rate in main: 66.67% (Passed 2 times, Failed 4 times)

Stack Traces | 14.4s run time
androidx.compose.ui.test.ComposeTimeoutException: Condition still not satisfied after 1000 ms
	at androidx.compose.ui.test.SkikoComposeUiTest.waitUntil(ComposeUiTest.skiko.kt:419)
	at androidx.compose.ui.test.ComposeUiTest.waitUntil$default(ComposeUiTest.skiko.kt:568)
	at org.meshtastic.feature.node.detail.NodeDetailCompassLifecycleTest$compassSelectionFollowsScreenLifecycleAndDismissal$1.invokeSuspend(NodeDetailCompassLifecycleTest.kt:140)
	at org.meshtastic.feature.node.detail.NodeDetailCompassLifecycleTest$compassSelectionFollowsScreenLifecycleAndDismissal$1.invoke(NodeDetailCompassLifecycleTest.kt)
	at org.meshtastic.feature.node.detail.NodeDetailCompassLifecycleTest$compassSelectionFollowsScreenLifecycleAndDismissal$1.invoke(NodeDetailCompassLifecycleTest.kt)
	at androidx.compose.ui.test.v2.ComposeUiTest_skikoKt$runComposeUiTest$1.invokeSuspend(ComposeUiTest.skiko.kt:89)
	at androidx.compose.ui.test.v2.ComposeUiTest_skikoKt$runComposeUiTest$1.invoke(ComposeUiTest.skiko.kt)
	at androidx.compose.ui.test.v2.ComposeUiTest_skikoKt$runComposeUiTest$1.invoke(ComposeUiTest.skiko.kt)
	at androidx.compose.ui.test.SkikoComposeUiTest$runTest$1$1$1$1$1.invokeSuspend(ComposeUiTest.skiko.kt:274)
	at androidx.compose.ui.test.SkikoComposeUiTest$runTest$1$1$1$1$1.invoke(ComposeUiTest.skiko.kt)
	at androidx.compose.ui.test.SkikoComposeUiTest$runTest$1$1$1$1$1.invoke(ComposeUiTest.skiko.kt)
	at androidx.compose.ui.scene.BaseComposeScene$withMonotonicFrameClock$2.invokeSuspend(BaseComposeScene.skiko.kt:293)
	at androidx.compose.ui.scene.BaseComposeScene$withMonotonicFrameClock$2.invoke(BaseComposeScene.skiko.kt)
	at androidx.compose.ui.scene.BaseComposeScene$withMonotonicFrameClock$2.invoke(BaseComposeScene.skiko.kt)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatched(Undispatched.kt:66)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:43)
	at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:497)
	at kotlinx.coroutines.BuildersKt.withContext(Unknown Source)
	at androidx.compose.ui.scene.BaseComposeScene.withMonotonicFrameClock$suspendImpl(BaseComposeScene.skiko.kt:292)
	at androidx.compose.ui.scene.BaseComposeScene.withMonotonicFrameClock(BaseComposeScene.skiko.kt)
	at androidx.compose.ui.test.SkikoComposeUiTest$runTest$1.invokeSuspend(ComposeUiTest.skiko.kt:273)
	at androidx.compose.ui.test.SkikoComposeUiTest$runTest$1.invoke(ComposeUiTest.skiko.kt)
	at androidx.compose.ui.test.SkikoComposeUiTest$runTest$1.invoke(ComposeUiTest.skiko.kt)
	at kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt$runTest$2$1$1.invokeSuspend(TestBuilders.kt:317)
	at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt:42)
	at androidx.compose.ui.test.SkikoComposeUiTest$runTest$1.invokeSuspend(ComposeUiTest.skiko.kt:273)
	at kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt$runTest$2$1$1.invokeSuspend(TestBuilders.kt:317)
Caused by: androidx.compose.ui.test.ComposeTimeoutException: Condition still not satisfied after 1000 ms
	at androidx.compose.ui.test.SkikoComposeUiTest.waitUntil(ComposeUiTest.skiko.kt:419)
	at androidx.compose.ui.test.ComposeUiTest.waitUntil$default(ComposeUiTest.skiko.kt:568)
	at org.meshtastic.feature.node.detail.NodeDetailCompassLifecycleTest$compassSelectionFollowsScreenLifecycleAndDismissal$1.invokeSuspend(NodeDetailCompassLifecycleTest.kt:140)
	at org.meshtastic.feature.node.detail.NodeDetailCompassLifecycleTest$compassSelectionFollowsScreenLifecycleAndDismissal$1.invoke(NodeDetailCompassLifecycleTest.kt)
	at org.meshtastic.feature.node.detail.NodeDetailCompassLifecycleTest$compassSelectionFollowsScreenLifecycleAndDismissal$1.invoke(NodeDetailCompassLifecycleTest.kt)
	at androidx.compose.ui.test.v2.ComposeUiTest_skikoKt$runComposeUiTest$1.invokeSuspend(ComposeUiTest.skiko.kt:89)
	at androidx.compose.ui.test.v2.ComposeUiTest_skikoKt$runComposeUiTest$1.invoke(ComposeUiTest.skiko.kt)
	at androidx.compose.ui.test.v2.ComposeUiTest_skikoKt$runComposeUiTest$1.invoke(ComposeUiTest.skiko.kt)
	at androidx.compose.ui.test.SkikoComposeUiTest$runTest$1$1$1$1$1.invokeSuspend(ComposeUiTest.skiko.kt:274)
	at androidx.compose.ui.test.SkikoComposeUiTest$runTest$1$1$1$1$1.invoke(ComposeUiTest.skiko.kt)
	at androidx.compose.ui.test.SkikoComposeUiTest$runTest$1$1$1$1$1.invoke(ComposeUiTest.skiko.kt)
	at androidx.compose.ui.scene.BaseComposeScene$withMonotonicFrameClock$2.invokeSuspend(BaseComposeScene.skiko.kt:293)
	at androidx.compose.ui.scene.BaseComposeScene$withMonotonicFrameClock$2.invoke(BaseComposeScene.skiko.kt)
	at androidx.compose.ui.scene.BaseComposeScene$withMonotonicFrameClock$2.invoke(BaseComposeScene.skiko.kt)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatched(Undispatched.kt:66)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:43)
	at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:497)
	at kotlinx.coroutines.BuildersKt.withContext(Unknown Source)
	at androidx.compose.ui.scene.BaseComposeScene.withMonotonicFrameClock$suspendImpl(BaseComposeScene.skiko.kt:292)
	at androidx.compose.ui.scene.BaseComposeScene.withMonotonicFrameClock(BaseComposeScene.skiko.kt)
	at androidx.compose.ui.test.SkikoComposeUiTest$runTest$1.invokeSuspend(ComposeUiTest.skiko.kt:273)
	at androidx.compose.ui.test.SkikoComposeUiTest$runTest$1.invoke(ComposeUiTest.skiko.kt)
	at androidx.compose.ui.test.SkikoComposeUiTest$runTest$1.invoke(ComposeUiTest.skiko.kt)
	at kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt$runTest$2$1$1.invokeSuspend(TestBuilders.kt:317)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
	at kotlinx.coroutines.test.TestDispatcher.processEvent$kotlinx_coroutines_test(TestDispatcher.kt:24)
	at kotlinx.coroutines.test.TestCoroutineScheduler.tryRunNextTaskUnless$kotlinx_coroutines_test(TestCoroutineScheduler.kt:98)
	at kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt$runTest$2$1$workRunner$1.invokeSuspend(TestBuilders.kt:326)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
	at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:256)
	at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:54)
	at kotlinx.coroutines.BuildersKt__BuildersKt.runBlockingImpl(Builders.kt:30)
	at kotlinx.coroutines.BuildersKt.runBlockingImpl(Unknown Source)
	at kotlinx.coroutines.BuildersKt__Builders_concurrentKt.runBlockingK(Builders.concurrent.kt:172)
	at kotlinx.coroutines.BuildersKt.runBlockingK(Unknown Source)
	at kotlinx.coroutines.BuildersKt__Builders_concurrentKt.runBlockingK$default(Builders.concurrent.kt:157)
	at kotlinx.coroutines.BuildersKt.runBlockingK$default(Unknown Source)
	at kotlinx.coroutines.test.TestBuildersJvmKt.createTestResult(TestBuildersJvm.kt:10)
	at kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt.runTest-8Mi8wO0(TestBuilders.kt:309)
	at kotlinx.coroutines.test.TestBuildersKt.runTest-8Mi8wO0(TestBuilders.kt:1)
	at kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt.runTest-8Mi8wO0(TestBuilders.kt:167)
	at kotlinx.coroutines.test.TestBuildersKt.runTest-8Mi8wO0(TestBuilders.kt:1)
	at androidx.compose.ui.test.SkikoComposeUiTest.runTest(ComposeUiTest.skiko.kt:261)
	at androidx.compose.ui.test.v2.ComposeUiTest_skikoKt.runSkikoComposeUiTest-uV_hrag(ComposeUiTest.skiko.kt:137)
	at androidx.compose.ui.test.v2.ComposeUiTest_skikoKt.runSkikoComposeUiTest-uV_hrag$default(ComposeUiTest.skiko.kt:119)
	at androidx.compose.ui.test.v2.ComposeUiTest_skikoKt.runComposeUiTest-exY8QGI(ComposeUiTest.skiko.kt:84)
	at androidx.compose.ui.test.v2.ComposeUiTest_skikoKt.runComposeUiTest-exY8QGI$default(ComposeUiTest.skiko.kt:78)
	at org.meshtastic.feature.node.detail.NodeDetailCompassLifecycleTest.compassSelectionFollowsScreenLifecycleAndDismissal(NodeDetailCompassLifecycleTest.kt:92)

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

@github-actions

This comment has been minimized.

@jamesarich

Copy link
Copy Markdown
Collaborator Author

Why shard-feature is red here, and why no fix is pushed for it

The shard-feature failure on this PR is one test, and it is not something this PR caused or can fix:

org.meshtastic.feature.node.detail.NodeDetailCompassLifecycleTest > compassSelectionFollowsScreenLifecycleAndDismissal()[jvm]ComposeTimeoutException: Condition still not satisfied after 1000 ms at NodeDetailCompassLifecycleTest.kt:140.

Codecov reports a 66.67% failure rate on main for it (passed 2, failed 4). It fails on CMP 1.11.1 and on 1.12.0-rc01 alike, so it is independent of the version this PR reverts.

Correction to this PR's own description: I originally counted this test among #6662's regressions. That was wrong. The CMP regression count is 17, not 18, and I have updated the description accordingly. A single local pass I got on this branch was luck at that flake rate, not verification — my mistake, and worth stating plainly since I used it as evidence.

What this PR does fix, confirmed by its own CI: shard-feature on this branch now fails only that one flaky test. All 17 CMP-caused failures are gone — ScannerViewModelTest (8), AndroidScannerViewModelBondingTest (3), MeshNotificationManagerImplTest (3), MeshNotificationManagerImplConversationTest (3). That is the intended effect and it is exactly what the artifact shows.

Deliberately not pushing a fix for the flake here. It belongs to a separate, already-scoped effort covering the order-dependent/flaky family on main (RadioConfigViewModelTest's Dispatchers.Main leak, ConnectionsViewModelTest, and now this test). Folding it into a one-line dependency revert would mix two unrelated changes and make the revert harder to reason about or roll back.

Note for whoever merges: because the blocking test is flaky rather than deterministic, re-running the job may well turn it green. If this lands that way, it merged on a retry that flipped a known flake — not on a clean signal. Worth recording as such rather than as "green".

🤖 Addressed by Claude Code

… rule

The rule described the symptom as wrong/null resource lookups. The real
cause is compose-resources AsyncCache.getOrLoad moving off the caller's
dispatcher onto a private CoroutineScope(SupervisorJob()), so every cold
resource read becomes a real cross-thread hop that advanceUntilIdle()
cannot drain — which also explains why affected classes pass or fail
depending on which test warms the process-wide cache first.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@jamesarich

Copy link
Copy Markdown
Collaborator Author

Correction: two classes I called pre-existing are actually CMP casualties

Superseding my previous comment on this point, and the earlier revision of the description.

I had reported :core:ui ConnectionsViewModelTest and :feature:settings RadioConfigViewModelTest as pre-existing failures unrelated to #6662, based on filtered runs against what I described as a clean checkout of d5848ad5e. That baseline was invalid. git merge-base --is-ancestor bc4e9da3a d5848ad5e returns true — d5848ad5e is downstream of the CMP bump and still pins compose-multiplatform = "1.12.0-rc01". So I never actually tested those two classes without the bump, and my "survives the revert" conclusion had no basis.

This branch's own CI settles it. :core:ui:allTests is in shard-core, which is SUCCESS. :feature:settings:allTests is in shard-feature, which fails on exactly one test — NodeDetailCompassLifecycleTest — so RadioConfigViewModelTest passes too. Both classes are green with CMP reverted.

They also fit the root cause exactly: because AsyncCache's cache is process-wide, whichever test warms a given string first pays the async cost and the rest hit it warm — so a class passes or fails depending on execution order. That explains why ConnectionsViewModelTest passed in shard-core at bc4e9da3a but failed 3 distinct methods in CI on another branch at the same base. What looked like pre-existing order-dependence was the CMP change making these tests racy.

Net effect on this PR: it fixes more than I claimed, not less. The description now reflects that, and the Renovate rule records the mechanism rather than my original mis-description of it.

NodeDetailCompassLifecycleTest remains genuinely independent — it fails on this branch, which is direct evidence it does not depend on the CMP version.

🤖 Addressed by Claude Code

An earlier count of 17/18 came from a regex over <testcase>...</testcase>,
which mis-associates failures across self-closing <testcase/> elements.
Parsed properly the bc4e9da run has 8 distinct CMP-caused failing test
methods (22 entries, inflated by Develocity's two retries per test), plus
one unrelated pre-existing flake.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@jamesarich

Copy link
Copy Markdown
Collaborator Author

Correction: the regression is 8 distinct test methods, not 17 or 18

My failure counts in earlier revisions of this description were wrong, and so were several of the test names. Correcting publicly because those numbers and names were handed to other work.

Cause of my error: I extracted failures with a regex over <testcase name="...">(.*?)</testcase>. Passing tests are serialised as self-closing <testcase ... /> with no closing tag, so that pattern runs past them to the next </testcase> and credits a passing test with a later test's <failure>. Re-parsed with xml.etree, counting <testcase> elements that actually have a <failure>/<error> child.

Correct figures for the bc4e9da3a run — 9 distinct methods, 24 entries (Develocity retries each test twice, which is where the inflation comes from):

class entries distinct
ScannerViewModelTest [jvm] 10 4
MeshNotificationManagerImplTest 6 2
MeshNotificationManagerImplConversationTest 3 1
AndroidScannerViewModelBondingTest 3 1
NodeDetailCompassLifecycleTest [jvm] 2 1 (pre-existing flake, not CMP)

So 8 CMP-caused distinct methods. The description now lists them individually.

Names I published that never failed: initChannels removes legacy categories and creates canonical channels, startBleScan succeeds while Connecting, bleDevicesForUi shows bonded devices only once they are visible via scan, startBleScan cancels active network scan, startBleAutoScan skips when device already selected, selecting BLE stops active network scan.

This sharpens the diagnosis rather than weakening it. All four real ScannerViewModelTest failures are scan-failure-message tests, and every one of the 8 is a test that reads a string resource and asserts on it. That is a tighter fit to the AsyncCache root cause than my inflated list, which included tests that touch no resources at all.

None of this changes what the revert does: shard-core is green and shard-feature fails only the pre-existing compass flake, so all 8 are fixed here.

🤖 Addressed by Claude Code

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

Labels

repo Repository maintenance

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant