diff options
Diffstat (limited to 'app/tests/test_hub.py')
| -rw-r--r-- | app/tests/test_hub.py | 181 |
1 files changed, 82 insertions, 99 deletions
diff --git a/app/tests/test_hub.py b/app/tests/test_hub.py index 94ea286..f49b8d1 100644 --- a/app/tests/test_hub.py +++ b/app/tests/test_hub.py @@ -375,11 +375,11 @@ class HubMenuTests(unittest.TestCase): # ("input") rows; the numbered steps start at the margin and # the indented rows carry a leading space of their own, so # they line up with the step text after the "N. " prefixes. - # They resolve live from the converter module, so a Settings - # change this session is reflected. + # They resolve at call time, so a Settings change this session + # is reflected. self.assertIn(([(" " + str(hub.converter_mod.BOOKS_FOLDER), "input")], 1), items) - self.assertIn(([(" " + str(hub.common.VOICES_DIR), "input")], 1), + self.assertIn(([(" " + str(hub._clone_wav_dir()), "input")], 1), items) self.assertIn(([(" " + str(hub.converter_mod.AUDIOBOOKS_FOLDER), "input")], 1), items) @@ -2314,7 +2314,7 @@ class ConvertFlowTests(unittest.TestCase): fields = self.tui.forms_seen[0][1] self.assertEqual([f["key"] for f in fields], ["backend", "qwen.mode", "qwen.speaker", - "qwen.clone_dir", "qwen.clone", + "qwen.clone", "qwen.qwen_instructions", "single_file"]) mode_field = self._field("mode") # Model names are padded to the widest ("CustomVoice"/"VoiceDesign" @@ -2331,25 +2331,18 @@ class ConvertFlowTests(unittest.TestCase): # collapses its column padding back to the gutter. self.assertTrue(mode_field["compact_label"]) speaker_field = self._field("speaker") - clone_dir_field = self._field("clone_dir") - # The .wav directory browser alerts on the .wavs it lists. - self.assertIs(clone_dir_field["info"], hub.common.wav_dir_info) - self.assertIs(clone_dir_field["preview"], hub.common.wav_dir_preview) clone_field = self._field("clone") design_field = self._field("qwen_instructions") - # Speaker shows in custom mode; the .wav directory browser and - # picker in clone mode and the instruction in design mode. + # Speaker shows in custom mode; the .wav picker in clone mode and + # the instruction in design mode. self.assertTrue(speaker_field["visible"](fields)) - self.assertFalse(clone_dir_field["visible"](fields)) self.assertFalse(clone_field["visible"](fields)) self.assertFalse(design_field["visible"](fields)) mode_field["value"] = "clone" self.assertFalse(speaker_field["visible"](fields)) - self.assertTrue(clone_dir_field["visible"](fields)) self.assertTrue(clone_field["visible"](fields)) mode_field["value"] = "design" self.assertFalse(speaker_field["visible"](fields)) - self.assertFalse(clone_dir_field["visible"](fields)) self.assertFalse(clone_field["visible"](fields)) self.assertTrue(design_field["visible"](fields)) @@ -2394,10 +2387,10 @@ class ConvertFlowTests(unittest.TestCase): self._convert(None, [self._ready("qwen", "qwen-tts")]) self.assertEqual(self._field("mode")["value"], "custom") - def test_qwen_clone_dir_defaults_to_the_project_voices(self): - # The Clone .wav directory is the shared directory widget, seeded - # with the project's ./voices; the picker starts on its first .wav - # (alphabetically), ignoring non-.wav files. + def test_qwen_clone_picker_lists_the_configured_directory(self): + # The Voice to clone picker lists the .wavs in the configured + # Clone .wav directory (the Settings entry), starting on the + # first one (alphabetically), ignoring non-.wav files. with tempfile.TemporaryDirectory() as td: root = Path(td) (root / "narrator.wav").write_bytes(b"") @@ -2407,7 +2400,7 @@ class ConvertFlowTests(unittest.TestCase): ("alice.wav", str(root / "alice.wav")), ("narrator.wav", str(root / "narrator.wav")), ] - with patch.object(hub.common, "VOICES_DIR", root), \ + with patch.object(hub.config, "CLONE_WAV_DIR", str(root)), \ patch.object(hub.qwen_backend, "QWEN_SPEAKERS", ["Vivian"]), \ patch.object(hub.common, "update_config_value"): self._answer_form(backend="qwen", mode="custom", @@ -2415,66 +2408,37 @@ class ConvertFlowTests(unittest.TestCase): cmd = self._convert(None, [self._ready("qwen", "qwen-tts")]) fields = self.tui.forms_seen[0][1] - clone_dir_field = self._field("clone_dir") clone_field = self._field("clone") self.assertIsNone(cmd[2]["clone"]) # custom mode: no clone - self.assertEqual(clone_dir_field["kind"], "dir") - self.assertEqual(clone_dir_field["value"], root) self.assertEqual(clone_field["kind"], "choice") self.assertEqual(clone_field["choices"](fields), expected_choices) self.assertEqual(clone_field["value"], expected_choices[0][1]) - def test_qwen_clone_picker_resets_when_the_directory_changes(self): - # Changing the directory browser re-points the picker at the new - # directory's first .wav; an empty directory clears the pick. - with tempfile.TemporaryDirectory() as td, \ - tempfile.TemporaryDirectory() as other, \ - tempfile.TemporaryDirectory() as nowhere: - root, other = Path(td), Path(other) - (root / "one.wav").write_bytes(b"") - (other / "beta.wav").write_bytes(b"") - (other / "alpha.wav").write_bytes(b"") - with patch.object(hub.common, "VOICES_DIR", root), \ - patch.object(hub.qwen_backend, "QWEN_SPEAKERS", ["Vivian"]), \ - patch.object(hub.common, "update_config_value"): - self._answer_form(backend="qwen", mode="clone", clone="") - self._convert(None, [self._ready("qwen", "qwen-tts")]) - fields = self.tui.forms_seen[0][1] - clone_dir_field = self._field("clone_dir") - clone_field = self._field("clone") - # The picker seeds itself with the default directory's first - # .wav, and follows the directory browser from there. - self.assertEqual(clone_field["value"], str(root / "one.wav")) - clone_dir_field["value"] = other - clone_dir_field["on_change"](fields) - self.assertEqual(clone_field["value"], str(other / "alpha.wav")) - clone_dir_field["value"] = Path(nowhere) / "no-such-dir" - clone_dir_field["on_change"](fields) - self.assertEqual(clone_field["value"], "") - def test_qwen_clone_picker_refuses_generate_without_wavs(self): - # No .wav files in the directory: the picker stays empty, opening - # it flashes the hint, and Generate! is refused with the same - # message naming the directory. + # No .wav files in the configured directory: the picker stays + # empty, opening it flashes the hint, and Generate! is refused + # with the same message naming the directory. with tempfile.TemporaryDirectory() as td: empty = Path(td) - with patch.object(hub.common, "VOICES_DIR", empty), \ + with patch.object(hub.config, "CLONE_WAV_DIR", str(empty)), \ patch.object(hub.qwen_backend, "QWEN_SPEAKERS", ["Vivian"]), \ patch.object(hub.common, "update_config_value"): self._answer_form(backend="qwen", mode="clone", clone="") cmd = self._convert(None, [self._ready("qwen", "qwen-tts")]) - self.assertIsNone(cmd[2]["clone"]) - fields = self.tui.forms_seen[0][1] - clone_field = self._field("clone") - self.assertEqual(clone_field["choices"](fields), []) - message = clone_field["on_empty_choices"](fields) - self.assertIn(str(empty), message) - self.assertIn("No .wav files", message) - self.assertEqual(clone_field["validate"](""), message) - self.assertIsNone(clone_field["validate"](str(empty / "x.wav"))) + self.assertIsNone(cmd[2]["clone"]) + fields = self.tui.forms_seen[0][1] + clone_field = self._field("clone") + self.assertEqual(clone_field["choices"](fields), []) + message = clone_field["on_empty_choices"](fields) + self.assertIn(str(empty), message) + self.assertIn("No .wav files", message) + self.assertIn("Check directory in Settings", message) + self.assertEqual(clone_field["validate"](""), message) + self.assertIsNone(clone_field["validate"]( + str(empty / "x.wav"))) # ------------------------------------------------------------------ # faster: remote server (no local voices.json) @@ -2598,7 +2562,7 @@ class ConvertFlowTests(unittest.TestCase): [f["key"] for f in fields], ["backend", "audiocpp.model_id", "audiocpp.audiocpp_voice", "audiocpp.instructions", "audiocpp.request_options", - "qwen.mode", "qwen.speaker", "qwen.clone_dir", + "qwen.mode", "qwen.speaker", "qwen.clone", "qwen.qwen_instructions", "single_file"]) # The form opens on the configured default (audio.cpp): its fields # show, the other backend's hide. Instructions shows too (optional @@ -2608,20 +2572,17 @@ class ConvertFlowTests(unittest.TestCase): for key in ("model_id", "audiocpp_voice", "instructions"): self.assertTrue(self._field(key)["visible"](fields)) self.assertFalse(self._field("request_options")["visible"](fields)) - for key in ("mode", "speaker", "clone_dir", "clone", - "qwen_instructions"): + for key in ("mode", "speaker", "clone", "qwen_instructions"): self.assertFalse(self._field(key)["visible"](fields)) # Picking qwen in the Backend field swaps which options show. fields[0]["value"] = "qwen" self.assertTrue(self._field("mode")["visible"](fields)) self.assertTrue(self._field("speaker")["visible"](fields)) - self.assertFalse(self._field("clone_dir")["visible"](fields)) self.assertFalse(self._field("clone")["visible"](fields)) - # qwen's clone mode hides the speaker and shows the .wav directory - # browser and the picker of the .wavs inside it. + # qwen's clone mode hides the speaker and shows the picker of the + # configured clone directory's .wavs. self._field("mode")["value"] = "clone" self.assertFalse(self._field("speaker")["visible"](fields)) - self.assertTrue(self._field("clone_dir")["visible"](fields)) self.assertTrue(self._field("clone")["visible"](fields)) for key in ("model_id", "audiocpp_voice", "instructions", "request_options"): @@ -2630,8 +2591,7 @@ class ConvertFlowTests(unittest.TestCase): fields[0]["value"] = "audiocpp" for key in ("model_id", "audiocpp_voice"): self.assertTrue(self._field(key)["visible"](fields)) - for key in ("mode", "speaker", "clone_dir", "clone", - "qwen_instructions"): + for key in ("mode", "speaker", "clone", "qwen_instructions"): self.assertFalse(self._field(key)["visible"](fields)) # ------------------------------------------------------------------ @@ -2654,8 +2614,7 @@ class ConvertFlowTests(unittest.TestCase): patch.object(hub.config, "STOP_SERVER_AND_EXIT", True): self._mock_preflight() self._answer_form(backend="sglomni", model_id=entry.key, - voice="Vivian", clone_dir="/tmp", - instructions="") + voice="Vivian", instructions="") cmd = self._convert(None, [self._ready("sglomni", "SGLang-Omni")]) self.assertEqual(cmd[1], hub.BACKEND_SGLOMNI) @@ -2671,21 +2630,19 @@ class ConvertFlowTests(unittest.TestCase): with tempfile.TemporaryDirectory() as td: ref = Path(td) / "ref.wav" ref.write_bytes(b"RIFF") - with patch.object(hub.config, "AUDIO_FORMAT", "m4b"), \ + with patch.object(hub.config, "CLONE_WAV_DIR", str(td)), \ + patch.object(hub.config, "AUDIO_FORMAT", "m4b"), \ patch.object(hub.config, "LANGUAGE", "English"), \ patch.object(hub.config, "SPEED", 1.0), \ patch.object(hub.config, "DEBUG", False), \ patch.object(hub.config, "STOP_SERVER_AND_EXIT", True): self._mock_preflight() self._answer_form(backend="sglomni", model_id=entry.key, - voice=str(ref), clone_dir=td, - instructions="") + voice=str(ref), instructions="") cmd = self._convert(None, [self._ready("sglomni", "SGLang-Omni")]) fields = self.tui.forms_seen[-1][1] voice_field = self._field("voice") - next(f for f in fields - if f["key"] == "sglomni.clone_dir")["value"] = td wav_labels = [label for label, _ in voice_field["choices"](fields)] kwargs = cmd[2] @@ -2708,7 +2665,7 @@ class ConvertFlowTests(unittest.TestCase): patch.object(hub.config, "STOP_SERVER_AND_EXIT", True): self._mock_preflight() self._answer_form(backend="sglomni", model_id=entry.key, - voice="", clone_dir="/tmp", instructions="") + voice="", instructions="") cmd = self._convert(None, [self._ready("sglomni", "SGLang-Omni")]) kwargs = cmd[2] @@ -2725,7 +2682,7 @@ class ConvertFlowTests(unittest.TestCase): patch.object(hub.config, "STOP_SERVER_AND_EXIT", True): self._mock_preflight() self._answer_form(backend="sglomni", model_id=entry.key, - voice="", clone_dir="/tmp", + voice="", instructions="A warm narrator.") cmd = self._convert(None, [self._ready("sglomni", "SGLang-Omni")]) @@ -2750,8 +2707,7 @@ class ConvertFlowTests(unittest.TestCase): self._mock_preflight() self._answer_form(backend="sglomni-remote", model_id="higgs_audio_v3_tts", - voice="narrator", clone_dir="/tmp", - instructions="") + voice="narrator", instructions="") cmd = self._convert( None, [self._remote("sglomni", "SGLang-Omni", url="http://sgl.local:8100")]) @@ -2807,8 +2763,7 @@ class ConvertFlowTests(unittest.TestCase): # built-in entry, no clone .wavs — those belong to the # clone-capable models' combined menu). self._answer_form(backend="sglomni", model_id="voxtral_tts", - voice="casual_male", clone_dir="/tmp", - instructions="") + voice="casual_male", instructions="") cmd = self._convert(None, statuses) fields = self.tui.forms_seen[-1][1] # The captured fields carry the form's opening state (the @@ -2846,9 +2801,8 @@ class ConvertFlowTests(unittest.TestCase): speaker -> the preset Voice menu; clone -> the combined Voice menu (the built-in default voice on top when the model can - narrate without a reference, then the clone directory's .wavs) - beside the Clone .wav directory browser; design -> the - Instructions box. + narrate without a reference, then the configured clone + directory's .wavs); design -> the Instructions box. """ def fake_preset_voices(entry): @@ -2864,7 +2818,8 @@ class ConvertFlowTests(unittest.TestCase): with tempfile.TemporaryDirectory() as td: ref = Path(td) / "ref.wav" ref.write_bytes(b"RIFF") - with patch.object(hub.config, "AUDIO_FORMAT", "m4b"), \ + with patch.object(hub.config, "CLONE_WAV_DIR", str(td)), \ + patch.object(hub.config, "AUDIO_FORMAT", "m4b"), \ patch.object(hub.config, "LANGUAGE", "English"), \ patch.object(hub.config, "SPEED", 1.0), \ patch.object(hub.config, "DEBUG", False), \ @@ -2874,7 +2829,7 @@ class ConvertFlowTests(unittest.TestCase): with self.subTest(entry=entry.key): overrides = {"backend": "sglomni", "model_id": entry.key, - "voice": "", "clone_dir": td, + "voice": "", "instructions": ""} if entry.capability == "speaker": overrides["voice"] = \ @@ -2894,9 +2849,6 @@ class ConvertFlowTests(unittest.TestCase): next(f for f in fields if f["key"] == "sglomni.model_id")["value"] = \ entry.key - next(f for f in fields - if f["key"] == "sglomni.clone_dir")["value"] \ - = td shown = {f["key"] for f in fields if f["key"].startswith("sglomni.") and f["visible"](fields)} @@ -2913,8 +2865,7 @@ class ConvertFlowTests(unittest.TestCase): self.assertEqual(kwargs.get("voice"), preset) self.assertNotIn("clone", kwargs) elif entry.capability == "clone": - expected = {"sglomni.model_id", "sglomni.voice", - "sglomni.clone_dir"} + expected = {"sglomni.model_id", "sglomni.voice"} if entry.requires_reference: # Cannot narrate without a reference: the # menu is the clone directory's .wavs only. @@ -3458,6 +3409,7 @@ class SettingsTests(unittest.TestCase): # fake config write restores these afterwards. _SETTING_KEYS = ("AUDIO_FORMAT", "AUDIO_BITRATE", "LANGUAGE", "CHUNK_SIZE", "INPUT_DIR", "OUTPUT_DIR", + "CLONE_WAV_DIR", "SPEED", "DEBUG", "STOP_SERVER_AND_EXIT", "AUDIOCPP_UNLOAD_MODELS", "QWEN_API_URL", @@ -3532,6 +3484,7 @@ class SettingsTests(unittest.TestCase): values = {"audio_format": "ogg", "audio_bitrate": " 192k ", "language": "en", "chunk_size": "300", "input_dir": " /books ", "output_dir": "/audiobooks", + "clone_wav_dir": " /refs/wavs ", "speed": "1.5", "debug": True, "stop_and_exit": False, "unload_models": True, @@ -3554,6 +3507,7 @@ class SettingsTests(unittest.TestCase): "CHUNK_SIZE": 300, "INPUT_DIR": "/books", "OUTPUT_DIR": "/audiobooks", + "CLONE_WAV_DIR": "/refs/wavs", "SPEED": 1.5, "DEBUG": True, "STOP_SERVER_AND_EXIT": False, @@ -3579,6 +3533,7 @@ class SettingsTests(unittest.TestCase): self.assertEqual(hub.config.CHUNK_SIZE, 300) self.assertEqual(hub.config.INPUT_DIR, "/books") self.assertEqual(hub.config.OUTPUT_DIR, "/audiobooks") + self.assertEqual(hub.config.CLONE_WAV_DIR, "/refs/wavs") self.assertEqual(hub.config.SPEED, 1.5) self.assertEqual(hub.config.DEBUG, True) self.assertEqual(hub.config.STOP_SERVER_AND_EXIT, False) @@ -3596,6 +3551,7 @@ class SettingsTests(unittest.TestCase): base = {"audio_format": "m4b", "audio_bitrate": "128k", "language": "English", "chunk_size": "250", "input_dir": "./input", "output_dir": "./output", + "clone_wav_dir": "./voices", "speed": "1.0", "debug": False, "stop_and_exit": True, "unload_models": True, @@ -3616,6 +3572,8 @@ class SettingsTests(unittest.TestCase): with self.assertRaises(ValueError): hub._apply_settings({**base, "output_dir": ""}) with self.assertRaises(ValueError): + hub._apply_settings({**base, "clone_wav_dir": " "}) + with self.assertRaises(ValueError): hub._apply_settings({**base, "audiocpp_port": "70000"}) with self.assertRaises(ValueError): hub._apply_settings({**base, @@ -3671,27 +3629,42 @@ class SettingsTests(unittest.TestCase): self.assertEqual(field["value"], hub.config.LANGUAGE) def test_directory_fields_are_browsers(self): - # The Input/Output Directory settings use the DOS-style directory - # browser, seeded with the configured folder resolved against the - # project root. + # The Input/Output/Clone .wav directory settings use the DOS-style + # directory browser, seeded with the configured folder resolved + # against the project root. fields = hub._settings_fields() for key, config_name in (("input_dir", "INPUT_DIR"), - ("output_dir", "OUTPUT_DIR")): + ("output_dir", "OUTPUT_DIR"), + ("clone_wav_dir", "CLONE_WAV_DIR")): field = next(f for f in fields if f["key"] == key) self.assertEqual(field["kind"], "dir") - self.assertTrue(field["label"].endswith("Directory")) self.assertEqual(field["value"], hub.converter_mod.resolve_dir( getattr(hub.config, config_name), key.removesuffix("_dir"))) self.assertIsNone(field["validate"](str(field["value"]))) self.assertIsNotNone(field["validate"](" ")) + input_field = next(f for f in fields if f["key"] == "input_dir") + output_field = next(f for f in fields if f["key"] == "output_dir") + self.assertTrue(input_field["label"].endswith("Directory")) + self.assertTrue(output_field["label"].endswith("Directory")) + # The clone .wav browser alerts on the .wavs it lists. + clone_field = next(f for f in fields if f["key"] == "clone_wav_dir") + self.assertEqual(clone_field["label"], "Clone .wav directory") + self.assertIs(clone_field["info"], hub.common.wav_dir_info) + self.assertIs(clone_field["preview"], hub.common.wav_dir_preview) with patch.object(hub.config, "INPUT_DIR", "books"): fields = hub._settings_fields() field = next(f for f in fields if f["key"] == "input_dir") self.assertEqual( field["value"], hub.converter_mod.BASE_DIR / "books") + with patch.object(hub.config, "CLONE_WAV_DIR", "refs"): + fields = hub._settings_fields() + field = next(f for f in fields if f["key"] == "clone_wav_dir") + self.assertEqual( + field["value"], + hub.converter_mod.BASE_DIR / "refs") def test_settings_menu_builds_form_and_saves(self): captured = {} @@ -3701,6 +3674,7 @@ class SettingsTests(unittest.TestCase): return {"audio_format": "ogg", "audio_bitrate": "192k", "language": "English", "chunk_size": "300", "input_dir": "/books", "output_dir": "/audiobooks", + "clone_wav_dir": "/refs/wavs", "speed": "1.0", "debug": False, "stop_and_exit": True, "unload_models": True, @@ -3723,6 +3697,7 @@ class SettingsTests(unittest.TestCase): self.assertEqual([f["key"] for f in captured["fields"]], ["audio_format", "audio_bitrate", "language", "chunk_size", "input_dir", "output_dir", + "clone_wav_dir", "speed", "debug", "stop_and_exit", "unload_models", "audiocpp_port", @@ -3735,6 +3710,7 @@ class SettingsTests(unittest.TestCase): self.assertEqual(kinds["audio_bitrate"], "text") self.assertEqual(kinds["input_dir"], "dir") self.assertEqual(kinds["output_dir"], "dir") + self.assertEqual(kinds["clone_wav_dir"], "dir") self.assertEqual(kinds["speed"], "text") self.assertEqual(kinds["debug"], "bool") self.assertEqual(kinds["audiocpp_port"], "text") @@ -3747,6 +3723,7 @@ class SettingsTests(unittest.TestCase): "audio.cpp remote URL") self.assertEqual(labels["input_dir"], "Input Directory") self.assertEqual(labels["output_dir"], "Output Directory") + self.assertEqual(labels["clone_wav_dir"], "Clone .wav directory") self.assertNotIn("(clone)", " ".join(labels.values())) # The language setting is a picker labelled "Language". self.assertEqual(labels["language"], "Language") @@ -3756,6 +3733,7 @@ class SettingsTests(unittest.TestCase): notes = {f["key"]: f.get("note") for f in captured["fields"]} self.assertIsNone(notes["input_dir"]) self.assertIsNone(notes["output_dir"]) + self.assertIsNone(notes["clone_wav_dir"]) self.assertIsNone(notes["debug"]) self.assertTrue(notes["stop_and_exit"]) self.assertTrue(notes["unload_models"]) @@ -3770,6 +3748,7 @@ class SettingsTests(unittest.TestCase): "chunk_size": "300", "input_dir": "/books", "output_dir": "/audiobooks", + "clone_wav_dir": "/refs/wavs", "speed": "1.0", "debug": False, "stop_and_exit": True, @@ -3887,6 +3866,7 @@ class SettingsTests(unittest.TestCase): return {"audio_format": "m4b", "audio_bitrate": "128k", "language": "English", "chunk_size": "250", "input_dir": "./input", "output_dir": "./output", + "clone_wav_dir": "./voices", "speed": "1.0", "debug": False, "stop_and_exit": True, "unload_models": True, "qwen_port": "7860", @@ -3920,6 +3900,7 @@ class SettingsTests(unittest.TestCase): original = {name: getattr(hub.config, name) for name in ("AUDIO_FORMAT", "AUDIO_BITRATE", "LANGUAGE", "CHUNK_SIZE", "INPUT_DIR", "OUTPUT_DIR", + "CLONE_WAV_DIR", "SPEED", "DEBUG", "STOP_SERVER_AND_EXIT", "AUDIOCPP_UNLOAD_MODELS", "QWEN_API_URL", @@ -3942,6 +3923,7 @@ class SettingsTests(unittest.TestCase): "CHUNK_SIZE = 250\n" 'INPUT_DIR = "./input"\n' 'OUTPUT_DIR = "./output"\n' + 'CLONE_WAV_DIR = "./voices"\n' "SPEED = 1.0\n" "DEBUG = False\n" "STOP_SERVER_AND_EXIT = True\n" @@ -3970,6 +3952,7 @@ class SettingsTests(unittest.TestCase): # The settings-only fields are written back unchanged. self.assertIn('INPUT_DIR = "', text) self.assertIn('OUTPUT_DIR = "', text) + self.assertIn('CLONE_WAV_DIR = "', text) self.assertIn("SPEED = 1.0", text) self.assertIn("DEBUG = False", text) # The running session also picked up the change in-memory. |
