From 8ab433b647135059a9def3ef2b42b659d810540d Mon Sep 17 00:00:00 2001 From: Jason Ross Date: Wed, 26 Aug 2026 22:24:10 -0500 Subject: [PATCH] C1 (#135): pin readSpec's three enum fallbacks WorkerEnumFallbackTest already existed for this defect class -- a name this build does not define, read above the try, throwing out of doWork entirely: FAILED with reschedule=false, empty output Data so the screen said "Conversion failed." with nothing else, and the staged file never deleted. It covered 2 of the 5 above-the-try reads. readSpec's three were the ones left, and all three were cold. The baseline is the part worth reviewing. readSpec returns the *entire* fallback spec the moment any one axis fails to resolve, so a test starting from MP4_H265 -- which is itself the fallback -- cannot tell a worker that read the spec correctly from one that gave up on it. These start from MKV/H.264, which differs on container and video codec at once, and assert the spec that actually reached the transcoder rather than only that a Result came back. Mutations run, four for three tests: KEY_CONTAINER `?: return fallback` -> `?: error(...)` -> container test red KEY_VIDEO_CODEC same -> video test red KEY_AUDIO_CODEC same -> audio test red fallback = MP4_H264 instead of MP4_H265 -> all three red The first three confirm the tests are isolated to their own axis; the fourth confirms they pin *which* spec ran, which is what "a Result at all" would have missed. readSpec is now fully covered, branches included. Co-Authored-By: Claude Opus 5 (1M context) --- .../work/WorkerEnumFallbackTest.kt | 64 ++++++++++++++++++- 1 file changed, 61 insertions(+), 3 deletions(-) diff --git a/app/src/test/java/org/libremediaconverter/work/WorkerEnumFallbackTest.kt b/app/src/test/java/org/libremediaconverter/work/WorkerEnumFallbackTest.kt index 32cea02..ce32a1e 100644 --- a/app/src/test/java/org/libremediaconverter/work/WorkerEnumFallbackTest.kt +++ b/app/src/test/java/org/libremediaconverter/work/WorkerEnumFallbackTest.kt @@ -22,6 +22,7 @@ import org.libremediaconverter.model.DeviceCodecs import org.libremediaconverter.model.EnginePreference import org.libremediaconverter.model.InputProbe import org.libremediaconverter.model.OutputFormat +import org.libremediaconverter.model.OutputSpec import org.libremediaconverter.model.QualityTier import org.robolectric.RobolectricTestRunner import org.robolectric.RuntimeEnvironment @@ -109,6 +110,50 @@ class WorkerEnumFallbackTest { ) } + @Test + fun `a container this build does not define falls back to the default spec`() { + assertFallsBackToDefault(container = "HOLOTAPE") + } + + @Test + fun `a video codec this build does not define falls back to the default spec`() { + assertFallsBackToDefault(video = "H267") + } + + @Test + fun `an audio codec this build does not define falls back to the default spec`() { + assertFallsBackToDefault(audio = "SUPER_AAC") + } + + /** + * Drives a job whose spec is [NOT_THE_FALLBACK] on every axis but the one named, and asserts the + * whole spec came back as [DEFAULT_SPEC]. + * + * **The baseline is the point.** `readSpec` returns the *entire* fallback spec the moment any + * one axis fails to resolve, so a test starting from `MP4_H265` -- which is itself the fallback + * -- could not tell a worker that read the spec correctly from one that gave up on it. Starting + * from MKV/H.264 makes the difference visible on two axes at once. + * + * Asserting the spec that *ran*, rather than only that a `Result` came back, is the other half: + * the defect these three are written for threw out of `doWork` entirely, so "a Result at all" + * would pass against a fallback to something arbitrary. + */ + private fun assertFallsBackToDefault( + container: String = NOT_THE_FALLBACK.container.name, + video: String = NOT_THE_FALLBACK.videoCodec.name, + audio: String = NOT_THE_FALLBACK.audioCodec.name, + ) { + val transcoder = RequestRecordingTranscoder() + ConversionDependencies.software = { transcoder } + + val result = runBlocking { + conversionWorker(container = container, video = video, audio = audio).doWork() + } + + assertEquals(ListenableWorker.Result.success(), stripOutput(result)) + assertEquals(listOf(DEFAULT_SPEC), transcoder.specs) + } + /** [ListenableWorker.Result.Success] compares its output data, which these tests do not pin. */ private fun stripOutput(result: ListenableWorker.Result): ListenableWorker.Result = if (result is ListenableWorker.Result.Success) ListenableWorker.Result.success() else result @@ -116,15 +161,18 @@ class WorkerEnumFallbackTest { private fun conversionWorker( quality: String = QualityTier.FAST.name, preference: String = EnginePreference.FORCE_SOFTWARE.name, + container: String = SPEC.container.name, + video: String = SPEC.videoCodec.name, + audio: String = SPEC.audioCodec.name, ): ConversionWorker = TestListenableWorkerBuilder( context = app, inputData = workDataOf( ConversionWorker.KEY_INPUT_URI to INPUT.toString(), ConversionWorker.KEY_DISPLAY_NAME to DISPLAY_NAME, ConversionWorker.KEY_SIZE_BYTES to INPUT_BYTES, - ConversionWorker.KEY_CONTAINER to SPEC.container.name, - ConversionWorker.KEY_VIDEO_CODEC to SPEC.videoCodec.name, - ConversionWorker.KEY_AUDIO_CODEC to SPEC.audioCodec.name, + ConversionWorker.KEY_CONTAINER to container, + ConversionWorker.KEY_VIDEO_CODEC to video, + ConversionWorker.KEY_AUDIO_CODEC to audio, ConversionWorker.KEY_QUALITY to quality, ConversionWorker.KEY_ENGINE_PREFERENCE to preference, ), @@ -146,6 +194,12 @@ class WorkerEnumFallbackTest { const val DISPLAY_NAME = "holiday.mp4" const val INPUT_BYTES = 1024L val SPEC = OutputFormat.MP4_H265.spec + + /** What `readSpec` returns when any axis fails to resolve. */ + val DEFAULT_SPEC = OutputFormat.MP4_H265.spec + + /** A spec that differs from [DEFAULT_SPEC] on container *and* video codec. See the helper. */ + val NOT_THE_FALLBACK = OutputFormat.MKV_H264.spec val CONVERSION_ID: UUID = UUID.fromString("00000000-0000-4000-8000-000000000021") val CONCAT_ID: UUID = UUID.fromString("00000000-0000-4000-8000-000000000022") } @@ -156,6 +210,9 @@ private class RequestRecordingTranscoder : SoftwareTranscoder { val qualities = mutableListOf() + /** The spec each run was asked for. Which one ran is what the three readSpec tests assert. */ + val specs = mutableListOf() + override suspend fun run( request: ConversionRequest, inputPath: String, @@ -164,6 +221,7 @@ private class RequestRecordingTranscoder : SoftwareTranscoder { onProgress: (Int) -> Unit, ) { qualities += request.quality + specs += request.spec output.writeBytes(ByteArray(OUTPUT_BYTES)) }