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