From 0e669fa61b2b3b6b74e48f7c4cd5292dedc692a8 Mon Sep 17 00:00:00 2001 From: demberto Date: Sat, 19 Nov 2022 13:06:11 +0530 Subject: [PATCH] remove: fixture factories, closes #74 --- CHANGELOG.md | 2 + tests/conftest.py | 27 +++-------- tests/test_channel.py | 105 +++++++++++++++++------------------------- tests/test_mixer.py | 51 +++++++++----------- tests/test_pattern.py | 47 ++++++++----------- tests/test_plugin.py | 54 ++++++++++------------ 6 files changed, 116 insertions(+), 170 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c3c9ec..4170514 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,7 +33,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed - `Track.locked_height` as what this quantity stores is unknown to me yet. +- Use of fixture factories in unittests [#74]. +[#74]: https://github.com/demberto/PyFLP/issues/74 [#92]: https://github.com/demberto/PyFLP/issues/92 [#99]: https://github.com/demberto/PyFLP/issues/99 [#100]: https://github.com/demberto/PyFLP/issues/100 diff --git a/tests/conftest.py b/tests/conftest.py index 0f0752c..b492d75 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,14 +1,8 @@ from __future__ import annotations import pathlib -import sys from typing import TypeVar -if sys.version_info >= (3, 8): - from typing import Protocol -else: - from typing_extensions import Protocol - import pytest import pyflp @@ -17,12 +11,7 @@ from pyflp._models import ModelBase from pyflp.mixer import Mixer -T = TypeVar("T", bound=ModelBase) - - -class ModelFixture(Protocol): - def __call__(self, suffix: str, type: type[T], *only: EventEnum) -> T: - ... +MT = TypeVar("MT", bound=ModelBase) @pytest.fixture(scope="session") @@ -55,12 +44,8 @@ def patterns(project: Project): return project.patterns -@pytest.fixture -def get_model(): - def wrapper(suffix: str, type: type[ModelBase], *only: EventEnum): - parsed = pyflp.parse(pathlib.Path(__file__).parent / "assets" / suffix) - if only: - return type(parsed.events.subtree(lambda e: e.id in only)) - return type(parsed.events) - - return wrapper +def get_model(suffix: str, type: type[MT], *only: EventEnum) -> MT: + parsed = pyflp.parse(pathlib.Path(__file__).parent / "assets" / suffix) + if only: + return type(parsed.events.subtree(lambda e: e.id in only)) + return type(parsed.events) diff --git a/tests/test_channel.py b/tests/test_channel.py index f01aee0..2f0992a 100644 --- a/tests/test_channel.py +++ b/tests/test_channel.py @@ -1,10 +1,9 @@ from __future__ import annotations import pathlib -from typing import Any, Callable +from typing import TypeVar import colour -import pytest from pyflp import Project from pyflp.channel import ( @@ -21,53 +20,35 @@ StretchMode, ) -from .conftest import ModelFixture +from .conftest import get_model -AutomationFixture = Callable[[str], Automation] -ChannelFixture = Callable[[str], Channel] -InstrumentFixture = Callable[[str], Instrument] -LayerFixture = Callable[[str], Layer] -SamplerFixture = Callable[[str], Sampler] +CT = TypeVar("CT", bound=Channel) -@pytest.fixture -def load_channel(get_model: ModelFixture): - def wrapper(preset: str, type: type[Channel] = Channel): - return get_model(f"channels/{preset}", type) +def _load_channel(preset: str, type: type[CT]): + return get_model(f"channels/{preset}", type) - return wrapper +# This is separated only to pass type checks +# (preset: str, type: type[CT] = Channel) -> CT messes inferred return type +def load_channel(preset: str): + return _load_channel(preset, Channel) -@pytest.fixture -def load_automation(load_channel: ModelFixture): - def wrapper(preset: str): - return load_channel(preset, Automation) - return wrapper +def load_automation(preset: str): + return _load_channel(preset, Automation) -@pytest.fixture -def load_instrument(load_channel: ModelFixture): - def wrapper(preset: str): - return load_channel(preset, Instrument) +def load_instrument(preset: str): + return _load_channel(preset, Instrument) - return wrapper +def load_layer(preset: str): + return _load_channel(preset, Layer) -@pytest.fixture -def load_layer(load_channel: Any): - def wrapper(preset: str): - return load_channel(preset, Layer) - return wrapper - - -@pytest.fixture -def load_sampler(load_channel: Any): - def wrapper(preset: str): - return load_channel(preset, Sampler) - - return wrapper +def load_sampler(preset: str): + return _load_channel(preset, Sampler) def test_channels(project: Project, rack: ChannelRack): @@ -78,21 +59,21 @@ def test_channels(project: Project, rack: ChannelRack): assert not rack.swing -def test_automation_lfo(load_automation: AutomationFixture): +def test_automation_lfo(): lfo = load_automation("automation-lfo.fst").lfo assert lfo.amount == 64 -def test_automation_points(load_automation: AutomationFixture): +def test_automation_points(): points = [point for point in load_automation("automation-points.fst")] assert [int(p.position or 0) for p in points] == [0, 8, 8, 16, 24, 32] -def test_channel_color(load_channel: ChannelFixture): +def test_channel_color(): assert load_channel("colored.fst").color == colour.Color("#1414FF") -def test_channel_enabled(load_channel: ChannelFixture): +def test_channel_enabled(): assert not load_channel("disabled.fst").enabled @@ -106,16 +87,16 @@ def test_channel_group(rack: ChannelRack): assert channel.group.name == "Unsorted" -def test_channel_icon(load_channel: ChannelFixture): +def test_channel_icon(): assert load_channel("iconified.fst").icon == 116 -def test_channel_pan(load_channel: ChannelFixture): +def test_channel_pan(): assert load_channel(r"100%-left.fst").pan == 0 assert load_channel(r"100%-right.fst").pan == 12800 -def test_channel_volume(load_channel: ChannelFixture): +def test_channel_volume(): assert load_channel("full-volume.fst").volume == 12800 assert not load_channel("zero-volume.fst").volume @@ -128,7 +109,7 @@ def test_channel_zipped(rack: ChannelRack): assert not channel.zipped -def test_instrument_delay(load_instrument: InstrumentFixture): +def test_instrument_delay(): delay = load_instrument("delay.fst").delay assert delay.feedback == 12800 assert delay.echoes == 10 @@ -140,7 +121,7 @@ def test_instrument_delay(load_instrument: InstrumentFixture): assert delay.time == 144 -def test_instrument_keyboard(load_instrument: InstrumentFixture): +def test_instrument_keyboard(): keyboard = load_instrument("keyboard.fst").keyboard assert keyboard.add_root assert keyboard.fine_tune == 100 @@ -149,7 +130,7 @@ def test_instrument_keyboard(load_instrument: InstrumentFixture): assert keyboard.root_note == 60 -def test_instrument_polyphony(load_instrument: InstrumentFixture): +def test_instrument_polyphony(): polyphony = load_instrument("polyphony.fst").polyphony assert polyphony.mono assert polyphony.porta @@ -157,11 +138,11 @@ def test_instrument_polyphony(load_instrument: InstrumentFixture): assert polyphony.slide == 820 -def test_instrument_routing(load_instrument: InstrumentFixture): +def test_instrument_routing(): assert load_instrument("routed.fst").insert == 125 -def test_instrument_time(load_instrument: InstrumentFixture): +def test_instrument_time(): time = load_instrument("time.fst").time assert time.full_porta assert time.gate == 450 @@ -169,7 +150,7 @@ def test_instrument_time(load_instrument: InstrumentFixture): assert time.swing == 64 -def test_instrument_tracking(load_instrument: InstrumentFixture): +def test_instrument_tracking(): tracking = load_instrument("tracking.fst").tracking assert tracking and len(tracking) == 2 @@ -181,18 +162,18 @@ def test_instrument_tracking(load_instrument: InstrumentFixture): # ! Apparently, layer children events aren't stored in presets -# def test_layer_children(load_layer: LayerFixture): +# def test_layer_children(): pass -def test_layer_crossfade(load_layer: LayerFixture): +def test_layer_crossfade(): assert load_layer("layer-crossfade.fst").crossfade -def test_layer_random(load_layer: LayerFixture): +def test_layer_random(): assert load_layer("layer-random.fst").random -def test_sampler_content(load_sampler: SamplerFixture): +def test_sampler_content(): content = load_sampler("sampler-content.fst").content assert content.keep_on_disk assert content.resample @@ -201,11 +182,11 @@ def test_sampler_content(load_sampler: SamplerFixture): assert content.declick_mode == DeclickMode.Generic -def test_sampler_cut_group(load_sampler: SamplerFixture): +def test_sampler_cut_group(): assert load_sampler("cut-groups.fst").cut_group == (1, 2) -def test_sampler_envelopes(load_sampler: SamplerFixture): +def test_sampler_envelopes(): envelopes = load_sampler("envelope.fst").envelopes assert envelopes and len(envelopes) == 5 @@ -233,14 +214,14 @@ def test_sampler_envelopes(load_sampler: SamplerFixture): assert mod_x.attack_tension == mod_x.release_tension == mod_x.decay_tension == 128 -def test_sampler_filter(load_sampler: SamplerFixture): +def test_sampler_filter(): filter = load_sampler("sampler-filter.fst").filter assert filter.mod_x == 0 assert filter.mod_y == 256 assert filter.type == FilterType.SVFLPx2 -def test_sampler_fx(load_sampler: SamplerFixture): +def test_sampler_fx(): fx = load_sampler("sampler-fx.fst").fx assert fx.boost == 128 assert fx.clip @@ -267,7 +248,7 @@ def test_sampler_fx(load_sampler: SamplerFixture): assert fx.trim == 256 -def test_sampler_lfo(load_sampler: SamplerFixture): +def test_sampler_lfo(): lfos = load_sampler("lfo.fst").lfos assert lfos and len(lfos) == 5 @@ -290,25 +271,25 @@ def test_sampler_lfo(load_sampler: SamplerFixture): assert mod_x.synced -def test_sampler_path(load_sampler: SamplerFixture): +def test_sampler_path(): assert load_sampler("sampler-path.fst").sample_path == pathlib.Path( r"%FLStudioFactoryData%\Data\Patches\Packs\Drums\Kicks\22in Kick.wav" ) -def test_sampler_pitch_shift(load_sampler: SamplerFixture): +def test_sampler_pitch_shift(): assert load_sampler("+4800-cents.fst").pitch_shift == 4800 assert load_sampler("-4800-cents.fst").pitch_shift == -4800 -def test_sampler_playback(load_sampler: SamplerFixture): +def test_sampler_playback(): playback = load_sampler("sampler-playback.fst").playback assert playback.use_loop_points assert playback.ping_pong_loop assert playback.start_offset == 1072693248 -def test_sampler_stretching(load_sampler: SamplerFixture): +def test_sampler_stretching(): stretching = load_sampler("sampler-stretching.fst").stretching assert stretching.mode == StretchMode.E3Generic assert stretching.multiplier == 0.25 diff --git a/tests/test_mixer.py b/tests/test_mixer.py index 9d2a45e..dc7f85b 100644 --- a/tests/test_mixer.py +++ b/tests/test_mixer.py @@ -1,44 +1,37 @@ from __future__ import annotations -from typing import Callable, cast +from typing import cast import colour -import pytest from pyflp.mixer import Insert, InsertDock, Mixer, MixerID, MixerParamsEvent -from .conftest import ModelFixture +from .conftest import get_model -InsertFixture = Callable[[str], Insert] +def get_insert(preset: str): + # Parse as Mixer to get events, because an Insert cannot parse + # MixerID.Params which holds most of its information. + mixer = get_model(f"inserts/{preset}", Mixer) -@pytest.fixture -def get_insert(get_model: ModelFixture): - def wrapper(preset: str): - # Parse as Mixer to get events, because an Insert cannot parse - # MixerID.Params which holds most of its information. - mixer = get_model(f"inserts/{preset}", Mixer) + # A preset stores items only for a single insert, currently thats 32 per + # insert. Pass these to Insert's constructor. This mimics Mixer's normal + # behaviour, however that depends on InsertID.Output as a marker to indicate + # the end of an Insert, which surprisingly isn't a part of presets. + params = cast(MixerParamsEvent, mixer.events.first(MixerID.Params)) + items = tuple(params.items.values())[0] + return Insert(mixer.events, index=0, max_slots=10, params=items) - # A preset stores items only for a single insert, currently thats 32 per - # insert. Pass these to Insert's constructor. This mimics Mixer's normal - # behaviour, however that depends on InsertID.Output as a marker to indicate - # the end of an Insert, which surprisingly isn't a part of presets. - params = cast(MixerParamsEvent, mixer.events.first(MixerID.Params)) - items = tuple(params.items.values())[0] - return Insert(mixer.events, index=0, max_slots=10, params=items) - return wrapper - - -def test_insert_bypassed(get_insert: InsertFixture): +def test_insert_bypassed(): assert get_insert("effects-bypassed.fst").bypassed -def test_insert_channels_swapped(get_insert: InsertFixture): +def test_insert_channels_swapped(): assert get_insert("channels-swapped.fst").channels_swapped -def test_insert_color(get_insert: InsertFixture): +def test_insert_color(): assert get_insert("colored.fst").color == colour.Color("#FF1414") @@ -53,20 +46,20 @@ def test_insert_dock(inserts: tuple[Insert, ...]): assert insert.dock == InsertDock.Middle -def test_insert_enabled(get_insert: InsertFixture): +def test_insert_enabled(): assert not get_insert("disabled.fst").enabled -def test_insert_locked(get_insert: InsertFixture): +def test_insert_locked(): assert get_insert("locked.fst").locked -def test_insert_pan(get_insert: InsertFixture): +def test_insert_pan(): assert get_insert(r"100%-left.fst").pan == -6400 assert get_insert(r"100%-right.fst").pan == 6400 -def test_insert_polarity_reversed(get_insert: InsertFixture): +def test_insert_polarity_reversed(): assert get_insert("polarity-reversed.fst").polarity_reversed @@ -74,12 +67,12 @@ def test_insert_routes(inserts: tuple[Insert, ...]): assert not tuple(inserts[5].routes) -def test_insert_stereo_separation(get_insert: InsertFixture): +def test_insert_stereo_separation(): assert get_insert(r"100%-merged.fst").stereo_separation == 64 assert get_insert(r"100%-separated.fst").stereo_separation == -64 -def test_insert_eq(get_insert: InsertFixture): +def test_insert_eq(): eq = get_insert("post-eq.fst").eq assert eq.low.freq == 0 assert eq.low.gain == 1800 diff --git a/tests/test_pattern.py b/tests/test_pattern.py index 25b7a6a..0027944 100644 --- a/tests/test_pattern.py +++ b/tests/test_pattern.py @@ -1,15 +1,14 @@ from __future__ import annotations -from typing import Callable, Tuple - import colour -import pytest -from pyflp.pattern import Note, Pattern, PatternID, Patterns +from pyflp.pattern import Pattern, PatternID, Patterns + +from .conftest import get_model -from .conftest import ModelFixture -NotesFixture = Callable[[str], Tuple[Note, ...]] +def get_notes(score: str): + return tuple(get_model(f"patterns/{score}", Pattern, *PatternID)) def test_patterns(patterns: Patterns): @@ -28,67 +27,59 @@ def test_pattern_names(patterns: Patterns): ) -@pytest.fixture -def get_notes(get_model: ModelFixture): - def wrapper(score: str): - return tuple(get_model(f"patterns/{score}", Pattern, *PatternID)) - - return wrapper - - -def test_empty_pattern(get_notes: NotesFixture): +def test_empty_pattern(): assert not len(get_notes("empty.fsc")) -def test_note_color(get_notes: NotesFixture): +def test_note_color(): assert get_notes("color-9.fsc")[0].midi_channel == 8 -def test_note_fine_pitch(get_notes: NotesFixture): +def test_note_fine_pitch(): assert [n.fine_pitch for n in get_notes("fine-pitch-min-max.fsc")] == [0, 240] -def test_note_group(get_notes: NotesFixture): +def test_note_group(): assert [n.group for n in get_notes("common-group.fsc")] == [1, 1] -def test_note_length(get_notes: NotesFixture): +def test_note_length(): assert get_notes("c5-1bar.fsc")[0].length == 384 -def test_note_mod_x(get_notes: NotesFixture): +def test_note_mod_x(): assert [n.mod_x for n in get_notes("modx-min-max.fsc")] == [255, 0] -def test_note_mod_y(get_notes: NotesFixture): +def test_note_mod_y(): assert [n.mod_y for n in get_notes("mody-min-max.fsc")] == [0, 255] -def test_note_key(get_notes: NotesFixture): +def test_note_key(): c_major = ["C5", "D5", "E5", "F5", "G5", "A5", "B5", "C6"] assert [n.key for n in get_notes("c-major-scale.fsc")] == c_major -def test_note_pan(get_notes: NotesFixture): +def test_note_pan(): assert [n.pan for n in get_notes("pan-min-max.fsc")] == [128, 0] -def test_note_position(get_notes: NotesFixture): +def test_note_position(): notes = get_notes("c-major-scale.fsc") assert [n.position for n in notes] == [x * 384 for x in range(8)] -def test_note_rack_channel(get_notes: NotesFixture): +def test_note_rack_channel(): assert set(n.rack_channel for n in get_notes("multi-channel.flp")) == set((0, 1)) -def test_note_release(get_notes: NotesFixture): +def test_note_release(): assert [n.release for n in get_notes("release-min-max.fsc")] == [0, 128] -def test_note_slide(get_notes: NotesFixture): +def test_note_slide(): assert get_notes("slide-note.fsc")[0].slide -def test_note_velocity(get_notes: NotesFixture): +def test_note_velocity(): assert [n.velocity for n in get_notes("velocity-min-max.fsc")] == [0, 128] diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 21fdb9d..46561f7 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -1,8 +1,6 @@ from __future__ import annotations -from typing import Callable, Type, TypeVar - -import pytest +from typing import TypeVar from pyflp.plugin import ( AnyPlugin, @@ -18,34 +16,28 @@ WrapperPage, ) -from .conftest import ModelFixture +from .conftest import get_model T = TypeVar("T", bound=AnyPlugin) -PluginFixture = Callable[[str, Type[T]], T] - -@pytest.fixture -def plugin(get_model: ModelFixture): - def wrapper(preset_file: str, type: type[T]): - suffix = f"plugins/{preset_file}" - return get_model(suffix, type, PluginID.Data, PluginID.Wrapper) - return wrapper +def get_plugin(preset_file: str, type: type[T]): + return get_model(f"plugins/{preset_file}", type, PluginID.Data, PluginID.Wrapper) -def test_fruity_balance(plugin: PluginFixture[FruityBalance]): - fruity_balance = plugin("fruity-balance.fst", FruityBalance) +def test_fruity_balance(): + fruity_balance = get_plugin("fruity-balance.fst", FruityBalance) assert fruity_balance.volume == 256 assert fruity_balance.pan == 0 -def test_fruity_center(plugin: PluginFixture[FruityCenter]): - fruity_center = plugin("fruity-center.fst", FruityCenter) +def test_fruity_center(): + fruity_center = get_plugin("fruity-center.fst", FruityCenter) assert not fruity_center.enabled -def test_fruity_fast_dist(plugin: PluginFixture[FruityFastDist]): - fruity_fast_dist = plugin("fruity-fast-dist.fst", FruityFastDist) +def test_fruity_fast_dist(): + fruity_fast_dist = get_plugin("fruity-fast-dist.fst", FruityFastDist) assert fruity_fast_dist.pre == 128 assert fruity_fast_dist.threshold == 10 assert fruity_fast_dist.kind == "A" @@ -53,22 +45,24 @@ def test_fruity_fast_dist(plugin: PluginFixture[FruityFastDist]): assert fruity_fast_dist.post == 128 -def test_fruity_send(plugin: PluginFixture[FruitySend]): - fruity_send = plugin("fruity-send.fst", FruitySend) +def test_fruity_send(): + fruity_send = get_plugin("fruity-send.fst", FruitySend) assert fruity_send.dry == 256 assert fruity_send.send_to == -1 assert fruity_send.pan == 0 assert fruity_send.volume == 256 -def test_fruity_soft_clipper(plugin: PluginFixture[FruitySoftClipper]): - fruity_soft_clipper = plugin("fruity-soft-clipper.fst", FruitySoftClipper) +def test_fruity_soft_clipper(): + fruity_soft_clipper = get_plugin("fruity-soft-clipper.fst", FruitySoftClipper) assert fruity_soft_clipper.threshold == 100 assert fruity_soft_clipper.post == 128 -def test_fruity_stereo_enhancer(plugin: PluginFixture[FruityStereoEnhancer]): - fruity_stereo_enhancer = plugin("fruity-stereo-enhancer.fst", FruityStereoEnhancer) +def test_fruity_stereo_enhancer(): + fruity_stereo_enhancer = get_plugin( + "fruity-stereo-enhancer.fst", FruityStereoEnhancer + ) assert fruity_stereo_enhancer.stereo_separation == 0 assert fruity_stereo_enhancer.effect_position == "post" assert fruity_stereo_enhancer.phase_offset == 0 @@ -77,14 +71,14 @@ def test_fruity_stereo_enhancer(plugin: PluginFixture[FruityStereoEnhancer]): assert fruity_stereo_enhancer.volume == 256 -def test_soundgoodizer(plugin: PluginFixture[Soundgoodizer]): - soundgoodizer = plugin("soundgoodizer.fst", Soundgoodizer) +def test_soundgoodizer(): + soundgoodizer = get_plugin("soundgoodizer.fst", Soundgoodizer) assert soundgoodizer.amount == 600 assert soundgoodizer.mode == "A" -def test_vst_plugin(plugin: PluginFixture[VSTPlugin]): - djmfilter = plugin("xfer-djmfilter.fst", VSTPlugin) +def test_vst_plugin(): + djmfilter = get_plugin("xfer-djmfilter.fst", VSTPlugin) assert djmfilter.name == "DJMFilter" assert djmfilter.vendor == "Xfer Records" assert ( @@ -93,8 +87,8 @@ def test_vst_plugin(plugin: PluginFixture[VSTPlugin]): ) -def test_fruity_wrapper(plugin: PluginFixture[VSTPlugin]): - wrapper = plugin("fruity-wrapper.fst", VSTPlugin) +def test_fruity_wrapper(): + wrapper = get_plugin("fruity-wrapper.fst", VSTPlugin) # WrapperEvent properties assert not wrapper.compact