Skip to content

test(router): stop the hashtag route test from hanging cache recovery tests in CI - #9050

Merged
rabble merged 1 commit into
mainfrom
fix/9022-hashtag-stats-stranded-open
Sep 10, 2026
Merged

rabble merged 1 commit into
mainfrom
fix/9022-hashtag-stats-stranded-open

Conversation

@realmeylisdev

@realmeylisdev realmeylisdev commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

Problem

Tests (shard 2/4) keeps timing out on pull requests that have nothing to do with caching. Two tests in test/services/cache_recovery_service_test.dart — "preserves pending uploads while clearing disposable Hive caches" and "preserves durable notification preferences while clearing caches" — each wait out the full 10-minute test timeout, so the shard loses 20 minutes and needs a re-run. #9022 records it on #8540, #8994 and #9018 within four hours, each under a different shuffle seed.

The hang is caused by a different test. CI runs every untagged test file in one merged process, and Hive tracks boxes by name for the whole process. The test navigating to /hashtag/rust leaves the shell entirely in test/router/app_shell_integration_test.dart (re-enabled by #8950) mounts the real HashtagFeedScreen. That screen's provider chain builds HashtagCacheService, whose initialize() calls Hive.openBox('hashtag_stats'). Inside the fake-async zone testWidgets runs in, that open never completes. Hive then keeps hashtag_stats in its private registry of in-flight opens for the rest of the process. Hive.isBoxOpen cannot see that registry, so neither the victim's own cleanup nor the root Hive harness in flutter_test_config.dart can clear it. Any later test that opens hashtag_stats waits on the stranded open until it times out, and both hanging tests open hashtag_stats. Whether the victim runs after the leaker depends on the shuffle seed, which is why only some seeds hang.

Fix

app_shell_integration_test.dart now overrides hashtagServiceProvider with a mock stubbed for the two calls the screen makes when it opens (getVideosByHashtags and subscribeToHashtagVideos). The five hashtag_feed_* screen tests already use this pattern. The test still asserts that the route lands on HashtagFeedScreen outside the shell; it just no longer touches Hive.

The unmount() doc comment cited HashtagService's 60-second periodic timer as the reason the container has to be disposed inside the test body. With the service mocked, no provider timer is left pending in this file, so the comment keeps the general rule and drops that example.

What this leaves alone

  • No production code changes. HashtagCacheService opens its box normally in the app; the hang exists only because a widget test started real Hive I/O inside fake async.
  • test(cache): cache_recovery_service_test hangs on a pending Hive box open under one shuffle seed #9022's suggested direction is not attempted here: making the harness able to see, and then await or fail, a pending openBox. This PR removes the test behind the current occurrences. The harness gap that lets a future leaker hide the same way stays open.
  • The other test files that mention hashtag routes were checked. The five hashtag_feed_* screen tests already override the provider. The router tests that navigate to a hashtag path never mount a widget tree, so none of them reach Hive.

Verification

A two-file bundle reproduces the merged-process order locally. It runs the leaker first and then the victim in one process, calls Hive.init in setUpAll the way an earlier suite does in CI, passes --dart-define=DIVINE_STRICT_CHANNELS=true as CI does, and uses --timeout=120s so the hang stops after two minutes instead of ten:

import 'router/app_shell_integration_test.dart' as leaker;
import 'services/cache_recovery_service_test.dart' as victim;

void main() {
  setUpAll(() {
    Hive.init(Directory.systemTemp.createTempSync('repro_9022').path);
  });
  group('router/app_shell_integration_test.dart', leaker.main);
  group('services/cache_recovery_service_test.dart', victim.main);
}
Run Before After
Full bundle 17 passed, 2 failed: both #9022 tests hit TimeoutException after 0:02:00 (4m04s) 19 passed (4s)
Only the /hashtag/rust test, plus the victim file The same two tests time out —
Every other test in the leaker file, plus the victim file 18 passed, no hang —

The two narrowed runs pin the leak to that single test.

On the rebased head:

  • app_shell_integration_test.dart (8/8) and cache_recovery_service_test.dart (11/11) pass on their own.
  • The logged type 'Null' is not a subtype lines, which come from unrelated mocks, are the same set with the same counts before and after. The new mock swallows no errors of its own.
  • A probe copy of the leaker file with c.dispose() removed passed 8/8, which confirms no provider timer is left pending in this file.
  • flutter analyze lib test integration_test and dart format are clean.
  • These ratchet checks pass: check_shared_setup_stubs, check_placeholder_tests, check_unpinned_unchanged_assertions, check_ungrouped_tests, check_skip_ceiling, check_l10n_delegates_ceiling, check_nostr_id_log_truncation, check_future_delayed_ceiling, check_process_global_mutations, check_shared_channel_overrides and check_http_overrides_isolation.

Closes #9022

Follow-up: #9053 tracks the pending-openBox harness gap left outside this point fix.

The /hashtag/rust test in app_shell_integration_test mounts
HashtagFeedScreen against the real hashtagServiceProvider, which builds
HashtagCacheService and calls Hive.openBox('hashtag_stats'). Inside
testWidgets' fake async that open never completes, so it stays pending
in Hive's private opening registry for the rest of the isolate. Any
later suite in the merged CI bundle that opens hashtag_stats then waits
on it until the 10-minute timeout: the cache_recovery_service_test hang
in #9022.

Override hashtagServiceProvider with a mock stubbed for the two calls
the screen makes on open, as the hashtag_feed_* screen tests already
do. The unmount() comment named HashtagService's 60s timer as its
example; with the service mocked no provider timer is left pending in
this file, so the example goes.
@realmeylisdev
realmeylisdev requested a review from a team as a code owner September 10, 2026 21:13
@realmeylisdev
realmeylisdev requested review from mbradley and rabble and removed request for a team September 10, 2026 21:13
@github-actions

Copy link
Copy Markdown

Mobile PR Preview

Preview refreshed for 04995d6

Last refresh: 04995d6 at 2026-09-10 21:17:27 UTC (preview run)

Property Value
Preview URL https://b7ec7533.openvine-app.pages.dev
Pages project openvine-app
Preview branch pr-9050
PR branch fix/9022-hashtag-stats-stranded-open
Commit 04995d6

@NotThatKindOfDrLiz NotThatKindOfDrLiz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The point fix is correct and fully verified on 04995d6f125de84916589a7694f210e001fa9a3f. #9053 now tracks the pending-openBox harness gap left outside this PR.

@rabble
rabble added this pull request to the merge queue Sep 10, 2026
Merged via the queue into main with commit 50c386c Sep 10, 2026
23 checks passed
@rabble
rabble deleted the fix/9022-hashtag-stats-stranded-open branch September 10, 2026 23:42
SSMG64 pushed a commit to IBackupStuff/divine-mobile that referenced this pull request Oct 22, 2026
…ivinevideo#9581)

* refactor(services): delete the dead HashtagCacheService Hive cache

getPopularHashtags() was the only method reading HashtagCacheService,
and it has had zero production callers since it was written. The
provider never wires ref.onDispose(service.dispose), so autoDispose
drops the object without closing its Hive box, verified live via the
Dart VM Service: the box opens on-demand, stays empty forever (nothing
ever wrote to it), and remains open long after the last screen that
touched it unmounts. divinevideo#9050 already had to mock hashtagServiceProvider
in a widget test to work around this exact box hanging fake-async
suites (divinevideo#9022); deleting the dead code removes the hazard at its root
instead of leaving the next test to rediscover it.

Also renames the app-layer HashtagStats model to HashtagVideoStats,
resolving its name collision with the still-inert Drift HashtagStats
table (tracked separately as a follow-up: the table has no production
writer either, but dropping it needs a real schema migration).

Closes divinevideo#6988

* fix(cache): clean up retired hashtag cache references

* fix(cache): delete stranded hashtag_stats files at startup

* refactor(services): give the legacy hashtag_stats box name one owner

CacheRecoveryService and HiveStorageService each independently declared
their own 'hashtag_stats' string literal to clean up the leftover Hive
box file from before HashtagCacheService was deleted. Give it a single
shared owner (HiveBoxNames.legacyHashtagStats, deliberately outside
`all` since nothing opens the box anymore) so the two can't drift.

Addresses rabble's review nit on divinevideo#9581.

---------

Co-authored-by: Liz Sweigart <127434495+NotThatKindOfDrLiz@users.noreply.github.com>
Co-authored-by: Daniel Cadenas <dcadenas@gmail.com>
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.

test(cache): cache_recovery_service_test hangs on a pending Hive box open under one shuffle seed

3 participants