77 lines
3.2 KiB
Python
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
|