package activator import ( "context" "errors" "os" "os/exec" "sync" "testing" "time" ) type producerContainment struct { cmd *exec.Cmd mu sync.Mutex closed int } func (c *producerContainment) KillAndEmpty(context.Context) error { err := c.cmd.Process.Kill() if errors.Is(err, os.ErrProcessDone) { return nil } return err } func (c *producerContainment) Close() error { c.mu.Lock(); defer c.mu.Unlock(); c.closed++; return nil } func TestClaimProducerCancellationStopsAndJoinsProcess(t *testing.T) { cmd := exec.Command("sleep", "30") if err := cmd.Start(); err != nil { t.Fatal(err) } p := newClaimProcess(cmd, nil) containment := &producerContainment{cmd: cmd} p.prepared = containment settled := false p.settle = func(context.Context) error { settled = true; return nil } ctx, cancel := context.WithCancel(t.Context()) finished := make(chan error, 1) p.bindProducer(RoleProducer{Context: ctx, Finish: func(err error) { finished <- err }}) cancel() select { case err := <-finished: if err != nil { t.Fatal(err) } case <-time.After(5 * time.Second): _ = cmd.Process.Kill() t.Fatal("producer drain did not join") } if !p.Exited() || cmd.ProcessState == nil || !settled { t.Fatal("ack preceded Wait or settlement") } if err := p.Stop(t.Context()); err != nil { t.Fatal(err) } containment.mu.Lock() defer containment.mu.Unlock() if containment.closed != 1 { t.Fatalf("containment closed %d times", containment.closed) } } func TestClaimProducerCancellationRetriesRootSettlement(t *testing.T) { t.Parallel() p := &claimProcess{root: true, done: make(chan struct{})} close(p.done) var attempts int p.settle = func(context.Context) error { attempts++ if attempts < 3 { return errors.New("root stop temporarily refused") } return nil } ctx, cancel := context.WithCancel(t.Context()) defer cancel() finished := make(chan error, 1) p.bindProducer(RoleProducer{Context: ctx, Finish: func(err error) { finished <- err }}) cancel() select { case err := <-finished: if err != nil || attempts != 3 { t.Fatalf("completion before root settlement: attempts=%d error=%v", attempts, err) } case <-time.After(3 * time.Second): t.Fatal("producer did not retry root settlement") } if err := p.Stop(t.Context()); err != nil || attempts != 3 { t.Fatalf("settled stop was not idempotent: attempts=%d error=%v", attempts, err) } }