carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package activator
2
3import (
4 "context"
5 "errors"
6 "os"
7 "os/exec"
8 "sync"
9 "testing"
10 "time"
11)
12
13type producerContainment struct {
14 cmd *exec.Cmd
15 mu sync.Mutex
16 closed int
17}
18
19func (c *producerContainment) KillAndEmpty(context.Context) error {
20 err := c.cmd.Process.Kill()
21 if errors.Is(err, os.ErrProcessDone) {
22 return nil
23 }
24 return err
25}
26func (c *producerContainment) Close() error { c.mu.Lock(); defer c.mu.Unlock(); c.closed++; return nil }
27func TestClaimProducerCancellationStopsAndJoinsProcess(t *testing.T) {
28 cmd := exec.Command("sleep", "30")
29 if err := cmd.Start(); err != nil {
30 t.Fatal(err)
31 }
32 p := newClaimProcess(cmd, nil)
33 containment := &producerContainment{cmd: cmd}
34 p.prepared = containment
35 settled := false
36 p.settle = func(context.Context) error { settled = true; return nil }
37 ctx, cancel := context.WithCancel(t.Context())
38 finished := make(chan error, 1)
39 p.bindProducer(RoleProducer{Context: ctx, Finish: func(err error) { finished <- err }})
40 cancel()
41 select {
42 case err := <-finished:
43 if err != nil {
44 t.Fatal(err)
45 }
46 case <-time.After(5 * time.Second):
47 _ = cmd.Process.Kill()
48 t.Fatal("producer drain did not join")
49 }
50 if !p.Exited() || cmd.ProcessState == nil || !settled {
51 t.Fatal("ack preceded Wait or settlement")
52 }
53 if err := p.Stop(t.Context()); err != nil {
54 t.Fatal(err)
55 }
56 containment.mu.Lock()
57 defer containment.mu.Unlock()
58 if containment.closed != 1 {
59 t.Fatalf("containment closed %d times", containment.closed)
60 }
61}
62
63func TestClaimProducerCancellationRetriesRootSettlement(t *testing.T) {
64 t.Parallel()
65 p := &claimProcess{root: true, done: make(chan struct{})}
66 close(p.done)
67 var attempts int
68 p.settle = func(context.Context) error {
69 attempts++
70 if attempts < 3 {
71 return errors.New("root stop temporarily refused")
72 }
73 return nil
74 }
75 ctx, cancel := context.WithCancel(t.Context())
76 defer cancel()
77 finished := make(chan error, 1)
78 p.bindProducer(RoleProducer{Context: ctx, Finish: func(err error) { finished <- err }})
79 cancel()
80 select {
81 case err := <-finished:
82 if err != nil || attempts != 3 {
83 t.Fatalf("completion before root settlement: attempts=%d error=%v", attempts, err)
84 }
85 case <-time.After(3 * time.Second):
86 t.Fatal("producer did not retry root settlement")
87 }
88 if err := p.Stop(t.Context()); err != nil || attempts != 3 {
89 t.Fatalf("settled stop was not idempotent: attempts=%d error=%v", attempts, err)
90 }
91}
92