Skip to content

Commit dbc6957

Browse files
authored
Merge pull request #148 from iautolab/chore/python314-deps
test: migrate test runner from unittest to pytest
2 parents e7ad5c2 + a043156 commit dbc6957

9 files changed

Lines changed: 58 additions & 35 deletions

File tree

.github/workflows/ci.yml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
strategy:
1515
fail-fast: false
1616
matrix:
17-
python-version: [ "3.11", "3.12" ]
17+
python-version: [ "3.11", "3.12", "3.13", "3.14" ]
1818
os: [ windows-latest, ubuntu-latest ]
1919

2020
defaults:
@@ -53,7 +53,6 @@ jobs:
5353
uses: FedericoCarboni/setup-ffmpeg@v2
5454
id: setup-ffmpeg
5555

56-
- name: Test with unittest
57-
working-directory: ./tests
56+
- name: Test with pytest
5857
run: |
59-
uv run --extra full --extra litellm python -m unittest discover -s . -p 'test_*.py'
58+
uv run --extra full --extra litellm pytest

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,9 @@ uv run ruff format --check openlrc/ tests/
321321
322322
# Type check
323323
uv run pyright openlrc/
324+
325+
# Tests
326+
uv run --extra full --extra litellm pytest
324327
```
325328
326329
For live translation testing as a developer (and for CI usage), set:

pyproject.toml

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ name = "openlrc"
77
version = "1.7.0a1"
88
description = "Transcribe (whisper) and translate (gpt) voice into LRC file."
99
authors = [{ name = "Hao Zheng", email = "zhenghaosustc@gmail.com" }]
10-
requires-python = ">=3.11, <3.13"
10+
requires-python = ">=3.11, <3.15"
1111
readme = "README.md"
1212
license = "MIT"
1313
keywords = ["openai-gpt3", "whisper", "voice transcribe", "lrc"]
@@ -21,7 +21,7 @@ classifiers = [
2121
dependencies = [
2222
"openai>=2.20.0",
2323
"anthropic>=0.90.0",
24-
"tiktoken>=0.8.0,<0.9",
24+
"tiktoken>=0.12.0,<0.14",
2525
"langcodes>=3.3.0,<4",
2626
"language-data~=1.1",
2727
"tqdm>=4.65.0,<5",
@@ -32,13 +32,12 @@ dependencies = [
3232
"lingua-language-detector>=1.3.2,<2",
3333
"filetype>=1.2.0,<2",
3434
"jaconvV2>=0.4,<0.5",
35-
"spacy>=3.8.5,<4",
35+
"spacy>=3.8.13,<3.8.14", # 3.8.14 has no cp314 wheels/sdist on PyPI (broken release, see explosion/spaCy#13949)
3636
"pysbd>=0.3.4,<0.4",
3737
"faster-whisper>=1.1.1,<2",
38-
"ffmpeg-normalize>=1.27.5,<2",
38+
"ffmpeg-normalize>=1.36.0,<2",
3939
"google-genai>=1.70.0",
40-
"json_repair==0.25.2",
41-
"onnxruntime>=1.20.0,<2",
40+
"json_repair>=0.50.0,<1",
4241
"pip>=25.1",
4342
]
4443

@@ -48,10 +47,10 @@ dependencies = [
4847
# Only needed when using noise_suppress=True in LRCer.run() or Preprocessor.run().
4948
full = [
5049
"dpdfnet>=0.6.0",
51-
"numba>=0.58.0", # first version supporting both Python 3.11 and 3.12
50+
"numba>=0.58.0", # librosa allows >=0.51 which breaks 3.11+ resolution
5251
]
5352
litellm = [
54-
"litellm>=1.60,<1.85",
53+
"litellm>=1.82,<2",
5554
]
5655

5756
[project.urls]
@@ -64,7 +63,15 @@ Homepage = "https://github.com/zh-plus/openlrc"
6463
openlrc = "openlrc.cli:main"
6564

6665
[dependency-groups]
67-
dev = ["pyright>=1.1", "ruff>=0.11"]
66+
dev = [
67+
"pyright>=1.1",
68+
"ruff>=0.11",
69+
"pytest>=8",
70+
"pytest-sugar>=1.1.1",
71+
]
72+
73+
[tool.pytest.ini_options]
74+
testpaths = ["tests"]
6875

6976
[tool.uv]
7077

tests/test_openlrc.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
from openlrc.transcribe import TranscriptionInfo
1313
from openlrc.utils import extend_filename
1414

15+
DATA_DIR = Path(__file__).parent / "data"
16+
1517
# Shared test config — avoids repeating these in every test method.
1618
_TEST_TRANSCRIPTION = TranscriptionConfig(whisper_model="tiny", compute_type="default", device="cpu")
1719

@@ -64,9 +66,9 @@ def _mock_create_chatbot(*args, **kwargs):
6466
@patch("openlrc.agents.create_chatbot", side_effect=_mock_create_chatbot)
6567
class TestLRCer(unittest.TestCase):
6668
def setUp(self) -> None:
67-
self.audio_path = Path("data/test_audio.wav")
68-
self.video_path = Path("data/test_video.mp4")
69-
self.nospeech_video_path = Path("data/test_nospeech_video.mp4")
69+
self.audio_path = DATA_DIR / "test_audio.wav"
70+
self.video_path = DATA_DIR / "test_video.mp4"
71+
self.nospeech_video_path = DATA_DIR / "test_nospeech_video.mp4"
7072

7173
def tearDown(self) -> None:
7274
def clear_paths(input_path):
@@ -89,7 +91,7 @@ def clear_paths(input_path):
8991

9092
self.video_path.with_suffix(".wav").unlink(missing_ok=True)
9193

92-
shutil.rmtree("data/preprocessed", ignore_errors=True)
94+
shutil.rmtree(DATA_DIR / "preprocessed", ignore_errors=True)
9395

9496
# ------------------------------------------------------------------
9597
# Pipeline tests (using new config API)
@@ -115,22 +117,22 @@ def test_multiple_audio_transcription_translation(self, _mock_chatbot):
115117
def test_audio_file_not_found(self, _mock_chatbot):
116118
lrcer = LRCer(transcription=_TEST_TRANSCRIPTION)
117119
with self.assertRaises(FileNotFoundError):
118-
lrcer.run("data/invalid.mp3")
120+
lrcer.run(DATA_DIR / "invalid.mp3")
119121

120122
@patch(
121123
"openlrc.translate.LLMTranslator.translate", MagicMock(return_value=["test translation1", "test translation2"])
122124
)
123125
def test_video_file_transcription_translation(self, _mock_chatbot):
124126
lrcer = LRCer(transcription=_TEST_TRANSCRIPTION)
125-
result = lrcer.run("data/test_video.mp4")
127+
result = lrcer.run(DATA_DIR / "test_video.mp4")
126128
self.assertTrue(result)
127129

128130
@patch(
129131
"openlrc.translate.LLMTranslator.translate", MagicMock(return_value=["test translation1", "test translation2"])
130132
)
131133
def test_nospeech_video_file_transcription_translation(self, _mock_chatbot):
132134
lrcer = LRCer(transcription=_TEST_TRANSCRIPTION)
133-
result = lrcer.run("data/test_nospeech_video.mp4")
135+
result = lrcer.run(DATA_DIR / "test_nospeech_video.mp4")
134136
self.assertTrue(result)
135137

136138
@patch("openlrc.translate.LLMTranslator.translate", MagicMock(side_effect=Exception("test exception")))
@@ -142,7 +144,7 @@ def test_translation_error(self, _mock_chatbot):
142144
@patch("openlrc.translate.LLMTranslator.translate", MagicMock(side_effect=Exception("test exception")))
143145
def test_skip_translation(self, _mock_chatbot):
144146
lrcer = LRCer(transcription=_TEST_TRANSCRIPTION)
145-
result = lrcer.run("data/test_video.mp4", skip_trans=True)
147+
result = lrcer.run(DATA_DIR / "test_video.mp4", skip_trans=True)
146148
self.assertTrue(result)
147149

148150
@patch(

tests/test_opt.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,17 @@
44
import json
55
import os
66
import unittest
7+
from pathlib import Path
78

89
from openlrc.opt import SubtitleOptimizer
910
from openlrc.subtitle import Subtitle
1011

12+
DATA_DIR = Path(__file__).parent / "data"
13+
1114

1215
class TestSubtitleOptimizer(unittest.TestCase):
1316
def setUp(self) -> None:
14-
self.subtitle = Subtitle.from_json("data/test_valid_subtitle.json")
17+
self.subtitle = Subtitle.from_json(DATA_DIR / "test_valid_subtitle.json")
1518

1619
def test_merge_same(self):
1720
subtitle = self.subtitle
@@ -75,12 +78,12 @@ def test_save(self):
7578
subtitle = self.subtitle
7679
optimizer = SubtitleOptimizer(subtitle)
7780
optimizer.perform_all()
78-
optimizer.save(output_name="data/test_subtitle_optimized.json")
81+
optimizer.save(output_name=DATA_DIR / "test_subtitle_optimized.json")
7982

80-
with open("data/test_subtitle_optimized.json", encoding="utf-8") as f:
83+
with open(DATA_DIR / "test_subtitle_optimized.json", encoding="utf-8") as f:
8184
optimized_subtitle = json.load(f)
8285

8386
self.assertEqual(optimized_subtitle["language"], "zh")
8487
self.assertEqual(len(optimized_subtitle["segments"]), 8)
8588

86-
os.remove("data/test_subtitle_optimized.json")
89+
os.remove(DATA_DIR / "test_subtitle_optimized.json")

tests/test_preprocess.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
from openlrc.preprocess import Preprocessor
1313

14+
DATA_DIR = Path(__file__).parent / "data"
15+
1416
# Inject lightweight fakes for the optional noise-suppression stack so these
1517
# tests run without the openlrc[full] extra installed.
1618
_dpdfnet = types.ModuleType("dpdfnet")
@@ -43,7 +45,7 @@ def write(self, data):
4345

4446
class TestPreprocessor(unittest.TestCase):
4547
def tearDown(self) -> None:
46-
preprocessed_path = Path("data/preprocessed")
48+
preprocessed_path = DATA_DIR / "preprocessed"
4749
shutil.rmtree(preprocessed_path, ignore_errors=True)
4850

4951
@patch.object(_dpdfnet, "enhance")
@@ -81,7 +83,7 @@ def test_noise_suppression_shape_mismatch_removes_partial_file(self, mock_unlink
8183
@patch("openlrc.preprocess.FFmpegNormalize")
8284
def test_loudness_normalization_returns_path_objects(self, mock_norm):
8385
mock_norm.return_value.run_normalization.return_value = None
84-
preprocessor = Preprocessor("data/test_audio.wav")
86+
preprocessor = Preprocessor(DATA_DIR / "test_audio.wav")
8587
ln_paths = preprocessor.loudness_normalization(preprocessor.audio_paths)
8688
self.assertIsInstance(ln_paths, list)
8789
self.assertIsInstance(ln_paths[0], Path)

tests/test_subtitle.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@
22
# All rights reserved.
33

44
import unittest
5+
from pathlib import Path
56

67
from openlrc.subtitle import Subtitle
78

9+
DATA_DIR = Path(__file__).parent / "data"
10+
811

912
class TestSubtitle(unittest.TestCase):
1013
def setUp(self) -> None:
11-
self.subtitle = Subtitle.from_json("data/test_valid_subtitle.json")
14+
self.subtitle = Subtitle.from_json(DATA_DIR / "test_valid_subtitle.json")
1215

1316
def check_content(self, subtitle, length=10):
1417
self.assertEqual(subtitle.lang, "zh")
@@ -24,13 +27,13 @@ def test_load_valid_json(self):
2427
self.check_content(self.subtitle)
2528

2629
def test_load_lrc(self):
27-
subtitle = Subtitle.from_file("data/test_subtitle.lrc")
30+
subtitle = Subtitle.from_file(DATA_DIR / "test_subtitle.lrc")
2831
self.check_content(subtitle, length=7)
2932

3033
def test_save_json(self):
3134
subtitle = self.subtitle
32-
subtitle.save("data/saved.json")
33-
loaded_subtitle = Subtitle.from_file("data/saved.json")
35+
subtitle.save(DATA_DIR / "saved.json")
36+
loaded_subtitle = Subtitle.from_file(DATA_DIR / "saved.json")
3437
self.check_content(loaded_subtitle)
3538
loaded_subtitle.filename.unlink()
3639

tests/test_transcribe.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
from openlrc.transcribe import Transcriber, TranscriptionInfo
1111

12+
DATA_DIR = Path(__file__).parent / "data"
13+
1214
return_tuple = (
1315
[
1416
Segment(
@@ -44,7 +46,7 @@
4446

4547
class TestTranscriber(unittest.TestCase):
4648
def setUp(self) -> None:
47-
self.audio_path = Path("data/test_audio.wav")
49+
self.audio_path = DATA_DIR / "test_audio.wav"
4850

4951
@patch("openlrc.transcribe.BatchedInferencePipeline")
5052
def test_transcribe_success(self, MockBatchedInferencePipeline):

tests/test_utils.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@
1414
parse_timestamp,
1515
)
1616

17+
DATA_DIR = Path(__file__).parent / "data"
18+
1719

1820
class TestUtils(unittest.TestCase):
1921
def setUp(self) -> None:
20-
self.audio_file = Path("data/test_audio.wav")
21-
self.video_file = Path("data/test_video.mp4")
22-
self.unsupported = Path("data/unsupported_file.xyz")
22+
self.audio_file = DATA_DIR / "test_audio.wav"
23+
self.video_file = DATA_DIR / "test_video.mp4"
24+
self.unsupported = DATA_DIR / "unsupported_file.xyz"
2325

2426
def tearDown(self) -> None:
2527
self.video_file.with_suffix(".wav").unlink(missing_ok=True)

0 commit comments

Comments
 (0)