minitor-go/monitor_test.go

333 lines
10 KiB
Go
Raw Normal View History

package main
import (
"testing"
"time"
)
// TestMonitorIsValid tests the Monitor.IsValid()
func TestMonitorIsValid(t *testing.T) {
cases := []struct {
monitor Monitor
expected bool
name string
}{
2022-01-27 00:34:31 +00:00
{Monitor{Command: []string{"echo", "test"}, AlertDown: []string{"log"}}, true, "Command only"},
{Monitor{ShellCommand: "echo test", AlertDown: []string{"log"}}, true, "CommandShell only"},
{Monitor{Command: []string{"echo", "test"}}, false, "No AlertDown"},
2019-10-04 23:23:48 +00:00
{Monitor{AlertDown: []string{"log"}}, false, "No commands"},
2022-01-27 00:34:31 +00:00
{Monitor{Command: []string{"echo", "test"}, AlertDown: []string{"log"}, AlertAfter: Ptr(-1)}, false, "Invalid alert threshold, -1"},
}
for _, c := range cases {
2024-11-14 19:35:26 +00:00
c := c
2021-05-11 04:00:58 +00:00
2024-11-14 19:35:26 +00:00
t.Run(c.name, func(t *testing.T) {
t.Parallel()
2021-05-11 04:00:58 +00:00
2024-11-14 19:35:26 +00:00
actual := c.monitor.IsValid()
if actual != c.expected {
t.Errorf("IsValid(%v), expected=%t actual=%t", c.name, c.expected, actual)
}
})
}
}
// TestMonitorShouldCheck tests the Monitor.ShouldCheck()
func TestMonitorShouldCheck(t *testing.T) {
timeNow := time.Now()
timeTenSecAgo := time.Now().Add(time.Second * -10)
timeTwentySecAgo := time.Now().Add(time.Second * -20)
2022-01-27 00:34:31 +00:00
fifteenSeconds := time.Second * 15
cases := []struct {
monitor Monitor
expected bool
name string
}{
{Monitor{}, true, "Empty"},
2022-01-27 00:34:31 +00:00
{Monitor{lastCheck: timeNow, CheckInterval: fifteenSeconds}, false, "Just checked"},
{Monitor{lastCheck: timeTenSecAgo, CheckInterval: fifteenSeconds}, false, "-10s"},
{Monitor{lastCheck: timeTwentySecAgo, CheckInterval: fifteenSeconds}, true, "-20s"},
}
for _, c := range cases {
2024-11-14 19:35:26 +00:00
c := c
t.Run(c.name, func(t *testing.T) {
t.Parallel()
actual := c.monitor.ShouldCheck()
if actual != c.expected {
t.Errorf("ShouldCheck(%v), expected=%t actual=%t", c.name, c.expected, actual)
}
})
}
}
// TestMonitorIsUp tests the Monitor.IsUp()
func TestMonitorIsUp(t *testing.T) {
cases := []struct {
monitor Monitor
expected bool
name string
}{
{Monitor{}, true, "Empty"},
{Monitor{alertCount: 1}, false, "Has alert"},
{Monitor{alertCount: -1}, false, "Negative alerts"},
{Monitor{alertCount: 0}, true, "No alerts"},
}
for _, c := range cases {
2024-11-14 19:35:26 +00:00
c := c
2021-05-11 04:00:58 +00:00
2024-11-14 19:35:26 +00:00
t.Run(c.name, func(t *testing.T) {
t.Parallel()
2021-05-11 04:00:58 +00:00
2024-11-14 19:35:26 +00:00
actual := c.monitor.IsUp()
if actual != c.expected {
t.Errorf("IsUp(%v), expected=%t actual=%t", c.name, c.expected, actual)
}
})
}
}
// TestMonitorGetAlertNames tests that proper alert names are returned
func TestMonitorGetAlertNames(t *testing.T) {
cases := []struct {
monitor Monitor
up bool
expected []string
name string
}{
{Monitor{}, true, nil, "Empty up"},
{Monitor{}, false, nil, "Empty down"},
{Monitor{AlertUp: []string{"alert"}}, true, []string{"alert"}, "Return up"},
{Monitor{AlertDown: []string{"alert"}}, false, []string{"alert"}, "Return down"},
}
for _, c := range cases {
2024-11-14 19:35:26 +00:00
c := c
2021-05-11 04:00:58 +00:00
2024-11-14 19:35:26 +00:00
t.Run(c.name, func(t *testing.T) {
t.Parallel()
2021-05-11 04:00:58 +00:00
2024-11-14 19:35:26 +00:00
actual := c.monitor.GetAlertNames(c.up)
if !EqualSliceString(actual, c.expected) {
t.Errorf("GetAlertNames(%v), expected=%v actual=%v", c.name, c.expected, actual)
}
})
}
}
// TestMonitorSuccess tests the Monitor.success()
func TestMonitorSuccess(t *testing.T) {
cases := []struct {
monitor Monitor
expectNotice bool
name string
}{
{Monitor{}, false, "Empty"},
{Monitor{alertCount: 0}, false, "No alerts"},
{Monitor{alertCount: 1}, true, "Has alert"},
}
for _, c := range cases {
2024-11-14 19:35:26 +00:00
c := c
2021-05-11 04:00:58 +00:00
2024-11-14 19:35:26 +00:00
t.Run(c.name, func(t *testing.T) {
t.Parallel()
2021-05-11 04:00:58 +00:00
2024-11-14 19:35:26 +00:00
notice := c.monitor.success()
hasNotice := (notice != nil)
2021-05-11 04:00:58 +00:00
2024-11-14 19:35:26 +00:00
if hasNotice != c.expectNotice {
t.Errorf("success(%v), expected=%t actual=%t", c.name, c.expectNotice, hasNotice)
}
})
}
}
// TestMonitorFailureAlertAfter tests that alerts will not trigger until
// hitting the threshold provided by AlertAfter
func TestMonitorFailureAlertAfter(t *testing.T) {
2022-01-27 00:34:31 +00:00
var alertEvery int = 1
cases := []struct {
monitor Monitor
expectNotice bool
name string
}{
2022-01-27 00:34:31 +00:00
{Monitor{AlertAfter: Ptr(1)}, true, "Empty"}, // Defaults to true because and AlertEvery default to 0
{Monitor{failureCount: 0, AlertAfter: Ptr(1), AlertEvery: &alertEvery}, true, "Alert after 1: first failure"},
{Monitor{failureCount: 1, AlertAfter: Ptr(1), AlertEvery: &alertEvery}, true, "Alert after 1: second failure"},
{Monitor{failureCount: 0, AlertAfter: Ptr(20), AlertEvery: &alertEvery}, false, "Alert after 20: first failure"},
{Monitor{failureCount: 19, AlertAfter: Ptr(20), AlertEvery: &alertEvery}, true, "Alert after 20: 20th failure"},
{Monitor{failureCount: 20, AlertAfter: Ptr(20), AlertEvery: &alertEvery}, true, "Alert after 20: 21st failure"},
}
for _, c := range cases {
2024-11-14 19:35:26 +00:00
c := c
2021-05-11 04:00:58 +00:00
2024-11-14 19:35:26 +00:00
t.Run(c.name, func(t *testing.T) {
t.Parallel()
2021-05-11 04:00:58 +00:00
2024-11-14 19:35:26 +00:00
notice := c.monitor.failure()
hasNotice := (notice != nil)
2021-05-11 04:00:58 +00:00
2024-11-14 19:35:26 +00:00
if hasNotice != c.expectNotice {
t.Errorf("failure(%v), expected=%t actual=%t", c.name, c.expectNotice, hasNotice)
}
})
}
}
// TestMonitorFailureAlertEvery tests that alerts will trigger
// on the expected intervals
func TestMonitorFailureAlertEvery(t *testing.T) {
2022-01-27 00:34:31 +00:00
var alertEvery0, alertEvery1, alertEvery2 int
alertEvery0 = 0
alertEvery1 = 1
alertEvery2 = 2
cases := []struct {
monitor Monitor
expectNotice bool
name string
}{
/*
2019-10-03 23:30:49 +00:00
TODO: Actually found a bug in original implementation. There is an inconsistency in the way AlertAfter is treated.
For "First alert only" (ie. AlertEvery=0), it is the number of failures to ignore before alerting, so AlertAfter=1
will ignore the first failure and alert on the second failure
For other intervals (ie. AlertEvery=1), it is essentially indexed on one. Essentially making AlertAfter=1 trigger
on the first failure.
2019-10-03 23:30:49 +00:00
For usabilty, this should be consistent. Consistent with what though? minitor-py? Or itself? Dun dun duuuunnnnn!
*/
2022-01-27 00:34:31 +00:00
{Monitor{AlertAfter: Ptr(1)}, true, "Empty"}, // Defaults to true because AlertAfter and AlertEvery default to nil
// Alert first time only, after 1
2022-01-27 00:34:31 +00:00
{Monitor{failureCount: 0, AlertAfter: Ptr(1), AlertEvery: &alertEvery0}, true, "Alert first time only after 1: first failure"},
{Monitor{failureCount: 1, AlertAfter: Ptr(1), AlertEvery: &alertEvery0}, false, "Alert first time only after 1: second failure"},
{Monitor{failureCount: 2, AlertAfter: Ptr(1), AlertEvery: &alertEvery0}, false, "Alert first time only after 1: third failure"},
// Alert every time, after 1
2022-01-27 00:34:31 +00:00
{Monitor{failureCount: 0, AlertAfter: Ptr(1), AlertEvery: &alertEvery1}, true, "Alert every time after 1: first failure"},
{Monitor{failureCount: 1, AlertAfter: Ptr(1), AlertEvery: &alertEvery1}, true, "Alert every time after 1: second failure"},
{Monitor{failureCount: 2, AlertAfter: Ptr(1), AlertEvery: &alertEvery1}, true, "Alert every time after 1: third failure"},
// Alert every other time, after 1
2022-01-27 00:34:31 +00:00
{Monitor{failureCount: 0, AlertAfter: Ptr(1), AlertEvery: &alertEvery2}, true, "Alert every other time after 1: first failure"},
{Monitor{failureCount: 1, AlertAfter: Ptr(1), AlertEvery: &alertEvery2}, false, "Alert every other time after 1: second failure"},
{Monitor{failureCount: 2, AlertAfter: Ptr(1), AlertEvery: &alertEvery2}, true, "Alert every other time after 1: third failure"},
{Monitor{failureCount: 3, AlertAfter: Ptr(1), AlertEvery: &alertEvery2}, false, "Alert every other time after 1: fourth failure"},
}
for _, c := range cases {
2024-11-14 19:35:26 +00:00
c := c
2019-10-03 03:52:34 +00:00
2024-11-14 19:35:26 +00:00
t.Run(c.name, func(t *testing.T) {
t.Parallel()
2021-05-11 04:00:58 +00:00
2024-11-14 19:35:26 +00:00
notice := c.monitor.failure()
hasNotice := (notice != nil)
2021-05-11 04:00:58 +00:00
2024-11-14 19:35:26 +00:00
if hasNotice != c.expectNotice {
t.Errorf("failure(%v), expected=%t actual=%t", c.name, c.expectNotice, hasNotice)
}
})
2019-10-03 03:52:34 +00:00
}
}
// TestMonitorFailureExponential tests that alerts will trigger
// with an exponential backoff after repeated failures
func TestMonitorFailureExponential(t *testing.T) {
2022-01-27 00:34:31 +00:00
var alertEveryExp int = -1
2019-10-03 03:52:34 +00:00
cases := []struct {
expectNotice bool
name string
}{
{true, "Alert exponential after 1: first failure"},
{true, "Alert exponential after 1: second failure"},
{false, "Alert exponential after 1: third failure"},
{true, "Alert exponential after 1: fourth failure"},
{false, "Alert exponential after 1: fifth failure"},
{false, "Alert exponential after 1: sixth failure"},
{false, "Alert exponential after 1: seventh failure"},
{true, "Alert exponential after 1: eighth failure"},
}
// Unlike previous tests, this one requires a static Monitor with repeated
// calls to the failure method
2022-01-27 00:34:31 +00:00
monitor := Monitor{failureCount: 0, AlertAfter: Ptr(1), AlertEvery: &alertEveryExp}
2021-05-11 04:00:58 +00:00
2019-10-03 03:52:34 +00:00
for _, c := range cases {
2024-11-14 19:35:26 +00:00
t.Run(c.name, func(t *testing.T) {
// NOTE: These tests are not parallel because they rely on the state of the Monitor
notice := monitor.failure()
hasNotice := (notice != nil)
if hasNotice != c.expectNotice {
t.Errorf("failure(%v), expected=%t actual=%t", c.name, c.expectNotice, hasNotice)
}
})
}
}
2019-10-03 16:22:13 +00:00
// TestMonitorCheck tests successful and failed commands and shell commands
func TestMonitorCheck(t *testing.T) {
type expected struct {
isSuccess bool
hasNotice bool
lastOutput string
}
2021-05-11 04:00:58 +00:00
2019-10-03 16:22:13 +00:00
cases := []struct {
monitor Monitor
expect expected
name string
}{
{
2022-01-27 00:34:31 +00:00
Monitor{Command: []string{"echo", "success"}},
2019-10-03 16:22:13 +00:00
expected{isSuccess: true, hasNotice: false, lastOutput: "success\n"},
"Test successful command",
},
{
2022-01-27 00:34:31 +00:00
Monitor{ShellCommand: "echo success"},
2019-10-03 16:22:13 +00:00
expected{isSuccess: true, hasNotice: false, lastOutput: "success\n"},
"Test successful command shell",
},
{
2022-01-27 00:34:31 +00:00
Monitor{Command: []string{"total", "failure"}},
2019-10-03 16:22:13 +00:00
expected{isSuccess: false, hasNotice: true, lastOutput: ""},
"Test failed command",
},
{
2022-01-27 00:34:31 +00:00
Monitor{ShellCommand: "false"},
expected{isSuccess: false, hasNotice: true, lastOutput: ""},
2019-10-03 16:22:13 +00:00
"Test failed command shell",
},
}
for _, c := range cases {
2024-11-14 19:35:26 +00:00
c := c
t.Run(c.name, func(t *testing.T) {
t.Parallel()
isSuccess, notice := c.monitor.Check()
if isSuccess != c.expect.isSuccess {
t.Errorf("Check(%v) (success), expected=%t actual=%t", c.name, c.expect.isSuccess, isSuccess)
}
hasNotice := (notice != nil)
if hasNotice != c.expect.hasNotice {
t.Errorf("Check(%v) (notice), expected=%t actual=%t", c.name, c.expect.hasNotice, hasNotice)
}
lastOutput := c.monitor.lastOutput
if lastOutput != c.expect.lastOutput {
t.Errorf("Check(%v) (output), expected=%v actual=%v", c.name, c.expect.lastOutput, lastOutput)
}
})
2019-10-03 16:22:13 +00:00
}
}