package app import ( "errors" "testing" ) func TestApplyProgress(t *testing.T) { boom := errors.New("modeset") for _, tc := range []struct { name string run func(*applyProgress, <-chan error, <-chan error) applyDecision failure bool }{ {"success", func(p *applyProgress, a, _ <-chan error) applyDecision { p.start(1, map[string]bool{"A": true}, nil) p.scanned(map[string]<-chan error{"A": a}) return p.readyEvent("A", a, a, nil) }, false}, {"scan error with previously ready output", func(p *applyProgress, a, _ <-chan error) applyDecision { p.completed = map[<-chan error]bool{a: true} p.start(1, map[string]bool{"A": true}, nil) d := p.scanError(boom) if later := p.scanned(map[string]<-chan error{"A": a}); later.reply { t.Fatalf("old ready output completed failed apply: %+v", later) } return d }, true}, {"ready error rollback", func(p *applyProgress, a, _ <-chan error) applyDecision { p.start(1, map[string]bool{"A": true}, nil) p.scanned(map[string]<-chan error{"A": a}) return p.readyEvent("A", a, a, boom) }, true}, {"timeout", func(p *applyProgress, a, _ <-chan error) applyDecision { p.start(1, map[string]bool{"A": true}, nil) p.scanned(map[string]<-chan error{"A": a}) return p.timeout(1) }, true}, {"restart replacement missing", func(p *applyProgress, a, _ <-chan error) applyDecision { p.start(1, map[string]bool{"A": true}, map[string]bool{"A": true}) p.stopped("A", a, true, nil) return p.scanned(nil) }, true}, {"unchanged not ready", func(p *applyProgress, a, _ <-chan error) applyDecision { p.start(1, map[string]bool{"A": true}, nil) return p.scanned(map[string]<-chan error{"A": a}) }, false}, {"hotplug during apply", func(p *applyProgress, a, b <-chan error) applyDecision { p.start(1, map[string]bool{"A": true, "B": true}, nil) p.scanned(map[string]<-chan error{"A": a, "B": b}) p.readyEvent("A", a, a, nil) return p.readyEvent("B", b, b, nil) }, false}, {"stale timeout", func(p *applyProgress, a, _ <-chan error) applyDecision { p.start(1, map[string]bool{"A": true}, nil) p.start(2, map[string]bool{"A": true}, nil) p.scanned(map[string]<-chan error{"A": a}) p.timeout(1) return p.readyEvent("A", a, a, nil) }, false}, {"stopped instance error ignored", func(p *applyProgress, a, b <-chan error) applyDecision { p.start(1, map[string]bool{"A": true}, map[string]bool{"A": true}) p.stopped("A", a, true, nil) p.scanned(map[string]<-chan error{"A": b}) if d := p.readyEvent("A", a, b, boom); d.reply { t.Fatalf("stopped instance failed the apply: %+v", d) } return p.readyEvent("A", b, b, nil) }, false}, {"disabled head stop", func(p *applyProgress, a, _ <-chan error) applyDecision { p.start(1, nil, map[string]bool{"A": false}) return p.stopped("A", a, false, nil) }, false}, } { t.Run(tc.name, func(t *testing.T) { p := &applyProgress{} a, b := make(chan error), make(chan error) d := tc.run(p, a, b) if tc.name == "unchanged not ready" { if d.reply || !p.active { t.Fatalf("premature: %+v", d) } return } if !d.reply || (d.err != nil) != tc.failure { t.Fatalf("decision: %+v", d) } if tc.name == "scan error with previously ready output" && !errors.Is(d.err, boom) { t.Fatalf("scan error lost: %v", d.err) } }) } }