Skip to content

Commit 07918fd

Browse files
authored
Added integration test covering real execution with all model architectures (#205)
* Added integration test covering real execution with all model architectures * Updated to latest artifacts action * Test disabling macos upper memory limit for integration tests on gha runners * Bumped version to release with fix * Attempt to switch to ubuntu-latest for integration tests due to MPS backend OOMing * Set up self hosted runner for integration tests * Swap python setup * Swap poetry before python again * Attempt n * Remove poetry caching for now * Install pipx * prefix with python * Fixed PATH * Restrict to one model for fast iteration right now * Re-enable all models in integration test
1 parent af25ca8 commit 07918fd

File tree

8 files changed

+204
-39
lines changed

8 files changed

+204
-39
lines changed
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
name: run-integration-tests
2+
3+
on:
4+
pull_request:
5+
6+
jobs:
7+
integration-test:
8+
runs-on: self-hosted
9+
env:
10+
AUDIO_SEPARATOR_MODEL_DIR: ${{ github.workspace }}/models
11+
12+
steps:
13+
- name: Checkout project
14+
uses: actions/checkout@v4
15+
16+
- name: Set up Python
17+
uses: actions/setup-python@v5
18+
with:
19+
python-version: '3.13'
20+
21+
- name: Install pipx
22+
run: python -m pip install --user pipx && python -m pipx ensurepath
23+
24+
- name: Install poetry
25+
run: python -m pipx install poetry
26+
27+
- name: Setup PATH
28+
run: echo "/root/.local/bin" >> $GITHUB_PATH
29+
30+
- name: Install system dependencies
31+
run: |
32+
apt-get update
33+
apt-get install -y ffmpeg libsamplerate0 libsamplerate-dev
34+
35+
- name: Set up Python
36+
uses: actions/setup-python@v5
37+
with:
38+
python-version: '3.13'
39+
cache: poetry
40+
41+
- name: Create models directory
42+
run: mkdir -p $AUDIO_SEPARATOR_MODEL_DIR
43+
44+
- name: Cache models directory
45+
uses: actions/cache@v3
46+
id: model-cache
47+
with:
48+
path: ${{ env.AUDIO_SEPARATOR_MODEL_DIR }}
49+
key: model-cache-${{ hashFiles('tests/integration/test_cli_integration.py') }}
50+
restore-keys: model-cache-
51+
52+
- name: Install Poetry dependencies
53+
run: poetry install -E cpu
54+
55+
- name: Display model cache status
56+
run: |
57+
echo "Model cache hit: ${{ steps.model-cache.outputs.cache-hit == 'true' }}"
58+
echo "Models directory contents:"
59+
ls -la $AUDIO_SEPARATOR_MODEL_DIR || echo "Directory empty or doesn't exist"
60+
61+
- name: Run integration tests
62+
run: poetry run pytest tests -v --cov=audio_separator --cov-report=xml
63+
64+
- name: Upload coverage reports to Codecov
65+
uses: codecov/codecov-action@v3
66+
env:
67+
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
68+
69+
- name: Upload test results
70+
if: always()
71+
uses: actions/upload-artifact@v4
72+
with:
73+
name: integration-test-results
74+
path: |
75+
*.flac
76+
tests/*.flac
Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
name: run-tests
1+
name: run-unit-tests
22

33
on:
4-
push:
54
pull_request:
65

76
jobs:
@@ -29,12 +28,7 @@ jobs:
2928
run: poetry install -E cpu
3029

3130
- name: Run unit tests with coverage
32-
run: poetry run pytest tests/unit --cov=./ --cov-report=xml
33-
34-
- name: Upload coverage reports to Codecov
35-
uses: codecov/codecov-action@v3
36-
env:
37-
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
31+
run: poetry run pytest tests/unit
3832

3933
test-macos:
4034
runs-on: macos-latest
@@ -61,12 +55,7 @@ jobs:
6155

6256
- name: Run unit tests with coverage
6357
run: |
64-
poetry run pytest tests/unit --cov=./ --cov-report=xml
65-
66-
- name: Upload coverage reports to Codecov
67-
uses: codecov/codecov-action@v3
68-
env:
69-
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
58+
poetry run pytest tests/unit
7059
7160
test-windows:
7261
runs-on: windows-latest
@@ -92,9 +81,4 @@ jobs:
9281
run: poetry install -E cpu
9382

9483
- name: Run unit tests with coverage
95-
run: poetry run pytest tests/unit --cov=./ --cov-report=xml
96-
97-
- name: Upload coverage reports to Codecov
98-
uses: codecov/codecov-action@v3
99-
env:
100-
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
84+
run: poetry run pytest tests/unit

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
*.onnx
1010
*.pth
1111
*.wav
12-
*.flac
12+
/*.flac
1313
*.mp3
1414
tests/model-metrics/results
1515
tests/model-metrics/datasets

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
44

55
[tool.poetry]
66
name = "audio-separator"
7-
version = "0.31.2"
7+
version = "0.31.3"
88
description = "Easy to use audio stem separation, using various models from UVR trained primarily by @Anjok07"
99
authors = ["Andrew Beveridge <[email protected]>"]
1010
license = "MIT"

tests/inputs/mardy20s.flac

2.36 MB
Binary file not shown.

tests/integration/README.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Integration Tests
2+
3+
These tests verify the end-to-end functionality of the audio-separator CLI.
4+
5+
## Running the tests
6+
7+
To run the integration tests, use:
8+
9+
```bash
10+
pytest tests/integration
11+
```
12+
13+
To run a specific model test, you can use pytest's parameter selection:
14+
15+
```bash
16+
# Run only the kuielab_b_vocals.onnx test
17+
pytest tests/integration/test_cli_integration.py::test_model_separation[kuielab_b_vocals.onnx-expected_files0]
18+
19+
# Run only the MGM_MAIN_v4.pth test
20+
pytest tests/integration/test_cli_integration.py::test_model_separation[MGM_MAIN_v4.pth-expected_files1]
21+
```
22+
23+
## Adding New Model Tests
24+
25+
To add a new model test, simply add a new entry to the `MODEL_PARAMS` list in the test file:
26+
27+
```python
28+
(
29+
"new_model_filename.onnx",
30+
["mardy20s_(Instrumental)_new_model_filename.flac", "mardy20s_(Vocals)_new_model_filename.flac"]
31+
),
32+
```
33+
34+
No additional test functions are needed.
35+
36+
## Notes
37+
38+
- These tests use actual audio files and models, and will run the full audio separation process.
39+
- Tests may take longer to run than unit tests, as they perform actual audio processing.
40+
- The model files will be automatically downloaded if they don't exist locally.
41+
- The test requires the test audio file at `tests/inputs/mardy20s.flac` to exist.
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import os
2+
import subprocess
3+
import pytest
4+
5+
6+
@pytest.fixture(name="input_file")
7+
def fixture_input_file():
8+
"""Fixture providing the test input audio file path."""
9+
return "tests/inputs/mardy20s.flac"
10+
11+
12+
@pytest.fixture(name="cleanup_output_files")
13+
def fixture_cleanup_output_files():
14+
"""Fixture to clean up output files before and after test."""
15+
# This list will be populated by the test functions
16+
output_files = []
17+
18+
# Yield to allow the test to run and add files to the list
19+
yield output_files
20+
21+
# Clean up output files after test
22+
for file in output_files:
23+
if os.path.exists(file):
24+
print(f"Test output file exists: {file}")
25+
# os.remove(file)
26+
27+
28+
def run_separation_test(model, audio_path, expected_files):
29+
"""Helper function to run a separation test with a specific model."""
30+
# Clean up any existing output files before the test
31+
for file in expected_files:
32+
if os.path.exists(file):
33+
print(f"Deleting existing test output file {file}")
34+
os.remove(file)
35+
36+
# Run the CLI command
37+
result = subprocess.run(["audio-separator", "-m", model, audio_path], capture_output=True, text=True, check=False) # Explicitly set check to False as we handle errors manually
38+
39+
# Check that the command completed successfully
40+
assert result.returncode == 0, f"Command failed with output: {result.stderr}"
41+
42+
# Check that the output files were created
43+
for file in expected_files:
44+
assert os.path.exists(file), f"Output file {file} was not created"
45+
assert os.path.getsize(file) > 0, f"Output file {file} is empty"
46+
47+
return result
48+
49+
50+
# Parameterized test for multiple models
51+
MODEL_PARAMS = [
52+
# (model_filename, expected_output_filenames)
53+
("kuielab_b_vocals.onnx", ["mardy20s_(Instrumental)_kuielab_b_vocals.flac", "mardy20s_(Vocals)_kuielab_b_vocals.flac"]),
54+
("MGM_MAIN_v4.pth", ["mardy20s_(Instrumental)_MGM_MAIN_v4.flac", "mardy20s_(Vocals)_MGM_MAIN_v4.flac"]),
55+
("UVR-MDX-NET-Inst_HQ_4.onnx", ["mardy20s_(Instrumental)_UVR-MDX-NET-Inst_HQ_4.flac", "mardy20s_(Vocals)_UVR-MDX-NET-Inst_HQ_4.flac"]),
56+
("2_HP-UVR.pth", ["mardy20s_(Instrumental)_2_HP-UVR.flac", "mardy20s_(Vocals)_2_HP-UVR.flac"]),
57+
(
58+
"htdemucs_6s.yaml",
59+
[
60+
"mardy20s_(Vocals)_htdemucs_6s.flac",
61+
"mardy20s_(Drums)_htdemucs_6s.flac",
62+
"mardy20s_(Bass)_htdemucs_6s.flac",
63+
"mardy20s_(Other)_htdemucs_6s.flac",
64+
"mardy20s_(Guitar)_htdemucs_6s.flac",
65+
"mardy20s_(Piano)_htdemucs_6s.flac",
66+
],
67+
),
68+
("model_bs_roformer_ep_937_sdr_10.5309.ckpt", ["mardy20s_(Drum-Bass)_model_bs_roformer_ep_937_sdr_10.flac", "mardy20s_(No Drum-Bass)_model_bs_roformer_ep_937_sdr_10.flac"]),
69+
("model_bs_roformer_ep_317_sdr_12.9755.ckpt", ["mardy20s_(Instrumental)_model_bs_roformer_ep_317_sdr_12.flac", "mardy20s_(Vocals)_model_bs_roformer_ep_317_sdr_12.flac"]),
70+
]
71+
72+
73+
@pytest.mark.parametrize("model,expected_files", MODEL_PARAMS)
74+
def test_model_separation(model, expected_files, input_file, cleanup_output_files):
75+
"""Parameterized test for multiple model files."""
76+
# Add files to the cleanup list
77+
cleanup_output_files.extend(expected_files)
78+
79+
# Run the test
80+
run_separation_test(model, input_file, expected_files)

tests/unit/test_cli.py

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -240,23 +240,7 @@ def test_cli_use_autocast_argument(common_expected_args):
240240
expected_args["use_autocast"] = True
241241

242242
# Assertions
243-
mock_separator.assert_called_once_with(**common_expected_args)
244-
245-
246-
# Test using use_autocast argument
247-
def test_cli_use_autocast_argument(common_expected_args):
248-
test_args = ["cli.py", "test_audio.mp3", "--use_autocast"]
249-
with patch("sys.argv", test_args):
250-
with patch("audio_separator.separator.Separator") as mock_separator:
251-
mock_separator_instance = mock_separator.return_value
252-
mock_separator_instance.separate.return_value = ["output_file.mp3"]
253-
main()
254-
255-
# Update expected args for this specific test
256-
common_expected_args["use_autocast"] = True
257-
258-
# Assertions
259-
mock_separator.assert_called_once_with(**common_expected_args)
243+
mock_separator.assert_called_once_with(**expected_args)
260244

261245

262246
# Test using custom_output_names argument

0 commit comments

Comments
 (0)