Skip to content

Commit fb24e1e

Browse files
committed
80% coverage on tests
1 parent 255bdbe commit fb24e1e

File tree

4 files changed

+294
-135
lines changed

4 files changed

+294
-135
lines changed

.github/workflows/ci.yml

Lines changed: 22 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -9,83 +9,26 @@ on:
99
branches: [main]
1010

1111
jobs:
12-
lint-and-format:
13-
name: "Lint & Format"
12+
ci:
13+
# The job name will be dynamically set based on the matrix task name
14+
name: ${{ matrix.task.name }}
1415
runs-on: ubuntu-latest
15-
steps:
16-
- name: "Checkout code"
17-
uses: actions/checkout@v4
18-
19-
- name: "Set up Python"
20-
uses: actions/setup-python@v5
21-
with:
22-
python-version: "3.11.9"
23-
24-
- name: "Install Poetry"
25-
run: |
26-
curl -sSL https://install.python-poetry.org | python3 -
27-
echo "$HOME/.local/bin" >> $GITHUB_PATH
28-
29-
- name: "Configure Poetry"
30-
run: |
31-
poetry config virtualenvs.create true
32-
poetry config virtualenvs.in-project true
33-
34-
- name: "Cache Poetry dependencies"
35-
uses: actions/cache@v4
36-
with:
37-
path: .venv
38-
key: poetry-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }}
39-
restore-keys: |
40-
poetry-${{ runner.os }}-
41-
42-
- name: "Install dependencies"
43-
run: poetry install
44-
45-
- name: "Run Linter & Formatter"
46-
run: |
47-
poetry run ruff check .
48-
poetry run ruff format --check .
49-
50-
type-check:
51-
name: "Type Checking"
52-
runs-on: ubuntu-latest
53-
steps:
54-
- name: "Checkout code"
55-
uses: actions/checkout@v4
56-
57-
- name: "Set up Python"
58-
uses: actions/setup-python@v5
59-
with:
60-
python-version: "3.11.9"
61-
62-
- name: "Install Poetry"
63-
run: |
64-
curl -sSL https://install.python-poetry.org | python3 -
65-
echo "$HOME/.local/bin" >> $GITHUB_PATH
66-
67-
- name: "Configure Poetry"
68-
run: |
69-
poetry config virtualenvs.create true
70-
poetry config virtualenvs.in-project true
71-
72-
- name: "Cache Poetry dependencies"
73-
uses: actions/cache@v4
74-
with:
75-
path: .venv
76-
key: poetry-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }}
77-
restore-keys: |
78-
poetry-${{ runner.os }}-
79-
80-
- name: "Install dependencies"
81-
run: poetry install
16+
strategy:
17+
# This ensures that if one matrix job fails, the others will continue to run
18+
fail-fast: false
19+
matrix:
20+
task:
21+
- name: "Lint & Format"
22+
run_command: |
23+
poetry run ruff check .
24+
poetry run ruff format --check .
25+
- name: "Type Checking"
26+
run_command: poetry run mypy agent-core agent-engine agent-concurrent agent-persist agent-sim
27+
- name: "Run Tests & Check Coverage"
28+
run_command: poetry run pytest --cov=agent_core --cov=agent_engine --cov-report=term-missing --cov-fail-under=80
29+
# Add a flag to indicate that this specific task needs graphviz
30+
needs_graphviz: true
8231

83-
- name: "Run Type Checker"
84-
run: poetry run mypy agent-core agent-engine agent-concurrent agent-persist agent-sim
85-
86-
test:
87-
name: "Run Tests & Check Coverage"
88-
runs-on: ubuntu-latest
8932
steps:
9033
- name: "Checkout code"
9134
uses: actions/checkout@v4
@@ -95,7 +38,9 @@ jobs:
9538
with:
9639
python-version: "3.11.9"
9740

41+
# This step will only run for the "Run Tests & Check Coverage" job
9842
- name: "Install Graphviz"
43+
if: matrix.task.needs_graphviz == true
9944
run: |
10045
sudo apt-get update
10146
sudo apt-get install -y graphviz graphviz-dev libgraphviz-dev pkg-config
@@ -121,5 +66,5 @@ jobs:
12166
- name: "Install dependencies"
12267
run: poetry install
12368

124-
- name: "Run Tests and Check Coverage"
125-
run: poetry run pytest --cov=agent_core --cov=agent_engine --cov-report=term-missing --cov-fail-under=80
69+
- name: "Run Task"
70+
run: ${{ matrix.task.run_command }}
Lines changed: 148 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,154 @@
1-
# tests/agent_core/core/ecs/test_component.py
2-
"""
3-
Unit tests for core ECS components in agent_core.
4-
"""
1+
# tests/agent-core/core/ecs/test_components.py
52

6-
import pytest
7-
from agent_core.core.ecs.component import ValidationComponent
3+
from agent_core.core.ecs.component import (
4+
ActionOutcomeComponent,
5+
ActionPlanComponent,
6+
AffectComponent,
7+
BeliefSystemComponent,
8+
CompetenceComponent,
9+
EmotionComponent,
10+
GoalComponent,
11+
TimeBudgetComponent,
12+
)
813

914

10-
class TestValidationComponent:
11-
"""Tests for the ValidationComponent."""
15+
class TestTimeBudgetComponent:
16+
"""Tests for the TimeBudgetComponent."""
1217

13-
def test_initialization(self):
14-
"""Tests that the component initializes with default values."""
15-
comp = ValidationComponent()
16-
assert comp.reflection_confidence_scores == {}
17-
assert comp.causal_model_confidence == 0.0
18+
def test_validation_success(self):
19+
"""Test that a valid component passes validation."""
20+
comp = TimeBudgetComponent(initial_time_budget=100.0)
21+
is_valid, errors = comp.validate("agent_1")
22+
assert is_valid
23+
assert not errors
1824

19-
def test_to_dict(self):
20-
"""Tests the serialization of the component's state."""
21-
comp = ValidationComponent()
22-
comp.reflection_confidence_scores = {10: 0.8}
23-
comp.causal_model_confidence = 0.95
25+
def test_validation_failure_negative_budget(self):
26+
"""Test that a negative current budget fails validation."""
27+
comp = TimeBudgetComponent(initial_time_budget=100.0)
28+
comp.current_time_budget = -10.0
29+
is_valid, errors = comp.validate("agent_1")
30+
assert not is_valid
31+
assert "current_time_budget cannot be negative" in errors[0]
32+
33+
def test_validation_failure_mismatched_active_state(self):
34+
"""Test validation failure when is_active contradicts the budget."""
35+
comp = TimeBudgetComponent(initial_time_budget=100.0)
36+
comp.current_time_budget = 0
37+
comp.is_active = True # Should be inactive
38+
is_valid, errors = comp.validate("agent_1")
39+
assert not is_valid
40+
assert "Entity marked active but has no time budget" in errors[0]
41+
42+
def test_auto_fix_negative_budget(self):
43+
"""Test that auto_fix corrects a negative budget."""
44+
comp = TimeBudgetComponent(initial_time_budget=100.0)
45+
comp.current_time_budget = -5.0
46+
fixed = comp.auto_fix("agent_1", {})
47+
assert fixed
48+
assert comp.current_time_budget == 0.0
49+
assert not comp.is_active
50+
51+
def test_to_dict_serialization(self):
52+
"""Test that the component serializes to a dictionary correctly."""
53+
comp = TimeBudgetComponent(initial_time_budget=150.0)
54+
comp.current_time_budget = 120.5
55+
comp.is_active = True
56+
data = comp.to_dict()
57+
assert data["initial_time_budget"] == 150.0
58+
assert data["current_time_budget"] == 120.5
59+
assert data["is_active"]
60+
61+
62+
class TestEmotionComponent:
63+
"""Tests for the EmotionComponent."""
64+
65+
def test_validation_out_of_bounds(self):
66+
"""Test that valence and arousal outside their bounds fail validation."""
67+
comp_valence = EmotionComponent(valence=1.5)
68+
is_valid, errors = comp_valence.validate("agent_1")
69+
assert not is_valid
70+
assert "Valence out of bounds" in errors[0]
71+
72+
comp_arousal = EmotionComponent(arousal=-0.5)
73+
is_valid, errors = comp_arousal.validate("agent_1")
74+
assert not is_valid
75+
assert "Arousal out of bounds" in errors[0]
76+
77+
def test_to_dict_serialization(self):
78+
"""Test correct serialization."""
79+
comp = EmotionComponent(valence=0.5, arousal=0.8, current_emotion_category="happy")
2480
data = comp.to_dict()
25-
assert data == {
26-
"confidence_scores": {10: 0.8},
27-
"causal_model_confidence": 0.95,
28-
}
29-
30-
@pytest.mark.parametrize(
31-
"scores, confidence, is_valid",
32-
[
33-
({}, 0.5, True),
34-
({1: 1.0}, 1.0, True),
35-
(None, 0.5, False), # Invalid scores type
36-
({}, "high", False), # Invalid confidence type
37-
],
38-
)
39-
def test_validation(self, scores, confidence, is_valid):
40-
"""Tests the validation logic for various states."""
41-
comp = ValidationComponent()
42-
comp.reflection_confidence_scores = scores
43-
comp.causal_model_confidence = confidence
44-
valid, errors = comp.validate("agent_1")
45-
assert valid is is_valid
46-
if not is_valid:
47-
assert len(errors) > 0
48-
49-
def test_auto_fix(self):
50-
"""
51-
Tests that the auto_fix method correctly resets an invalid state and
52-
returns True, indicating a fix was made.
53-
"""
54-
comp = ValidationComponent()
55-
# Set an invalid state that auto_fix is designed to correct
56-
comp.reflection_confidence_scores = None
57-
58-
# ACT: Run the auto_fix method
59-
# The auto_fix for ValidationComponent doesn't do anything, so we expect False
60-
was_fixed = comp.auto_fix("agent_1", {})
61-
62-
# ASSERT: The auto_fix method for this component doesn't fix this, so it should return False
63-
assert was_fixed is False
81+
assert data["valence"] == 0.5
82+
assert data["arousal"] == 0.8
83+
assert data["current_emotion_category"] == "happy"
84+
85+
86+
class TestAffectComponent:
87+
"""Tests for the AffectComponent."""
88+
89+
def test_validation_nan_dissonance(self):
90+
"""Test that NaN cognitive dissonance fails validation."""
91+
comp = AffectComponent(affective_buffer_maxlen=10)
92+
comp.cognitive_dissonance = float("nan")
93+
is_valid, errors = comp.validate("agent_1")
94+
assert not is_valid
95+
assert "Cognitive dissonance is not a finite number" in errors[0]
96+
97+
98+
class TestCompetenceComponent:
99+
"""Tests for the CompetenceComponent."""
100+
101+
def test_auto_fix_incorrect_type(self):
102+
"""Test that auto_fix corrects the type of action_counts."""
103+
comp = CompetenceComponent()
104+
# Intentionally set to a wrong type
105+
comp.action_counts = {}
106+
fixed = comp.auto_fix("agent_1", {})
107+
assert fixed
108+
from collections import defaultdict
109+
110+
assert isinstance(comp.action_counts, defaultdict)
111+
112+
113+
class TestBeliefSystemComponent:
114+
"""Tests for the BeliefSystemComponent."""
115+
116+
def test_validation_wrong_types(self):
117+
"""Test that incorrect attribute types fail validation."""
118+
comp = BeliefSystemComponent()
119+
comp.belief_base = [] # Should be a dict
120+
is_valid, errors = comp.validate("agent_1")
121+
assert not is_valid
122+
assert "'belief_base' attribute must be a dictionary" in errors[0]
123+
124+
125+
# You can continue adding simple test classes for other components
126+
# to quickly boost coverage.
127+
128+
129+
def test_goal_component_validation():
130+
"""Test GoalComponent validation logic."""
131+
comp = GoalComponent(embedding_dim=10)
132+
comp.current_symbolic_goal = "explore"
133+
# Fails because 'explore' is not in the symbolic_goals_data
134+
is_valid, errors = comp.validate("agent_1")
135+
assert not is_valid
136+
assert "not in goal data" in errors[0]
137+
138+
# Should pass now
139+
comp.symbolic_goals_data["explore"] = {}
140+
is_valid, errors = comp.validate("agent_1")
141+
assert is_valid
142+
143+
144+
def test_action_plan_and_outcome_components():
145+
"""Simple validation and serialization tests for action-related components."""
146+
plan = ActionPlanComponent()
147+
is_valid, _ = plan.validate("agent_1")
148+
assert is_valid
149+
assert isinstance(plan.to_dict(), dict)
150+
151+
outcome = ActionOutcomeComponent()
152+
is_valid, _ = outcome.validate("agent_1")
153+
assert is_valid
154+
assert isinstance(outcome.to_dict(), dict)

0 commit comments

Comments
 (0)