Files
daglib/tests/test_example_build-graph.py
T
2026-10-10 11:05:04 -05:00

77 lines
3.2 KiB
Python

import pytest
from daglib.dag import DAG
from daglib.operations import all_nodes
@pytest.fixture
def build_graph() -> DAG[str]:
graph = DAG[str]()
graph.add_edge("checkout", "compile")
graph.add_edge("toolchain", "compile")
graph.add_edge("compile", "package")
graph.add_edge("package", "deploy")
return graph
def test_subgraph_reverse_mutation_preserves_parent_edge_consistency(build_graph: DAG[str]) -> None:
assert "compile" in build_graph["toolchain"]
assert "toolchain" in build_graph.reverse["compile"]
pipeline = build_graph.subgraph("checkout")
pipeline.reverse["compile"].discard("toolchain")
forward_has_edge = "compile" in build_graph["toolchain"]
reverse_has_edge = "toolchain" in build_graph.reverse["compile"]
assert forward_has_edge == reverse_has_edge, "Subgraph mutation desynchronized the parent DAG's edge indexes"
assert forward_has_edge
def test_subgraph_reverse_mutation_preserves_parent_topological_dependencies(build_graph: DAG[str]) -> None:
assert "toolchain" in build_graph.topo_sort()
pipeline = build_graph.subgraph("checkout")
pipeline.reverse["compile"].discard("toolchain")
assert "compile" in build_graph["toolchain"]
order = build_graph.topo_sort()
assert "toolchain" in order, "Topological sorting omitted a node with an outgoing edge in the parent DAG"
assert order.index("toolchain") < order.index("compile")
def test_build_subgraph_excludes_external_dependencies(build_graph: DAG[str]) -> None:
pipeline = build_graph.subgraph("checkout")
assert all_nodes(pipeline) == {"checkout", "compile", "package", "deploy"}
assert set(pipeline.reverse["compile"]) == {"checkout"}
assert len(pipeline) == 3
order = pipeline.topo_sort()
assert set(order) == all_nodes(pipeline)
assert order.index("checkout") < order.index("compile") < order.index("package") < order.index("deploy")
@pytest.mark.parametrize("mutate_parent", [False, True], ids=["child-mutation", "parent-mutation"])
@pytest.mark.parametrize("mutation", ["add", "remove", "reverse-remove", "discard-node"])
def test_build_subgraph_mutations_are_isolated(build_graph: DAG[str], mutate_parent: bool, mutation: str) -> None:
pipeline = build_graph.subgraph("checkout")
changed, protected = (build_graph, pipeline) if mutate_parent else (pipeline, build_graph)
successors = {node: set(protected[node]) for node in protected}
predecessors = {node: set(protected.reverse[node]) for node in protected.reverse}
match mutation:
case "add":
changed["package"].add("publish")
case "remove":
changed["compile"].discard("package")
case "reverse-remove":
changed.reverse["compile"].discard("checkout")
case "discard-node":
changed.discard_node("compile")
assert {node: set(protected[node]) for node in protected} == successors
assert {node: set(protected.reverse[node]) for node in protected.reverse} == predecessors
for graph in (changed, protected):
forward_edges = {(source, target) for source in graph for target in graph[source]}
reverse_edges = {(source, target) for target in graph.reverse for source in graph.reverse[target]}
assert forward_edges == reverse_edges