From 79884c781a8465499d79505bc11ab093f7574316 Mon Sep 17 00:00:00 2001 From: UUBulb <35923940+uubulb@users.noreply.github.com> Date: Fri, 7 Mar 2025 20:23:18 +0800 Subject: [PATCH] fix: ignore the duration of out-of-bound rules (#1019) --- model/alertrule.go | 6 +- model/alertrule_test.go | 310 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 313 insertions(+), 3 deletions(-) create mode 100644 model/alertrule_test.go diff --git a/model/alertrule.go b/model/alertrule.go index fe73219..f347044 100644 --- a/model/alertrule.go +++ b/model/alertrule.go @@ -109,12 +109,12 @@ func (r *AlertRule) Check(points [][]bool) (int, bool) { continue } else { // 常规报警 - if duration > durations[ruleIndex] { - durations[ruleIndex] = duration - } if hasPassedRule = boundCheck(len(points), duration, hasPassedRule); hasPassedRule { continue } + if duration > durations[ruleIndex] { + durations[ruleIndex] = duration + } total, fail := duration, 0 for timeTick := len(points) - duration; timeTick < len(points); timeTick++ { if !points[timeTick][ruleIndex] { diff --git a/model/alertrule_test.go b/model/alertrule_test.go new file mode 100644 index 0000000..6b327e6 --- /dev/null +++ b/model/alertrule_test.go @@ -0,0 +1,310 @@ +package model + +import ( + "slices" + "testing" +) + +type arSt struct { + msg string + rule *AlertRule + points [][]bool + expD int + exp bool +} + +func TestCycleRules(t *testing.T) { + cases := []arSt{ + { + rule: &AlertRule{ + Rules: []*Rule{ + { + Type: "_cycle", + }, + }, + }, + msg: "CyclePass", + points: [][]bool{{false}, {true}}, + expD: 1, + exp: true, + }, + { + rule: &AlertRule{ + Rules: []*Rule{ + { + Type: "_cycle", + }, + }, + }, + msg: "CycleFail", + points: [][]bool{{true}, {false}}, + expD: 1, + exp: false, + }, + } + + for _, c := range cases { + d, passed := c.rule.Check(c.points) + assertEq(t, c.msg, c.expD, d) + assertEq(t, c.msg, c.exp, passed) + } +} + +func TestOfflineRules(t *testing.T) { + cases := []arSt{ + { + rule: &AlertRule{ + Rules: []*Rule{ + { + Type: "offline", + Duration: 10, + }, + }, + }, + msg: "OfflineLast", + points: append([][]bool{{true}}, repeat([]bool{false}, 9)...), + expD: 10, + exp: true, + }, + { + rule: &AlertRule{ + Rules: []*Rule{ + { + Type: "offline", + Duration: 10, + }, + }, + }, + msg: "OfflineMiddle", + points: mod(repeat([]bool{false}, 10), true, 5), + expD: 5, + exp: true, + }, + { + rule: &AlertRule{ + Rules: []*Rule{ + { + Type: "offline", + Duration: 10, + }, + }, + }, + msg: "OfflineFirst", + points: mod(repeat([]bool{false}, 10), true, 9), + expD: 1, + exp: true, + }, + { + rule: &AlertRule{ + Rules: []*Rule{ + { + Type: "offline", + Duration: 10, + }, + }, + }, + msg: "OfflineBoundCheck", + points: repeat([]bool{false}, 9), + expD: 0, + exp: true, + }, + } + + for _, c := range cases { + d, passed := c.rule.Check(c.points) + assertEq(t, c.msg, c.expD, d) + assertEq(t, c.msg, c.exp, passed) + } +} + +func TestGeneralRules(t *testing.T) { + cases := []arSt{ + { + rule: &AlertRule{ + Rules: []*Rule{ + { + Duration: 10, + }, + }, + }, + msg: "GeneralFail", + points: repeat([]bool{false}, 10), + expD: 10, + exp: false, + }, + { + rule: &AlertRule{ + Rules: []*Rule{ + { + Duration: 10, + }, + }, + }, + msg: "GeneralFail80%", + points: slices.Concat(repeat([]bool{false}, 8), repeat([]bool{true}, 2)), + expD: 10, + exp: false, + }, + { + rule: &AlertRule{ + Rules: []*Rule{ + { + Duration: 10, + }, + }, + }, + msg: "GeneralPass30%", + points: slices.Concat(repeat([]bool{false}, 7), repeat([]bool{true}, 3)), + expD: 10, + exp: true, + }, + { + rule: &AlertRule{ + Rules: []*Rule{ + { + Duration: 10, + }, + }, + }, + msg: "GeneralPass", + points: slices.Concat(repeat([]bool{false}, 4), repeat([]bool{true}, 6)), + expD: 10, + exp: true, + }, + { + rule: &AlertRule{ + Rules: []*Rule{ + { + Duration: 10, + }, + }, + }, + msg: "GeneralBoundCheck", + points: repeat([]bool{false}, 9), + expD: 0, + exp: true, + }, + } + + for _, c := range cases { + d, passed := c.rule.Check(c.points) + assertEq(t, c.msg, c.expD, d) + assertEq(t, c.msg, c.exp, passed) + } +} + +func TestCombinedRules(t *testing.T) { + cases := []arSt{ + { + rule: &AlertRule{ + Rules: []*Rule{ + { + Type: "offline", + Duration: 10, + }, + { + Duration: 10, + }, + }, + }, + msg: "OfflineGeneralOfflinePass", + points: slices.Concat(repeat([]bool{false, true}, 2), repeat([]bool{true, false}, 8)), + expD: 1, + exp: true, + }, + { + rule: &AlertRule{ + Rules: []*Rule{ + { + Type: "offline", + Duration: 10, + }, + { + Duration: 10, + }, + }, + }, + msg: "OfflineGeneralOfflineFail", + points: slices.Concat(repeat([]bool{false, false}, 2), repeat([]bool{false, true}, 8)), + expD: 10, + exp: true, + }, + { + rule: &AlertRule{ + Rules: []*Rule{ + { + Duration: 10, + }, + { + Type: "offline", + Duration: 10, + }, + }, + }, + msg: "GeneralOffline", + points: slices.Concat(repeat([]bool{false, true}, 2), repeat([]bool{true, false}, 8)), + expD: 10, + exp: true, + }, + { + rule: &AlertRule{ + Rules: []*Rule{ + { + Duration: 10, + }, + { + Duration: 30, + }, + }, + }, + msg: "GeneralGeneral", + points: slices.Concat(repeat([]bool{false, true}, 2), repeat([]bool{false, false}, 28)), + expD: 30, + exp: false, + }, + { + rule: &AlertRule{ + Rules: []*Rule{ + { + Duration: 10, + }, + { + Duration: 30, + }, + }, + }, + msg: "CombinedBoundCheck", + points: slices.Concat(repeat([]bool{false, true}, 2), repeat([]bool{false, false}, 27)), + expD: 10, + exp: true, + }, + } + + for _, c := range cases { + d, passed := c.rule.Check(c.points) + assertEq(t, c.msg, c.expD, d) + assertEq(t, c.msg, c.exp, passed) + } +} + +func repeat[S ~[]E, E any](x S, count int) []S { + var slices []S + for range count { + tmp := make([]E, len(x)) + copy(tmp, x) + slices = append(slices, tmp) + } + return slices +} + +func mod[S ~[][]E, E any](x S, val E, i int) S { + x[i][0] = val + return x +} + +func assertEq(t *testing.T, msg string, exp, act any) { + t.Helper() + if exp != act { + t.Fatalf("failed to test for %s. exp=[%v] but act=[%v]", msg, exp, act) + } +}