293 lines
8.6 KiB
Python
293 lines
8.6 KiB
Python
import pytest
|
|
from numpy.testing import assert_equal, assert_allclose
|
|
from numpy import pi
|
|
|
|
from ..ports import Port, PortList
|
|
from ..error import PortError
|
|
from ..pattern import Pattern
|
|
|
|
|
|
def test_port_init() -> None:
|
|
p = Port(offset=(10, 20), rotation=pi / 2, ptype="test")
|
|
assert_equal(p.offset, [10, 20])
|
|
assert p.rotation == pi / 2
|
|
assert p.ptype == "test"
|
|
|
|
|
|
def test_port_transform() -> None:
|
|
p = Port(offset=(10, 0), rotation=0)
|
|
p.rotate_around((0, 0), pi / 2)
|
|
assert_allclose(p.offset, [0, 10], atol=1e-10)
|
|
assert p.rotation is not None
|
|
assert_allclose(p.rotation, pi / 2, atol=1e-10)
|
|
|
|
p.mirror(0) # Mirror across x axis (axis 0): in-place relative to offset
|
|
assert_allclose(p.offset, [0, 10], atol=1e-10)
|
|
# rotation was pi/2 (90 deg), mirror across x (0 deg) -> -pi/2 == 3pi/2
|
|
assert p.rotation is not None
|
|
assert_allclose(p.rotation, 3 * pi / 2, atol=1e-10)
|
|
|
|
|
|
def test_port_flip_across() -> None:
|
|
p = Port(offset=(10, 0), rotation=0)
|
|
p.flip_across(axis=1) # Mirror across x=0: flips x-offset
|
|
assert_equal(p.offset, [-10, 0])
|
|
# rotation was 0, mirrored(1) -> pi
|
|
assert p.rotation is not None
|
|
assert_allclose(p.rotation, pi, atol=1e-10)
|
|
|
|
|
|
def test_port_measure_travel() -> None:
|
|
p1 = Port((0, 0), 0)
|
|
p2 = Port((10, 5), pi) # Facing each other
|
|
|
|
(travel, jog), rotation = p1.measure_travel(p2)
|
|
assert travel == 10
|
|
assert jog == 5
|
|
assert rotation == pi
|
|
|
|
|
|
def test_port_list_measure_travel() -> None:
|
|
class MyPorts(PortList):
|
|
def __init__(self) -> None:
|
|
self._ports = {
|
|
"A": Port((0, 0), 0),
|
|
"B": Port((10, 5), pi),
|
|
}
|
|
|
|
@property
|
|
def ports(self) -> dict[str, Port]:
|
|
return self._ports
|
|
|
|
@ports.setter
|
|
def ports(self, val: dict[str, Port]) -> None:
|
|
self._ports = val
|
|
|
|
pl = MyPorts()
|
|
(travel, jog), rotation = pl.measure_travel("A", "B")
|
|
assert travel == 10
|
|
assert jog == 5
|
|
assert rotation == pi
|
|
|
|
|
|
def test_port_describe_any_rotation() -> None:
|
|
p = Port((0, 0), None)
|
|
assert p.describe() == "pos=(0, 0), rot=any"
|
|
|
|
|
|
def test_port_list_rename() -> None:
|
|
class MyPorts(PortList):
|
|
def __init__(self) -> None:
|
|
self._ports = {"A": Port((0, 0), 0)}
|
|
|
|
@property
|
|
def ports(self) -> dict[str, Port]:
|
|
return self._ports
|
|
|
|
@ports.setter
|
|
def ports(self, val: dict[str, Port]) -> None:
|
|
self._ports = val
|
|
|
|
pl = MyPorts()
|
|
pl.rename_ports({"A": "B"})
|
|
assert "A" not in pl.ports
|
|
assert "B" in pl.ports
|
|
|
|
|
|
def test_port_list_rename_missing_port_raises() -> None:
|
|
class MyPorts(PortList):
|
|
def __init__(self) -> None:
|
|
self._ports = {"A": Port((0, 0), 0)}
|
|
|
|
@property
|
|
def ports(self) -> dict[str, Port]:
|
|
return self._ports
|
|
|
|
@ports.setter
|
|
def ports(self, val: dict[str, Port]) -> None:
|
|
self._ports = val
|
|
|
|
pl = MyPorts()
|
|
with pytest.raises(PortError, match="Ports to rename were not found"):
|
|
pl.rename_ports({"missing": "B"})
|
|
assert set(pl.ports) == {"A"}
|
|
|
|
|
|
def test_port_list_rename_colliding_targets_raises() -> None:
|
|
class MyPorts(PortList):
|
|
def __init__(self) -> None:
|
|
self._ports = {"A": Port((0, 0), 0), "B": Port((1, 0), 0)}
|
|
|
|
@property
|
|
def ports(self) -> dict[str, Port]:
|
|
return self._ports
|
|
|
|
@ports.setter
|
|
def ports(self, val: dict[str, Port]) -> None:
|
|
self._ports = val
|
|
|
|
pl = MyPorts()
|
|
with pytest.raises(PortError, match="Renamed ports would collide"):
|
|
pl.rename_ports({"A": "C", "B": "C"})
|
|
assert set(pl.ports) == {"A", "B"}
|
|
|
|
|
|
def test_port_list_add_port_pair_requires_distinct_names() -> None:
|
|
class MyPorts(PortList):
|
|
def __init__(self) -> None:
|
|
self._ports: dict[str, Port] = {}
|
|
|
|
@property
|
|
def ports(self) -> dict[str, Port]:
|
|
return self._ports
|
|
|
|
@ports.setter
|
|
def ports(self, val: dict[str, Port]) -> None:
|
|
self._ports = val
|
|
|
|
pl = MyPorts()
|
|
with pytest.raises(PortError, match="Port names must be distinct"):
|
|
pl.add_port_pair(names=("A", "A"))
|
|
assert not pl.ports
|
|
|
|
|
|
def test_port_list_plugged() -> None:
|
|
class MyPorts(PortList):
|
|
def __init__(self) -> None:
|
|
self._ports = {"A": Port((10, 10), 0), "B": Port((10, 10), pi)}
|
|
|
|
@property
|
|
def ports(self) -> dict[str, Port]:
|
|
return self._ports
|
|
|
|
@ports.setter
|
|
def ports(self, val: dict[str, Port]) -> None:
|
|
self._ports = val
|
|
|
|
pl = MyPorts()
|
|
pl.plugged({"A": "B"})
|
|
assert not pl.ports # Both should be removed
|
|
|
|
|
|
def test_port_list_plugged_empty_raises() -> None:
|
|
class MyPorts(PortList):
|
|
def __init__(self) -> None:
|
|
self._ports = {"A": Port((10, 10), 0), "B": Port((10, 10), pi)}
|
|
|
|
@property
|
|
def ports(self) -> dict[str, Port]:
|
|
return self._ports
|
|
|
|
@ports.setter
|
|
def ports(self, val: dict[str, Port]) -> None:
|
|
self._ports = val
|
|
|
|
pl = MyPorts()
|
|
with pytest.raises(PortError, match="Must provide at least one port connection"):
|
|
pl.plugged({})
|
|
assert set(pl.ports) == {"A", "B"}
|
|
|
|
|
|
def test_port_list_plugged_missing_port_raises() -> None:
|
|
class MyPorts(PortList):
|
|
def __init__(self) -> None:
|
|
self._ports = {"A": Port((10, 10), 0), "B": Port((10, 10), pi)}
|
|
|
|
@property
|
|
def ports(self) -> dict[str, Port]:
|
|
return self._ports
|
|
|
|
@ports.setter
|
|
def ports(self, val: dict[str, Port]) -> None:
|
|
self._ports = val
|
|
|
|
pl = MyPorts()
|
|
with pytest.raises(PortError, match="Connection source ports were not found"):
|
|
pl.plugged({"missing": "B"})
|
|
assert set(pl.ports) == {"A", "B"}
|
|
|
|
|
|
def test_port_list_plugged_reused_port_raises_atomically() -> None:
|
|
class MyPorts(PortList):
|
|
def __init__(self) -> None:
|
|
self._ports = {"A": Port((0, 0), None), "B": Port((0, 0), None), "C": Port((0, 0), None)}
|
|
|
|
@property
|
|
def ports(self) -> dict[str, Port]:
|
|
return self._ports
|
|
|
|
@ports.setter
|
|
def ports(self, val: dict[str, Port]) -> None:
|
|
self._ports = val
|
|
|
|
for connections in ({"A": "A"}, {"A": "B", "C": "B"}):
|
|
pl = MyPorts()
|
|
with pytest.raises(PortError, match="Each port may appear in at most one connection"):
|
|
pl.plugged(connections)
|
|
assert set(pl.ports) == {"A", "B", "C"}
|
|
|
|
pl = MyPorts()
|
|
with pytest.raises(PortError, match="Connection destination ports were not found"):
|
|
pl.plugged({"A": "missing"})
|
|
assert set(pl.ports) == {"A", "B", "C"}
|
|
|
|
|
|
def test_port_list_plugged_mismatch() -> None:
|
|
class MyPorts(PortList):
|
|
def __init__(self) -> None:
|
|
self._ports = {
|
|
"A": Port((10, 10), 0),
|
|
"B": Port((11, 10), pi), # Offset mismatch
|
|
}
|
|
|
|
@property
|
|
def ports(self) -> dict[str, Port]:
|
|
return self._ports
|
|
|
|
@ports.setter
|
|
def ports(self, val: dict[str, Port]) -> None:
|
|
self._ports = val
|
|
|
|
pl = MyPorts()
|
|
with pytest.raises(PortError):
|
|
pl.plugged({"A": "B"})
|
|
|
|
|
|
def test_port_list_check_ports_duplicate_map_in_values_raise() -> None:
|
|
class MyPorts(PortList):
|
|
def __init__(self) -> None:
|
|
self._ports = {"A": Port((0, 0), 0), "B": Port((0, 0), 0)}
|
|
|
|
@property
|
|
def ports(self) -> dict[str, Port]:
|
|
return self._ports
|
|
|
|
@ports.setter
|
|
def ports(self, val: dict[str, Port]) -> None:
|
|
self._ports = val
|
|
|
|
pl = MyPorts()
|
|
with pytest.raises(PortError, match="Duplicate values in `map_in`"):
|
|
pl.check_ports({"X", "Y"}, map_in={"A": "X", "B": "X"})
|
|
assert set(pl.ports) == {"A", "B"}
|
|
|
|
|
|
def test_pattern_plug_rejects_map_out_on_connected_ports_atomically() -> None:
|
|
host = Pattern(ports={"A": Port((0, 0), 0)})
|
|
other = Pattern(ports={"X": Port((0, 0), pi), "Y": Port((5, 0), 0)})
|
|
|
|
with pytest.raises(PortError, match="`map_out` keys conflict with connected ports"):
|
|
host.plug(other, {"A": "X"}, map_out={"X": "renamed", "Y": "out"}, append=True)
|
|
|
|
assert set(host.ports) == {"A"}
|
|
|
|
|
|
def test_find_transform_requires_connection_map() -> None:
|
|
host = Pattern(ports={"A": Port((0, 0), 0)})
|
|
other = Pattern(ports={"X": Port((0, 0), pi)})
|
|
|
|
with pytest.raises(PortError, match="at least one port connection"):
|
|
host.find_transform(other, {})
|
|
|
|
with pytest.raises(PortError, match="at least one port connection"):
|
|
Pattern.find_port_transform({}, {}, {})
|