1// Copyright 2015 Prometheus Team
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14package types
15
16import (
17	"reflect"
18	"sort"
19	"strconv"
20	"testing"
21	"time"
22
23	"github.com/prometheus/client_golang/prometheus"
24	"github.com/prometheus/common/model"
25	"github.com/stretchr/testify/require"
26)
27
28func TestAlertMerge(t *testing.T) {
29	now := time.Now()
30
31	// By convention, alert A is always older than alert B.
32	pairs := []struct {
33		A, B, Res *Alert
34	}{
35		{
36			// Both alerts have the Timeout flag set.
37			// StartsAt is defined by Alert A.
38			// EndsAt is defined by Alert B.
39			A: &Alert{
40				Alert: model.Alert{
41					StartsAt: now.Add(-2 * time.Minute),
42					EndsAt:   now.Add(2 * time.Minute),
43				},
44				UpdatedAt: now,
45				Timeout:   true,
46			},
47			B: &Alert{
48				Alert: model.Alert{
49					StartsAt: now.Add(-time.Minute),
50					EndsAt:   now.Add(3 * time.Minute),
51				},
52				UpdatedAt: now.Add(time.Minute),
53				Timeout:   true,
54			},
55			Res: &Alert{
56				Alert: model.Alert{
57					StartsAt: now.Add(-2 * time.Minute),
58					EndsAt:   now.Add(3 * time.Minute),
59				},
60				UpdatedAt: now.Add(time.Minute),
61				Timeout:   true,
62			},
63		},
64		{
65			// Alert A has the Timeout flag set while Alert B has it unset.
66			// StartsAt is defined by Alert A.
67			// EndsAt is defined by Alert B.
68			A: &Alert{
69				Alert: model.Alert{
70					StartsAt: now.Add(-time.Minute),
71					EndsAt:   now.Add(3 * time.Minute),
72				},
73				UpdatedAt: now,
74				Timeout:   true,
75			},
76			B: &Alert{
77				Alert: model.Alert{
78					StartsAt: now,
79					EndsAt:   now.Add(2 * time.Minute),
80				},
81				UpdatedAt: now.Add(time.Minute),
82			},
83			Res: &Alert{
84				Alert: model.Alert{
85					StartsAt: now.Add(-time.Minute),
86					EndsAt:   now.Add(2 * time.Minute),
87				},
88				UpdatedAt: now.Add(time.Minute),
89			},
90		},
91		{
92			// Alert A has the Timeout flag unset while Alert B has it set.
93			// StartsAt is defined by Alert A.
94			// EndsAt is defined by Alert A.
95			A: &Alert{
96				Alert: model.Alert{
97					StartsAt: now.Add(-time.Minute),
98					EndsAt:   now.Add(3 * time.Minute),
99				},
100				UpdatedAt: now,
101			},
102			B: &Alert{
103				Alert: model.Alert{
104					StartsAt: now,
105					EndsAt:   now.Add(2 * time.Minute),
106				},
107				UpdatedAt: now.Add(time.Minute),
108				Timeout:   true,
109			},
110			Res: &Alert{
111				Alert: model.Alert{
112					StartsAt: now.Add(-time.Minute),
113					EndsAt:   now.Add(3 * time.Minute),
114				},
115				UpdatedAt: now.Add(time.Minute),
116				Timeout:   true,
117			},
118		},
119		{
120			// Both alerts have the Timeout flag unset and are not resolved.
121			// StartsAt is defined by Alert A.
122			// EndsAt is defined by Alert A.
123			A: &Alert{
124				Alert: model.Alert{
125					StartsAt: now.Add(-time.Minute),
126					EndsAt:   now.Add(3 * time.Minute),
127				},
128				UpdatedAt: now,
129			},
130			B: &Alert{
131				Alert: model.Alert{
132					StartsAt: now,
133					EndsAt:   now.Add(2 * time.Minute),
134				},
135				UpdatedAt: now.Add(time.Minute),
136			},
137			Res: &Alert{
138				Alert: model.Alert{
139					StartsAt: now.Add(-time.Minute),
140					EndsAt:   now.Add(3 * time.Minute),
141				},
142				UpdatedAt: now.Add(time.Minute),
143			},
144		},
145		{
146			// Both alerts have the Timeout flag unset and are not resolved.
147			// StartsAt is defined by Alert A.
148			// EndsAt is defined by Alert B.
149			A: &Alert{
150				Alert: model.Alert{
151					StartsAt: now.Add(-time.Minute),
152					EndsAt:   now.Add(3 * time.Minute),
153				},
154				UpdatedAt: now,
155			},
156			B: &Alert{
157				Alert: model.Alert{
158					StartsAt: now.Add(-time.Minute),
159					EndsAt:   now.Add(4 * time.Minute),
160				},
161				UpdatedAt: now.Add(time.Minute),
162			},
163			Res: &Alert{
164				Alert: model.Alert{
165					StartsAt: now.Add(-time.Minute),
166					EndsAt:   now.Add(4 * time.Minute),
167				},
168				UpdatedAt: now.Add(time.Minute),
169			},
170		},
171		{
172			// Both alerts have the Timeout flag unset, A is resolved while B isn't.
173			// StartsAt is defined by Alert A.
174			// EndsAt is defined by Alert B.
175			A: &Alert{
176				Alert: model.Alert{
177					StartsAt: now.Add(-3 * time.Minute),
178					EndsAt:   now.Add(-time.Minute),
179				},
180				UpdatedAt: now,
181			},
182			B: &Alert{
183				Alert: model.Alert{
184					StartsAt: now.Add(-2 * time.Minute),
185					EndsAt:   now.Add(time.Minute),
186				},
187				UpdatedAt: now.Add(time.Minute),
188			},
189			Res: &Alert{
190				Alert: model.Alert{
191					StartsAt: now.Add(-3 * time.Minute),
192					EndsAt:   now.Add(time.Minute),
193				},
194				UpdatedAt: now.Add(time.Minute),
195			},
196		},
197		{
198			// Both alerts have the Timeout flag unset, B is resolved while A isn't.
199			// StartsAt is defined by Alert A.
200			// EndsAt is defined by Alert B.
201			A: &Alert{
202				Alert: model.Alert{
203					StartsAt: now.Add(-2 * time.Minute),
204					EndsAt:   now.Add(3 * time.Minute),
205				},
206				UpdatedAt: now,
207			},
208			B: &Alert{
209				Alert: model.Alert{
210					StartsAt: now.Add(-2 * time.Minute),
211					EndsAt:   now,
212				},
213				UpdatedAt: now.Add(time.Minute),
214			},
215			Res: &Alert{
216				Alert: model.Alert{
217					StartsAt: now.Add(-2 * time.Minute),
218					EndsAt:   now,
219				},
220				UpdatedAt: now.Add(time.Minute),
221			},
222		},
223		{
224			// Both alerts are resolved (EndsAt < now).
225			// StartsAt is defined by Alert B.
226			// EndsAt is defined by Alert A.
227			A: &Alert{
228				Alert: model.Alert{
229					StartsAt: now.Add(-3 * time.Minute),
230					EndsAt:   now.Add(-time.Minute),
231				},
232				UpdatedAt: now.Add(-time.Minute),
233			},
234			B: &Alert{
235				Alert: model.Alert{
236					StartsAt: now.Add(-4 * time.Minute),
237					EndsAt:   now.Add(-2 * time.Minute),
238				},
239				UpdatedAt: now.Add(time.Minute),
240			},
241			Res: &Alert{
242				Alert: model.Alert{
243					StartsAt: now.Add(-4 * time.Minute),
244					EndsAt:   now.Add(-1 * time.Minute),
245				},
246				UpdatedAt: now.Add(time.Minute),
247			},
248		},
249	}
250
251	for i, p := range pairs {
252		p := p
253		t.Run(strconv.Itoa(i), func(t *testing.T) {
254			if res := p.A.Merge(p.B); !reflect.DeepEqual(p.Res, res) {
255				t.Errorf("unexpected merged alert %#v", res)
256			}
257			if res := p.B.Merge(p.A); !reflect.DeepEqual(p.Res, res) {
258				t.Errorf("unexpected merged alert %#v", res)
259			}
260		})
261	}
262}
263
264func TestCalcSilenceState(t *testing.T) {
265
266	var (
267		pastStartTime = time.Now()
268		pastEndTime   = time.Now()
269
270		futureStartTime = time.Now().Add(time.Hour)
271		futureEndTime   = time.Now().Add(time.Hour)
272	)
273
274	expected := CalcSilenceState(futureStartTime, futureEndTime)
275	require.Equal(t, SilenceStatePending, expected)
276
277	expected = CalcSilenceState(pastStartTime, futureEndTime)
278	require.Equal(t, SilenceStateActive, expected)
279
280	expected = CalcSilenceState(pastStartTime, pastEndTime)
281	require.Equal(t, SilenceStateExpired, expected)
282}
283
284func TestSilenceExpired(t *testing.T) {
285	now := time.Now()
286	silence := Silence{StartsAt: now, EndsAt: now}
287	require.True(t, silence.Expired())
288
289	silence = Silence{StartsAt: now.Add(time.Hour), EndsAt: now.Add(time.Hour)}
290	require.True(t, silence.Expired())
291
292	silence = Silence{StartsAt: now, EndsAt: now.Add(time.Hour)}
293	require.False(t, silence.Expired())
294}
295
296func TestAlertSliceSort(t *testing.T) {
297	var (
298		a1 = &Alert{
299			Alert: model.Alert{
300				Labels: model.LabelSet{
301					"job":       "j1",
302					"instance":  "i1",
303					"alertname": "an1",
304				},
305			},
306		}
307		a2 = &Alert{
308			Alert: model.Alert{
309				Labels: model.LabelSet{
310					"job":       "j1",
311					"instance":  "i1",
312					"alertname": "an2",
313				},
314			},
315		}
316		a3 = &Alert{
317			Alert: model.Alert{
318				Labels: model.LabelSet{
319					"job":       "j2",
320					"instance":  "i1",
321					"alertname": "an1",
322				},
323			},
324		}
325		a4 = &Alert{
326			Alert: model.Alert{
327				Labels: model.LabelSet{
328					"alertname": "an1",
329				},
330			},
331		}
332		a5 = &Alert{
333			Alert: model.Alert{
334				Labels: model.LabelSet{
335					"alertname": "an2",
336				},
337			},
338		}
339	)
340
341	cases := []struct {
342		alerts AlertSlice
343		exp    AlertSlice
344	}{
345		{
346			alerts: AlertSlice{a2, a1},
347			exp:    AlertSlice{a1, a2},
348		},
349		{
350			alerts: AlertSlice{a3, a2, a1},
351			exp:    AlertSlice{a1, a2, a3},
352		},
353		{
354			alerts: AlertSlice{a4, a2, a4},
355			exp:    AlertSlice{a2, a4, a4},
356		},
357		{
358			alerts: AlertSlice{a5, a4},
359			exp:    AlertSlice{a4, a5},
360		},
361	}
362
363	for _, tc := range cases {
364		sort.Stable(tc.alerts)
365		if !reflect.DeepEqual(tc.alerts, tc.exp) {
366			t.Fatalf("expected %v but got %v", tc.exp, tc.alerts)
367		}
368	}
369}
370
371type fakeRegisterer struct {
372	registeredCollectors []prometheus.Collector
373}
374
375func (r *fakeRegisterer) Register(prometheus.Collector) error {
376	return nil
377}
378
379func (r *fakeRegisterer) MustRegister(c ...prometheus.Collector) {
380	r.registeredCollectors = append(r.registeredCollectors, c...)
381}
382
383func (r *fakeRegisterer) Unregister(prometheus.Collector) bool {
384	return false
385}
386
387func TestNewMarkerRegistersMetrics(t *testing.T) {
388	fr := fakeRegisterer{}
389	NewMarker(&fr)
390
391	if len(fr.registeredCollectors) == 0 {
392		t.Error("expected NewMarker to register metrics on the given registerer")
393	}
394}
395