Skip to content

Commit ab15eb4

Browse files
committed
Create test_island_child_placement.py
1 parent 2a6993f commit ab15eb4

File tree

1 file changed

+242
-0
lines changed

1 file changed

+242
-0
lines changed
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
"""
2+
Tests for verifying child programs are placed in the correct target island.
3+
4+
This test specifically catches the bug where children inherit their parent's island
5+
instead of being placed in the target island that was requested for the iteration.
6+
"""
7+
8+
import unittest
9+
10+
from openevolve.config import Config, DatabaseConfig
11+
from openevolve.database import ProgramDatabase, Program
12+
13+
14+
class TestIslandChildPlacement(unittest.TestCase):
15+
"""Test that child programs are placed in the correct island"""
16+
17+
def setUp(self):
18+
"""Set up test database with multiple islands"""
19+
config = Config()
20+
config.database.num_islands = 3
21+
config.database.population_size = 100
22+
self.db = ProgramDatabase(config.database)
23+
24+
def test_child_inherits_parent_island_when_no_target_specified(self):
25+
"""Test that child inherits parent's island when no target_island is given"""
26+
# Add parent to island 0
27+
parent = Program(
28+
id="parent_0",
29+
code="def parent(): pass",
30+
generation=0,
31+
metrics={"combined_score": 0.5},
32+
)
33+
self.db.add(parent, target_island=0)
34+
35+
# Add child without specifying target_island
36+
child = Program(
37+
id="child_0",
38+
code="def child(): pass",
39+
generation=1,
40+
parent_id="parent_0",
41+
metrics={"combined_score": 0.6},
42+
)
43+
self.db.add(child) # No target_island specified
44+
45+
# Child should inherit parent's island (island 0)
46+
self.assertEqual(child.metadata.get("island"), 0)
47+
self.assertIn("child_0", self.db.islands[0])
48+
49+
def test_child_placed_in_target_island_when_specified(self):
50+
"""Test that child is placed in target_island when explicitly specified"""
51+
# Add parent to island 0
52+
parent = Program(
53+
id="parent_1",
54+
code="def parent(): pass",
55+
generation=0,
56+
metrics={"combined_score": 0.5},
57+
)
58+
self.db.add(parent, target_island=0)
59+
60+
# Add child with explicit target_island=2
61+
child = Program(
62+
id="child_1",
63+
code="def child(): pass",
64+
generation=1,
65+
parent_id="parent_1",
66+
metrics={"combined_score": 0.6},
67+
)
68+
self.db.add(child, target_island=2)
69+
70+
# Child should be in island 2, NOT island 0
71+
self.assertEqual(child.metadata.get("island"), 2)
72+
self.assertIn("child_1", self.db.islands[2])
73+
self.assertNotIn("child_1", self.db.islands[0])
74+
75+
76+
class TestEmptyIslandChildPlacement(unittest.TestCase):
77+
"""
78+
Test the critical bug: when sampling from an empty island falls back to
79+
another island's parent, the child should still go to the TARGET island.
80+
"""
81+
82+
def setUp(self):
83+
"""Set up test database with programs only in island 0"""
84+
config = Config()
85+
config.database.num_islands = 3
86+
config.database.population_size = 100
87+
self.db = ProgramDatabase(config.database)
88+
89+
# Add programs ONLY to island 0
90+
for i in range(5):
91+
program = Program(
92+
id=f"island0_prog_{i}",
93+
code=f"def func_{i}(): pass",
94+
generation=0,
95+
metrics={"combined_score": 0.5 + i * 0.1},
96+
)
97+
self.db.add(program, target_island=0)
98+
99+
# Verify setup: island 0 has programs, islands 1 and 2 are empty
100+
self.assertGreater(len(self.db.islands[0]), 0)
101+
self.assertEqual(len(self.db.islands[1]), 0)
102+
self.assertEqual(len(self.db.islands[2]), 0)
103+
104+
def test_sample_from_empty_island_returns_fallback_parent(self):
105+
"""Test that sampling from empty island falls back to available programs"""
106+
# Sample from empty island 1
107+
parent, inspirations = self.db.sample_from_island(island_id=1)
108+
109+
# Should return a parent (from island 0 via fallback)
110+
self.assertIsNotNone(parent)
111+
# Parent is from island 0
112+
self.assertEqual(parent.metadata.get("island"), 0)
113+
114+
def test_child_should_go_to_target_island_not_parent_island(self):
115+
"""
116+
CRITICAL TEST: This tests the bug reported in issue #391.
117+
118+
When we want to add a child to island 1 (empty), but the parent came
119+
from island 0 (via fallback sampling), the child should still be
120+
placed in island 1 (the TARGET), not island 0 (the parent's island).
121+
"""
122+
# Sample from empty island 1 - will fall back to island 0
123+
parent, inspirations = self.db.sample_from_island(island_id=1)
124+
125+
# Parent is from island 0 (the only island with programs)
126+
self.assertEqual(parent.metadata.get("island"), 0)
127+
128+
# Create a child program
129+
child = Program(
130+
id="child_for_island_1",
131+
code="def evolved(): pass",
132+
generation=1,
133+
parent_id=parent.id,
134+
metrics={"combined_score": 0.8},
135+
)
136+
137+
# THIS IS THE BUG: If we don't pass target_island, child goes to parent's island
138+
# Simulating current behavior (without fix):
139+
self.db.add(child) # No target_island - inherits parent's island
140+
141+
# BUG: Child ends up in island 0 (parent's island) instead of island 1 (target)
142+
# This assertion will FAIL with current code, demonstrating the bug
143+
self.assertEqual(
144+
child.metadata.get("island"), 1,
145+
"Child should be in target island 1, not parent's island 0. "
146+
"This is the bug from issue #391!"
147+
)
148+
149+
def test_explicit_target_island_overrides_parent_inheritance(self):
150+
"""Test that explicit target_island works even with fallback parent"""
151+
# Sample from empty island 2 - will fall back to island 0
152+
parent, inspirations = self.db.sample_from_island(island_id=2)
153+
154+
# Parent is from island 0
155+
self.assertEqual(parent.metadata.get("island"), 0)
156+
157+
# Create child and explicitly specify target island
158+
child = Program(
159+
id="child_for_island_2",
160+
code="def evolved(): pass",
161+
generation=1,
162+
parent_id=parent.id,
163+
metrics={"combined_score": 0.8},
164+
)
165+
166+
# With explicit target_island, child should go to island 2
167+
self.db.add(child, target_island=2)
168+
169+
# This should work - explicit target_island is respected
170+
self.assertEqual(child.metadata.get("island"), 2)
171+
self.assertIn("child_for_island_2", self.db.islands[2])
172+
173+
174+
class TestIslandPopulationGrowth(unittest.TestCase):
175+
"""
176+
Test that simulates multiple evolution iterations and checks
177+
that all islands eventually get populated.
178+
"""
179+
180+
def setUp(self):
181+
"""Set up test database"""
182+
config = Config()
183+
config.database.num_islands = 3
184+
config.database.population_size = 100
185+
self.db = ProgramDatabase(config.database)
186+
187+
def test_islands_should_all_get_populated(self):
188+
"""
189+
Simulate evolution and verify all islands get programs.
190+
191+
This test demonstrates the feedback loop bug: if children always
192+
inherit parent's island, and we start with only island 0 populated,
193+
all new programs will go to island 0.
194+
"""
195+
# Start with initial program in island 0 only
196+
initial = Program(
197+
id="initial",
198+
code="def initial(): pass",
199+
generation=0,
200+
metrics={"combined_score": 0.5},
201+
)
202+
self.db.add(initial, target_island=0)
203+
204+
# Simulate 9 iterations, targeting islands in round-robin fashion
205+
for i in range(9):
206+
target_island = i % 3 # 0, 1, 2, 0, 1, 2, 0, 1, 2
207+
208+
# Sample from target island (may fall back if empty)
209+
parent, _ = self.db.sample_from_island(island_id=target_island)
210+
211+
# Create child
212+
child = Program(
213+
id=f"child_{i}",
214+
code=f"def child_{i}(): pass",
215+
generation=1,
216+
parent_id=parent.id,
217+
metrics={"combined_score": 0.5 + i * 0.05},
218+
)
219+
220+
# BUG: Current code doesn't pass target_island, so child inherits parent's island
221+
# This simulates the current buggy behavior:
222+
self.db.add(child) # No target_island
223+
224+
# Check island populations
225+
island_sizes = [len(self.db.islands[i]) for i in range(3)]
226+
227+
# With the bug, ALL programs end up in island 0
228+
# This assertion will FAIL, demonstrating the bug
229+
self.assertGreater(
230+
island_sizes[1], 0,
231+
f"Island 1 should have programs but has {island_sizes[1]}. "
232+
f"All islands: {island_sizes}. Bug: all programs went to island 0!"
233+
)
234+
self.assertGreater(
235+
island_sizes[2], 0,
236+
f"Island 2 should have programs but has {island_sizes[2]}. "
237+
f"All islands: {island_sizes}. Bug: all programs went to island 0!"
238+
)
239+
240+
241+
if __name__ == "__main__":
242+
unittest.main()

0 commit comments

Comments
 (0)