test: wait for the snackbar instead of assuming one looper round - #344
Merged
Merged
Conversation
The three offer assertions asserted immediately after a single idle(). SnackbarManager shows one bar at a time and hands them to BaseTransientBottomBar.showView — the call that attaches the view they look up — through the main looper, so the number of rounds between show() and the view existing is not a constant a test gets to assume. They now wait for the view, bounded, and say what they were waiting for when it never arrives. This is a mitigation, not a diagnosis. CI saw the lookup come back null once and it has not reproduced since: twenty-plus runs of the class alone and of the whole suite, both variants, plus a constructed case queueing a second bar behind the first. The cause is still unknown and the issue should stay open until CI gives more evidence. An @after that drained the looper was tried as well, to clear any bar left current in the manager, and reverted: it ran the activity recreate the tap tests leave queued on purpose, against an activity the try-with-resources had already destroyed, failing two of them. That is recorded next to the @after so it is not attempted again.
2 tasks
Review asked for the new guard to be mutation-checked rather than trusted. Setting AWAIT_ROUNDS to zero kills exactly one assertion — the post-rotation wait — so that is the only site where the bar needs looper rounds to arrive. At the other three it is already attached and the wait is insurance. All four keep it, because the sites differ only by whether an activity was recreated first, and that is not something a reader should have to re-derive to know whether waiting is required. Also from review: the shared failure message becomes a constant with a one-argument overload rather than the same literal at three call sites; the unfound root cause is marked TODO(#343) rather than only described; AWAIT_ROUND_MS gains its own comment; and the paragraph narrating how many times it was run locally is gone — it was going to rot, and it overstated what was run, saying "more than twenty runs of the class alone" when it was fifteen of the class and six of the suite.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Addresses #343. Deliberately does not close it — see below.
The three snackbar assertions in
GaugeThemeToggleTestlooked the bar up immediately after a singleidle().SnackbarManagershows one bar at a time and hands them toBaseTransientBottomBar.showView— the call that actually attaches the view being looked up — through the main looper, so the number of rounds betweenshow()and the view existing is not a constant a test gets to assume. They now wait for the view, bounded to 20 × 25 ms (well inside the bar's 7 s life), and report what they were waiting for if it never arrives.What I could not do
Reproduce it. CI saw the null once. Since then: 15 runs of the class alone, 6 runs of the whole suite, the release variant as well as debug, and a constructed case with
monitoringStoppedset so a second bar queues behind the first and forces the manager's hand-off. All green, every time.So the mechanism is unknown, and the issue text's guess — that it waits on a layout pass — is wrong: Material's bytecode shows
showView()attaches the view and only defers visibility until layout, andfindViewByIdfinds invisible views. I have corrected that on the issue.This change cannot be worse than what it replaces, and it turns a bare "expected not null" into a bounded wait that names its subject. But it is a mitigation, not a fix, and calling it one would be claiming a guard I never watched fail.
Also recorded: something that did not work
An
@Afterdraining the looper, to clear any bar left current in the manager, seemed like sensible insurance. It ran the activity recreate that the tap tests leave queued on purpose, against an activity the try-with-resources had already destroyed, and failed two tests with "Cannot recreate activity since it's destroyed already". Reverted, and written next to the@Afterso the idea is not had twice.Verified
733 tests green on both the debug and release variants (CI runs
./gradlew test, which is both — worth knowing, since running onlytestDebugUnitTestlocally covers half of what CI does). Lint clean.From review
Mutation-checked, and it changed what this PR claims. Setting
AWAIT_ROUNDSto zero kills exactly one assertion: the post-rotation wait intheOfferSurvivesARotationInsideItsWindow. So that is the only site where the bar genuinely needs looper rounds to arrive — at the other three it is already attached when the assertion runs, and the wait is insurance rather than a guard. That is now said in the code instead of implied.All four keep the wait anyway. The sites differ only by whether an activity was recreated first, and which of those needs waiting is not a property a reader should have to re-derive.
Corrected an overstatement of my own. The helper's JavaDoc said "more than twenty local runs of the class alone". It was fifteen of the class and six of the whole suite. That paragraph is gone rather than fixed — run counts rot, and they belong here, not in the source.
One review finding I did not take. The Spec axis argued the change sits on nondeterministic ground that no bound can fix: that
SnackbarManager.cancelSnackbarLockedreturnsfalsewithout clearingcurrentSnackbarwhen itsWeakReferencehas been collected, wedging the queue after a GC. The first half is right — I disassembled it. The conclusion is backwards. Inshow(), thatfalseis what takes the branch tocurrentSnackbar = null; showNextSnackbarLocked(), so a collected reference is the recovery path, not a wedge. The wedge case would be a live callback, which resolves through the looper — exactly what a bounded wait absorbs.