alkaid_release_platform/tests/test_flow.py
ekko.bao 0a0f6a6054 初次创建仓库提交代码
1. 已经构建好了架子了。
2. 添加了示例的插件
2025-04-21 06:37:06 +00:00

443 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from pathlib import Path
import pytest
import sys, os
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
from thirdparty.py_trees.trees import BehaviourTree
from core.behavior_tree import ReleaseFlowActionDecorator, ReleaseFlowBuilder, ReleaseFlowAction
from thirdparty.py_trees.common import Status, Access
from core.logger import get_logger
log = get_logger()
class ActionTest(ReleaseFlowAction):
"""测试用的Action节点"""
def __init__(self, name: str, return_status: Status, description: str = ""):
super().__init__(name, description)
self.return_status = return_status
self.process_called = False
self.setup_called = False
self.shutdown_called = False
def setup(self):
self.setup_called = True
log.info(f"setup: {self.name}")
def process(self):
self.process_called = True
self.status = self.return_status
log.info(f"process: {self.name}, status: {self.status}")
def update(self):
return self.status
def shutdown(self):
self.shutdown_called = True
class ActionTestBlackboard(ReleaseFlowAction):
"""测试Blackboard功能的Action节点"""
def __init__(self, name: str, description: str = ""):
super().__init__(name, description)
self.blackboard.register_key("test_key", access=Access.WRITE)
self.blackboard.register_key("read_only_key", access=Access.READ)
def process(self):
self.status = Status.SUCCESS
log.info(f"process: {self.name}, status: {self.status}, test_key: {self.blackboard.test_key}")
def update(self):
return self.status
class ActionTestBlackboard1(ReleaseFlowAction):
"""测试Blackboard功能的Action节点"""
def __init__(self, name: str, description: str = ""):
super().__init__(name, description)
self.blackboard.require_key("test_key")
def process(self):
self.status = Status.SUCCESS
log.info(f"process: {self.name}, status: {self.status}, test_key: {self.blackboard.test_key}")
def update(self):
return self.status
@pytest.fixture
def builder():
"""创建ReleaseFlowBuilder实例的fixture"""
return ReleaseFlowBuilder()
def test_single_action():
"""测试单个Action节点的执行"""
action = ActionTest("test_action", Status.SUCCESS)
action.setup()
assert action.setup_called
action.tick_once()
assert action.process_called
assert action.status == Status.SUCCESS
action.shutdown()
assert action.shutdown_called
def test_sequence(builder):
"""测试Sequence节点的执行"""
success_action = ActionTest("success_action", Status.SUCCESS)
failure_action = ActionTest("failure_action", Status.FAILURE)
with builder.sequence("test_sequence") as b:
b.action(success_action)
b.action(failure_action)
flow = builder.build()
tree = BehaviourTree(root=flow)
tree.setup()
tree.tick()
assert failure_action.process_called
assert flow.status == Status.FAILURE
assert success_action.process_called
def test_selector(builder):
"""测试Selector节点的执行"""
failure_action = ActionTest("failure_action", Status.FAILURE)
success_action = ActionTest("success_action", Status.SUCCESS)
with builder.selector("test_selector") as b:
b.action(failure_action)
b.action(success_action)
flow = builder.build()
tree = BehaviourTree(root=flow)
tree.setup()
tree.tick()
assert flow.status == Status.SUCCESS
assert failure_action.process_called
assert success_action.process_called
def test_blackboard():
"""测试Blackboard功能"""
action = ActionTestBlackboard("test_blackboard")
action1 = ActionTestBlackboard1("test_blackboard_read")
# 测试写入权限
action.blackboard.test_key = "test_value"
# 测试共享
assert action1.blackboard.test_key == "test_value"
# 测试只读权限
with pytest.raises(AttributeError):
action.blackboard.read_only_key = "new_value"
# 测试必需key
with pytest.raises(AttributeError):
_ = action.blackboard.required_key
def test_parallel(builder):
"""测试Parallel节点的执行"""
success_action1 = ActionTest("success_action1", Status.SUCCESS)
success_action2 = ActionTest("success_action2", Status.SUCCESS)
failure_action = ActionTest("failure_action", Status.FAILURE)
with builder.parallel("test_parallel") as b:
b.action(success_action1)
b.action(success_action2)
b.action(failure_action)
flow = builder.build()
tree = BehaviourTree(root=flow)
tree.setup()
tree.tick()
assert flow.status == Status.FAILURE
assert success_action1.process_called
assert success_action2.process_called
assert failure_action.process_called
def test_nested_behavior_tree(builder):
"""测试嵌套的行为树结构"""
success_action1 = ActionTest("success_action1", Status.SUCCESS)
success_action2 = ActionTest("success_action2", Status.SUCCESS)
failure_action = ActionTest("failure_action", Status.FAILURE)
with builder.sequence("root_sequence") as b:
with b.selector("nested_selector") as s:
s.action(failure_action)
s.action(success_action1)
b.action(success_action2)
flow = builder.build()
tree = BehaviourTree(root=flow)
tree.setup()
tree.tick()
assert flow.status == Status.SUCCESS
assert failure_action.process_called
assert success_action1.process_called
assert success_action2.process_called
def test_parallel_success_on_all(builder):
"""测试并行节点SuccessOnAll策略"""
success_action1 = ActionTest("success_action1", Status.SUCCESS)
success_action2 = ActionTest("success_action2", Status.SUCCESS)
success_action3 = ActionTest("success_action3", Status.SUCCESS)
with builder.parallel("test_parallel_success_on_all") as b:
b.action(success_action1)
b.action(success_action2)
b.action(success_action3)
flow = builder.build()
tree = BehaviourTree(root=flow)
tree.setup()
tree.tick()
assert flow.status == Status.SUCCESS
assert success_action1.process_called
assert success_action2.process_called
assert success_action3.process_called
def test_parallel_success_on_one(builder):
"""测试并行节点SuccessOnOne策略"""
from thirdparty.py_trees.common import ParallelPolicy
running_action1 = ActionTest("running_action1", Status.RUNNING)
success_action = ActionTest("success_action", Status.SUCCESS)
running_action2 = ActionTest("running_action2", Status.RUNNING)
with builder.parallel("test_parallel_success_on_one",
policy=ParallelPolicy.SuccessOnOne()) as b:
b.action(running_action1)
b.action(success_action)
b.action(running_action2)
flow = builder.build()
tree = BehaviourTree(root=flow)
tree.setup()
tree.tick()
assert flow.status == Status.SUCCESS
assert running_action1.process_called
assert success_action.process_called
assert running_action2.process_called
def test_multiple_executions(builder):
"""测试行为树的多次执行"""
action1 = ActionTest("action1", Status.SUCCESS)
action2 = ActionTest("action2", Status.SUCCESS)
with builder.sequence("test_multiple_executions") as b:
b.action(action1)
b.action(action2)
flow = builder.build()
tree = BehaviourTree(root=flow)
tree.setup()
# 第一次执行
tree.tick()
assert flow.status == Status.SUCCESS
assert action1.process_called
assert action2.process_called
# 重置状态
action1.process_called = False
action2.process_called = False
# 第二次执行
tree.tick()
assert flow.status == Status.SUCCESS
assert action1.process_called
assert action2.process_called
def test_error_handling(builder):
"""测试行为树的错误处理"""
# 测试空的行为树
with pytest.raises(Exception) as excinfo:
builder.build()
assert "No nodes added to behavior tree" in str(excinfo.value)
# 测试未闭合的节点
with pytest.raises(Exception) as excinfo:
with builder.sequence("unclosed_sequence") as b:
b.action(ActionTest("action", Status.SUCCESS))
b.action(ActionTest("action", Status.SUCCESS))
# 不调用end()或使用with语句的__exit__
builder.build()
assert "Unclosed nodes" in str(excinfo.value)
class ActionWithError(ReleaseFlowAction):
"""测试异常处理的Action节点"""
def __init__(self, name: str, description: str = ""):
super().__init__(name, description)
self.process_called = False
def process(self):
self.process_called = True
raise Exception("测试异常")
def update(self):
return Status.FAILURE
def test_action_exception_handling(builder):
"""测试Action节点异常处理"""
error_action = ActionWithError("error_action")
with builder.sequence("test_error_handling") as b:
b.action(error_action)
flow = builder.build()
tree = BehaviourTree(root=flow)
tree.setup()
# 执行时应该捕获异常并返回FAILURE状态
tree.tick()
assert error_action.process_called
assert flow.status == Status.FAILURE
def test_decorator():
"""测试ReleaseFlowActionDecorator装饰器"""
@ReleaseFlowActionDecorator
class DecoratedAction(ReleaseFlowAction):
def process(self):
self.status = Status.SUCCESS
def update(self):
return self.status
# 测试自动生成名称
action1 = DecoratedAction()
assert action1.name == f"{Path(__file__).stem}.DecoratedAction"
# 测试自定义名称
action2 = DecoratedAction(name="custom_name")
assert action2.name == "custom_name"
# 测试功能
action2.setup()
action2.tick_once()
assert action2.status == Status.SUCCESS
def test_complex_behavior_tree(builder):
"""测试复杂的行为树结构"""
success_action1 = ActionTest("success_action1", Status.SUCCESS)
success_action2 = ActionTest("success_action2", Status.SUCCESS)
failure_action1 = ActionTest("failure_action1", Status.FAILURE)
success_action3 = ActionTest("success_action3", Status.SUCCESS)
with builder.sequence("root") as b:
with b.parallel("parallel_group") as p:
p.action(success_action1)
p.action(success_action2)
with b.selector("selector_group") as s:
s.action(failure_action1)
s.action(success_action3)
flow = builder.build()
tree = BehaviourTree(root=flow)
tree.setup()
tree.tick()
assert flow.status == Status.SUCCESS
assert success_action1.process_called
assert success_action2.process_called
assert failure_action1.process_called
assert success_action3.process_called
def test_blackboard_advanced():
"""测试Blackboard的高级功能"""
class ActionWrite(ReleaseFlowAction):
def __init__(self, name: str, description: str = ""):
super().__init__(name, description)
self.blackboard.register_key("counter", access=Access.WRITE)
self.blackboard.counter = 0
def process(self):
self.blackboard.counter += 1
self.status = Status.SUCCESS
def update(self):
return self.status
class ActionRead(ReleaseFlowAction):
def __init__(self, name: str, description: str = ""):
super().__init__(name, description)
self.blackboard.register_key("counter", access=Access.READ)
self.value = 0
def process(self):
self.value = self.blackboard.counter
self.status = Status.SUCCESS
def update(self):
return self.status
# 创建动作节点
write_action = ActionWrite("write_action")
read_action = ActionRead("read_action")
# 设置初始值
write_action.blackboard.counter = 10
# 测试读取
assert read_action.blackboard.counter == 10
# 测试写入
write_action.blackboard.counter = 20
assert read_action.blackboard.counter == 20
# 测试只读权限
with pytest.raises(AttributeError):
read_action.blackboard.counter = 30
def test_action_exception_handling_with_sequence(builder):
"""测试Action节点异常处理后行为树继续执行的能力"""
error_action = ActionWithError("error_action")
success_action = ActionTest("success_action", Status.SUCCESS)
with builder.sequence("test_error_handling_sequence") as b:
b.action(error_action)
b.action(success_action)
flow = builder.build()
tree = BehaviourTree(root=flow)
tree.setup()
# 执行时应该捕获异常并返回FAILURE状态但不会中断行为树的执行
tree.tick()
# 验证error_action被标记为已处理
assert error_action.process_called
# 验证序列节点状态为FAILURE因为第一个节点失败
assert flow.status == Status.FAILURE
# 验证success_action没有被执行因为序列节点在第一个节点失败后停止
assert not success_action.process_called
def test_action_exception_handling_with_selector(builder):
"""测试Action节点异常处理后选择器继续尝试其他节点"""
error_action = ActionWithError("error_action")
success_action = ActionTest("success_action", Status.SUCCESS)
with builder.selector("test_error_handling_selector") as b:
b.action(error_action)
b.action(success_action)
flow = builder.build()
tree = BehaviourTree(root=flow)
tree.setup()
# 执行时应该捕获异常并继续尝试下一个节点
tree.tick()
# 验证error_action被标记为已处理
assert error_action.process_called
# 验证success_action被执行
assert success_action.process_called
# 验证选择器节点状态为SUCCESS因为第二个节点成功
assert flow.status == Status.SUCCESS