vibe-proxy/backend/sdk/cliproxy/executionregistry/registry_test.go
2026-08-24 00:10:41 +02:00

385 lines
9.9 KiB
Go

package executionregistry
import (
"context"
"errors"
"sync/atomic"
"testing"
"time"
)
func TestDrainRejectsLateInstallAndCancelsBoundScopes(t *testing.T) {
registry := New()
pending, errBegin := registry.BeginDispatch()
if errBegin != nil {
t.Fatal(errBegin)
}
scope, errInstall := registry.Install(pending, ScopeSpec{RequestID: "req-1", CredentialID: "cred-1", Model: "gpt", Kind: "http", StartedAt: time.Now()})
if errInstall != nil {
t.Fatal(errInstall)
}
closed := atomic.Int32{}
if errBind := scope.Bind(func() error {
closed.Add(1)
go scope.End("canceled")
return nil
}); errBind != nil {
t.Fatal(errBind)
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
if errDrain := registry.Drain(ctx); errDrain != nil {
t.Fatal(errDrain)
}
if closed.Load() != 1 {
t.Fatalf("close calls = %d", closed.Load())
}
if _, errLate := registry.BeginDispatch(); !errors.Is(errLate, ErrRegistryNotAccepting) {
t.Fatalf("late dispatch error = %v", errLate)
}
}
func TestScopeEndIsExactlyOnce(t *testing.T) {
registry := New()
pending, errBegin := registry.BeginDispatch()
if errBegin != nil {
t.Fatal(errBegin)
}
scope, errInstall := registry.Install(pending, ScopeSpec{})
if errInstall != nil {
t.Fatal(errInstall)
}
closed := atomic.Int32{}
if errBind := scope.Bind(func() error {
closed.Add(1)
return nil
}); errBind != nil {
t.Fatal(errBind)
}
done := make(chan struct{})
go func() {
scope.End("complete")
close(done)
}()
scope.End("duplicate")
<-done
if closed.Load() != 1 {
t.Fatalf("close calls = %d, want 1", closed.Load())
}
}
func TestDrainWaitsForPendingDispatch(t *testing.T) {
registry := New()
pending, errBegin := registry.BeginDispatch()
if errBegin != nil {
t.Fatal(errBegin)
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
done := make(chan error, 1)
go func() { done <- registry.Drain(ctx) }()
select {
case errDrain := <-done:
t.Fatalf("Drain() returned before pending dispatch ended: %v", errDrain)
case <-time.After(20 * time.Millisecond):
}
pending.End()
if errDrain := <-done; errDrain != nil {
t.Fatalf("Drain() error = %v", errDrain)
}
}
func TestWaitPendingDoesNotDrainActiveScope(t *testing.T) {
registry := New()
activePending, errBegin := registry.BeginDispatch()
if errBegin != nil {
t.Fatal(errBegin)
}
scope, errInstall := registry.Install(activePending, ScopeSpec{})
if errInstall != nil {
t.Fatal(errInstall)
}
defer scope.End("test cleanup")
pending, errBegin := registry.BeginDispatch()
if errBegin != nil {
t.Fatal(errBegin)
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
done := make(chan error, 1)
go func() { done <- registry.WaitPending(ctx) }()
select {
case errWait := <-done:
t.Fatalf("WaitPending() returned before pending dispatch ended: %v", errWait)
case <-time.After(20 * time.Millisecond):
}
pending.End()
if errWait := <-done; errWait != nil {
t.Fatalf("WaitPending() error = %v", errWait)
}
nextPending, errNext := registry.BeginDispatch()
if errNext != nil {
t.Fatalf("WaitPending() stopped registry acceptance: %v", errNext)
}
nextPending.End()
}
func TestDrainReturnsWhenBlockingResourceCloseExceedsContext(t *testing.T) {
registry := New()
pending, errBegin := registry.BeginDispatch()
if errBegin != nil {
t.Fatal(errBegin)
}
scope, errInstall := registry.Install(pending, ScopeSpec{})
if errInstall != nil {
t.Fatal(errInstall)
}
started := make(chan struct{})
release := make(chan struct{})
if errBind := scope.Bind(func() error {
close(started)
<-release
return nil
}); errBind != nil {
t.Fatal(errBind)
}
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond)
defer cancel()
errDrain := registry.Drain(ctx)
if !errors.Is(errDrain, context.DeadlineExceeded) {
t.Fatalf("Drain() error = %v, want context deadline exceeded", errDrain)
}
select {
case <-started:
default:
t.Fatal("Drain() did not start closing the bound resource")
}
if state := State(registry.state.Load()); state != StateDraining {
t.Fatalf("registry state = %v, want draining", state)
}
ended := make(chan struct{})
go func() {
scope.End("canceled")
close(ended)
}()
close(release)
select {
case <-ended:
case <-time.After(time.Second):
t.Fatal("Scope.End() did not wait for resource close completion")
}
if errDrain = registry.Drain(context.Background()); errDrain != nil {
t.Fatalf("Drain() after resource close = %v", errDrain)
}
}
func TestDrainWaitsForBlockingResourceClose(t *testing.T) {
registry := New()
pending, errBegin := registry.BeginDispatch()
if errBegin != nil {
t.Fatal(errBegin)
}
scope, errInstall := registry.Install(pending, ScopeSpec{})
if errInstall != nil {
t.Fatal(errInstall)
}
started := make(chan struct{})
release := make(chan struct{})
if errBind := scope.Bind(func() error {
close(started)
<-release
return nil
}); errBind != nil {
t.Fatal(errBind)
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
done := make(chan error, 1)
go func() { done <- registry.Drain(ctx) }()
select {
case <-started:
case <-time.After(time.Second):
t.Fatal("Drain() did not close the bound resource")
}
go scope.End("canceled")
select {
case errDrain := <-done:
t.Fatalf("Drain() returned before the resource close completed: %v", errDrain)
case <-time.After(20 * time.Millisecond):
}
close(release)
if errDrain := <-done; errDrain != nil {
t.Fatalf("Drain() error = %v", errDrain)
}
}
func TestConcurrentDrainWaitsForBlockingResourceClose(t *testing.T) {
registry := New()
pending, errBegin := registry.BeginDispatch()
if errBegin != nil {
t.Fatal(errBegin)
}
scope, errInstall := registry.Install(pending, ScopeSpec{})
if errInstall != nil {
t.Fatal(errInstall)
}
started := make(chan struct{})
release := make(chan struct{})
if errBind := scope.Bind(func() error {
close(started)
<-release
return nil
}); errBind != nil {
t.Fatal(errBind)
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
firstDrain := make(chan error, 1)
go func() { firstDrain <- registry.Drain(ctx) }()
select {
case <-started:
case <-time.After(time.Second):
t.Fatal("first Drain() did not close the bound resource")
}
ended := make(chan struct{})
go func() {
scope.End("canceled")
close(ended)
}()
select {
case <-ended:
t.Fatal("Scope.End() returned before the resource close completed")
case <-time.After(20 * time.Millisecond):
}
secondDrain := make(chan error, 1)
go func() { secondDrain <- registry.Drain(ctx) }()
select {
case errDrain := <-secondDrain:
t.Fatalf("second Drain() returned before resource close completed: %v", errDrain)
case <-time.After(20 * time.Millisecond):
}
close(release)
select {
case <-ended:
case <-time.After(time.Second):
t.Fatal("Scope.End() did not complete after the resource close")
}
if errDrain := <-firstDrain; errDrain != nil {
t.Fatalf("first Drain() error = %v", errDrain)
}
if errDrain := <-secondDrain; errDrain != nil {
t.Fatalf("second Drain() error = %v", errDrain)
}
}
func TestConcurrentCloseWaitsForBlockingResourceClose(t *testing.T) {
registry := New()
pending, errBegin := registry.BeginDispatch()
if errBegin != nil {
t.Fatal(errBegin)
}
scope, errInstall := registry.Install(pending, ScopeSpec{})
if errInstall != nil {
t.Fatal(errInstall)
}
started := make(chan struct{})
release := make(chan struct{})
if errBind := scope.Bind(func() error {
close(started)
<-release
return nil
}); errBind != nil {
t.Fatal(errBind)
}
firstClose := make(chan error, 1)
go func() { firstClose <- registry.Close() }()
select {
case <-started:
case <-time.After(time.Second):
t.Fatal("first Close() did not close the bound resource")
}
secondClose := make(chan error, 1)
go func() { secondClose <- registry.Close() }()
select {
case errClose := <-secondClose:
t.Fatalf("second Close() returned before resource close completed: %v", errClose)
case <-time.After(20 * time.Millisecond):
}
close(release)
if errClose := <-firstClose; errClose != nil {
t.Fatalf("first Close() error = %v", errClose)
}
if errClose := <-secondClose; errClose != nil {
t.Fatalf("second Close() error = %v", errClose)
}
}
func TestDrainRejectsLateBind(t *testing.T) {
registry := New()
pending, errBegin := registry.BeginDispatch()
if errBegin != nil {
t.Fatal(errBegin)
}
scope, errInstall := registry.Install(pending, ScopeSpec{})
if errInstall != nil {
t.Fatal(errInstall)
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
done := make(chan error, 1)
go func() { done <- registry.Drain(ctx) }()
deadline := time.After(time.Second)
for State(registry.state.Load()) == StateAccepting {
select {
case <-deadline:
t.Fatal("registry did not begin draining")
default:
time.Sleep(time.Millisecond)
}
}
if errBind := scope.Bind(func() error { return nil }); !errors.Is(errBind, ErrRegistryNotAccepting) {
t.Fatalf("Bind() error = %v, want ErrRegistryNotAccepting", errBind)
}
scope.End("canceled")
if errDrain := <-done; errDrain != nil {
t.Fatalf("Drain() error = %v", errDrain)
}
}
func TestDrainRejectsLateInstall(t *testing.T) {
registry := New()
pending, errBegin := registry.BeginDispatch()
if errBegin != nil {
t.Fatal(errBegin)
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
done := make(chan error, 1)
go func() { done <- registry.Drain(ctx) }()
deadline := time.After(time.Second)
for State(registry.state.Load()) == StateAccepting {
select {
case <-deadline:
t.Fatal("registry did not begin draining")
default:
time.Sleep(time.Millisecond)
}
}
if _, errInstall := registry.Install(pending, ScopeSpec{}); !errors.Is(errInstall, ErrRegistryNotAccepting) {
t.Fatalf("Install() error = %v, want ErrRegistryNotAccepting", errInstall)
}
if errDrain := <-done; errDrain != nil {
t.Fatalf("Drain() error = %v", errDrain)
}
}