Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
95 lines
2.3 KiB
Go
95 lines
2.3 KiB
Go
package agenthost
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestRunSupervisedWorkersStartsAllAndStopsOnContextCancel(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
started := make(chan struct{}, 3)
|
|
worker := func(ctx context.Context) error {
|
|
started <- struct{}{}
|
|
<-ctx.Done()
|
|
return ctx.Err()
|
|
}
|
|
done := make(chan error, 1)
|
|
go func() {
|
|
done <- runSupervisedWorkers(ctx, []func(context.Context) error{worker, worker, worker})
|
|
}()
|
|
for range 3 {
|
|
select {
|
|
case <-started:
|
|
case <-time.After(time.Second):
|
|
t.Fatal("not every Host worker started")
|
|
}
|
|
}
|
|
cancel()
|
|
select {
|
|
case err := <-done:
|
|
if !errors.Is(err, context.Canceled) {
|
|
t.Fatalf("Run() error = %v, want context canceled", err)
|
|
}
|
|
case <-time.After(time.Second):
|
|
t.Fatal("Run() did not stop after context cancellation")
|
|
}
|
|
}
|
|
|
|
func TestRunSupervisedWorkersCancelsSiblingsWhenOneWorkerFails(t *testing.T) {
|
|
wantErr := errors.New("worker infrastructure failed")
|
|
started := make(chan struct{}, 2)
|
|
var canceled sync.WaitGroup
|
|
canceled.Add(2)
|
|
sibling := func(ctx context.Context) error {
|
|
started <- struct{}{}
|
|
<-ctx.Done()
|
|
canceled.Done()
|
|
return ctx.Err()
|
|
}
|
|
failing := func(context.Context) error {
|
|
for range 2 {
|
|
<-started
|
|
}
|
|
return wantErr
|
|
}
|
|
|
|
err := runSupervisedWorkers(context.Background(), []func(context.Context) error{sibling, failing, sibling})
|
|
if !errors.Is(err, wantErr) {
|
|
t.Fatalf("Run() error = %v, want %v", err, wantErr)
|
|
}
|
|
done := make(chan struct{})
|
|
go func() {
|
|
canceled.Wait()
|
|
close(done)
|
|
}()
|
|
select {
|
|
case <-done:
|
|
case <-time.After(time.Second):
|
|
t.Fatal("worker failure did not cancel sibling workers")
|
|
}
|
|
}
|
|
|
|
func TestNilHostRunIsNoop(t *testing.T) {
|
|
var host *Host
|
|
if err := host.Run(context.Background()); err != nil {
|
|
t.Fatalf("Run() error = %v, want nil", err)
|
|
}
|
|
}
|
|
|
|
func TestHostRunPropagatesWorkerInfrastructureFailure(t *testing.T) {
|
|
wantErr := errors.New("scheduler unavailable")
|
|
host := New(Config{Scheduler: failingRunScheduler{err: wantErr}})
|
|
if err := host.Run(context.Background()); !errors.Is(err, wantErr) {
|
|
t.Fatalf("Run() error = %v, want %v", err, wantErr)
|
|
}
|
|
}
|
|
|
|
type failingRunScheduler struct{ err error }
|
|
|
|
func (scheduler failingRunScheduler) Sleep(context.Context, time.Duration) error {
|
|
return scheduler.err
|
|
}
|