194 lines
6.5 KiB
Python
194 lines
6.5 KiB
Python
"""
|
|
This program has been developed by students from the bachelor Computer Science at Utrecht
|
|
University within the Software Project course.
|
|
© Copyright Utrecht University (Department of Information and Computing Sciences)
|
|
"""
|
|
|
|
import uuid
|
|
|
|
import pytest
|
|
|
|
from control_backend.agents.bdi.agentspeak_ast import AstProgram
|
|
from control_backend.agents.bdi.agentspeak_generator import AgentSpeakGenerator
|
|
from control_backend.schemas.program import (
|
|
BasicNorm,
|
|
ConditionalNorm,
|
|
Gesture,
|
|
GestureAction,
|
|
Goal,
|
|
InferredBelief,
|
|
KeywordBelief,
|
|
LLMAction,
|
|
LogicalOperator,
|
|
Phase,
|
|
Plan,
|
|
Program,
|
|
SemanticBelief,
|
|
SpeechAction,
|
|
Trigger,
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def generator():
|
|
return AgentSpeakGenerator()
|
|
|
|
|
|
def test_generate_empty_program(generator):
|
|
prog = Program(phases=[])
|
|
code = generator.generate(prog)
|
|
assert 'phase("end").' in code
|
|
assert "!notify_cycle" in code
|
|
|
|
|
|
def test_generate_basic_norm(generator):
|
|
norm = BasicNorm(id=uuid.uuid4(), name="n1", norm="be nice")
|
|
phase = Phase(id=uuid.uuid4(), norms=[norm], goals=[], triggers=[])
|
|
prog = Program(phases=[phase])
|
|
|
|
code = generator.generate(prog)
|
|
assert f'norm("be nice") :- phase("{phase.id}").' in code
|
|
|
|
|
|
def test_generate_critical_norm(generator):
|
|
norm = BasicNorm(id=uuid.uuid4(), name="n1", norm="safety", critical=True)
|
|
phase = Phase(id=uuid.uuid4(), norms=[norm], goals=[], triggers=[])
|
|
prog = Program(phases=[phase])
|
|
|
|
code = generator.generate(prog)
|
|
assert f'critical_norm("safety") :- phase("{phase.id}").' in code
|
|
|
|
|
|
def test_generate_conditional_norm(generator):
|
|
cond = KeywordBelief(id=uuid.uuid4(), name="k1", keyword="please")
|
|
norm = ConditionalNorm(id=uuid.uuid4(), name="n1", norm="help", condition=cond)
|
|
phase = Phase(id=uuid.uuid4(), norms=[norm], goals=[], triggers=[])
|
|
prog = Program(phases=[phase])
|
|
|
|
code = generator.generate(prog)
|
|
assert 'norm("help")' in code
|
|
assert 'keyword_said("please")' in code
|
|
assert f"force_norm_{generator._slugify_str(norm.norm)}" in code
|
|
|
|
|
|
def test_generate_goal_and_plan(generator):
|
|
action = SpeechAction(id=uuid.uuid4(), name="s1", text="hello")
|
|
plan = Plan(id=uuid.uuid4(), name="p1", steps=[action])
|
|
# IMPORTANT: can_fail must be False for +achieved_ belief to be added
|
|
goal = Goal(id=uuid.uuid4(), name="g1", description="desc", plan=plan, can_fail=False)
|
|
phase = Phase(id=uuid.uuid4(), norms=[], goals=[goal], triggers=[])
|
|
prog = Program(phases=[phase])
|
|
|
|
code = generator.generate(prog)
|
|
# Check trigger for goal
|
|
goal_slug = generator._slugify_str(goal.name)
|
|
assert f"+!{goal_slug}" in code
|
|
assert f'phase("{phase.id}")' in code
|
|
assert '!say("hello")' in code
|
|
|
|
# Check success belief addition
|
|
assert f"+achieved_{goal_slug}" in code
|
|
|
|
|
|
def test_generate_subgoal(generator):
|
|
subplan = Plan(id=uuid.uuid4(), name="p2", steps=[])
|
|
subgoal = Goal(id=uuid.uuid4(), name="sub1", description="sub", plan=subplan)
|
|
|
|
plan = Plan(id=uuid.uuid4(), name="p1", steps=[subgoal])
|
|
goal = Goal(id=uuid.uuid4(), name="g1", description="main", plan=plan)
|
|
phase = Phase(id=uuid.uuid4(), norms=[], goals=[goal], triggers=[])
|
|
prog = Program(phases=[phase])
|
|
|
|
code = generator.generate(prog)
|
|
subgoal_slug = generator._slugify_str(subgoal.name)
|
|
# Main goal calls subgoal
|
|
assert f"!{subgoal_slug}" in code
|
|
# Subgoal plan exists
|
|
assert f"+!{subgoal_slug}" in code
|
|
|
|
|
|
def test_generate_trigger(generator):
|
|
cond = SemanticBelief(id=uuid.uuid4(), name="s1", description="desc")
|
|
plan = Plan(id=uuid.uuid4(), name="p1", steps=[])
|
|
trigger = Trigger(id=uuid.uuid4(), name="t1", condition=cond, plan=plan)
|
|
phase = Phase(id=uuid.uuid4(), norms=[], goals=[], triggers=[trigger])
|
|
prog = Program(phases=[phase])
|
|
|
|
code = generator.generate(prog)
|
|
# Trigger logic is added to check_triggers
|
|
assert f"{generator.slugify(cond)}" in code
|
|
assert f'notify_trigger_start("{generator.slugify(trigger)}")' in code
|
|
assert f'notify_trigger_end("{generator.slugify(trigger)}")' in code
|
|
|
|
|
|
def test_phase_transition(generator):
|
|
phase1 = Phase(id=uuid.uuid4(), name="p1", norms=[], goals=[], triggers=[])
|
|
phase2 = Phase(id=uuid.uuid4(), name="p2", norms=[], goals=[], triggers=[])
|
|
prog = Program(phases=[phase1, phase2])
|
|
|
|
code = generator.generate(prog)
|
|
assert "transition_phase" in code
|
|
assert f'phase("{phase1.id}")' in code
|
|
assert f'phase("{phase2.id}")' in code
|
|
assert "force_transition_phase" in code
|
|
|
|
|
|
def test_astify_gesture(generator):
|
|
gesture = Gesture(type="single", name="wave")
|
|
action = GestureAction(id=uuid.uuid4(), name="g1", gesture=gesture)
|
|
ast = generator._astify(action)
|
|
assert str(ast) == 'gesture("single", "wave")'
|
|
|
|
|
|
def test_astify_llm_action(generator):
|
|
action = LLMAction(id=uuid.uuid4(), name="l1", goal="be funny")
|
|
ast = generator._astify(action)
|
|
assert str(ast) == 'reply_with_goal("be funny")'
|
|
|
|
|
|
def test_astify_inferred_belief_and(generator):
|
|
left = KeywordBelief(id=uuid.uuid4(), name="k1", keyword="a")
|
|
right = KeywordBelief(id=uuid.uuid4(), name="k2", keyword="b")
|
|
inf = InferredBelief(
|
|
id=uuid.uuid4(), name="i1", operator=LogicalOperator.AND, left=left, right=right
|
|
)
|
|
|
|
ast = generator._astify(inf)
|
|
assert 'keyword_said("a") & keyword_said("b")' == str(ast)
|
|
|
|
|
|
def test_astify_inferred_belief_or(generator):
|
|
left = KeywordBelief(id=uuid.uuid4(), name="k1", keyword="a")
|
|
right = KeywordBelief(id=uuid.uuid4(), name="k2", keyword="b")
|
|
inf = InferredBelief(
|
|
id=uuid.uuid4(), name="i1", operator=LogicalOperator.OR, left=left, right=right
|
|
)
|
|
|
|
ast = generator._astify(inf)
|
|
assert 'keyword_said("a") | keyword_said("b")' == str(ast)
|
|
|
|
|
|
def test_astify_semantic_belief(generator):
|
|
sb = SemanticBelief(id=uuid.uuid4(), name="s1", description="desc")
|
|
ast = generator._astify(sb)
|
|
assert str(ast) == f"semantic_{generator._slugify_str(sb.name)}"
|
|
|
|
|
|
def test_slugify_not_implemented(generator):
|
|
with pytest.raises(NotImplementedError):
|
|
generator.slugify("not a program element")
|
|
|
|
|
|
def test_astify_not_implemented(generator):
|
|
with pytest.raises(NotImplementedError):
|
|
generator._astify("not a program element")
|
|
|
|
|
|
def test_process_phase_transition_from_none(generator):
|
|
# Initialize AstProgram manually as we are bypassing generate()
|
|
generator._asp = AstProgram()
|
|
# Should safely return doing nothing
|
|
generator._add_phase_transition(None, None)
|
|
|
|
assert len(generator._asp.plans) == 0
|