ci / go (push) Waiting to run
ci / go-db (agent) (push) Waiting to run
ci / go-db (config) (push) Waiting to run
ci / go-db (db) (push) Waiting to run
ci / go-db (evidence) (push) Waiting to run
ci / go-db (llmrec) (push) Waiting to run
ci / go-db (server) (push) Waiting to run
detections / detections (push) Waiting to run
web / web (push) Waiting to run
docs / links (push) Canceled after 0s
110 lines
3.1 KiB
Go
110 lines
3.1 KiB
Go
package server
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
"testing"
|
|
)
|
|
|
|
func TestTaskLifecycleSnapshotCopiesContextSlices(t *testing.T) {
|
|
task := &Task{
|
|
Status: "running",
|
|
SourceTaskIDs: []int64{11, 12},
|
|
CompanyIDs: []int64{21, 22},
|
|
}
|
|
|
|
snapshot := task.lifecycleSnapshot()
|
|
snapshot.SourceTaskIDs[0] = 99
|
|
snapshot.CompanyIDs[0] = 98
|
|
|
|
fresh := task.lifecycleSnapshot()
|
|
if got := fresh.SourceTaskIDs[0]; got != 11 {
|
|
t.Fatalf("snapshot source IDs alias task storage: got %d", got)
|
|
}
|
|
if got := fresh.CompanyIDs[0]; got != 21 {
|
|
t.Fatalf("snapshot company IDs alias task storage: got %d", got)
|
|
}
|
|
|
|
sources := []int64{31, 32}
|
|
companies := []int64{41, 42}
|
|
task.updateLifecycle(func(state *taskLifecycleState) {
|
|
state.SourceTaskIDs = sources
|
|
state.CompanyIDs = companies
|
|
})
|
|
sources[0] = 97
|
|
companies[0] = 96
|
|
|
|
fresh = task.lifecycleSnapshot()
|
|
if got := fresh.SourceTaskIDs[0]; got != 31 {
|
|
t.Fatalf("lifecycle update retained caller source slice: got %d", got)
|
|
}
|
|
if got := fresh.CompanyIDs[0]; got != 41 {
|
|
t.Fatalf("lifecycle update retained caller company slice: got %d", got)
|
|
}
|
|
}
|
|
|
|
func TestTaskLifecycleSnapshotConcurrentConsistency(t *testing.T) {
|
|
task := &Task{}
|
|
writeState := func(generation int64, queued bool) {
|
|
task.updateLifecycle(func(state *taskLifecycleState) {
|
|
state.Status = map[bool]string{true: "queued", false: "paused"}[queued]
|
|
state.Queued = queued
|
|
state.Paused = !queued
|
|
state.QueuedAt = generation
|
|
state.QueueMode = map[bool]string{true: "resume", false: "bootstrap"}[queued]
|
|
state.CompletedAt = generation
|
|
state.FirstRunAt = generation
|
|
state.DeadlineAt = generation
|
|
state.SourceTaskIDs = []int64{generation, generation + 1}
|
|
state.CompanyIDs = []int64{generation, generation + 1}
|
|
})
|
|
}
|
|
writeState(1, true)
|
|
|
|
var wg sync.WaitGroup
|
|
for writer := 0; writer < 4; writer++ {
|
|
writer := writer
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
for i := 1; i <= 1000; i++ {
|
|
generation := int64(writer*1000 + i)
|
|
writeState(generation, generation%2 == 0)
|
|
}
|
|
}()
|
|
}
|
|
|
|
errCh := make(chan error, 4)
|
|
for reader := 0; reader < 4; reader++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
for i := 0; i < 1000; i++ {
|
|
state := task.lifecycleSnapshot()
|
|
if state.Queued == state.Paused {
|
|
errCh <- fmt.Errorf("queued=%v paused=%v", state.Queued, state.Paused)
|
|
return
|
|
}
|
|
wantStatus := map[bool]string{true: "queued", false: "paused"}[state.Queued]
|
|
wantMode := map[bool]string{true: "resume", false: "bootstrap"}[state.Queued]
|
|
if state.Status != wantStatus || state.QueueMode != wantMode {
|
|
errCh <- fmt.Errorf("inconsistent status/mode: %+v", state)
|
|
return
|
|
}
|
|
generation := state.QueuedAt
|
|
if state.CompletedAt != generation || state.FirstRunAt != generation || state.DeadlineAt != generation ||
|
|
len(state.SourceTaskIDs) != 2 || len(state.CompanyIDs) != 2 ||
|
|
state.SourceTaskIDs[0] != generation || state.CompanyIDs[0] != generation {
|
|
errCh <- fmt.Errorf("mixed lifecycle snapshot: %+v", state)
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
wg.Wait()
|
|
close(errCh)
|
|
for err := range errCh {
|
|
t.Fatal(err)
|
|
}
|
|
}
|