Skip to content

Commit 08ce5ba

Browse files
committed
Validate loop control behavior across runtime contexts
1 parent 66d6282 commit 08ce5ba

3 files changed

Lines changed: 72 additions & 1 deletion

File tree

ROADMAP.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ Goal: improve language ergonomics for complex script logic and recovery behavior
226226

227227
- [x] Ensure new control flow integrates with step quota accounting.
228228
- [ ] Ensure new constructs integrate with recursion/memory quotas.
229-
- [ ] Validate behavior inside class methods, blocks, and capability callbacks.
229+
- [x] Validate behavior inside class methods, blocks, and capability callbacks.
230230

231231
### Testing and Docs
232232

vibes/capability_db_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,48 @@ end`)
165165
}
166166
}
167167

168+
func TestDBCapabilityEachLoopControlCannotCrossCallbackBoundary(t *testing.T) {
169+
stub := &dbCapabilityStub{
170+
eachRows: []Value{
171+
NewHash(map[string]Value{"id": NewString("p-1")}),
172+
NewHash(map[string]Value{"id": NewString("p-2")}),
173+
},
174+
}
175+
engine := MustNewEngine(Config{})
176+
script, err := engine.Compile(`def break_from_callback()
177+
db.each("Player") do |row|
178+
if row[:id] == "p-2"
179+
break
180+
end
181+
end
182+
end
183+
184+
def next_from_callback()
185+
db.each("Player") do |row|
186+
if row[:id] == "p-2"
187+
next
188+
end
189+
end
190+
end`)
191+
if err != nil {
192+
t.Fatalf("compile failed: %v", err)
193+
}
194+
195+
_, err = script.Call(context.Background(), "break_from_callback", nil, CallOptions{
196+
Capabilities: []CapabilityAdapter{MustNewDBCapability("db", stub)},
197+
})
198+
if err == nil || !strings.Contains(err.Error(), "break used outside of loop") {
199+
t.Fatalf("expected callback break outside-loop error, got %v", err)
200+
}
201+
202+
_, err = script.Call(context.Background(), "next_from_callback", nil, CallOptions{
203+
Capabilities: []CapabilityAdapter{MustNewDBCapability("db", stub)},
204+
})
205+
if err == nil || !strings.Contains(err.Error(), "next used outside of loop") {
206+
t.Fatalf("expected callback next outside-loop error, got %v", err)
207+
}
208+
}
209+
168210
func TestDBCapabilityRejectsCallableUpdateAttributes(t *testing.T) {
169211
stub := &dbCapabilityStub{}
170212
engine := MustNewEngine(Config{})

vibes/runtime_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1005,6 +1005,35 @@ func TestLoopControlNestedAndBlockBoundaryBehavior(t *testing.T) {
10051005
}
10061006
}
10071007

1008+
func TestLoopControlInsideClassMethods(t *testing.T) {
1009+
script := compileScript(t, `
1010+
class Counter
1011+
def self.collect(limit)
1012+
out = []
1013+
n = 0
1014+
while n < limit
1015+
n = n + 1
1016+
if n % 2 == 0
1017+
next
1018+
end
1019+
if n > 5
1020+
break
1021+
end
1022+
out = out + [n]
1023+
end
1024+
out
1025+
end
1026+
end
1027+
1028+
def run(limit)
1029+
Counter.collect(limit)
1030+
end
1031+
`)
1032+
1033+
result := callFunc(t, script, "run", []Value{NewInt(10)})
1034+
compareArrays(t, result, []Value{NewInt(1), NewInt(3), NewInt(5)})
1035+
}
1036+
10081037
func TestDurationMethods(t *testing.T) {
10091038
script := compileScript(t, `
10101039
def duration_helpers()

0 commit comments

Comments
 (0)