179 lines
6.5 KiB
Go
179 lines
6.5 KiB
Go
package check
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
)
|
|
|
|
// memTries is the tries state as the tools see it, over a memKV.
|
|
type memTries struct{ kv *memKV }
|
|
|
|
func (m memTries) Keys() ([]string, error) { return m.kv.Keys(context.Background()) }
|
|
func (m memTries) Get(key string) (json.RawMessage, error) {
|
|
v, _, _, err := m.kv.Get(context.Background(), key)
|
|
return v, err
|
|
}
|
|
func (m memTries) Create(key string, value []byte) error {
|
|
_, err := m.kv.Create(context.Background(), key, value)
|
|
return err
|
|
}
|
|
|
|
var tryTrunks = map[string]string{repoA: trunkA, repoSDK: trunkS, hqRepo: trunkA}
|
|
|
|
func readTry(t testing.TB, kv *memKV, id string) Try {
|
|
t.Helper()
|
|
raw, _, found, _ := kv.Get(context.Background(), id)
|
|
if !found {
|
|
t.Fatalf("no try %s", id)
|
|
}
|
|
tr, err := DecodeTry(raw)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return tr
|
|
}
|
|
|
|
func TestATryIsAskedQueuedOncePerHeadAndAtMostFourAtOnce(t *testing.T) { // ADR 0501 §5
|
|
kv := newMemKV()
|
|
store := memTries{kv}
|
|
at := time.Date(2026, 10, 22, 18, 0, 0, 0, time.UTC)
|
|
id, err := AskTry(store, tryTrunks, TryAsk{Repository: repoA, Head: head1, Trunk: "main"}, at)
|
|
if err != nil || !strings.HasPrefix(id, "try-") {
|
|
t.Fatalf("id %q, %v", id, err)
|
|
}
|
|
tr := readTry(t, kv, id)
|
|
if tr.State != TryQueued || tr.Trunk != trunkA || tr.Branch != "main" || tr.Head != head1 || tr.Repository != repoA {
|
|
t.Errorf("try = %+v, want queued on the trunk's head", tr)
|
|
}
|
|
again, err := AskTry(store, tryTrunks, TryAsk{Repository: repoA, Head: head1, Trunk: "main"}, at.Add(time.Second))
|
|
if err != nil || again != id {
|
|
t.Errorf("a second ask of the same head answered %q, %v, want %q", again, err, id)
|
|
}
|
|
heads := []string{head2, trunkA2, trunkS2}
|
|
for i, h := range heads {
|
|
if _, err := AskTry(store, tryTrunks, TryAsk{Repository: repoA, Head: h, Trunk: trunkA}, at.Add(time.Duration(i+2)*time.Second)); err != nil {
|
|
t.Fatalf("try %d: %v", i+2, err)
|
|
}
|
|
}
|
|
if _, err := AskTry(store, tryTrunks, TryAsk{Repository: repoSDK, Head: head1}, at.Add(time.Minute)); err == nil || !strings.Contains(err.Error(), "4 tries wait or run") {
|
|
t.Errorf("a fifth try answered %v, want refused naming the four", err)
|
|
}
|
|
}
|
|
|
|
func TestATryOfARepositoryWithNoCheckedLevelIsRefused(t *testing.T) { // the agreed error of the try
|
|
store := memTries{newMemKV()}
|
|
for _, repo := range []string{"novox/no-trunk", "novox/mesh-lab"} { // hq is checked since R4g (ADR 0527): see tryTrunks
|
|
_, err := AskTry(store, tryTrunks, TryAsk{Repository: repo, Head: head1, Trunk: "main"}, time.Now())
|
|
if err == nil || err.Error() != "no level of "+repo+" is checked by the checker" {
|
|
t.Errorf("%s: %v", repo, err)
|
|
}
|
|
}
|
|
for _, bad := range []TryAsk{{Repository: repoA, Head: "abc"}, {Repository: repoA, Head: head1, Level: "e2e"},
|
|
{Repository: repoA, Head: head1, Tests: "a b"}, {Repository: "x", Head: head1}, {Repository: repoA, Head: head1, Trunk: "-x"}} {
|
|
if _, err := AskTry(store, tryTrunks, bad, time.Now()); err == nil {
|
|
t.Errorf("%+v was taken", bad)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestATryIsJudgedAsAPullRequestsHeadAndOwesTheForgeNothing(t *testing.T) { // ADR 0501 §5
|
|
ctx := context.Background()
|
|
kv := newMemKV()
|
|
id, err := AskTry(memTries{kv}, tryTrunks, TryAsk{Repository: repoA, Head: head1, Trunk: trunkA}, time.Now())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
b := newBench(t)
|
|
tr := &Tryer{Tries: kv, Judge: b.ex}
|
|
raw, _, _, _ := kv.Get(ctx, id)
|
|
tr.Put(ctx, id, raw)
|
|
tr.Wait()
|
|
got := readTry(t, kv, id)
|
|
if got.State != TryPass || got.Took == "" || got.Head != head1 || got.Trunk != trunkA || got.Said == "" || got.Levels[delivery.LevelUnit].Run != 2 {
|
|
t.Fatalf("try = %+v", got)
|
|
}
|
|
fetch := b.steps.tool("fetch")
|
|
if len(fetch) != 1 || fetch[0].args["commit"] != head1 || fetch[0].args["base"] != trunkA || fetch[0].args["ref"] != "" {
|
|
t.Errorf("fetch = %+v, want the head merged onto the trunk, with no pull request's ref", fetch)
|
|
}
|
|
if keys, _ := b.prev.Keys(ctx); len(keys) != 0 {
|
|
t.Errorf("a try wrote the previews %v", keys)
|
|
}
|
|
// An ended try is not run again.
|
|
raw, _, _, _ = kv.Get(ctx, id)
|
|
tr.Put(ctx, id, raw)
|
|
tr.Wait()
|
|
if n := len(b.steps.tool("fetch")); n != 1 {
|
|
t.Errorf("an ended try was fetched again: %d fetches", n)
|
|
}
|
|
}
|
|
|
|
func TestATryOfOneLevelAndSomeTestsRunsThemAloneAndKeepsNothing(t *testing.T) {
|
|
ctx := context.Background()
|
|
kv := newMemKV()
|
|
id, err := AskTry(memTries{kv}, tryTrunks, TryAsk{Repository: repoA, Head: head1, Level: delivery.LevelIntegration, Tests: "TestBus|TestStore"}, time.Now())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
b := newBench(t)
|
|
tr := &Tryer{Tries: kv, Judge: b.ex}
|
|
raw, _, _, _ := kv.Get(ctx, id)
|
|
tr.Put(ctx, id, raw)
|
|
tr.Wait()
|
|
tests := b.steps.tool("test")
|
|
if len(tests) != 1 || tests[0].args["level"] != delivery.LevelIntegration || tests[0].args["run"] != "TestBus|TestStore" {
|
|
t.Fatalf("tests = %+v, want the integration level alone with the run pattern", tests)
|
|
}
|
|
if n := len(b.steps.tool("digest")); n != 0 {
|
|
t.Errorf("%d digests for a try of some tests", n)
|
|
}
|
|
if keys, _ := b.kept.Keys(ctx); len(keys) != 0 {
|
|
t.Errorf("kept %v from a try of some tests", keys)
|
|
}
|
|
if got := readTry(t, kv, id); got.State != TryPass || !strings.Contains(got.Said, "only the tests TestBus|TestStore") {
|
|
t.Errorf("try = %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestAFailingTryNamesWhatFailedAndAStepThatCannotRunIsAnError(t *testing.T) {
|
|
ctx := context.Background()
|
|
kv := newMemKV()
|
|
b := newBench(t)
|
|
b.test = func(c stepCall) (any, error) { return captured[Tested](t, "go-test-failing"), nil }
|
|
id, _ := AskTry(memTries{kv}, tryTrunks, TryAsk{Repository: repoA, Head: head1}, time.Now())
|
|
tr := &Tryer{Tries: kv, Judge: b.ex}
|
|
raw, _, _, _ := kv.Get(ctx, id)
|
|
tr.Put(ctx, id, raw)
|
|
tr.Wait()
|
|
if got := readTry(t, kv, id); got.State != TryFail || len(got.Failed) != 1 || !strings.Contains(got.Failed[0], "TestThreeIsFour") {
|
|
t.Errorf("try = %+v", got)
|
|
}
|
|
// No build node is no error since R4g: the try's ask waits in the check stream until a node pulls it (ADR 0525 §4).
|
|
}
|
|
|
|
func TestATryLeftRunningIsTakenOver(t *testing.T) {
|
|
ctx := context.Background()
|
|
kv := newMemKV()
|
|
id, _ := AskTry(memTries{kv}, tryTrunks, TryAsk{Repository: repoA, Head: head1}, time.Now())
|
|
// Left running by a checker that stopped.
|
|
raw, rev, _, _ := kv.Get(ctx, id)
|
|
cur, _ := DecodeTry(raw)
|
|
cur.State = TryRunning
|
|
data, _ := encodeTry(cur)
|
|
if _, err := kv.Update(ctx, id, data, rev); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
b := newBench(t)
|
|
tr := &Tryer{Tries: kv, Judge: b.ex}
|
|
tr.Reconsider(ctx)
|
|
tr.Wait()
|
|
if got := readTry(t, kv, id); got.State != TryPass {
|
|
t.Errorf("the try left running = %+v, want taken over and passed", got)
|
|
}
|
|
}
|