1
2
3
4
5
6
7
8
9
10
11
12
13
14 package test
15
16 import (
17 "encoding/json"
18 "fmt"
19 "sync"
20 "testing"
21 "time"
22
23 "github.com/prometheus/alertmanager/api/v2/models"
24 )
25
26
27
28 type Collector struct {
29 t *testing.T
30 name string
31 opts *AcceptanceOpts
32
33 collected map[float64][]models.GettableAlerts
34 expected map[Interval][]models.GettableAlerts
35
36 mtx sync.RWMutex
37 }
38
39 func (c *Collector) String() string {
40 return c.name
41 }
42
43
44
45 func (c *Collector) Collected() map[float64][]models.GettableAlerts {
46 c.mtx.RLock()
47 defer c.mtx.RUnlock()
48 return c.collected
49 }
50
51 func batchesEqual(as, bs models.GettableAlerts, opts *AcceptanceOpts) bool {
52 if len(as) != len(bs) {
53 return false
54 }
55
56 for _, a := range as {
57 found := false
58 for _, b := range bs {
59 if equalAlerts(a, b, opts) {
60 found = true
61 break
62 }
63 }
64 if !found {
65 return false
66 }
67 }
68 return true
69 }
70
71
72
73 func (c *Collector) latest() float64 {
74 c.mtx.RLock()
75 defer c.mtx.RUnlock()
76 var latest float64
77 for iv := range c.expected {
78 if iv.end > latest {
79 latest = iv.end
80 }
81 }
82 return latest
83 }
84
85
86
87 func (c *Collector) Want(iv Interval, alerts ...*TestAlert) {
88 c.mtx.Lock()
89 defer c.mtx.Unlock()
90 var nas models.GettableAlerts
91 for _, a := range alerts {
92 nas = append(nas, a.nativeAlert(c.opts))
93 }
94
95 c.expected[iv] = append(c.expected[iv], nas)
96 }
97
98
99 func (c *Collector) add(alerts ...*models.GettableAlert) {
100 c.mtx.Lock()
101 defer c.mtx.Unlock()
102 arrival := c.opts.relativeTime(time.Now())
103
104 c.collected[arrival] = append(c.collected[arrival], models.GettableAlerts(alerts))
105 }
106
107 func (c *Collector) Check() string {
108 report := fmt.Sprintf("\ncollector %q:\n\n", c)
109
110 c.mtx.RLock()
111 defer c.mtx.RUnlock()
112 for iv, expected := range c.expected {
113 report += fmt.Sprintf("interval %v\n", iv)
114
115 var alerts []models.GettableAlerts
116 for at, got := range c.collected {
117 if iv.contains(at) {
118 alerts = append(alerts, got...)
119 }
120 }
121
122 for _, exp := range expected {
123 found := len(exp) == 0 && len(alerts) == 0
124
125 report += "---\n"
126
127 for _, e := range exp {
128 report += fmt.Sprintf("- %v\n", c.opts.alertString(e))
129 }
130
131 for _, a := range alerts {
132 if batchesEqual(exp, a, c.opts) {
133 found = true
134 break
135 }
136 }
137
138 if found {
139 report += " [ ✓ ]\n"
140 } else {
141 c.t.Fail()
142 report += " [ ✗ ]\n"
143 }
144 }
145 }
146
147
148 var totalExp, totalAct int
149 for _, exp := range c.expected {
150 for _, e := range exp {
151 totalExp += len(e)
152 }
153 }
154 for _, act := range c.collected {
155 for _, a := range act {
156 if len(a) == 0 {
157 c.t.Error("received empty notifications")
158 }
159 totalAct += len(a)
160 }
161 }
162 if totalExp != totalAct {
163 c.t.Fail()
164 report += fmt.Sprintf("\nExpected total of %d alerts, got %d", totalExp, totalAct)
165 }
166
167 if c.t.Failed() {
168 report += "\nreceived:\n"
169
170 for at, col := range c.collected {
171 for _, alerts := range col {
172 report += fmt.Sprintf("@ %v\n", at)
173 for _, a := range alerts {
174 report += fmt.Sprintf("- %v\n", c.opts.alertString(a))
175 }
176 }
177 }
178 }
179
180 return report
181 }
182
183
184
185 func alertsToString(as []*models.GettableAlert) (string, error) {
186 b, err := json.Marshal(as)
187 if err != nil {
188 return "", err
189 }
190
191 return string(b), nil
192 }
193
194
195 func CompareCollectors(a, b *Collector, opts *AcceptanceOpts) (bool, error) {
196 f := func(collected map[float64][]models.GettableAlerts) []*models.GettableAlert {
197 result := []*models.GettableAlert{}
198 for _, batches := range collected {
199 for _, batch := range batches {
200 for _, alert := range batch {
201 result = append(result, alert)
202 }
203 }
204 }
205 return result
206 }
207
208 aAlerts := f(a.Collected())
209 bAlerts := f(b.Collected())
210
211 if len(aAlerts) != len(bAlerts) {
212 aAsString, err := alertsToString(aAlerts)
213 if err != nil {
214 return false, err
215 }
216 bAsString, err := alertsToString(bAlerts)
217 if err != nil {
218 return false, err
219 }
220
221 err = fmt.Errorf(
222 "first collector has %v alerts, second collector has %v alerts\n%v\n%v",
223 len(aAlerts), len(bAlerts),
224 aAsString, bAsString,
225 )
226 return false, err
227 }
228
229 for _, aAlert := range aAlerts {
230 found := false
231 for _, bAlert := range bAlerts {
232 if equalAlerts(aAlert, bAlert, opts) {
233 found = true
234 break
235 }
236 }
237
238 if !found {
239 aAsString, err := alertsToString([]*models.GettableAlert{aAlert})
240 if err != nil {
241 return false, err
242 }
243 bAsString, err := alertsToString(bAlerts)
244 if err != nil {
245 return false, err
246 }
247
248 err = fmt.Errorf(
249 "could not find matching alert for alert from first collector\n%v\nin alerts of second collector\n%v",
250 aAsString, bAsString,
251 )
252
253 return false, err
254 }
255 }
256
257 return true, nil
258 }
259
View as plain text