@@ -461,38 +461,42 @@ type flatScorer struct{}
461461
462462func (flatScorer ) Score (context.Context , entity.Batch ) (float64 , error ) { return 0.5 , nil }
463463
464- // PathPassed on a green all-succeed build is the join the generator exists to
465- // consume: same scorer price, different evidence, different rank.
466- func TestBestFirst_EvidencePathPassedRanksTheGreenDependencyFirst ( t * testing. T ) {
467- batches := []entity. Batch {
468- { ID : "q/built" , State : entity . BatchStateSpeculating },
469- { ID : "q/fresh" , State : entity . BatchStateSpeculating },
470- { ID : "q/H" , State : entity . BatchStateSpeculating , Dependencies : [] string { "q/built" , "q/fresh" }},
471- }
472- built := entity.SpeculationPathSet {
464+ func evidencePredictor ( t * testing. T , factors evidence. Factors ) predictor. Predictor {
465+ t . Helper ()
466+ pred , err := evidence . New (predictor. Config { QueueName : "q" }, flatScorer {}, factors , tally . NoopScope )
467+ require . NoError ( t , err )
468+ return pred
469+ }
470+
471+ func allSucceedSet ( head string , status entity. SpeculationPathStatus ) entity. SpeculationPathSet {
472+ return entity.SpeculationPathSet {
473473 Queue : "q" ,
474- Head : "q/built" ,
474+ Head : head ,
475475 Paths : []entity.SpeculationPathEntry {{
476476 ID : "p1" ,
477- Status : entity . SpeculationPathStatusPassed ,
477+ Status : status ,
478478 Path : entity.SpeculationPath {
479- Head : "q/built" ,
479+ Head : head ,
480480 Dependencies : []entity.PathDependency {{
481481 Batch : "q/dep0" ,
482482 Assumption : entity .DependencyAssumptionSucceeds ,
483483 }},
484484 },
485485 }},
486486 }
487- pred , err := evidence .New (
488- predictor.Config {QueueName : "q" },
489- flatScorer {},
490- evidence.Factors {PathPassed : 9 , PathFailed : 1 , Merging : 1 , Cancelling : 1 },
491- tally .NoopScope ,
492- )
493- require .NoError (t , err )
487+ }
494488
495- iter , err := New (pred ).Generate (context .Background (), batches , []entity.SpeculationPathSet {built })
489+ // PathPassed on a green all-succeed build is the join the generator exists to
490+ // consume: same scorer price, different evidence, different rank.
491+ func TestBestFirst_EvidencePathPassedRanksTheGreenDependencyFirst (t * testing.T ) {
492+ batches := []entity.Batch {
493+ {ID : "q/built" , State : entity .BatchStateSpeculating },
494+ {ID : "q/fresh" , State : entity .BatchStateSpeculating },
495+ {ID : "q/H" , State : entity .BatchStateSpeculating , Dependencies : []string {"q/built" , "q/fresh" }},
496+ }
497+ pred := evidencePredictor (t , evidence.Factors {PathPassed : 9 , PathFailed : 1 , Merging : 1 , Cancelling : 1 })
498+
499+ iter , err := New (pred ).Generate (context .Background (), batches , []entity.SpeculationPathSet {allSucceedSet ("q/built" , entity .SpeculationPathStatusPassed )})
496500 require .NoError (t , err )
497501 cands := forHead (drainAll (t , iter ), "q/H" )
498502 require .NotEmpty (t , cands )
@@ -511,25 +515,56 @@ func TestBestFirst_EvidencePathPassedRanksTheGreenDependencyFirst(t *testing.T)
511515 assert .Greater (t , cands [0 ].RankingScore , failScore )
512516}
513517
518+ func TestBestFirst_EvidencePathFailedPrefersTheFailedSide (t * testing.T ) {
519+ batches := []entity.Batch {
520+ {ID : "q/failed" , State : entity .BatchStateSpeculating },
521+ {ID : "q/H" , State : entity .BatchStateSpeculating , Dependencies : []string {"q/failed" }},
522+ }
523+ pred := evidencePredictor (t , evidence.Factors {PathPassed : 1 , PathFailed : 0.25 , Merging : 1 , Cancelling : 1 })
524+
525+ iter , err := New (pred ).Generate (context .Background (), batches , []entity.SpeculationPathSet {allSucceedSet ("q/failed" , entity .SpeculationPathStatusFailed )})
526+ require .NoError (t , err )
527+ cands := forHead (drainAll (t , iter ), "q/H" )
528+ require .Len (t , cands , 2 )
529+ assert .Equal (t , entity .DependencyAssumptionFails , assumptionFor (cands [0 ].Path , "q/failed" ))
530+ assert .Equal (t , entity .DependencyAssumptionSucceeds , assumptionFor (cands [1 ].Path , "q/failed" ))
531+ assert .Greater (t , cands [0 ].RankingScore , cands [1 ].RankingScore )
532+ }
533+
534+ func TestBestFirst_EvidenceCancellingPrefersTheFailedSide (t * testing.T ) {
535+ batches := []entity.Batch {
536+ {ID : "q/stopping" , State : entity .BatchStateCancelling },
537+ {ID : "q/H" , State : entity .BatchStateSpeculating , Dependencies : []string {"q/stopping" }},
538+ }
539+ pred := evidencePredictor (t , evidence.Factors {PathPassed : 1 , PathFailed : 1 , Merging : 1 , Cancelling : 0.25 })
540+
541+ iter , err := New (pred ).Generate (context .Background (), batches , nil )
542+ require .NoError (t , err )
543+ cands := drainAll (t , iter )
544+ require .Len (t , cands , 2 )
545+ assert .Equal (t , entity .DependencyAssumptionFails , assumptionFor (cands [0 ].Path , "q/stopping" ))
546+ assert .Equal (t , entity .DependencyAssumptionSucceeds , assumptionFor (cands [1 ].Path , "q/stopping" ))
547+ assert .Greater (t , cands [0 ].RankingScore , cands [1 ].RankingScore )
548+ }
549+
514550// A merging dependency is still in progress — the merge can fail — so it stays
515- // an open question here like any other. Whether a path betting against it is
516- // worth funding is a matter of price, which is the scorer's to say, not a
517- // state the search hard-codes.
551+ // an open question here like any other. How much it is worth is a predictor
552+ // price, not a fact the search hard-codes.
518553func TestBestFirst_MergingDependencyStaysOpen (t * testing.T ) {
519554 batches := []entity.Batch {
520555 {ID : "q/landing" , State : entity .BatchStateMerging },
521556 {ID : "q/H" , State : entity .BatchStateSpeculating , Dependencies : []string {"q/landing" }},
522557 }
523- sc := newCountingPredictor ( map [ string ] float64 { "q/landing" : 0.9 })
558+ pred := evidencePredictor ( t , evidence. Factors { PathPassed : 1 , PathFailed : 1 , Merging : 19 , Cancelling : 1 })
524559
525- iter , err := New (sc ).Generate (context .Background (), batches , nil )
560+ iter , err := New (pred ).Generate (context .Background (), batches , nil )
526561 require .NoError (t , err )
527562 cands := drainAll (t , iter )
528563
529- assert .Equal (t , 1 , sc .calls ["q/landing" ], "a merging dependency is priced like any other" )
530564 require .Len (t , cands , 2 , "both sides of a merge that has not landed yet" )
531565 assert .Equal (t , entity .DependencyAssumptionSucceeds , assumptionFor (cands [0 ].Path , "q/landing" ))
532566 assert .Equal (t , entity .DependencyAssumptionFails , assumptionFor (cands [1 ].Path , "q/landing" ))
567+ assert .Greater (t , cands [0 ].RankingScore , cands [1 ].RankingScore )
533568}
534569
535570func TestBestFirst_GeneratesOnlyWhatIsPulled (t * testing.T ) {
0 commit comments