diff options
| author | historia <historiavg@proton.me> | 2026-09-01 03:17:01 -0400 |
|---|---|---|
| committer | historia <historiavg@proton.me> | 2026-09-01 03:17:01 -0400 |
| commit | 058b19e7a65b40b1024a4fdeb2233062ff273cfd (patch) | |
| tree | fe3643872cd6b317a88eec950ae6ecc4d81d843d /app/tests/test_runview.py | |
| parent | 10e72d4960e865acf5346ab8cf518ed5844fe45c (diff) | |
| download | tts-audiobook-generator-058b19e7a65b40b1024a4fdeb2233062ff273cfd.tar.gz | |
fix: better errors for generate all models
Diffstat (limited to 'app/tests/test_runview.py')
| -rw-r--r-- | app/tests/test_runview.py | 132 |
1 files changed, 132 insertions, 0 deletions
diff --git a/app/tests/test_runview.py b/app/tests/test_runview.py index f8ab452..4e3f9fe 100644 --- a/app/tests/test_runview.py +++ b/app/tests/test_runview.py @@ -118,6 +118,97 @@ class StateTransitionTests(_FakeTui, unittest.TestCase): self.assertEqual(view.phase, "error") self.assertTrue(view.error_message) + def test_chunk_failure_message_names_the_model_and_reason(self): + # An "All" run stamps chunk_failed with the generating model and + # the server's error detail; the message carries both, and the + # results row keeps the reason (book_done(ok=False) has none). + view, _ = self.make_view() + view.handle_event({"kind": "book", "index": 1, "total": 3, + "name": "b.txt", "model": "m1"}) + view.handle_event({"kind": "chunk_failed", "chunk": 1, "total": 1, + "error": "vocoder backend buffer allocation " + "failed", "model": "m1"}) + self.assertEqual(view.error_message, + "m1: chunk 1/1 failed — vocoder backend buffer " + "allocation failed") + view.handle_event({"kind": "book_done", "name": "b.txt", + "ok": False, "model": "m1"}) + self.assertEqual(view.book_results, + [("b.txt", False, [], + "vocoder backend buffer allocation failed", + "m1")]) + + def test_chunk_failure_without_detail_keeps_the_count(self): + # No server error detail: the message (and the row reason) still + # say which chunk of how many failed. + view, _ = self.make_view() + view.handle_event({"kind": "book", "index": 1, "total": 1, + "name": "b", "model": "m1"}) + view.handle_event({"kind": "chunk_failed", "chunk": 2, "total": 4, + "model": "m1"}) + self.assertEqual(view.error_message, "m1: chunk 2/4 failed") + view.handle_event({"kind": "book_done", "name": "b", "ok": False, + "model": "m1"}) + self.assertEqual(view.book_results[0][3], "chunk 2/4 failed") + + def test_new_book_clears_a_stale_failure_message(self): + # The heart of the "Chunk 1/1 failed persisted for every later + # model" bug: a failure message from one book of an "All" run must + # not linger under the next book's progress. + view, _ = self.make_view() + view.handle_event({"kind": "book", "index": 1, "total": 2, + "name": "b.txt", "model": "m1"}) + view.handle_event({"kind": "chunk_failed", "chunk": 1, "total": 1, + "error": "boom", "model": "m1"}) + view.handle_event({"kind": "book_done", "name": "b.txt", + "ok": False, "model": "m1"}) + self.assertTrue(view.error_message) + view.handle_event({"kind": "book", "index": 2, "total": 2, + "name": "b.txt", "model": "m2"}) + self.assertEqual(view.error_message, "") + self.assertEqual(view._book_error, "") + + def test_done_message_lists_the_failed_models(self): + # The terminal "done" screen names what failed instead of showing + # the last chunk failure (or a bare count). + view, _ = self.make_view() + for index, (model, ok) in enumerate( + [("m1", False), ("m2", True), ("m3", False)], 1): + view.handle_event({"kind": "book", "index": index, "total": 3, + "name": "b.txt", "model": model}) + view.handle_event({"kind": "book_done", "name": "b.txt", + "ok": ok, "model": model}) + view.handle_event({"kind": "done", "ok": 1, "total": 3}) + self.assertEqual(view.phase, "error") + self.assertEqual(view.error_message, + "2 of 3 book(s) failed: m1, m3") + + def test_done_message_caps_the_failed_model_list(self): + # More failures than fit the two detail lines: the list is capped + # with the leftover count (the rows carry the full list). + view, _ = self.make_view() + for index in range(1, 8): + view.handle_event({"kind": "book", "index": index, "total": 7, + "name": "b.txt", "model": f"m{index}"}) + view.handle_event({"kind": "book_done", "name": "b.txt", + "ok": False, "model": f"m{index}"}) + view.handle_event({"kind": "done", "ok": 0, "total": 7}) + self.assertEqual( + view.error_message, + "7 of 7 book(s) failed: m1, m2, m3, m4, m5, … +2 more") + + def test_done_all_failed_rows_only_names_models_of_failures(self): + # A model that cannot even start (audiobook.py's constructor + # failure path) reports book_failed without a model field: the + # name stands in for the model in the summary. + view, _ = self.make_view() + view.handle_event({"kind": "book_failed", "name": "m1", + "error": "voice 'x' is not available", + "files": []}) + view.handle_event({"kind": "done", "ok": 0, "total": 1}) + self.assertEqual(view.error_message, + "1 of 1 book(s) failed: m1") + def test_server_exit_during_boot_is_error(self): view, _ = self.make_view() view.handle_event({"kind": "starting", "name": "audiocpp"}) @@ -273,6 +364,47 @@ class RenderTests(_FakeTui, unittest.TestCase): text = self._strings(screen) self.assertIn("book.txt — m1", text) + def test_summary_shows_failed_rows_first_with_their_reason(self): + # With dozens of "All"-run results, the failures must be visible + # without scrolling past the successes, and each failed row says + # why it failed. + view, screen = self.make_view() + view.handle_event({"kind": "book", "index": 1, "total": 3, + "name": "book.txt", "model": "m1"}) + view.handle_event({"kind": "book_done", "name": "book.txt", + "ok": True, "model": "m1"}) + view.handle_event({"kind": "book", "index": 2, "total": 3, + "name": "book.txt", "model": "m2"}) + view.handle_event({"kind": "chunk_failed", "chunk": 1, "total": 1, + "error": "missing model root: dac", + "model": "m2"}) + view.handle_event({"kind": "book_done", "name": "book.txt", + "ok": False, "model": "m2"}) + view.handle_event({"kind": "book", "index": 3, "total": 3, + "name": "book.txt", "model": "m3"}) + view.handle_event({"kind": "book_done", "name": "book.txt", + "ok": True, "model": "m3"}) + view.handle_event({"kind": "done", "ok": 2, "total": 3}) + view.render() + text = self._strings(screen) + fail_pos = text.index("[FAIL]") + ok_pos = text.index("[OK]") + self.assertLess(fail_pos, ok_pos) + self.assertIn("book.txt — m2: missing model root: dac", text) + + def test_progress_line_shows_the_model_of_a_chunk_failure(self): + # While the run is live, the message line names the model that + # failed (not just the chunk counters). + view, screen = self.make_view() + view.handle_event({"kind": "book", "index": 4, "total": 30, + "name": "book.txt", "model": "DramaBox-GGUF"}) + view.handle_event({"kind": "chunk_failed", "chunk": 1, "total": 1, + "error": "vocoder backend buffer allocation " + "failed", "model": "DramaBox-GGUF"}) + view.render() + text = self._strings(screen) + self.assertIn("DramaBox-GGUF: chunk 1/1 failed", text) + def test_stopping_status_shows_elapsed(self): view, screen = self.make_view() view.handle_event({"kind": "book", "index": 1, "total": 1, |
