Files
pyflowx/tests/test_dynamic.py
T

175 lines
5.5 KiB
Python

"""动态任务生成(dynamic=True)测试。
覆盖:
* 基本动态生成:fn 返回单个 spec / list[spec]。
* 生成任务 result.value 被替换为生成任务名列表。
* 新 spec 自动依赖生成方。
* 多级动态生成(生成的任务再生成)。
* 非 dependency 策略下 dynamic=True 抛 ValueError。
* 非动态任务不受影响。
* 空列表 / None 返回值不生成任务。
"""
from __future__ import annotations
import pytest
import pyflowx as px
from pyflowx import Graph, TaskSpec
def test_dynamic_single_spec() -> None:
"""fn 返回单个 TaskSpec,动态注册并执行。"""
def generator() -> TaskSpec:
return TaskSpec("spawned", fn=lambda: 42)
graph = Graph.from_specs([TaskSpec("gen", fn=generator, dynamic=True)])
report = px.run(graph, strategy="dependency")
assert report.success
assert "spawned" in report.results
assert report["spawned"] == 42
# 生成任务的 result.value 被替换为生成任务名列表
assert report["gen"] == ["spawned"]
def test_dynamic_list_of_specs() -> None:
"""fn 返回 list[TaskSpec],全部注册并执行。"""
def generator() -> list[TaskSpec]:
return [TaskSpec(f"task_{i}", fn=lambda i=i: i * 10) for i in range(3)]
graph = Graph.from_specs([TaskSpec("gen", fn=generator, dynamic=True)])
report = px.run(graph, strategy="dependency")
assert report.success
assert report["task_0"] == 0
assert report["task_1"] == 10
assert report["task_2"] == 20
assert report["gen"] == ["task_0", "task_1", "task_2"]
def test_dynamic_auto_depends_on_generator() -> None:
"""生成的 spec 自动依赖生成方,保证执行顺序。"""
order: list[str] = []
def generator() -> list[TaskSpec]:
order.append("gen")
return [TaskSpec("child", fn=lambda: order.append("child"))]
graph = Graph.from_specs([TaskSpec("gen", fn=generator, dynamic=True)])
report = px.run(graph, strategy="dependency")
assert report.success
assert order.index("gen") < order.index("child")
def test_dynamic_multi_level() -> None:
"""生成的任务本身也是 dynamic,再生成下一级。"""
def level1() -> list[TaskSpec]:
return [TaskSpec("level2", fn=level2, dynamic=True)]
def level2() -> list[TaskSpec]:
return [TaskSpec("leaf", fn=lambda: "done")]
graph = Graph.from_specs([TaskSpec("level1", fn=level1, dynamic=True)])
report = px.run(graph, strategy="dependency")
assert report.success
assert report["leaf"] == "done"
assert report["level2"] == ["leaf"]
assert report["level1"] == ["level2"]
def test_dynamic_with_existing_deps() -> None:
"""生成的 spec 可引用图中已有任务。"""
def base_task() -> int:
return 100
def generator(base_task: int) -> list[TaskSpec]:
return [TaskSpec("consumer", fn=lambda: base_task + 1)]
graph = Graph.from_specs(
[
TaskSpec("base_task", fn=base_task),
TaskSpec("gen", fn=generator, depends_on=("base_task",), dynamic=True),
]
)
report = px.run(graph, strategy="dependency")
assert report.success
assert report["consumer"] == 101
def test_dynamic_non_dependency_strategy_raises() -> None:
"""非 dependency 策略下使用 dynamic=True 抛 ValueError。"""
graph = Graph.from_specs([TaskSpec("gen", fn=lambda: None, dynamic=True)])
with pytest.raises(ValueError, match="仅支持 strategy='dependency'"):
px.run(graph, strategy="sequential")
def test_dynamic_none_return() -> None:
"""dynamic 任务返回 None 时不生成新任务。"""
def generator() -> None:
return None
graph = Graph.from_specs([TaskSpec("gen", fn=generator, dynamic=True)])
report = px.run(graph, strategy="dependency")
assert report.success
# result.value 仍为 None(无生成任务)
assert report["gen"] is None
def test_dynamic_empty_list_return() -> None:
"""dynamic 任务返回空列表时不生成新任务。"""
def generator() -> list[TaskSpec]:
return []
graph = Graph.from_specs([TaskSpec("gen", fn=generator, dynamic=True)])
report = px.run(graph, strategy="dependency")
assert report.success
assert report["gen"] == []
def test_non_dynamic_task_unaffected() -> None:
"""非动态任务的行为不受影响。"""
graph = Graph.from_specs(
[
TaskSpec("a", fn=lambda: 1),
TaskSpec("b", fn=lambda a: a + 1, depends_on=("a",)),
]
)
report = px.run(graph, strategy="dependency")
assert report.success
assert report["a"] == 1
assert report["b"] == 2
def test_dynamic_with_task_decorator() -> None:
"""@task 装饰器支持 dynamic 参数。"""
@px.task(dynamic=True)
def gen() -> list[TaskSpec]:
return [TaskSpec(f"s{i}", fn=lambda i=i: i) for i in range(3)]
graph = Graph.from_specs([gen])
report = px.run(graph, strategy="dependency")
assert report.success
assert report["s0"] == 0
assert report["s1"] == 1
assert report["s2"] == 2
def test_dynamic_spec_with_explicit_depends_on() -> None:
"""生成的 spec 已有 depends_on 时不重复添加生成方。"""
def generator() -> list[TaskSpec]:
return [TaskSpec("child", fn=lambda: 1, depends_on=("gen",))]
graph = Graph.from_specs([TaskSpec("gen", fn=generator, dynamic=True)])
report = px.run(graph, strategy="dependency")
assert report.success
# depends_on 不重复
child_spec = graph.specs["child"]
assert child_spec.depends_on.count("gen") == 1