@ -2156,3 +2156,101 @@ func TestStaleResultsHandler(t *testing.T) {
assert . Equal ( t , tc . finalStateCount , len ( existingStatesForRule ) )
}
}
func TestStaleResults ( t * testing . T ) {
getCacheID := func ( t * testing . T , rule * models . AlertRule , result eval . Result ) string {
t . Helper ( )
labels := data . Labels { }
for key , value := range rule . Labels {
labels [ key ] = value
}
for key , value := range result . Instance {
labels [ key ] = value
}
lbls := models . InstanceLabels ( labels )
key , err := lbls . StringKey ( )
require . NoError ( t , err )
return key
}
checkExpectedStates := func ( t * testing . T , actual [ ] * state . State , expected map [ string ] struct { } ) {
t . Helper ( )
require . Len ( t , actual , len ( expected ) )
for _ , currentState := range actual {
_ , ok := expected [ currentState . CacheId ]
require . Truef ( t , ok , "State %s is not expected. States: %v" , currentState . CacheId , expected )
}
}
t . Run ( "should mark missing states as stale" , func ( t * testing . T ) {
// init
ctx := context . Background ( )
_ , dbstore := tests . SetupTestEnv ( t , 1 )
clk := clock . NewMock ( )
clk . Set ( time . Now ( ) )
st := state . NewManager ( log . New ( "test_stale_results_handler" ) , testMetrics . GetStateMetrics ( ) , nil , dbstore , dbstore , & dashboards . FakeDashboardService { } , & image . NoopImageService { } , clk )
orgID := rand . Int63 ( )
rule := tests . CreateTestAlertRule ( t , ctx , dbstore , 10 , orgID )
initResults := eval . Results {
eval . Result {
Instance : data . Labels { "test1" : "testValue1" } ,
State : eval . Alerting ,
EvaluatedAt : clk . Now ( ) ,
} ,
eval . Result {
Instance : data . Labels { "test1" : "testValue2" } ,
State : eval . Alerting ,
EvaluatedAt : clk . Now ( ) ,
} ,
eval . Result {
Instance : data . Labels { "test1" : "testValue3" } ,
State : eval . Normal ,
EvaluatedAt : clk . Now ( ) ,
} ,
}
initStates := map [ string ] struct { } {
getCacheID ( t , rule , initResults [ 0 ] ) : { } ,
getCacheID ( t , rule , initResults [ 1 ] ) : { } ,
getCacheID ( t , rule , initResults [ 2 ] ) : { } ,
}
// Init
processed := st . ProcessEvalResults ( ctx , clk . Now ( ) , rule , initResults , nil )
checkExpectedStates ( t , processed , initStates )
currentStates := st . GetStatesForRuleUID ( orgID , rule . UID )
checkExpectedStates ( t , currentStates , initStates )
staleDuration := 2 * time . Duration ( rule . IntervalSeconds ) * time . Second
clk . Add ( staleDuration )
results := eval . Results {
eval . Result {
Instance : data . Labels { "test1" : "testValue1" } ,
State : eval . Alerting ,
EvaluatedAt : clk . Now ( ) ,
} ,
}
clk . Add ( time . Nanosecond ) // we use time now when calculate stale states. Evaluation tick and real time are not the same. usually, difference is way greater than nanosecond.
expectedStaleReturned := getCacheID ( t , rule , initResults [ 1 ] )
processed = st . ProcessEvalResults ( ctx , clk . Now ( ) , rule , results , nil )
checkExpectedStates ( t , processed , map [ string ] struct { } {
getCacheID ( t , rule , results [ 0 ] ) : { } ,
expectedStaleReturned : { } ,
} )
for _ , s := range processed {
if s . CacheId == expectedStaleReturned {
assert . Truef ( t , s . Resolved , "Returned stale state should have Resolved set to true" )
assert . Equal ( t , eval . Normal , s . State )
assert . Equal ( t , models . StateReasonMissingSeries , s . StateReason )
break
}
}
currentStates = st . GetStatesForRuleUID ( orgID , rule . UID )
checkExpectedStates ( t , currentStates , map [ string ] struct { } {
getCacheID ( t , rule , results [ 0 ] ) : { } ,
} )
} )
}