diff options
Diffstat (limited to 'app/tests/test_backends_audiocpp.py')
| -rw-r--r-- | app/tests/test_backends_audiocpp.py | 163 |
1 files changed, 152 insertions, 11 deletions
diff --git a/app/tests/test_backends_audiocpp.py b/app/tests/test_backends_audiocpp.py index 3e97b82..6f97438 100644 --- a/app/tests/test_backends_audiocpp.py +++ b/app/tests/test_backends_audiocpp.py @@ -1,5 +1,6 @@ """Tests for the audio.cpp backend setup module (backends/audiocpp.py).""" +import argparse import io import json import sys @@ -983,9 +984,10 @@ class TranscribeWavDirTests(unittest.TestCase): self._td.cleanup() def test_transcribes_to_stem_map_with_absolute_paths(self): - transcripts = {str(self.narrator): "First.", - str(self.other): "Second."} - with patch.object(make_server.voices, "transcribe_reference_audio", + transcripts = {str(self.narrator): ("First.", "ok"), + str(self.other): ("Second.", "ok")} + with patch.object(make_server.voices, + "transcribe_reference_audio_detailed", side_effect=lambda path, model_name="base": transcripts[path]): result = make_server.voices.transcribe_wav_dir( @@ -994,14 +996,27 @@ class TranscribeWavDirTests(unittest.TestCase): self.assertEqual(result["narrator"], "First.") def test_failed_transcription_keeps_empty_string(self): - with patch.object(make_server.voices, "transcribe_reference_audio", - return_value=None): + with patch.object(make_server.voices, + "transcribe_reference_audio_detailed", + return_value=(None, "no speech detected")): result = make_server.voices.transcribe_wav_dir([self.narrator], "base") self.assertEqual(result["narrator"], "") + def test_failed_transcription_prints_the_reason(self): + buffer = io.StringIO() + with patch.object(make_server.voices, + "transcribe_reference_audio_detailed", + return_value=(None, "faster_whisper heard no speech")), \ + redirect_stdout(buffer): + make_server.voices.transcribe_wav_dir([self.narrator], "base") + output = buffer.getvalue() + self.assertIn("No transcript for 'narrator'", output) + self.assertIn("faster_whisper heard no speech", output) + def test_whisper_model_name_passed_through(self): - with patch.object(make_server.voices, "transcribe_reference_audio", - return_value="text") as mock_transcribe: + with patch.object(make_server.voices, + "transcribe_reference_audio_detailed", + return_value=("text", "ok")) as mock_transcribe: make_server.voices.transcribe_wav_dir([self.narrator], "large-v3") self.assertEqual(mock_transcribe.call_args.kwargs["model_name"], "large-v3") @@ -1015,6 +1030,122 @@ class TranscribeWavDirTests(unittest.TestCase): self.assertIn("other|World.", text) +class TranscribePlanTests(unittest.TestCase): + """_transcribe: plan application and transcript wipe protection.""" + + def setUp(self): + self._td = tempfile.TemporaryDirectory() + self.folder = Path(self._td.name) + self.narrator = self.folder / "narrator.wav" + self.narrator.write_bytes(b"x") + self.args = argparse.Namespace(input_dir=self.folder, + whisper_model="base") + + def tearDown(self): + self._td.cleanup() + + def test_all_mode_retranscribes_everything(self): + with patch.object(make_server.voices, + "transcribe_reference_audio_detailed", + return_value=("New words.", "ok")): + transcripts, write = make_server.voices._transcribe( + self.args, {"mode": "all", "missing": [], "existing": {}}) + self.assertTrue(write) + self.assertEqual(transcripts, {"narrator": "New words."}) + + def test_empty_retranscription_keeps_existing_transcript(self): + # A failed re-transcription must never overwrite known-good text + # with a blank: a blank prompt_text entry makes the server reject + # every clone request for that voice. + with patch.object(make_server.voices, + "transcribe_reference_audio_detailed", + return_value=(None, "backend broken")), \ + redirect_stdout(io.StringIO()) as buffer: + transcripts, write = make_server.voices._transcribe( + self.args, {"mode": "all", "missing": [], + "existing": {"narrator": "Good words."}}) + self.assertTrue(write) + self.assertEqual(transcripts, {"narrator": "Good words."}) + self.assertIn("Kept the existing transcript for 'narrator'", + buffer.getvalue()) + + def test_missing_mode_merges_new_with_existing(self): + with patch.object(make_server.voices, + "transcribe_reference_audio_detailed", + return_value=("Fresh text.", "ok")): + transcripts, _write = make_server.voices._transcribe( + self.args, {"mode": "missing", "missing": [self.narrator], + "existing": {}}) + self.assertEqual(transcripts, {"narrator": "Fresh text."}) + + def test_unusable_backend_warns_with_the_reason(self): + buffer = io.StringIO() + with patch.object(make_server.voices, "whisper_backend_problem", + return_value="faster_whisper is installed but " + "failed to import: boom"), \ + patch.object(make_server.voices, + "transcribe_reference_audio_detailed", + return_value=("text", "ok")), \ + redirect_stdout(buffer): + make_server.voices._transcribe( + self.args, {"mode": "all", "missing": [], "existing": {}}) + output = buffer.getvalue() + self.assertIn("No usable Whisper backend", output) + self.assertIn("failed to import: boom", output) + + +class WizardTranscribeStepTests(unittest.TestCase): + """The setup lane's transcribe step: rc reflects unusable transcripts.""" + + def setUp(self): + self._td = tempfile.TemporaryDirectory() + self.folder = Path(self._td.name) + (self.folder / "narrator.wav").write_bytes(b"x") + self.args = argparse.Namespace(input_dir=None, whisper_model="base") + self.settings = { + "audiocpp_dir": self.folder, + "wav_dir": self.folder, + "include_clone": True, + "plan": {"mode": "all", "missing": [], "existing": {}}, + "build": None, + "model_entries": [], + } + + def tearDown(self): + self._td.cleanup() + + def _transcribe_step(self): + lanes = make_server.wizard._execute_lanes(self.settings, self.args) + return lanes[0].steps[0] + + def test_blank_transcripts_fail_the_step(self): + step = self._transcribe_step() + with patch.object(make_server.voices, + "transcribe_reference_audio_detailed", + return_value=(None, "broken backend")), \ + redirect_stdout(io.StringIO()) as buffer: + rc = step.work(None, None) + self.assertEqual(rc, 1) + self.assertIn("No transcript for: narrator", buffer.getvalue()) + + def test_good_transcripts_pass_the_step(self): + step = self._transcribe_step() + with patch.object(make_server.voices, + "transcribe_reference_audio_detailed", + return_value=("Words.", "ok")), \ + redirect_stdout(io.StringIO()): + self.assertEqual(step.work(None, None), 0) + + def test_no_clone_families_passes_without_transcribing(self): + self.settings["include_clone"] = False + self.settings["plan"] = None + step = self._transcribe_step() + with patch.object(make_server.voices, + "transcribe_reference_audio_detailed") as mock_transcribe: + self.assertEqual(step.work(None, None), 0) + mock_transcribe.assert_not_called() + + class DesignPackageTests(unittest.TestCase): """Voice-design package detection.""" @@ -1594,14 +1725,24 @@ class NonInteractiveMainTests(unittest.TestCase): argv = ["backends/audiocpp.py"] + argv transcribe_effect = transcribe if transcribe is not None \ else MagicMock() + + def detailed(path, model_name="base"): + result = transcribe_effect(path, model_name=model_name) + if isinstance(result, tuple): + return result + return (result, "ok" if result + else "faster_whisper is not installed (test stub)") + with patch.object(sys, "argv", argv), \ patch.object(make_server.build, "find_local_checkout", return_value=None if no_checkout else self.checkout), \ - patch.object(make_server.voices, "transcribe_reference_audio", - side_effect=transcribe_effect), \ - patch.object(make_server.voices, "whisper_backend_available", - return_value=whisper): + patch.object(make_server.voices, + "transcribe_reference_audio_detailed", + side_effect=detailed), \ + patch.object(make_server.voices, "whisper_backend_problem", + return_value=None if whisper else + "faster_whisper is not installed"): return make_server.wizard.main() def _args(self, *extra): |
