210 lines
4.4 KiB
Python
210 lines
4.4 KiB
Python
from typing import List, Type
|
|
|
|
import pytest
|
|
|
|
from ray.data._internal.logical.interfaces.optimizer import Rule
|
|
from ray.data._internal.logical.ruleset import Ruleset
|
|
|
|
|
|
def test_add_rule():
|
|
class A(Rule):
|
|
pass
|
|
|
|
ruleset = Ruleset([A])
|
|
assert list(ruleset) == [A]
|
|
|
|
|
|
def test_add_rule_with_dependencies():
|
|
class A(Rule):
|
|
pass
|
|
|
|
class B(Rule):
|
|
@classmethod
|
|
def dependencies(cls) -> List[Type[Rule]]:
|
|
return [A]
|
|
|
|
ruleset = Ruleset([A])
|
|
ruleset.add(B)
|
|
assert list(ruleset) == [A, B]
|
|
|
|
|
|
def test_add_rule_with_dependents():
|
|
class A(Rule):
|
|
pass
|
|
|
|
class B(Rule):
|
|
@classmethod
|
|
def dependents(cls) -> List[Type[Rule]]:
|
|
return [A]
|
|
|
|
ruleset = Ruleset([A])
|
|
ruleset.add(B)
|
|
assert list(ruleset) == [B, A]
|
|
|
|
|
|
def test_add_rule_with_multiple_dependencies():
|
|
class A(Rule):
|
|
pass
|
|
|
|
class B(Rule):
|
|
pass
|
|
|
|
class C(Rule):
|
|
@classmethod
|
|
def dependencies(cls) -> List[Type[Rule]]:
|
|
return [A, B]
|
|
|
|
ruleset = Ruleset([A, B])
|
|
ruleset.add(C)
|
|
|
|
rules = list(ruleset)
|
|
assert set(rules) == {A, B, C}
|
|
assert rules.index(A) < rules.index(B)
|
|
assert rules.index(B) < rules.index(C)
|
|
|
|
|
|
def test_add_rule_with_multiple_dependents():
|
|
class A(Rule):
|
|
pass
|
|
|
|
class B(Rule):
|
|
pass
|
|
|
|
class C(Rule):
|
|
@classmethod
|
|
def dependents(cls) -> List[Type[Rule]]:
|
|
return [A, B]
|
|
|
|
ruleset = Ruleset([A, B])
|
|
ruleset.add(C)
|
|
|
|
rules = list(ruleset)
|
|
assert set(rules) == {A, B, C}
|
|
assert rules[0] == C
|
|
|
|
|
|
def test_add_rule_with_missing_dependencies():
|
|
class A(Rule):
|
|
pass
|
|
|
|
class B(Rule):
|
|
@classmethod
|
|
def dependencies(cls) -> List[Type[Rule]]:
|
|
return [A]
|
|
|
|
ruleset = Ruleset()
|
|
ruleset.add(B)
|
|
assert list(ruleset) == [B]
|
|
|
|
|
|
def test_add_rule_with_missing_dependents():
|
|
class A(Rule):
|
|
pass
|
|
|
|
class B(Rule):
|
|
@classmethod
|
|
def dependents(cls) -> List[Type[Rule]]:
|
|
return [A]
|
|
|
|
ruleset = Ruleset()
|
|
ruleset.add(B)
|
|
assert list(ruleset) == [B]
|
|
|
|
|
|
def test_add_rule_with_cycle_raises_error():
|
|
class A(Rule):
|
|
@classmethod
|
|
def dependencies(cls) -> List[Type[Rule]]:
|
|
return [B]
|
|
|
|
class B(Rule):
|
|
@classmethod
|
|
def dependencies(cls) -> List[Type[Rule]]:
|
|
return [A]
|
|
|
|
ruleset = Ruleset([A])
|
|
with pytest.raises(ValueError):
|
|
ruleset.add(B)
|
|
|
|
|
|
def test_edge_declared_from_both_ends_is_deduped():
|
|
# A must precede B, declared redundantly from both ends. The edge should be
|
|
# recorded once, not double-counted.
|
|
class A(Rule):
|
|
@classmethod
|
|
def dependents(cls) -> List[Type[Rule]]:
|
|
return [B]
|
|
|
|
class B(Rule):
|
|
@classmethod
|
|
def dependencies(cls) -> List[Type[Rule]]:
|
|
return [A]
|
|
|
|
ruleset = Ruleset([A, B])
|
|
|
|
nodes, indegree = ruleset._build_graph()
|
|
node_a = next(n for n in nodes if n.rule is A)
|
|
node_b = next(n for n in nodes if n.rule is B)
|
|
assert indegree[id(node_b)] == 1 # not 2
|
|
assert [n.rule for n in node_a.dependents] == [B] # not [B, B]
|
|
assert list(ruleset) == [A, B]
|
|
|
|
|
|
def test_disjoint_cycle_with_independent_root_raises_error():
|
|
# An acyclic root (A) alongside a disjoint cycle (B <-> C) must still be
|
|
# detected as a cycle -- the presence of a root must not mask it.
|
|
class A(Rule):
|
|
pass
|
|
|
|
class B(Rule):
|
|
@classmethod
|
|
def dependencies(cls) -> List[Type[Rule]]:
|
|
return [C]
|
|
|
|
class C(Rule):
|
|
@classmethod
|
|
def dependencies(cls) -> List[Type[Rule]]:
|
|
return [B]
|
|
|
|
ruleset = Ruleset([A, B])
|
|
with pytest.raises(ValueError):
|
|
ruleset.add(C)
|
|
|
|
|
|
def test_remove_rule():
|
|
class A(Rule):
|
|
pass
|
|
|
|
ruleset = Ruleset([A])
|
|
ruleset.remove(A)
|
|
assert list(ruleset) == []
|
|
|
|
|
|
def test_remove_rule_not_in_ruleset():
|
|
class A(Rule):
|
|
pass
|
|
|
|
ruleset = Ruleset([])
|
|
with pytest.raises(ValueError):
|
|
ruleset.remove(A)
|
|
|
|
|
|
def test_remove_rule_with_dependants():
|
|
class A(Rule):
|
|
pass
|
|
|
|
class B(Rule):
|
|
@classmethod
|
|
def dependencies(cls) -> List[Type[Rule]]:
|
|
return [A]
|
|
|
|
ruleset = Ruleset([A, B])
|
|
ruleset.remove(A)
|
|
assert list(ruleset) == [B]
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import sys
|
|
|
|
sys.exit(pytest.main(["-v", __file__]))
|