Six JVM test gaps from the 2026-08-26 coverage read, each with a sibling test that already models it #132
Closed
opened 2026-08-27 02:20:52 +00:00 by JMR-dev
·
4 comments
No Branch/Tag Specified
main
fix/102-picker-back-press-overshoot
fix/268-saf-picker-determinism
feat/ogg-vorbis-libvorbis
feat/expedited-conversion-work
fix/gate-cache-in-worktrees
chore/gate-runs-shellcheck
test/publish-delete-arm-real-provider
docs/e8-instrumented-coverage
docs/api37-carrier-count-drift
docs/e7-second-constraint
test/publish-to-a-real-saf-destination
fix/api37-report-match-line
fix/api37-task-snapshot-crash
test/join-failure-message-on-device
docs/e2e-read-findings-e7
test/cancelling-a-running-export
test/reattach-to-a-running-job
test/content-uri-reaches-ffmpeg
fix/launcher-wiring-waits-for-the-pick
fix/cancel-tests-need-a-slower-encode
test/cancelling-a-running-join
test/cancelling-a-running-session
test/notification-cancel-action
test/ffmpeg-progress-is-observed
test/fallback-asserts-the-path
test/flac-and-opus-assert-their-format
docs/e2e-read-findings
docs/wave4-coverage-numbers
fix/injectable-startup-sweep-scope
test/session-outcome-seam
test/launcher-callback-identity
test/theme-follows-system-dark
test/audio-drop-arm
fix/rotation-waits-for-recreation
fix/convert-guards-on-ready
test/retry-save-mime
test/hardware-progress-reaches-workmanager
test/ffprobe-mapping-seam
test/device-codec-enumeration-seam
test/unknown-container-row
test/null-message-fallbacks
test/cancel-reaches-workmanager
docs/coverage-wave3-recovery
test/concat-engine-seam
docs/coverage-wave3
test/mediaprobe-merge-seam
test/adaptive-shell-wiring
test/aac-audio-args
test/notification-progress-text
test/media3-muxer-guard
test/hardware-fallback-and-cancellation
test/unprobeable-join-clip
test/one-branch-outcomes
test/foreground-type-regimes
fix/bound-wedge-diagnostics
docs/coverage-wave2
test/screen-wiring
test/viewmodel-setters
test/join-state-mapping
test/conversion-state-mapping
test/dedupe-user-messages
fix/restore-stack-merges
test/refused-jobs
test/concatworker-failure-arms
test/container-capabilities-audio
test/readspec-enum-fallbacks
test/outputpublisher-seams
test/mediaprobe-track-seam
test/outputpublisher-partial-branches
test/fake-provider-scaffolding
docs/coverage-read-findings
chore/gitignore-kotlin
test/bound-the-hangs
docs/coverage-remeasure
ci/baseline-counter-precision
fix/invalid-suggestion-chip
ci/wedged-leg-report
fix/reattachment-overwrites-pick
test/theme-live-branches
fix/failed-save-retry
fix/empty-composition-crash
ci/advisory-failure-report
docs/seven-run-counts
test/release-permission-guard
ci/build-workflow-permissions
docs/api37-point-release
docs/benchmark-populate-path
fix/dead-assertion-probe-test
ci/actionlint
test/device-codecs-encode-consequence
fix/sdkmanager-pipefail
docs/readme-restart-claim
fix/saf-picker-root-discovery
fix/probe-dispatcher-seam
test/media3engine-mime-tables
test/mediaprobe-pure-helpers
fix/codec-vocabulary-drift
docs/robolectric-rationale-correction
docs/api37-advisory-counts
test/r38-8-saf-e2e
test/r38-7-join-states
test/r38-6-conversion-states
test/r38-5-state-seam
fix/jacoco-robolectric-coverage
docs/instrumented-tests-correction
test/r38-2-filecard
test/r38-4-advanced-picker
test/r38-3-pickers
tools/file-issue-script
tools/api-37-emulator
fix/review-app-gaps
docs/review-corrections
No results found.
Labels
Clear labels
above-cut
accessibility
backlog
bug
confirmed
documentation
duplicate
enhancement
good first issue
help wanted
invalid
plausible
question
sev:high
sev:low
sev:medium
wontfix
Worked autonomously overnight: local, JVM-verifiable, no product decision
Barrier affecting people with disabilities
Held for manual review: product/UX call, CI/workflow, hardware, or unverifiable here
Something isn't working
Reviewer demonstrated the defect
Improvements or additions to documentation
This issue or pull request already exists
New feature or request
Good for newcomers
Extra attention is needed
This doesn't seem right
Reviewer could not fully demonstrate it; treat as unproven
Further information is requested
High severity
Low severity
Medium severity
This will not be worked on
Milestone
No items
No Milestone
Projects
Clear projects
No projects
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: JMR-dev/LibreMediaConverter#132
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Filed from a coverage read on
main@dc8b7c3, 2026-08-26. The number is not the reason — see the last section. Companion ticket: #133 holds the seam questions from the same read; the code findings aredocs/coverage-read-findings.md(PR #131).Re-measured with
./gradlew :app:jacocoTestReport: 84.9% line (1971/2321), 63.8% branch (900/1410), against 456 JVM tests in 68 classes. (CLAUDE.mdquotes 454/67 from four hours earlier; percentages unchanged, so nothing there is stale.)Read this first: what "gap" means here
JaCoCo in this repo measures
testDebugUnitTestonly. Every item below was checked against theandroidTestsuite by name before being listed, because that boundary is what made #52's premise an artifact and what #84, #85, #86 and #88 each had to correct for. These seven are untested, not merely unmeasured.Three near-misses were checked and dropped rather than listed:
ConcatWorker.doWork's two input guards (:40,:42) — covered byUnopenableUriTest.aJoinWithNoInputArrayFailsWithAMessageandConcatWorkerTest.aSingleInputFailsWithAnActionableMessage.ConcatWorker.getForegroundInfo(:133-137) — #88 settled this as a named exemption. Not reopened here.ConversionForegroundType.current()'s API 33/34 arms — looked like the sharpest item in the read. #88 established the CI matrix covers all three regimes, and the premise worth re-checking was whether #122's wedge still kills the 33/34 legs.Corrected after filing. This bullet first said "it does not", from one green run. #122 is not resolved — it fired on run
33033036857, a docs-only PR:wedged: yes — gradle was killed after 1200s,failed: unknown, 23m08s. It is intermittent (five of the last six completed legs passed in ~7 minutes), and what it costs is the verdict, not the execution:received: 60means all sixty tests still reported, so the API 33 regime was exercised — the leg simply could not have said so if one had broken.Still not an item on this ticket. #88's reasoning holds. But a
@Config(sdk = 33)/@Config(sdk = 34)JVM test is three lines of insurance against a leg that cannot be relied on to go red, and is worth folding in alongside item 1, which is in the same file's neighbourhood. Evidence is indocs/coverage-read-findings.md.Children
Decomposed 2026-08-27. #134 first — it unblocks two of the others and changes nothing in production.
readSpec's three enum fallbacksvalidateInputQueryhas never been handed a cursor rowConcatWorker's cancellation and foreground-denied armsConversionWorker's missing-URI and invalid-spec refusalsOutputPublisher#134 is not in the six above. It came out of decomposition: items 3 and 6 are both cursor-shaped, and
the provider that can drive them exists but cannot answer wrongly. Filing it separately rather than
duplicating the work in two children follows #57, which was the same shape — an enabler with no
behaviour change, picked up first.
Four of the seven depend on nothing and can be taken in any order.
The six
Each has a sibling already in the suite that establishes the pattern, which is what makes these small diffs rather than new harness work.
work/ConversionWorker.kt:332,335,338?: return fallbackarms ofreadSpec()WorkerEnumFallbackTestmodel/ContainerCapabilities.kt:227-229,232-234,241-243,247-250+:101-102validate, six refusal messagesContainerCapabilitiesTest— has the video twin of eachconvert/InputQuery.kt:90,104-105,107-108OutputPublisherPublishTest'sFakeSafProvider— same packagework/ConcatWorker.kt:92,95-96,105-106FOREGROUND_DENIEDWorkerCancellationTest,DeniedForegroundStartTestwork/ConversionWorker.kt:62and:124-126KEY_INPUT_URI; theValidation.InvalidrefusalWorkerEnumFallbackTest,SpaceCheckTestconvert/OutputPublisher.kt:197,235,258,271destinationIsKnownEmpty; two null guardsOutputPublisherPublishTest,StagingSweepTest1.
readSpec()'s three fallbacks — the sharpest item here.WorkerEnumFallbackTestexists for this exact defect class and its KDoc states the signature precisely: a name this build does not define, read above thetry, threw out ofdoWork()entirely — FAILED withreschedule = false, empty outputDataso the screen said "Conversion failed." with nothing else, and the staged file never deleted. It covers 2 of the 5 above-the-tryreads (quality, engine preference).readSpec()is called at:69, also above thetry, and its container / video / audio reads are all cold.Done means each of the three names independently unresolvable, each falling back to
OutputFormat.MP4_H265.specwithout throwing.Mutation: change one
?: return fallbackto?: error(...)— the test must go red naming that axis, not time out.2. The audio half of
validate.Six user-visible refusal strings with no test: unidentifiable source audio on a COPY (
:227), a container that cannot hold the copied source (:232), a codec the container cannot carry (:241), a codec this app cannot encode (:247), plus both arms ofaccepts(_, AudioCodec, _)at:101-102— including theerror("Resolve COPY to a concrete codec…")guard whose video twin is already tested atContainerCapabilitiesTest:110.Done means each refusal named by its message and its suggestions asserted valid, mirroring the video cases beside them.
Mutation: swap
CARRIES_AUDIOforCARRIES_VIDEOinvalidateAudio— the container-cannot-hold tests must go red.3.
InputQuery's cursor half.firstRow's body (:90),displayNameOrNull(:104-105) andsizeOrNull(:107-108) have never executed.UnknownInputSizeTestis the sibling for the file but not for this: its KDoc is explicit that it drives the case where no provider is registered, so the query returns null andmeasureanswers instead. Nothing in the suite has ever handedInputQuerya row.Untested as a result: display name resolution, size from
OpenableColumns.SIZE, bothisNullguards, the missing-column guard, and the negative-size rejection at:108— which is one of the two placesCLAUDE.md's zero-vs-unknown distinction is actually enforced.The harness for this already exists and is in the same package.
OutputPublisherPublishTestdeclaresinternal open class FakeSafProvider : ContentProvider()inorg.libremediaconverter.convert, returning aMatrixCursorover exactlyOpenableColumns.DISPLAY_NAMEandOpenableColumns.SIZE— the two columnsInputQueryreads — withRobolectric.buildContentProvider(...).create(info)beside it. It isopenalready. What it does not yet do is misbehave: its row is always(file.name, file.length()), so covering the null, missing-column and negative-size cases means one subclass or one knob on it, not a new harness.Done means a row with a name and size, a row with the columns absent, a row with
SIZEnull, and a row with a negativeSIZEfalling through tomeasure.Mutation: drop
.takeIf { it >= 0 }from:108— the negative-size test must go red.4.
ConcatWorker's failure branches.ConversionWorkerhasWorkerCancellationTestandDeniedForegroundStartTest; its twin has neither. Untested: theCancellationExceptionarm that deletes the staged file and rethrows (:92, 95-96) — the ruledefect-audit.mdD10 exists for — andFOREGROUND_DENIED(:105-106), of the threeFailureOutcomearms the only one cold.Done means both, asserted on the staged file's absence and on the
KEY_ERRORvalue respectively.Mutation: change the cancellation arm to return
Result.failure()instead of rethrowing — the test must go red.5. Two more in
ConversionWorker.:62— a missingKEY_INPUT_URIreturning"No input file."is untested everywhere:ForcedFailureTest.aMissingInputFailsRatherThanCrashingpasses a URI to a nonexistent file, which is a different path.:124-126— theValidation.Invalidrefusal, whose comment names its reason for existing: a job enqueued before the settings changed, or a hand-builtConversionWorker.request(...). That is a real arrival path (WorkManager keeps queued work for about a week, the same premiseWorkerEnumFallbackTestis written on) and nothing exercises it.Mutation: delete the
if (validation is Validation.Invalid)block — the test must go red on the error message, not on a downstream conversion failure.6.
OutputPublisheredges.destinationIsKnownEmpty(:197) has three untested short-circuits — noSIZEcolumn, no row, null value — and each one must answer false, because the KDoc is explicit that "I could not tell" must never authorise a delete. That is the guard standing between a failed save and deleting a file the user already had. Plusstaged.parentFile == null(:235) andlistFiles()returning null (:258).These are cursor-shaped too, so they take the same
FakeSafProviderextension item 3 needs. Doing 3 and 7 together is one piece of harness work and two sets of assertions.Mutation: change
:197'ssize >= 0 &&tosize >= -1 &&— the missing-column test must go red.What this ticket is not
MediaProbe's extractor half and twoOutputPublisheredges. That is #133, which also records whyAndroidDeviceCodecs.probe()was considered and left out.docs/coverage-read-findings.md, PR #131.Not the acceptance
The coverage number. These six are worth roughly 22 lines of 2321 and will barely move it. JaCoCo did not count a Robolectric test in this repo until #76, #52's entire premise was an artifact of that, and
CLAUDE.mdis explicit that the norm is behaviours having tests that bite, not a percentage rising. Every item above carries a mutation for that reason. A test that does not go red when its line is reverted has not closed its row.All children are implemented, and the batch integrates. Verified locally rather than assumed, because eight PRs touching overlapping files is exactly where a clean-per-PR result stops meaning much.
Merged all eight branches onto current
mainin a throwaway branch:ktlintCheck,detekt,lintDebug,testDebugUnitTest,compileDebugAndroidTestKotlinBranch moved more than line, which is what this batch was aimed at — nearly every test here targets a guard rather than a new code path.
Files this batch was about, after integration:
InputQuery.ktOutputPublisher.ktContainerCapabilities.ktMediaProbe.ktConcatWorker.ktConversionWorker.ktWhat remains in those files is the native/device half and the named exemptions recorded in each PR — not unclaimed gaps.
Follow-up worth its own ticket, not folded in here:
CLAUDE.md's coverage entry quotes 84.9%/63.8% and instructs re-measuring before quoting. It goes stale the moment this batch lands. Updating it now would be quoting a number that is not true ofmainyet, which is the exact failure that entry documents about itself.Residual-gap audit: what is still cold after all ten children, and why
The children are done, so the honest closing question is not "did coverage go up" but "of the lines still never executed, does each one map to something already named?" I rebuilt the integration branch (all eight PR heads are ancestors of it — #151 is stacked on #149 on #144, so the tip carries the stack), ran
jacocoTestReport, and took every line withci == 0.Everything mapped except one.
ConversionWorker.kt — 15 lines, all named
runMedia3OrFallBack,isCancellationisCancellationis only called from line 213FFmpegKitConfig.getSafParameterForReadand its elvisgetForegroundInfooverrideTestListenableWorkerBuilderdoes notConcatWorker.kt — one line did not map
suspend fun doWorksignatureConcatEngine.joinsuccess pathNamingPublisher's KDoc already says no JVM test gets past itgetForegroundInfoCLAUDE.md"No input files."UnopenableUriTest.aJoinWithNoInputArrayFailsWithAMessage"Pick at least two files to join."The one gap, and why it hid
Lines 40 and 42 are two arms of the same guard, four lines apart. JaCoCo shows both cold and cannot tell them apart, because it measures
testDebugUnitTestonly and line 40's test lives inandroidTest. Reading the report alone, either both look like gaps or — if you know the join flow is e2e-covered — both look accounted for. Onlygrepover the androidTest source separates them:"No input files."appears there,"Pick at least two files to join."appears nowhere.That is the generalisable finding from this audit, and it is worth more than the line itself: on this repo, "cold in JaCoCo" and "untested" differ by whatever androidTest happens to cover, so a residual-line audit has to read both source sets or it will mis-sort adjacent branches.
Closed in
test/refused-jobs(#148), where the file's existing charter — "jobs the worker refuses before it converts anything" — already covers it. Two tests, each killed by exactly one mutation:a join of a single file is refused with a message rather than joineduris.size < 2→< 3a join of two files is not refused for its countThe control refuses the space rather than running the job, so it proves execution cleared line 42 without touching the native engine.
Also cold, and correctly so
ConversionNotifications.areEnabled()(60–62) — F5 indocs/coverage-read-findings.md, already merged. No callers; a test would pin dead code. Line 30 is thebuild$defaultbridge, an artifact.MediaProbe.kt's residual lines are accounted for in #150's PR body (the FFprobe half and the two catch arms) and are not re-audited here.Running mutation total across the batch: 50 run, 45 red. The five green are all written down — three unfalsifiable guards recorded as named exemptions in test KDoc, one seam I had put in the wrong place (#143), and one bad mutation of mine that reached the same return by a different route.
Follow-up wave filed as #153.
After this ticket's children were done I ran a repo-wide sweep rather than the three-file residual audit that closed this one out, and it found a coherent second wave — chiefly that neither ViewModel's
WorkInfo→ UI state machine has any test that chooses which branch it takes. Bothobserveblocks execute on every test that drives a real worker, so they never read as cold; what a real worker cannot produce is aRUNNINGprogress read, anENQUEUEDwith a retry count, aSUCCEEDEDwith no output path, or a blank failure message.The detail that made it worth filing:
ReattachmentTestalready drives all sixWorkInfo.Stateconstants — intoReattachment.choose, a different function that encodes the same enqueued-means-retry rule. So that rule is tested in one of its two homes, and the untested one is the copy the user's screen reads.#153 carries the full accounting, including a table of what stays cold on purpose so the next sweep does not re-derive it.
All seven children closed. Five of them were finished on 2026-08-27 and stayed open for a bookkeeping reason worth recording.
#135, #136, #138, #139 and #141 each had a PR carrying
Closes #NNN. GitHub only fires a closing keyword when the PR merges into the default branch — those five merged into their stack bases during the async-retarget race (#160, now inCLAUDE.md), so the keywords never ran. #160 restored the content tomainbut did not carry the keywords, so the work landed and the tickets stayed open.That is the same failure as #160 wearing a different hat:
MERGEDwas not evidence the work reachedmain, and a closed keyword is not evidence either. Worth knowing that the bookkeeping fails silently in exactly the same way the content did.Verified before closing, against
mainatd354f64— by re-running each ticket's own named mutation, not by checking files exist:?: return fallback→error(...)COPY -> errorarm removedthrow e→Result.failure()Two of those needed adapting and the adaptation is on the tickets: #136's literal mutation cannot compile (
codecis anAudioCodec, soCARRIES_VIDEOis a type error, not a behaviour change), and #141's line had moved when the seam was cut.59 tests across the five classes, 0 failures. Full suite on
main: 546 tests in 76 classes, 88.9% line, 75.4% branch.