diff --git a/tests/test_dag.py b/tests/test_dag.py index 415a89a..606cad6 100644 --- a/tests/test_dag.py +++ b/tests/test_dag.py @@ -18,194 +18,190 @@ class TestDAGInit: assert g.on_remove is None -class TestDAGAddEdge: - """Test adding edges.""" +class TestDAGOps: + class TestDAGAddEdge: + """Test adding edges.""" - def test_add_single_edge(self) -> None: - g = DAG[str]() - g.add_edge("a", "b") - assert "b" in g["a"] - assert "a" in g.reverse["b"] + def test_add_single_edge(self) -> None: + g = DAG[str]() + g.add_edge("a", "b") + assert "b" in g["a"] + assert "a" in g.reverse["b"] - def test_add_multiple_edges(self) -> None: - g = DAG[str]() - g.add_edge("a", "b") - g.add_edge("a", "c") - g.add_edge("b", "c") - assert "b" in g["a"] - assert "c" in g["a"] - assert "c" in g["b"] + def test_add_multiple_edges(self) -> None: + g = DAG[str]() + g.add_edge("a", "b") + g.add_edge("a", "c") + g.add_edge("b", "c") + assert "b" in g["a"] + assert "c" in g["a"] + assert "c" in g["b"] - def test_add_edge_creates_nodes(self) -> None: - g = DAG[str]() - g.add_edge("a", "b") - assert "a" in g._succ - assert "b" in g._pred + def test_add_edge_creates_nodes(self) -> None: + g = DAG[str]() + g.add_edge("a", "b") + assert "a" in g._succ + assert "b" in g._pred - def test_self_loop_raises_error(self) -> None: - g = DAG[str]() - with pytest.raises(ValueError, match="Self-loops are not allowed"): - g.add_edge("a", "a") + def test_self_loop_raises_error(self) -> None: + g = DAG[str]() + with pytest.raises(ValueError, match="Self-loops are not allowed"): + g.add_edge("a", "a") - def test_add_duplicate_edge_idempotent(self) -> None: - g = DAG[str]() - g.add_edge("a", "b") - g.add_edge("a", "b") - assert len(g["a"]) == 1 + def test_add_duplicate_edge_idempotent(self) -> None: + g = DAG[str]() + g.add_edge("a", "b") + g.add_edge("a", "b") + assert len(g["a"]) == 1 + + class TestDAGRemoveEdge: + """Test removing edges.""" + + def test_remove_existing_edge(self) -> None: + g = DAG[str]() + g.add_edge("a", "b") + g.remove_edge("a", "b") + assert "b" not in g["a"] + assert "a" not in g.reverse["b"] + + def test_remove_nonexistent_edge_missing_ok(self) -> None: + g = DAG[str]() + g.remove_edge("a", "b", missing_ok=True) # should not raise + + def test_remove_nonexistent_edge_error(self) -> None: + g = DAG[str]() + with pytest.raises(KeyError): + g.remove_edge("a", "b", missing_ok=False) + + class TestDAGDiscardNode: + """Test node removal.""" + + def test_discard_node_removes_outgoing_edges(self) -> None: + g = DAG[str]() + g.add_edge("a", "b") + g.add_edge("a", "c") + g.discard_node("a") + assert "a" not in g._succ + assert "a" not in g.reverse["b"] + assert "a" not in g.reverse["c"] + + def test_discard_node_removes_incoming_edges(self) -> None: + g = DAG[str]() + g.add_edge("a", "c") + g.add_edge("b", "c") + g.discard_node("c") + assert "c" not in g._pred + assert "c" not in g["a"] + assert "c" not in g["b"] + + def test_discard_nonexistent_node(self) -> None: + g = DAG[str]() + g.discard_node("z") # should not raise -class TestDAGRemoveEdge: - """Test removing edges.""" +class TestDAGBasicOps: + class TestSetItem: + """Test dictionary-style assignment.""" - def test_remove_existing_edge(self) -> None: - g = DAG[str]() - g.add_edge("a", "b") - g.remove_edge("a", "b") - assert "b" not in g["a"] - assert "a" not in g.reverse["b"] + def test_with_set(self) -> None: + g = DAG[str]() + g["a"] = {"b", "c"} + assert "b" in g["a"] + assert "c" in g["a"] - def test_remove_nonexistent_edge_missing_ok(self) -> None: - g = DAG[str]() - g.remove_edge("a", "b", missing_ok=True) # should not raise + def test_with_list(self) -> None: + g = DAG[str]() + g["a"] = ["b", "c"] + assert "b" in g["a"] + assert "c" in g["a"] - def test_remove_nonexistent_edge_error(self) -> None: - g = DAG[str]() - with pytest.raises(KeyError): - g.remove_edge("a", "b", missing_ok=False) + def test_with_string(self) -> None: + g = DAG[str]() + g["a"] = "b" + assert "b" in g["a"] + + def test_with_single_item(self) -> None: + g = DAG[int]() + g[1] = 2 + assert 2 in g[1] + + def test_updates_reverse(self) -> None: + g = DAG[str]() + g["a"] = {"b", "c"} + assert "a" in g.reverse["b"] + assert "a" in g.reverse["c"] + + class TestGetItem: + """Test dictionary-style access.""" + + def test_getitem_returns_dagset(self) -> None: + g = DAG[str]() + g.add_edge("a", "b") + dagset = g["a"] + assert "b" in dagset + + def test_getitem_empty_node(self) -> None: + g = DAG[str]() + dagset = g["nonexistent"] + assert len(dagset) == 0 + + def test_getitem_mutation_updates_graph(self) -> None: + g = DAG[str]() + g["a"].add("b") + assert "b" in g["a"] + assert "a" in g.reverse["b"] + + def test_getitem_mutation_triggers_callbacks(self) -> None: + added: list[tuple[str, str]] = [] + g = DAG[str]() + g.on_add = lambda u, v: added.append((u, v)) + g["a"].add("b") + assert ("a", "b") in added + + class TestDAGDelItem: + """Test del operation.""" + + def test_removes_node(self) -> None: + g = DAG[str]() + g.add_edge("a", "b") + del g["a"] + assert "a" not in g._succ -class TestDAGDiscardNode: - """Test node removal.""" +class TestDAGIterOps: + class TestIter: + """Test iteration.""" - def test_discard_node_removes_outgoing_edges(self) -> None: - g = DAG[str]() - g.add_edge("a", "b") - g.add_edge("a", "c") - g.discard_node("a") - assert "a" not in g._succ - assert "a" not in g.reverse["b"] - assert "a" not in g.reverse["c"] + def test_empty(self) -> None: + g = DAG[str]() + assert list(g) == [] - def test_discard_node_removes_incoming_edges(self) -> None: - g = DAG[str]() - g.add_edge("a", "c") - g.add_edge("b", "c") - g.discard_node("c") - assert "c" not in g._pred - assert "c" not in g["a"] - assert "c" not in g["b"] + def test_returns_nodes(self) -> None: + g = DAG[str]() + g.add_edge("a", "b") + g.add_edge("b", "c") + assert {"a", "b"} == set(g) - def test_discard_nonexistent_node(self) -> None: - g = DAG[str]() - g.discard_node("z") # should not raise + class TestLen: + """Test length (edge count).""" + def test_len_empty(self) -> None: + g = DAG[str]() + assert len(g) == 0 -class TestDAGGetItem: - """Test dictionary-style access.""" + def test_len_counts_edges(self) -> None: + g = DAG[str]() + g.add_edge("a", "b") + g.add_edge("a", "c") + g.add_edge("b", "c") + assert len(g) == 3 - def test_getitem_returns_dagset(self) -> None: - g = DAG[str]() - g.add_edge("a", "b") - dagset = g["a"] - assert "b" in dagset - - def test_getitem_empty_node(self) -> None: - g = DAG[str]() - dagset = g["nonexistent"] - assert len(dagset) == 0 - - def test_getitem_mutation_updates_graph(self) -> None: - g = DAG[str]() - g["a"].add("b") - assert "b" in g["a"] - assert "a" in g.reverse["b"] - - def test_getitem_mutation_triggers_callbacks(self) -> None: - added: list[tuple[str, str]] = [] - g = DAG[str]() - g.on_add = lambda u, v: added.append((u, v)) - g["a"].add("b") - assert ("a", "b") in added - - -class TestDAGSetItem: - """Test dictionary-style assignment.""" - - def test_setitem_with_set(self) -> None: - g = DAG[str]() - g["a"] = {"b", "c"} - assert "b" in g["a"] - assert "c" in g["a"] - - def test_setitem_with_list(self) -> None: - g = DAG[str]() - g["a"] = ["b", "c"] - assert "b" in g["a"] - assert "c" in g["a"] - - def test_setitem_with_string(self) -> None: - g = DAG[str]() - g["a"] = "b" - assert "b" in g["a"] - - def test_setitem_with_single_item(self) -> None: - g = DAG[int]() - g[1] = 2 - assert 2 in g[1] - - def test_setitem_updates_reverse(self) -> None: - g = DAG[str]() - g["a"] = {"b", "c"} - assert "a" in g.reverse["b"] - assert "a" in g.reverse["c"] - - -class TestDAGDelItem: - """Test del operation.""" - - def test_delitem_removes_node(self) -> None: - g = DAG[str]() - g.add_edge("a", "b") - del g["a"] - assert "a" not in g._succ - - -class TestDAGIter: - """Test iteration.""" - - def test_iter_empty(self) -> None: - g = DAG[str]() - assert list(g) == [] - - def test_iter_returns_nodes(self) -> None: - g = DAG[str]() - g.add_edge("a", "b") - g.add_edge("b", "c") - nodes = set(g) - assert "a" in nodes - assert "b" in nodes - - -class TestDAGLen: - """Test length (edge count).""" - - def test_len_empty(self) -> None: - g = DAG[str]() - assert len(g) == 0 - - def test_len_counts_edges(self) -> None: - g = DAG[str]() - g.add_edge("a", "b") - g.add_edge("a", "c") - g.add_edge("b", "c") - assert len(g) == 3 - - def test_len_after_removal(self) -> None: - g = DAG[str]() - g.add_edge("a", "b") - g.add_edge("a", "c") - g.remove_edge("a", "b") - assert len(g) == 1 + def test_len_after_removal(self) -> None: + g = DAG[str]() + g.add_edge("a", "b") + g.add_edge("a", "c") + g.remove_edge("a", "b") + assert len(g) == 1 class TestDAGReverse: @@ -252,24 +248,6 @@ class TestDAGCallbacks: assert ("a", "b") in removed -class TestDAGRepr: - """Test string representation.""" - - def test_repr_empty(self) -> None: - g = DAG[str]() - r = repr(g) - assert "DAG" in r - assert "{}" in r - - def test_repr_with_edges(self) -> None: - g = DAG[str]() - g.add_edge("a", "b") - r = repr(g) - assert "DAG" in r - assert "a" in r - assert "b" in r - - class TestDAGComplexScenarios: """Test complex usage patterns."""