9 Commits

Author SHA1 Message Date
e9d1e7ffa2 check vban direction
check that index is numeric

patch bump
2023-08-10 21:07:29 +01:00
08525b086c patch bump 2023-08-10 16:28:59 +01:00
cf88b0a63b add poetry test scripts for each kind 2023-08-10 16:27:49 +01:00
4a397d8d96 avoid using keyword as variable name 2023-08-10 16:25:31 +01:00
65fb8990c9 make better use of pattern matching features
error test updated
2023-08-10 16:24:30 +01:00
8c220eb491 refactor target
add error test for ValueError

test badges updated

patch bump
2023-08-09 16:37:10 +01:00
e38922d8a0 upd test badges 2023-08-07 15:49:44 +01:00
96f3faae28 add error tests 2023-08-07 15:39:45 +01:00
bd57f78e8f fix test names 2023-08-07 15:39:22 +01:00
12 changed files with 112 additions and 29 deletions

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "voicemeeter-api"
version = "2.4.4"
version = "2.4.7"
description = "A Python wrapper for the Voiceemeter API"
authors = ["onyx-and-iris <code@onyxandiris.online>"]
license = "MIT"
@@ -33,7 +33,10 @@ levels = "scripts:ex_levels"
midi = "scripts:ex_midi"
obs = "scripts:ex_obs"
observer = "scripts:ex_observer"
test = "scripts:test"
basic = "scripts:test_basic"
banana = "scripts:test_banana"
potato = "scripts:test_potato"
all = "scripts:test_all"
[tool.tox]
legacy_tox_ini = """

View File

@@ -1,3 +1,4 @@
import os
import subprocess
import sys
from pathlib import Path
@@ -38,5 +39,21 @@ def ex_observer():
subprocess.run([sys.executable, str(scriptpath)])
def test():
def test_basic():
os.environ["KIND"] = "basic"
subprocess.run(["tox"])
def test_banana():
os.environ["KIND"] = "banana"
subprocess.run(["tox"])
def test_potato():
os.environ["KIND"] = "potato"
subprocess.run(["tox"])
def test_all():
steps = [test_basic, test_banana, test_potato]
[step() for step in steps]

View File

@@ -1,3 +1,4 @@
import os
import random
import sys
from dataclasses import dataclass
@@ -30,8 +31,10 @@ class Data:
return (2 * self.phys_in) + (8 * self.virt_in)
# let's keep things random
KIND_ID = random.choice(tuple(kind_id.name.lower() for kind_id in KindId))
# get KIND_ID from env var, otherwise set to random
KIND_ID = os.environ.get(
"KIND", random.choice(tuple(kind_id.name.lower() for kind_id in KindId))
)
vm = voicemeeterlib.api(KIND_ID)
kind = kindmap(KIND_ID)

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="68" height="20" role="img" aria-label="tests: 155"><title>tests: 155</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="68" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="37" height="20" fill="#555"/><rect x="37" width="31" height="20" fill="#4c1"/><rect width="68" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="195" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="270">tests</text><text x="195" y="140" transform="scale(.1)" fill="#fff" textLength="270">tests</text><text aria-hidden="true" x="515" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="210">155</text><text x="515" y="140" transform="scale(.1)" fill="#fff" textLength="210">155</text></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="68" height="20" role="img" aria-label="tests: 159"><title>tests: 159</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="68" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="37" height="20" fill="#555"/><rect x="37" width="31" height="20" fill="#4c1"/><rect width="68" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="195" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="270">tests</text><text x="195" y="140" transform="scale(.1)" fill="#fff" textLength="270">tests</text><text aria-hidden="true" x="515" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="210">159</text><text x="515" y="140" transform="scale(.1)" fill="#fff" textLength="210">159</text></g></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="68" height="20" role="img" aria-label="tests: 112"><title>tests: 112</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="68" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="37" height="20" fill="#555"/><rect x="37" width="31" height="20" fill="#4c1"/><rect width="68" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="195" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="270">tests</text><text x="195" y="140" transform="scale(.1)" fill="#fff" textLength="270">tests</text><text aria-hidden="true" x="515" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="210">112</text><text x="515" y="140" transform="scale(.1)" fill="#fff" textLength="210">112</text></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="68" height="20" role="img" aria-label="tests: 116"><title>tests: 116</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="68" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="37" height="20" fill="#555"/><rect x="37" width="31" height="20" fill="#4c1"/><rect width="68" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="195" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="270">tests</text><text x="195" y="140" transform="scale(.1)" fill="#fff" textLength="270">tests</text><text aria-hidden="true" x="515" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="210">116</text><text x="515" y="140" transform="scale(.1)" fill="#fff" textLength="210">116</text></g></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="68" height="20" role="img" aria-label="tests: 164"><title>tests: 164</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="68" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="37" height="20" fill="#555"/><rect x="37" width="31" height="20" fill="#4c1"/><rect width="68" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="195" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="270">tests</text><text x="195" y="140" transform="scale(.1)" fill="#fff" textLength="270">tests</text><text aria-hidden="true" x="515" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="210">164</text><text x="515" y="140" transform="scale(.1)" fill="#fff" textLength="210">164</text></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="68" height="20" role="img" aria-label="tests: 184"><title>tests: 184</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="68" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="37" height="20" fill="#555"/><rect x="37" width="31" height="20" fill="#4c1"/><rect width="68" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="195" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="270">tests</text><text x="195" y="140" transform="scale(.1)" fill="#fff" textLength="270">tests</text><text aria-hidden="true" x="515" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="210">184</text><text x="515" y="140" transform="scale(.1)" fill="#fff" textLength="210">184</text></g></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -25,7 +25,11 @@ Function Get-TimeStamp {
if ($MyInvocation.InvocationName -ne ".") {
Invoke-Expression ".\.venv\Scripts\Activate.ps1"
RunTests
@("potato") | ForEach-Object {
$env:KIND = $_
RunTests
}
Invoke-Expression "deactivate"
}

View File

@@ -12,37 +12,37 @@ class TestUserConfigs:
def setup_class(cls):
vm.apply_config("example")
def test_it_vm_config_string(self):
def test_it_tests_vm_config_string(self):
assert "PhysStrip" in vm.strip[data.phys_in].label
assert "VirtStrip" in vm.strip[data.virt_in].label
assert "PhysBus" in vm.bus[data.phys_out].label
assert "VirtBus" in vm.bus[data.virt_out].label
def test_it_vm_config_bool(self):
def test_it_tests_vm_config_bool(self):
assert vm.strip[0].A1 == True
@pytest.mark.skipif(
data.name != "potato",
reason="Skip test if kind is not potato",
)
def test_it_vm_config_bool_strip_eq_on(self):
def test_it_tests_vm_config_bool_strip_eq_on(self):
assert vm.strip[data.phys_in].eq.on == True
@pytest.mark.skipif(
data.name != "banana",
reason="Skip test if kind is not banana",
)
def test_it_vm_config_bool_bus_eq_ab(self):
def test_it_tests_vm_config_bool_bus_eq_ab(self):
assert vm.bus[data.phys_out].eq.ab == True
@pytest.mark.skipif(
"not config.getoption('--run-slow')",
reason="Only run when --run-slow is given",
)
def test_it_vm_config_busmode(self):
def test_it_tests_vm_config_busmode(self):
assert vm.bus[data.phys_out].mode.get() == "composite"
def test_it_vm_config_bass_med_high(self):
def test_it_tests_vm_config_bass_med_high(self):
assert vm.strip[data.virt_in].bass == -3.2
assert vm.strip[data.virt_in].mid == 1.5
assert vm.strip[data.virt_in].high == 2.1

47
tests/test_errors.py Normal file
View File

@@ -0,0 +1,47 @@
import pytest
import voicemeeterlib
from tests import data, vm
class TestErrors:
__test__ = True
def test_it_tests_an_unknown_kind(self):
with pytest.raises(
voicemeeterlib.error.VMError,
match="Unknown Voicemeeter kind 'unknown_kind'",
):
voicemeeterlib.api("unknown_kind")
def test_it_tests_an_unknown_parameter(self):
with pytest.raises(
voicemeeterlib.error.CAPIError,
match="VBVMR_SetParameterFloat returned -3",
) as exc_info:
vm.set("unknown.parameter", 1)
e = exc_info.value
assert e.code == -3
assert e.fn_name == "VBVMR_SetParameterFloat"
def test_it_tests_an_unknown_config_name(self):
EXPECTED_MSG = (
"No config with name 'unknown' is loaded into memory",
f"Known configs: {list(vm.configs.keys())}",
)
with pytest.raises(voicemeeterlib.error.VMError) as exc_info:
vm.apply_config("unknown")
e = exc_info.value
assert e.message == "\n".join(EXPECTED_MSG)
def test_it_tests_an_invalid_config_key(self):
CONFIG = {
"strip-0": {"A1": True, "B1": True, "gain": -6.0},
"bus-0": {"mute": True, "eq": {"on": True}},
"unknown-0": {"state": True},
"vban-out-1": {"name": "streamname"},
}
with pytest.raises(ValueError, match="invalid config key 'unknown-0'"):
vm.apply(CONFIG)

View File

@@ -10,7 +10,7 @@ class TestRemoteFactories:
data.name != "basic",
reason="Skip test if kind is not basic",
)
def test_it_vm_remote_attrs_for_basic(self):
def test_it_tests_vm_remote_attrs_for_basic(self):
assert hasattr(vm, "strip")
assert hasattr(vm, "bus")
assert hasattr(vm, "command")
@@ -28,7 +28,7 @@ class TestRemoteFactories:
data.name != "banana",
reason="Skip test if kind is not banana",
)
def test_it_vm_remote_attrs_for_banana(self):
def test_it_tests_vm_remote_attrs_for_banana(self):
assert hasattr(vm, "strip")
assert hasattr(vm, "bus")
assert hasattr(vm, "command")
@@ -48,7 +48,7 @@ class TestRemoteFactories:
data.name != "potato",
reason="Skip test if kind is not potato",
)
def test_it_vm_remote_attrs_for_potato(self):
def test_it_tests_vm_remote_attrs_for_potato(self):
assert hasattr(vm, "strip")
assert hasattr(vm, "bus")
assert hasattr(vm, "command")

View File

@@ -299,16 +299,25 @@ class Remote(CBindings):
minor delay between each recursion
"""
def param(key):
obj, m2, *rem = key.split("-")
index = int(m2) if m2.isnumeric() else int(*rem)
if obj in ("strip", "bus", "button"):
return getattr(self, obj)[index]
elif obj == "vban":
return getattr(getattr(self, obj), f"{m2}stream")[index]
raise ValueError(obj)
def target(key):
match key.split("-"):
case ["strip" | "bus" | "button" as kls, index] if index.isnumeric():
target = getattr(self, kls)
case [
"vban",
"in" | "instream" | "out" | "outstream" as direction,
index,
] if index.isnumeric():
target = getattr(
self.vban, f"{direction.removesuffix('stream')}stream"
)
case _:
ERR_MSG = f"invalid config key '{key}'"
self.logger.error(ERR_MSG)
raise ValueError(ERR_MSG)
return target[int(index)]
[param(key).apply(datum).then_wait() for key, datum in data.items()]
[target(key).apply(di).then_wait() for key, di in data.items()]
def apply_config(self, name):
"""applies a config from memory"""

View File

@@ -172,8 +172,8 @@ class VbanMidiOutstream(VbanOutstream):
def _make_stream_pair(remote, kind):
num_instream, num_outstream, num_midi, num_text = kind.vban
def _make_cls(i, dir):
match dir:
def _make_cls(i, direction):
match direction:
case "in":
if i < num_instream:
return VbanAudioInstream(remote, i)