quizbowl-submission / tests /test_utils.py
Maharshi Gor
First Working commit
193db9d
raw
history blame
6.56 kB
import pytest
from workflows.errors import CyclicDependencyError, UnknownVariableError, WorkflowError
from workflows.utils import _create_variable_step_mapping, create_dependency_graph, topological_sort
# Dummy classes to simulate Workflow, Step, and Field
class DummyField:
def __init__(self, name, type="str", variable=None):
self.name = name
self.type = type
# For input fields, variable property is needed
self.variable = variable if variable is not None else name
class DummyStep:
def __init__(self, input_fields, output_fields):
self.input_fields = input_fields
self.output_fields = output_fields
class DummyWorkflow:
def __init__(self, steps):
# steps is a dict with key as step_id and value as DummyStep
self.steps = steps
# Tests for _create_variable_step_mapping
def test_create_variable_step_mapping_success():
# Create a workflow with two steps producing unique output variables
step_a = DummyStep(input_fields=[], output_fields=[DummyField("out1")])
step_b = DummyStep(input_fields=[], output_fields=[DummyField("out2")])
workflow = DummyWorkflow({"A": step_a, "B": step_b})
mapping = _create_variable_step_mapping(workflow)
assert mapping == {"A.out1": "A", "B.out2": "B"}
def test_create_variable_step_mapping_duplicate():
# Create a workflow where two steps produce an output with same name
step_a = DummyStep(input_fields=[], output_fields=[DummyField("out"), DummyField("out")])
workflow = DummyWorkflow({"A": step_a})
with pytest.raises(WorkflowError):
_create_variable_step_mapping(workflow)
def test_create_variable_step_mapping_empty():
"""Test _create_variable_step_mapping with an empty workflow should return an empty mapping."""
workflow = DummyWorkflow({})
mapping = _create_variable_step_mapping(workflow)
assert mapping == {}
def test_create_variable_step_mapping_multiple_outputs():
"""Test a workflow where a single step produces multiple outputs with unique names."""
step = DummyStep(input_fields=[], output_fields=[DummyField("out1"), DummyField("out2")])
workflow = DummyWorkflow({"A": step})
mapping = _create_variable_step_mapping(workflow)
assert mapping == {"A.out1": "A", "A.out2": "A"}
# Tests for create_dependency_graph
def test_create_dependency_graph_success_with_dependency():
# Step A produces 'A.out', which is used as input in step B
step_a = DummyStep(input_fields=[], output_fields=[DummyField("out")])
# For input_fields, explicitly set variable to reference A.out
step_b = DummyStep(input_fields=[DummyField("dummy", variable="A.out")], output_fields=[DummyField("result")])
workflow = DummyWorkflow({"A": step_a, "B": step_b})
# No external input provided for A.out so dependency must be created
deps = create_dependency_graph(workflow, input_values={})
# Step B depends on step A
assert deps["B"] == {"A"}
# Step A has no dependencies
assert deps["A"] == set()
def test_create_dependency_graph_success_with_external_input():
# Step B expects an input, but it is provided externally
step_b = DummyStep(
input_fields=[DummyField("param", variable="external_param")], output_fields=[DummyField("result")]
)
workflow = DummyWorkflow({"B": step_b})
# Provide external input for external_param
deps = create_dependency_graph(workflow, input_values={"external_param": 42})
# With external input, no dependency is needed
assert deps["B"] == set()
def test_create_dependency_graph_unknown_variable():
# Step B expects an input that is neither produced by any step nor provided externally
step_b = DummyStep(
input_fields=[DummyField("param", variable="non_existent")], output_fields=[DummyField("result")]
)
workflow = DummyWorkflow({"B": step_b})
with pytest.raises(UnknownVariableError):
_ = create_dependency_graph(workflow, input_values={})
def test_create_dependency_graph_complex():
"""Test create_dependency_graph on a more complex workflow with multiple dependencies."""
# Step A produces A.out, Step B uses A.out, Step C uses B.out, and Step D uses both A.out and B.out
step_a = DummyStep(input_fields=[], output_fields=[DummyField("out")])
step_b = DummyStep(input_fields=[DummyField("inp", variable="A.out")], output_fields=[DummyField("out")])
step_c = DummyStep(input_fields=[DummyField("inp", variable="B.out")], output_fields=[DummyField("result")])
step_d = DummyStep(
input_fields=[DummyField("inp1", variable="A.out"), DummyField("inp2", variable="B.out")],
output_fields=[DummyField("final")],
)
workflow = DummyWorkflow({"A": step_a, "B": step_b, "C": step_c, "D": step_d})
# Provide external input for "B.out" so that step B's output isn't expected to come from a step
# However, to simulate dependency, assume external input is not provided for the dependencies used in step C and D
# Therefore, workflow must resolve A.out for step B, and then step B produces B.out for steps C and D.
# Let's not provide any external input, so both dependencies are created.
deps = create_dependency_graph(workflow, input_values={})
# Expected dependencies:
# B depends on A
# C depends on B
# D depends on both A and B
assert deps["B"] == {"A"}
assert deps["C"] == {"B"}
assert deps["D"] == {"A", "B"}
# Tests for topological_sort
def test_topological_sort_success():
# Create a simple dependency graph: A -> B -> C
deps = {"A": set(), "B": {"A"}, "C": {"B"}}
order = topological_sort(deps)
# Check that order satisfies dependencies: A before B, B before C
assert order.index("A") < order.index("B") < order.index("C")
def test_topological_sort_cycle():
# Create a cyclic dependency: A -> B and B -> A
deps = {"A": {"B"}, "B": {"A"}}
with pytest.raises(CyclicDependencyError):
_ = topological_sort(deps)
def test_topological_sort_single_node():
"""Test topological_sort on a graph with a single node and no dependencies."""
deps = {"A": set()}
order = topological_sort(deps)
assert order == ["A"]
def test_topological_sort_disconnected():
"""Test topological_sort on a graph with disconnected nodes (no dependencies among them)."""
deps = {"A": set(), "B": set(), "C": set()}
order = topological_sort(deps)
# The order can be in any permutation, but must contain all nodes
assert set(order) == {"A", "B", "C"}