Skip to content

fix(shop-bento): eliminate flaky ShopBentoViewModelTest + version bump - #250

Merged
kcw-grunt merged 5 commits into
developfrom
beta-release
Jul 22, 2026
Merged

fix(shop-bento): eliminate flaky ShopBentoViewModelTest + version bump#250
kcw-grunt merged 5 commits into
developfrom
beta-release

Conversation

@grunt-claude-bot

@grunt-claude-bot grunt-claude-bot commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

📱 Description

This PR fixes an intermittent CI failure in ShopBentoViewModelTest (TurbineAssertionError: Unconsumed events found) that only reproduced under the full test suite, never in isolation. Root cause was a missing MainDispatcherRule, so the test ran against whatever Dispatchers.Main was left as by other test classes sharing the same JVM fork. Also includes the pending v4.10.5 version bump for this beta release.

Platform

  • Android

🎯 Type of Change

  • 🐛 Bug fix
  • ✨ New feature
  • 🔧 Refactoring (code change that neither fixes a bug nor adds a feature)
  • 📚 Documentation update
  • 🎨 UI/UX improvement
  • ⚡ Performance improvement
  • 🧪 Test addition or improvement

📋 Changes

New Components Added

None

Modifications

  • ShopBentoViewModel.kt: Consolidated two independent viewModelScope.launch blocks (each calling _state.update separately for settingRepository.settings and shopProxyRepository.shopProxy) into a single combine()-driven collector, so state observers can no longer see transient, incomplete intermediate states.
  • ShopBentoViewModelTest.kt: Added @get:Rule val mainDispatcherRule = MainDispatcherRule(), matching the pattern already used in UnLockViewModelTest/BWSenderIntegrationTest/TxRepositoryImplIntegrationTest. Replaced advanceTimeBy(100) with advanceUntilIdle() in the affected test.
  • app/build.gradle.kts: Version bump versionCode 202506342202506343, versionName v4.10.4v4.10.5.

Removals

None

📊 Statistics

  • Additions: 39 lines
  • Deletions: 36 lines
  • Files Changed: 3
  • Commits: 2

🧪 Tests Status

  • Tests ran successfully locally?
  • Added more tests? No new tests added — fixed the determinism of the existing flaky test instead.
  • Code coverage percentage of the codebase: not measured for this change

🎯 Reviewers

@kcw-grunt, @josikie

kcw-grunt and others added 2 commits July 22, 2026 20:27
ShopBentoViewModelTest had no MainDispatcherRule, unlike every other
ViewModel test in this codebase, so Dispatchers.Main was never
explicitly installed for it. ShopBentoViewModel.init launches its
work via viewModelScope (backed by Dispatchers.Main), so this test was
actually running against whatever Dispatchers.Main happened to be left
as by other test classes sharing the same Gradle test JVM fork —
explaining why `testBrainwalletReleaseUnitTest` failed intermittently
with `TurbineAssertionError: Unconsumed events found` only under the
full suite (order/load-dependent) and never in isolation. Added
MainDispatcherRule to match the established pattern (see
UnLockViewModelTest, BWSenderIntegrationTest, TxRepositoryImplIntegrationTest).

Also collapsed ShopBentoViewModel's two independent viewModelScope.launch
blocks (one collecting settingRepository.settings, one collecting
shopProxyRepository.shopProxy) into a single combine()-driven collector.
Previously each block called _state.update independently, so real
observers of `state` (not just the test) could see transient,
incomplete intermediate states depending on collector interleaving.

Swapped the fixed-duration advanceTimeBy(100) for advanceUntilIdle()
in the affected test, which deterministically drains all pending
coroutine work instead of guessing a virtual-time delay.

Verified with repeated full `testBrainwalletReleaseUnitTest` runs
(previously failed roughly 1 in 3-5 runs under the full 415-test suite).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
kcw-grunt
kcw-grunt previously approved these changes Jul 22, 2026

@kcw-grunt kcw-grunt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 🤖 🚀

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Learned something here.

fun `init - sets shopBaseUrl from widget`() = runTest {
turbineScope {
shopProxyFlow.emit(listOf(ShopProxy(widget = "https://shop.example.com", shopCards = emptyList())))
shopProxyFlow.emit(listOf(ShopProxy(widget = "https://embed.bitrefill.com", shopCards = emptyList())))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessary to change but used a more relevant domain

kcw-grunt
kcw-grunt previously approved these changes Jul 22, 2026
The unit-test job's Gradle daemon was being killed mid-build
("The message received from the daemon indicates that the daemon has
disappeared... it may have been killed or may have crashed"),
consistently right as testBrainwalletDebugUnitTest started after a
heavy multi-module build (KSP across 5 modules, detekt across 4,
dataBinding, bw-gdlib texture packing, 415 unit tests).

-Xmx4g for the Gradle daemon plus AGP-forked unit-test worker JVMs
plus OS/container overhead was exceeding the resource_class: large
executor's available RAM. Lowered the daemon heap to -Xmx3g and
org.gradle.workers.max/--max-workers from 2 to 1 to leave more
headroom for the forked test JVMs, without changing the resource
class (and its higher CircleCI billing tier).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
android-build-logic's DetektSetup.attachDetektTask() wires `detekt`
as a dependency of every compile*/assemble* task project-wide
(tasks.whenTaskAdded { if (name.startsWith("compile") || ...) dependsOn(detekt) }),
so a plain `./gradlew testBrainwalletDebugUnitTest` was also running
3-4 full detekt passes (autoCorrect=true, parallel=true, HTML/XML/TXT/
SARIF/Markdown reports across app/core/iap/general-purpose-app) that
have nothing to do with running tests.

That's real CPU/memory work competing with compilation and the forked
unit-test JVMs on the resource-constrained `large` executor, on top of
the daemon heap/worker tuning from the previous commit. Verified
locally: `-x detekt` removes all detekt tasks from the graph (confirmed
via --dry-run) with no other effect, and the full unit test run still
passes.

Scoped to this CI job's command line only, not the shared build-logic,
so local dev/lint workflows are unaffected.

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

@kcw-grunt kcw-grunt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

YAY LGTM

@kcw-grunt
kcw-grunt merged commit 116df6a into develop Jul 22, 2026
2 checks passed
@kcw-grunt
kcw-grunt deleted the beta-release branch July 22, 2026 21:13
Comment thread .circleci/config.yml
- run:
name: "Execute Unit Tests"
command: ./gradlew testBrainwalletDebugUnitTest --no-daemon --max-workers=1
# -x detekt: android-build-logic's DetektSetup.attachDetektTask() wires

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The build has inflated to many more modules and the detekt was causing extra linting causing the CI to fail.

This was referenced Jul 27, 2026
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.

3 participants