150 lines
6.0 KiB
Go
150 lines
6.0 KiB
Go
package check
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
)
|
|
|
|
func assignerOn(instance string, online bool) PresenceEvent {
|
|
key, _ := delivery.PresencePartKey(delivery.AssignerModule, instance)
|
|
return PresenceEvent{Key: key, Online: online}
|
|
}
|
|
|
|
func compEvent(t *testing.T, v delivery.CompositionVerdict) CompositionEvent {
|
|
t.Helper()
|
|
data, err := delivery.EncodeCompositionVerdict(v)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return CompositionEvent{Value: data}
|
|
}
|
|
|
|
var passed = delivery.CompositionVerdict{Group: "feat/x", Heads: "0123456789ab", State: delivery.CompositionPass, SDK: "x", Machines: []string{"g14"}}
|
|
|
|
// 1297/B3d: with no assigner online, the wait answers ErrAssignerNotRunning as soon as both watches handed what stands.
|
|
func TestNoAssignerAnswersAtOnce(t *testing.T) {
|
|
presence, comps := make(chan PresenceEvent, 4), make(chan CompositionEvent, 4)
|
|
presence <- assignerOn("novox", false)
|
|
presence <- PresenceEvent{Settled: true}
|
|
comps <- CompositionEvent{Settled: true}
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
|
defer cancel()
|
|
if _, err := AwaitComposition(ctx, presence, comps); !errors.Is(err, ErrAssignerNotRunning) {
|
|
t.Fatalf("answered %v, want the assigner not running", err)
|
|
}
|
|
}
|
|
|
|
// 1297/B3d: with the assigner online it waits for the key, past a composition that waits, and answers the verdict.
|
|
func TestAnOnlineAssignerIsWaitedFor(t *testing.T) {
|
|
presence, comps := make(chan PresenceEvent, 4), make(chan CompositionEvent, 4)
|
|
presence <- assignerOn("novox", true)
|
|
presence <- PresenceEvent{Settled: true}
|
|
comps <- CompositionEvent{Settled: true}
|
|
done := make(chan error, 1)
|
|
var got delivery.CompositionVerdict
|
|
go func() {
|
|
var err error
|
|
got, err = AwaitComposition(context.Background(), presence, comps)
|
|
done <- err
|
|
}()
|
|
comps <- compEvent(t, delivery.CompositionVerdict{Group: "feat/x", Heads: "0123456789ab", State: delivery.CompositionWaits, Words: "w"})
|
|
select {
|
|
case err := <-done:
|
|
t.Fatalf("answered before the composition was written: %v", err)
|
|
case <-time.After(50 * time.Millisecond):
|
|
}
|
|
comps <- compEvent(t, passed)
|
|
if err := <-done; err != nil || got.State != delivery.CompositionPass {
|
|
t.Fatalf("answered %+v, %v", got, err)
|
|
}
|
|
// And the last instance going offline during a wait ends it as not running.
|
|
presence2, comps2 := make(chan PresenceEvent, 4), make(chan CompositionEvent, 4)
|
|
presence2 <- assignerOn("novox", true)
|
|
presence2 <- PresenceEvent{Settled: true}
|
|
comps2 <- CompositionEvent{Settled: true}
|
|
presence2 <- assignerOn("novox", false)
|
|
if _, err := AwaitComposition(context.Background(), presence2, comps2); !errors.Is(err, ErrAssignerNotRunning) {
|
|
t.Errorf("the assigner going offline answered %v", err)
|
|
}
|
|
}
|
|
|
|
// 1297/B3d: a passing check with no assigner says at once, loud, that its composition is not judged; never a pass of it.
|
|
func TestAPassingCheckWithNoAssignerSaysTheCompositionIsNotJudged(t *testing.T) {
|
|
b := newBench(t)
|
|
b.ex.Compositions = &fakeCompositions{err: ErrAssignerNotRunning}
|
|
v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))).Verdict
|
|
if v.Composition == nil || v.Composition.State != delivery.CompositionNotRun || v.Composition.Words != delivery.AssignerNotRunning {
|
|
t.Fatalf("composition = %+v", v.Composition)
|
|
}
|
|
if words := compositionWords(v.Composition, 0); words != "composition not judged: the assigner is not running" {
|
|
t.Errorf("the line says %q", words)
|
|
}
|
|
}
|
|
|
|
// 1297/B3d: the assigner starting writes the composition later, and the checker takes it into the verdict that said it
|
|
// was not judged, writing the status again; a failed one fails the check.
|
|
func TestTheAssignerStartingBringsTheComposition(t *testing.T) {
|
|
for _, comp := range []delivery.CompositionVerdict{passed,
|
|
{Group: "feat/x", Heads: "", State: delivery.CompositionFail, SDK: "x", Machine: "g14", Words: "g14 runs web, which the change removes"}} {
|
|
ch, cs := checkerWithNotJudged(t)
|
|
comp.Heads = cs.HeadsHash
|
|
ch.Composed(context.Background(), comp)
|
|
raw, _, _, _ := ch.Verdicts.Get(context.Background(), cs.Key)
|
|
v, err := delivery.DecodeVerdict(raw)
|
|
if err != nil || v.Composition == nil || v.Composition.State != comp.State {
|
|
t.Fatalf("after the composition came, the verdict is %+v, %v", v, err)
|
|
}
|
|
if comp.State == delivery.CompositionFail && (v.State != delivery.VerdictFail || v.Failed.Machine != "g14") {
|
|
t.Errorf("a failed composition left the verdict %+v", v)
|
|
}
|
|
owed := ch.Owed.(*memKV)
|
|
keys, _ := owed.Keys(context.Background())
|
|
if len(keys) == 0 {
|
|
t.Fatal("no status was written again")
|
|
}
|
|
data, _, _, _ := owed.Get(context.Background(), keys[0])
|
|
o, _ := delivery.DecodeCheckerOwed(data)
|
|
if !strings.Contains(o.Text, "composition passed") && !strings.Contains(o.Text, "g14 runs web") {
|
|
t.Errorf("the status's line says %q", o.Text)
|
|
}
|
|
}
|
|
}
|
|
|
|
// checkerWithNotJudged is a checker holding a candidate whose passing verdict said its composition was not judged.
|
|
func checkerWithNotJudged(t *testing.T) (*Checker, *Case) {
|
|
t.Helper()
|
|
cs := caseOf(t, pullOf(repoA, 7, head1))
|
|
raw := candidateOf(t, cs.Group, pullOf(repoA, 7, head1))
|
|
ch := &Checker{Verdicts: newMemKV(), Owed: newMemKV(), Trunks: fixedTrunks{cs.Trunk}}
|
|
ckey, _ := delivery.CandidatesKey(cs.Group)
|
|
ch.init()
|
|
c, err := delivery.DecodeCandidate(raw)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ch.known[ckey] = c
|
|
if got := CaseOf(c, ch.heads()); got == nil || got.Key != cs.Key {
|
|
t.Fatalf("the case is %+v, want %s", got, cs.Key)
|
|
}
|
|
v := delivery.Verdict{Group: cs.Group, Heads: cs.Heads, State: delivery.VerdictPass,
|
|
Levels: map[string]delivery.LevelResult{delivery.LevelUnit: {Run: 1}, delivery.LevelIntegration: {Run: 1}},
|
|
Composition: &delivery.Composition{State: delivery.CompositionNotRun, Words: delivery.AssignerNotRunning}}
|
|
data, err := delivery.EncodeVerdict(v)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := ch.Verdicts.Create(context.Background(), cs.Key, data); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return ch, cs
|
|
}
|
|
|
|
type fixedTrunks struct{ heads map[string]string }
|
|
|
|
func (f fixedTrunks) Heads() map[string]string { return f.heads }
|