test_config.py 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. """Tests for configuration loading."""
  2. import tempfile
  3. from pathlib import Path
  4. from stt_daemon.config import load_config
  5. def test_load_config_defaults():
  6. """Test that load_config returns defaults when no file exists."""
  7. with tempfile.TemporaryDirectory() as tmpdir:
  8. # Change to temp dir and load (no config.toml exists)
  9. import os
  10. old_cwd = os.getcwd()
  11. try:
  12. os.chdir(tmpdir)
  13. cfg = load_config()
  14. assert cfg.audio.backend == "parec"
  15. assert cfg.audio.device == "VirtualMicSink.monitor"
  16. assert cfg.audio.sample_rate == 16000
  17. assert cfg.vad.aggressiveness == 2
  18. assert cfg.whisper.model == "small"
  19. assert cfg.whisper.language is None
  20. assert cfg.output.stdout is True
  21. finally:
  22. os.chdir(old_cwd)
  23. def test_load_config_from_file():
  24. """Test loading config from a TOML file."""
  25. config_toml = """
  26. [audio]
  27. backend = "sounddevice"
  28. device = "hw:0"
  29. sample_rate = 44100
  30. [vad]
  31. aggressiveness = 3
  32. pre_roll_ms = 500
  33. [whisper]
  34. model = "base"
  35. device = "cuda"
  36. language = "es"
  37. [output]
  38. stdout = false
  39. jsonl_dir = "logs"
  40. """
  41. with tempfile.TemporaryDirectory() as tmpdir:
  42. config_path = Path(tmpdir) / "config.toml"
  43. config_path.write_text(config_toml)
  44. cfg = load_config(str(config_path))
  45. assert cfg.audio.backend == "sounddevice"
  46. assert cfg.audio.device == "hw:0"
  47. assert cfg.audio.sample_rate == 44100
  48. assert cfg.vad.aggressiveness == 3
  49. assert cfg.vad.pre_roll_ms == 500
  50. assert cfg.whisper.model == "base"
  51. assert cfg.whisper.device == "cuda"
  52. assert cfg.whisper.language == "es"
  53. assert cfg.output.stdout is False
  54. assert cfg.output.jsonl_dir == "logs"
  55. def test_language_normalization_auto():
  56. """Test that language 'auto' is normalized to None."""
  57. config_toml = """
  58. [whisper]
  59. language = "auto"
  60. """
  61. with tempfile.TemporaryDirectory() as tmpdir:
  62. config_path = Path(tmpdir) / "config.toml"
  63. config_path.write_text(config_toml)
  64. cfg = load_config(str(config_path))
  65. assert cfg.whisper.language is None
  66. def test_language_normalization_empty_string():
  67. """Test that language '' is normalized to None."""
  68. config_toml = """
  69. [whisper]
  70. language = ""
  71. """
  72. with tempfile.TemporaryDirectory() as tmpdir:
  73. config_path = Path(tmpdir) / "config.toml"
  74. config_path.write_text(config_toml)
  75. cfg = load_config(str(config_path))
  76. assert cfg.whisper.language is None
  77. def test_language_normalization_none():
  78. """Test that language 'none' is normalized to None."""
  79. config_toml = """
  80. [whisper]
  81. language = "none"
  82. """
  83. with tempfile.TemporaryDirectory() as tmpdir:
  84. config_path = Path(tmpdir) / "config.toml"
  85. config_path.write_text(config_toml)
  86. cfg = load_config(str(config_path))
  87. assert cfg.whisper.language is None
  88. def test_language_explicit_en():
  89. """Test that explicit language codes are preserved."""
  90. config_toml = """
  91. [whisper]
  92. language = "en"
  93. """
  94. with tempfile.TemporaryDirectory() as tmpdir:
  95. config_path = Path(tmpdir) / "config.toml"
  96. config_path.write_text(config_toml)
  97. cfg = load_config(str(config_path))
  98. assert cfg.whisper.language == "en"
  99. def test_partial_config():
  100. """Test that partial configs merge with defaults."""
  101. config_toml = """
  102. [audio]
  103. device = "custom_device"
  104. """
  105. with tempfile.TemporaryDirectory() as tmpdir:
  106. config_path = Path(tmpdir) / "config.toml"
  107. config_path.write_text(config_toml)
  108. cfg = load_config(str(config_path))
  109. # Overridden
  110. assert cfg.audio.device == "custom_device"
  111. # Defaults
  112. assert cfg.audio.backend == "parec"
  113. assert cfg.audio.sample_rate == 16000
  114. assert cfg.vad.aggressiveness == 2
  115. def test_chunking_and_confidence_fields():
  116. """New chunking/gate fields load from TOML."""
  117. config_toml = """
  118. [vad]
  119. chunk_pause_ms = 500
  120. soft_max_s = 8.0
  121. rms_floor = 0.01
  122. trailing_trim_ms = 150
  123. [whisper]
  124. no_speech_prob_max = 0.7
  125. avg_logprob_min = -0.8
  126. """
  127. with tempfile.TemporaryDirectory() as tmpdir:
  128. config_path = Path(tmpdir) / "config.toml"
  129. config_path.write_text(config_toml)
  130. cfg = load_config(str(config_path))
  131. assert cfg.vad.chunk_pause_ms == 500
  132. assert cfg.vad.soft_max_s == 8.0
  133. assert cfg.vad.rms_floor == 0.01
  134. assert cfg.vad.trailing_trim_ms == 150
  135. assert cfg.whisper.no_speech_prob_max == 0.7
  136. assert cfg.whisper.avg_logprob_min == -0.8
  137. def test_chunking_defaults():
  138. """New fields have sane defaults."""
  139. with tempfile.TemporaryDirectory() as tmpdir:
  140. import os
  141. old_cwd = os.getcwd()
  142. try:
  143. os.chdir(tmpdir)
  144. cfg = load_config()
  145. finally:
  146. os.chdir(old_cwd)
  147. assert cfg.vad.chunk_pause_ms == 350
  148. assert cfg.vad.soft_max_s == 10.0
  149. assert cfg.vad.rms_floor == 0.008
  150. assert cfg.vad.trailing_trim_ms == 200
  151. assert cfg.whisper.no_speech_prob_max == 0.6
  152. assert cfg.whisper.avg_logprob_min == -1.0
  153. def test_output_defaults_disable_audio_saving():
  154. with tempfile.TemporaryDirectory() as tmpdir:
  155. import os
  156. old_cwd = os.getcwd()
  157. try:
  158. os.chdir(tmpdir)
  159. cfg = load_config()
  160. finally:
  161. os.chdir(old_cwd)
  162. assert cfg.output.save_audio is False
  163. assert cfg.output.audio_dir == "audio"
  164. def test_output_save_audio_fields():
  165. config_toml = """
  166. [output]
  167. save_audio = true
  168. audio_dir = "clips"
  169. """
  170. with tempfile.TemporaryDirectory() as tmpdir:
  171. config_path = Path(tmpdir) / "config.toml"
  172. config_path.write_text(config_toml)
  173. cfg = load_config(str(config_path))
  174. assert cfg.output.save_audio is True
  175. assert cfg.output.audio_dir == "clips"
  176. def test_daemon_defaults():
  177. with tempfile.TemporaryDirectory() as tmpdir:
  178. import os
  179. old_cwd = os.getcwd()
  180. try:
  181. os.chdir(tmpdir)
  182. cfg = load_config()
  183. finally:
  184. os.chdir(old_cwd)
  185. assert cfg.daemon.queue_size == 50
  186. assert cfg.daemon.backlog_warn_at == 5
  187. def test_daemon_config_fields():
  188. config_toml = """
  189. [daemon]
  190. queue_size = 20
  191. backlog_warn_at = 3
  192. """
  193. with tempfile.TemporaryDirectory() as tmpdir:
  194. config_path = Path(tmpdir) / "config.toml"
  195. config_path.write_text(config_toml)
  196. cfg = load_config(str(config_path))
  197. assert cfg.daemon.queue_size == 20
  198. assert cfg.daemon.backlog_warn_at == 3
  199. def test_unknown_sections_warn_and_are_ignored(caplog):
  200. """A typo'd section like [assitant] must warn, not silently use defaults."""
  201. config_toml = """
  202. [assitant]
  203. require_viewer = false
  204. """
  205. with tempfile.TemporaryDirectory() as tmpdir:
  206. config_path = Path(tmpdir) / "config.toml"
  207. config_path.write_text(config_toml)
  208. with caplog.at_level("WARNING", logger="stt_daemon.config"):
  209. cfg = load_config(str(config_path))
  210. assert "assitant" in caplog.text
  211. assert cfg.assistant.require_viewer is True
  212. def test_cpu_threads_default_is_auto():
  213. """cpu_threads defaults to 0 (CTranslate2's automatic choice)."""
  214. with tempfile.TemporaryDirectory() as tmpdir:
  215. import os
  216. old_cwd = os.getcwd()
  217. try:
  218. os.chdir(tmpdir)
  219. cfg = load_config()
  220. finally:
  221. os.chdir(old_cwd)
  222. assert cfg.whisper.cpu_threads == 0
  223. def test_cpu_threads_config_field():
  224. config_toml = """
  225. [whisper]
  226. cpu_threads = 16
  227. """
  228. with tempfile.TemporaryDirectory() as tmpdir:
  229. config_path = Path(tmpdir) / "config.toml"
  230. config_path.write_text(config_toml)
  231. cfg = load_config(str(config_path))
  232. assert cfg.whisper.cpu_threads == 16
  233. def test_language_strategy_defaults_to_static():
  234. """language_strategy defaults to 'static'."""
  235. with tempfile.TemporaryDirectory() as tmpdir:
  236. import os
  237. old_cwd = os.getcwd()
  238. try:
  239. os.chdir(tmpdir)
  240. cfg = load_config()
  241. finally:
  242. os.chdir(old_cwd)
  243. assert cfg.whisper.language_strategy == "static"
  244. def test_language_strategy_sticky_is_parsed():
  245. """language_strategy 'sticky' is parsed correctly."""
  246. config_toml = """
  247. [whisper]
  248. language_strategy = "sticky"
  249. """
  250. with tempfile.TemporaryDirectory() as tmpdir:
  251. config_path = Path(tmpdir) / "config.toml"
  252. config_path.write_text(config_toml)
  253. cfg = load_config(str(config_path))
  254. assert cfg.whisper.language_strategy == "sticky"
  255. def test_unknown_language_strategy_falls_back_to_static(caplog):
  256. """An unknown language_strategy value falls back to 'static' with a warning."""
  257. config_toml = """
  258. [whisper]
  259. language_strategy = "bogus"
  260. """
  261. with tempfile.TemporaryDirectory() as tmpdir:
  262. config_path = Path(tmpdir) / "config.toml"
  263. config_path.write_text(config_toml)
  264. with caplog.at_level("WARNING", logger="stt_daemon.config"):
  265. cfg = load_config(str(config_path))
  266. assert cfg.whisper.language_strategy == "static"
  267. assert "Unknown language_strategy" in caplog.text
  268. assert "bogus" in caplog.text
  269. def test_sticky_redetect_logprob_defaults_to_minus_0_8():
  270. """sticky_redetect_logprob defaults to -0.8."""
  271. with tempfile.TemporaryDirectory() as tmpdir:
  272. import os
  273. old_cwd = os.getcwd()
  274. try:
  275. os.chdir(tmpdir)
  276. cfg = load_config()
  277. finally:
  278. os.chdir(old_cwd)
  279. assert cfg.whisper.sticky_redetect_logprob == -0.8
  280. def test_sticky_redetect_logprob_is_parsed():
  281. """sticky_redetect_logprob is parsed from TOML."""
  282. config_toml = """
  283. [whisper]
  284. sticky_redetect_logprob = -0.5
  285. """
  286. with tempfile.TemporaryDirectory() as tmpdir:
  287. config_path = Path(tmpdir) / "config.toml"
  288. config_path.write_text(config_toml)
  289. cfg = load_config(str(config_path))
  290. assert cfg.whisper.sticky_redetect_logprob == -0.5