Files

155 lines
4.8 KiB
Go

package process
import (
"testing"
"time"
"github.com/kandev/kandev/internal/agentctl/server/adapter"
)
// TestSendUpdateBlockingSendsImmediatelyWhenChannelHasRoom pins the common
// case: a converted send behaves exactly like the non-blocking one did
// whenever the channel isn't actually full.
func TestSendUpdateBlockingSendsImmediatelyWhenChannelHasRoom(t *testing.T) {
m := &Manager{
logger: newTestLogger(t),
updatesCh: make(chan adapter.AgentEvent, 1),
}
stopCh := make(chan struct{})
m.stopChSnapshot.Store(stopCh)
if ok := m.sendUpdateBlocking(adapter.AgentEvent{Type: adapter.EventTypeError, Error: "boom"}); !ok {
t.Fatal("sendUpdateBlocking() = false, want true (channel had room)")
}
select {
case event := <-m.updatesCh:
if event.Error != "boom" {
t.Fatalf("Error = %q, want %q", event.Error, "boom")
}
default:
t.Fatal("event was not enqueued on updatesCh")
}
}
// TestSendUpdateBlockingParksUntilRoomFreesUp pins AC-EXECUTORS-SURVIVAL-001.5/.6:
// a full channel must PARK the producer rather than discard the event, and
// deliver it once a consumer drains the channel.
func TestSendUpdateBlockingParksUntilRoomFreesUp(t *testing.T) {
m := &Manager{
logger: newTestLogger(t),
updatesCh: make(chan adapter.AgentEvent, 1),
}
stopCh := make(chan struct{})
m.stopChSnapshot.Store(stopCh)
m.updatesCh <- adapter.AgentEvent{Type: adapter.EventTypeComplete} // fill the buffer
done := make(chan bool, 1)
go func() {
done <- m.sendUpdateBlocking(adapter.AgentEvent{Type: adapter.EventTypeError, Error: "parked"})
}()
select {
case <-done:
t.Fatal("sendUpdateBlocking returned before the channel had room -- it must park, not discard")
case <-time.After(50 * time.Millisecond):
}
<-m.updatesCh // drain the blocking event, freeing a slot
select {
case ok := <-done:
if !ok {
t.Fatal("sendUpdateBlocking() = false once room freed up, want true")
}
case <-time.After(time.Second):
t.Fatal("sendUpdateBlocking did not unblock after the channel drained")
}
select {
case event := <-m.updatesCh:
if event.Error != "parked" {
t.Fatalf("Error = %q, want %q", event.Error, "parked")
}
default:
t.Fatal("the parked event was never delivered")
}
}
// TestSendUpdateBlockingReleasesOnStop pins the "pause not a hang" half of
// the design: a producer parked on a full channel must be released the
// instant the instance's stop signal fires, without waiting for a consumer
// that may never arrive.
func TestSendUpdateBlockingReleasesOnStop(t *testing.T) {
m := &Manager{
logger: newTestLogger(t),
updatesCh: make(chan adapter.AgentEvent, 1),
}
stopCh := make(chan struct{})
m.stopChSnapshot.Store(stopCh)
m.updatesCh <- adapter.AgentEvent{Type: adapter.EventTypeComplete} // fill the buffer
done := make(chan bool, 1)
go func() {
done <- m.sendUpdateBlocking(adapter.AgentEvent{Type: adapter.EventTypeError, Error: "released"})
}()
select {
case <-done:
t.Fatal("sendUpdateBlocking returned before stop was signalled")
case <-time.After(50 * time.Millisecond):
}
close(stopCh)
select {
case ok := <-done:
if ok {
t.Fatal("sendUpdateBlocking() = true after a stop release, want false (event must not be delivered)")
}
case <-time.After(time.Second):
t.Fatal("sendUpdateBlocking did not release on stop")
}
}
// TestSendUpdateBlockingSendsWithRoomEvenWithNoStopChannelEstablished pins
// that the fast, common path never depends on a stop channel existing at
// all: when there's room, the send just happens.
func TestSendUpdateBlockingSendsWithRoomEvenWithNoStopChannelEstablished(t *testing.T) {
m := &Manager{
logger: newTestLogger(t),
updatesCh: make(chan adapter.AgentEvent, 1),
}
if ok := m.sendUpdateBlocking(adapter.AgentEvent{Type: adapter.EventTypeError}); !ok {
t.Fatal("sendUpdateBlocking() = false with room available, want true regardless of stop-channel state")
}
}
// TestSendUpdateBlockingReturnsFalseImmediatelyWhenNeverStartedAndFull pins
// the defensive behaviour for a Manager whose stop channel was never
// established (Start has not run) and whose channel is actually full: treat
// it as already stopped rather than parking forever with no lifecycle that
// could ever release it.
func TestSendUpdateBlockingReturnsFalseImmediatelyWhenNeverStartedAndFull(t *testing.T) {
m := &Manager{
logger: newTestLogger(t),
updatesCh: make(chan adapter.AgentEvent, 1),
}
m.updatesCh <- adapter.AgentEvent{Type: adapter.EventTypeComplete} // fill the buffer
done := make(chan bool, 1)
go func() {
done <- m.sendUpdateBlocking(adapter.AgentEvent{Type: adapter.EventTypeError})
}()
select {
case ok := <-done:
if ok {
t.Fatal("sendUpdateBlocking() = true with no stop channel established, want false")
}
case <-time.After(time.Second):
t.Fatal("sendUpdateBlocking did not return promptly with no stop channel established")
}
}