diff --git a/masque/ports.py b/masque/ports.py index 3a67003..a02be1c 100644 --- a/masque/ports.py +++ b/masque/ports.py @@ -331,6 +331,10 @@ class PortList(metaclass=ABCMeta): missing = set(mapping) - set(self.ports) if missing: raise PortError(f'Ports to rename were not found: {missing}') + renamed_targets = [vv for vv in mapping.values() if vv is not None] + duplicate_targets = {vv for vv in renamed_targets if renamed_targets.count(vv) > 1} + if duplicate_targets: + raise PortError(f'Renamed ports would collide: {duplicate_targets}') for kk, vv in mapping.items(): if vv is None or vv != kk: diff --git a/masque/test/test_ports.py b/masque/test/test_ports.py index 0291a1c..191387f 100644 --- a/masque/test/test_ports.py +++ b/masque/test/test_ports.py @@ -89,6 +89,25 @@ def test_port_list_rename_missing_port_raises() -> None: 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: