121 lines
4.0 KiB
Go
121 lines
4.0 KiB
Go
package agent
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
var taskFixture = TaskIdentity{DispatcherID: "c046b893-8628-4589-ae50-619d049248a6", TenantID: 42, TaskID: "task-a"}
|
|
|
|
func TestTaskCallsPauseHangupWaitsForCallAndRefusesNewWork(t *testing.T) {
|
|
var calls TaskCalls
|
|
canceled := make(chan struct{})
|
|
release, err := calls.Register(taskFixture, "call-a", func() { close(canceled) })
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
|
defer cancel()
|
|
completed := make(chan error, 1)
|
|
go func() { completed <- calls.Apply(ctx, taskFixture, "pause", "hangup") }()
|
|
select {
|
|
case <-canceled:
|
|
case <-ctx.Done():
|
|
t.Fatal("pause did not request hangup")
|
|
}
|
|
if _, err := calls.Register(taskFixture, "call-b", func() {}); !errors.Is(err, ErrTaskAdmissionClosed) {
|
|
t.Fatalf("pause admitted another call: %v", err)
|
|
}
|
|
select {
|
|
case err := <-completed:
|
|
t.Fatalf("pause acknowledged before active call ended: %v", err)
|
|
default:
|
|
}
|
|
release()
|
|
if err := <-completed; err != nil {
|
|
t.Fatalf("pause did not wait for confirmed completion: %v", err)
|
|
}
|
|
if err := calls.Apply(ctx, taskFixture, "resume", ""); err != nil {
|
|
t.Fatalf("Dispatcher-approved resume was rejected: %v", err)
|
|
}
|
|
releaseAgain, err := calls.Register(taskFixture, "call-b", func() {})
|
|
if err != nil {
|
|
t.Fatalf("resumed task did not admit work: %v", err)
|
|
}
|
|
releaseAgain()
|
|
}
|
|
|
|
func TestTaskCallsStopDrainKeepsBarrierAfterTimeoutAndCannotResume(t *testing.T) {
|
|
var calls TaskCalls
|
|
var hangups atomic.Int32
|
|
release, err := calls.Register(taskFixture, "call-a", func() { hangups.Add(1) })
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond)
|
|
defer cancel()
|
|
if err := calls.Apply(ctx, taskFixture, "stop", "drain"); !errors.Is(err, context.DeadlineExceeded) {
|
|
t.Fatalf("stop/drain acknowledged an active call: %v", err)
|
|
}
|
|
if hangups.Load() != 0 {
|
|
t.Fatal("drain hung up an active call")
|
|
}
|
|
if _, err := calls.Register(taskFixture, "call-b", func() {}); !errors.Is(err, ErrTaskStopped) {
|
|
t.Fatalf("stop barrier reopened after timeout: %v", err)
|
|
}
|
|
release()
|
|
if err := calls.Apply(context.Background(), taskFixture, "stop", "drain"); err != nil {
|
|
t.Fatalf("re-delivered stop did not complete after draining: %v", err)
|
|
}
|
|
if err := calls.Apply(context.Background(), taskFixture, "resume", ""); !errors.Is(err, ErrTaskStopped) {
|
|
t.Fatalf("stopped task resumed: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestTaskCallsPreserveTaskIsolationAndValidateControls(t *testing.T) {
|
|
var calls TaskCalls
|
|
for _, tc := range []struct{ action, policy string }{
|
|
{"pause", ""}, {"resume", "hangup"}, {"restart", "hangup"}, {"stop", "unknown"},
|
|
} {
|
|
if err := calls.Apply(context.Background(), taskFixture, tc.action, tc.policy); err == nil {
|
|
t.Fatalf("invalid control %q/%q was accepted", tc.action, tc.policy)
|
|
}
|
|
}
|
|
if _, err := calls.Register(TaskIdentity{}, "call", func() {}); err == nil {
|
|
t.Fatal("missing task identity was accepted")
|
|
}
|
|
if err := calls.Apply(context.Background(), TaskIdentity{}, "stop", "hangup"); err == nil {
|
|
t.Fatal("control without an approved task identity was accepted")
|
|
}
|
|
if _, err := calls.Register(taskFixture, "call", nil); err == nil {
|
|
t.Fatal("missing call cancellation was accepted")
|
|
}
|
|
other := taskFixture
|
|
other.TaskID = "task-b"
|
|
releaseOther, err := calls.Register(other, "call-a", func() {})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
releaseA, err := calls.Register(taskFixture, "call-a", func() {})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := calls.Register(taskFixture, "call-a", func() {}); err == nil {
|
|
t.Fatal("duplicate live execution was admitted")
|
|
}
|
|
releaseA()
|
|
releaseA() // repeated terminal observation must not close the channel twice
|
|
if err := calls.Apply(context.Background(), taskFixture, "pause", "drain"); err != nil {
|
|
t.Fatalf("released call remained active: %v", err)
|
|
}
|
|
releaseOtherB, err := calls.Register(other, "call-b", func() {})
|
|
if err != nil {
|
|
t.Fatalf("other task was fenced by a different task: %v", err)
|
|
}
|
|
releaseOtherB()
|
|
releaseOther()
|
|
}
|