Skip to content

Commit

Permalink
More idiomatic py.testing (#41)
Browse files Browse the repository at this point in the history
* Make py.test use more idiomatic (parametrize instead of internal looping)

* CI: show tests and test durations
  • Loading branch information
akx authored Nov 22, 2023
1 parent 8dcae6d commit c81dfe2
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 92 deletions.
3 changes: 1 addition & 2 deletions .github/workflows/pytest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@ jobs:
python -m pip install --upgrade pip
pip install -e ".[tests]"
- name: Test with pytest
run: |
pytest --cov=./ --cov-report=xml -n auto
run: pytest --cov=./ --cov-report=xml -n auto --durations=0 -v
- name: Codecov
uses: codecov/[email protected]
build:
Expand Down
2 changes: 0 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,6 @@ dependencies = [
tests = [
"pytest-cov",
"pytest-xdist[psutil]",
"setuptools",
"tqdm",
]

[project.urls]
Expand Down
Empty file added tests/__init__.py
Empty file.
105 changes: 49 additions & 56 deletions tests/test_pianoroll.py
Original file line number Diff line number Diff line change
@@ -1,71 +1,64 @@
#!/usr/bin/python3 python
from operator import attrgetter

"""Testing creating pianorolls of notes.
"""

from pathlib import Path

from tqdm import tqdm
import pytest

from miditoolkit import MidiFile
from miditoolkit.constants import PITCH_RANGE
from miditoolkit.pianoroll import notes2pianoroll, pianoroll2notes
from tests.utils import MIDI_PATHS

test_sets = [
{"pitch_range": (0, 127)},
{"pitch_range": (24, 96)},
{"pitch_range": (24, 116), "pitch_offset": 12},
{"pitch_range": (6, 96), "pitch_offset": 12},
{"pitch_range": (24, 96), "pitch_offset": 12, "velocity_threshold": 36},
]

def test_pianoroll():
midi_paths = list(Path("tests", "testcases").glob("**/*.mid"))
test_sets = [
{"pitch_range": (0, 127)},
{"pitch_range": (24, 96)},
{"pitch_range": (24, 116), "pitch_offset": 12},
{"pitch_range": (6, 96), "pitch_offset": 12},
{"pitch_range": (24, 96), "pitch_offset": 12, "velocity_threshold": 36},
]

for path in tqdm(midi_paths, desc="Checking pianoroll conversion"):
midi = MidiFile(path)

for track in midi.instruments:
# We do a first notes -> pianoroll -> notes conversion before
# This step is required as the pianoroll conversion is lossy with overlapping notes.
# notes2pianoroll has a "last income priority" logic, for which if a notes is occurs
# when another one of the same pitch is already being played, this new note will be
# represented and will end the previous one (if they have different velocities).
@pytest.mark.parametrize("midi_path", MIDI_PATHS, ids=attrgetter("name"))
@pytest.mark.parametrize("test_set", test_sets)
def test_pianoroll(midi_path, test_set):
"""Testing creating pianorolls of notes."""

for test_set in test_sets:
# Set pitch range parameters
pitch_range = test_set.get("pitch_range", PITCH_RANGE)
if "pitch_offset" in test_set:
pitch_range = (
max(PITCH_RANGE[0], pitch_range[0] - test_set["pitch_offset"]),
min(PITCH_RANGE[1], pitch_range[1] + test_set["pitch_offset"]),
)
# Set pitch range parameters
pitch_range = test_set.get("pitch_range", PITCH_RANGE)
if "pitch_offset" in test_set:
pitch_range = (
max(PITCH_RANGE[0], pitch_range[0] - test_set["pitch_offset"]),
min(PITCH_RANGE[1], pitch_range[1] + test_set["pitch_offset"]),
)

# First pianoroll <--> notes conversion, losing overlapping notes
pianoroll = notes2pianoroll(track.notes, **test_set)
new_notes = pianoroll2notes(pianoroll, pitch_range=pitch_range)
midi = MidiFile(midi_path)

# Second one, notes -> pianoroll -> new notes should be equal
new_pianoroll = notes2pianoroll(new_notes, **test_set)
new_new_notes = pianoroll2notes(new_pianoroll, pitch_range=pitch_range)
if "velocity_threshold" in test_set:
new_notes = [
note
for note in new_notes
if note.velocity >= test_set["velocity_threshold"]
]
for track in midi.instruments:
# We do a first notes -> pianoroll -> notes conversion before
# This step is required as the pianoroll conversion is lossy with overlapping notes.
# notes2pianoroll has a "last income priority" logic, for which if a notes is occurs
# when another one of the same pitch is already being played, this new note will be
# represented and will end the previous one (if they have different velocities).

# Assert notes are all retrieved
assert len(new_notes) == len(
new_new_notes
), "Number of notes changed in pianoroll conversion"
for note1, note2 in zip(new_notes, new_new_notes):
# We don't test the resampling factor as it might later the number of notes
assert (
note1 == note2
), "Notes before and after pianoroll conversion are not the same"
# First pianoroll <--> notes conversion, losing overlapping notes
pianoroll = notes2pianoroll(track.notes, **test_set)
new_notes = pianoroll2notes(pianoroll, pitch_range=pitch_range)

# Second one, notes -> pianoroll -> new notes should be equal
new_pianoroll = notes2pianoroll(new_notes, **test_set)
new_new_notes = pianoroll2notes(new_pianoroll, pitch_range=pitch_range)
if "velocity_threshold" in test_set:
new_notes = [
note
for note in new_notes
if note.velocity >= test_set["velocity_threshold"]
]

if __name__ == "__main__":
test_pianoroll()
# Assert notes are all retrieved
assert len(new_notes) == len(
new_new_notes
), "Number of notes changed in pianoroll conversion"
for note1, note2 in zip(new_notes, new_new_notes):
# We don't test the resampling factor as it might later the number of notes
assert (
note1 == note2
), "Notes before and after pianoroll conversion are not the same"
47 changes: 15 additions & 32 deletions tests/test_read_dump.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,22 @@
#!/usr/bin/python3 python
from operator import attrgetter

"""Testing that a MIDI loaded and saved unchanged is indeed the save as before.
"""

import shutil
from pathlib import Path

from tqdm import tqdm
import pytest

from miditoolkit import MidiFile
from tests.utils import MIDI_PATHS


def test_load_dump():
midi_paths = list(Path("tests", "testcases").glob("**/*.mid"))
out_path = Path("tests", "tmp", "load_dump")
out_path.mkdir(parents=True, exist_ok=True)

for path in tqdm(midi_paths, desc="Checking midis load/save"):
midi = MidiFile(path)
# Writing it unchanged
midi.dump(out_path / path.name)
# Loading it back
midi2 = MidiFile(out_path / path.name)

# Sorting the notes, as after dump the order might have changed
for track1, track2 in zip(midi.instruments, midi2.instruments):
track1.notes.sort(key=lambda x: (x.start, x.pitch, x.end, x.velocity))
track2.notes.sort(key=lambda x: (x.start, x.pitch, x.end, x.velocity))

assert midi == midi2

# deletes tmp directory after tests
shutil.rmtree(out_path)
@pytest.mark.parametrize("midi_path", MIDI_PATHS, ids=attrgetter("name"))
def test_load_dump(midi_path, tmp_path):
"""Test that a MIDI loaded and saved unchanged is indeed the save as before."""
midi1 = MidiFile(midi_path)
dump_path = tmp_path / midi_path.name
midi1.dump(dump_path) # Writing it unchanged
midi2 = MidiFile(dump_path) # Loading it back

# Sorting the notes, as after dump the order might have changed
for track1, track2 in zip(midi1.instruments, midi2.instruments):
track1.notes.sort(key=lambda x: (x.start, x.pitch, x.end, x.velocity))
track2.notes.sort(key=lambda x: (x.start, x.pitch, x.end, x.velocity))

if __name__ == "__main__":
test_load_dump()
assert midi1 == midi2
5 changes: 5 additions & 0 deletions tests/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from pathlib import Path

HERE = Path(__file__).parent

MIDI_PATHS = sorted((HERE / "testcases").rglob("*.mid"))

0 comments on commit c81dfe2

Please sign in to comment.