1package app
2
3import (
4 "errors"
5 "testing"
6)
7
8func TestApplyProgress(t *testing.T) {
9 boom := errors.New("modeset")
10 for _, tc := range []struct {
11 name string
12 run func(*applyProgress, <-chan error, <-chan error) applyDecision
13 failure bool
14 }{
15 {"success", func(p *applyProgress, a, _ <-chan error) applyDecision {
16 p.start(1, map[string]bool{"A": true}, nil)
17 p.scanned(map[string]<-chan error{"A": a})
18 return p.readyEvent("A", a, a, nil)
19 }, false},
20 {"scan error with previously ready output", func(p *applyProgress, a, _ <-chan error) applyDecision {
21 p.completed = map[<-chan error]bool{a: true}
22 p.start(1, map[string]bool{"A": true}, nil)
23 d := p.scanError(boom)
24 if later := p.scanned(map[string]<-chan error{"A": a}); later.reply {
25 t.Fatalf("old ready output completed failed apply: %+v", later)
26 }
27 return d
28 }, true},
29 {"ready error rollback", func(p *applyProgress, a, _ <-chan error) applyDecision {
30 p.start(1, map[string]bool{"A": true}, nil)
31 p.scanned(map[string]<-chan error{"A": a})
32 return p.readyEvent("A", a, a, boom)
33 }, true},
34 {"timeout", func(p *applyProgress, a, _ <-chan error) applyDecision {
35 p.start(1, map[string]bool{"A": true}, nil)
36 p.scanned(map[string]<-chan error{"A": a})
37 return p.timeout(1)
38 }, true},
39 {"restart replacement missing", func(p *applyProgress, a, _ <-chan error) applyDecision {
40 p.start(1, map[string]bool{"A": true}, map[string]bool{"A": true})
41 p.stopped("A", a, true, nil)
42 return p.scanned(nil)
43 }, true},
44 {"unchanged not ready", func(p *applyProgress, a, _ <-chan error) applyDecision {
45 p.start(1, map[string]bool{"A": true}, nil)
46 return p.scanned(map[string]<-chan error{"A": a})
47 }, false},
48 {"hotplug during apply", func(p *applyProgress, a, b <-chan error) applyDecision {
49 p.start(1, map[string]bool{"A": true, "B": true}, nil)
50 p.scanned(map[string]<-chan error{"A": a, "B": b})
51 p.readyEvent("A", a, a, nil)
52 return p.readyEvent("B", b, b, nil)
53 }, false},
54 {"stale timeout", func(p *applyProgress, a, _ <-chan error) applyDecision {
55 p.start(1, map[string]bool{"A": true}, nil)
56 p.start(2, map[string]bool{"A": true}, nil)
57 p.scanned(map[string]<-chan error{"A": a})
58 p.timeout(1)
59 return p.readyEvent("A", a, a, nil)
60 }, false},
61 {"stopped instance error ignored", func(p *applyProgress, a, b <-chan error) applyDecision {
62 p.start(1, map[string]bool{"A": true}, map[string]bool{"A": true})
63 p.stopped("A", a, true, nil)
64 p.scanned(map[string]<-chan error{"A": b})
65 if d := p.readyEvent("A", a, b, boom); d.reply {
66 t.Fatalf("stopped instance failed the apply: %+v", d)
67 }
68 return p.readyEvent("A", b, b, nil)
69 }, false},
70 {"disabled head stop", func(p *applyProgress, a, _ <-chan error) applyDecision {
71 p.start(1, nil, map[string]bool{"A": false})
72 return p.stopped("A", a, false, nil)
73 }, false},
74 } {
75 t.Run(tc.name, func(t *testing.T) {
76 p := &applyProgress{}
77 a, b := make(chan error), make(chan error)
78 d := tc.run(p, a, b)
79 if tc.name == "unchanged not ready" {
80 if d.reply || !p.active {
81 t.Fatalf("premature: %+v", d)
82 }
83 return
84 }
85 if !d.reply || (d.err != nil) != tc.failure {
86 t.Fatalf("decision: %+v", d)
87 }
88 if tc.name == "scan error with previously ready output" && !errors.Is(d.err, boom) {
89 t.Fatalf("scan error lost: %v", d.err)
90 }
91 })
92 }
93}