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