Merge pull request 'The checker lays every repository a build requires beside it, and says a try's end (issue 1921, issue 1966)' (#161) from fix/1966-checker-before-group into main
This commit was merged in pull request #161.
This commit is contained in:
@@ -2,7 +2,13 @@ package check
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BesideRef is a sibling repository a repository's tests read, placed beside the fetched tree as a plain directory (ADR
|
// BesideRef is a sibling repository a repository's tests read, placed beside the fetched tree as a plain directory (ADR
|
||||||
@@ -41,8 +47,10 @@ const (
|
|||||||
// checks/deps). Which commit of each is besideCommit's rule. No repository's tests read the controller, which ended by its
|
// checks/deps). Which commit of each is besideCommit's rule. No repository's tests read the controller, which ended by its
|
||||||
// unassignment (novox/hq ADR 0590, issue 1126), and its repository is emptied and archived (ADR 0593 §11), so it has no
|
// unassignment (novox/hq ADR 0590, issue 1126), and its repository is emptied and archived (ADR 0593 §11), so it has no
|
||||||
// siblings placed either. mesh-sdk reads mesh-contracts and mesh-delivery (its bootstrapped seats against their contracts files). The catalogue's tests read the node-engine since its composer command moved there (ADR 0593
|
// siblings placed either. mesh-sdk reads mesh-contracts and mesh-delivery (its bootstrapped seats against their contracts files). The catalogue's tests read the node-engine since its composer command moved there (ADR 0593
|
||||||
// §7): mesh-build-go builds it for a compose ask. The node-engine's read mesh-networking since route-proxy's proofs of a
|
// §7): mesh-build-go builds it for a compose ask, and with it every repository its composer requires at the zero version
|
||||||
// composition moved into them (ADR 0593).
|
// (issue 1921: with mesh-sdk alone beside, that build looked them up on the network and failed): those are not named
|
||||||
|
// here but derived from each sibling's go.mod (zeroVersionSiblings), so no rule can lack one. The node-engine's read
|
||||||
|
// mesh-networking since route-proxy's proofs of a composition moved into them (ADR 0593).
|
||||||
var besideRules = map[string][]string{
|
var besideRules = map[string][]string{
|
||||||
nodeEngineRepo: {catalogueRepository, SDKRepository, networkingRepository},
|
nodeEngineRepo: {catalogueRepository, SDKRepository, networkingRepository},
|
||||||
SDKRepository: {catalogueRepository, nodeEngineRepo, contractsRepository, deliveryRepository},
|
SDKRepository: {catalogueRepository, nodeEngineRepo, contractsRepository, deliveryRepository},
|
||||||
@@ -67,7 +75,7 @@ func (j *Examiner) besideFor(ctx context.Context, c *Case, p Pull) ([]BesideRef,
|
|||||||
if p.Trunk != "" {
|
if p.Trunk != "" {
|
||||||
out = append(out, BesideRef{Repository: p.Repository, Commit: p.Trunk})
|
out = append(out, BesideRef{Repository: p.Repository, Commit: p.Trunk})
|
||||||
}
|
}
|
||||||
for _, repo := range besideRules[p.Repository] {
|
place := func(repo string) {
|
||||||
switch {
|
switch {
|
||||||
case repo == nodeEngineRepo && c.member(repo) != nil:
|
case repo == nodeEngineRepo && c.member(repo) != nil:
|
||||||
// A group's check judges each member against the group's heads (ADR 0573 §4, ADR 0482 §2), and the group's
|
// A group's check judges each member against the group's heads (ADR 0573 §4, ADR 0482 §2), and the group's
|
||||||
@@ -97,9 +105,89 @@ func (j *Examiner) besideFor(ctx context.Context, c *Case, p Pull) ([]BesideRef,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for _, repo := range besideRules[p.Repository] {
|
||||||
|
place(repo)
|
||||||
|
}
|
||||||
|
// A sibling a test builds needs every repository its go.mod requires at the zero version beside it too: the build
|
||||||
|
// resolves them from what lies beside, with nothing fetched (issue 1921). Its go.mod names them all, indirect ones
|
||||||
|
// included, so one level is enough.
|
||||||
|
derived, err := j.zeroVersionSiblings(ctx, p.Repository, out)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, repo := range derived {
|
||||||
|
place(repo)
|
||||||
|
}
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// zeroVersionSiblings are the repositories the root go.mod of each sibling in refs requires at the zero version that are
|
||||||
|
// not beside already, sorted; none where the checker reads no forge. A sibling with no root go.mod requires none; one the
|
||||||
|
// forge does not answer for is an error, never a sibling left out in silence.
|
||||||
|
func (j *Examiner) zeroVersionSiblings(ctx context.Context, own string, refs []BesideRef) ([]string, error) {
|
||||||
|
if j.Forge == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
have := map[string]bool{own: true}
|
||||||
|
for _, r := range refs {
|
||||||
|
have[r.Repository] = true
|
||||||
|
}
|
||||||
|
var out []string
|
||||||
|
for _, r := range refs {
|
||||||
|
if r.Repository == own {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
text, err := j.Forge.File(ctx, r.Repository, "go.mod", r.Commit)
|
||||||
|
if errors.Is(err, ErrNotFound) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("the go.mod of %s at %.12s, beside the change, cannot be read%s: %w", r.Repository, r.Commit, unheardWords(err), err)
|
||||||
|
}
|
||||||
|
for _, path := range zeroVersionRequires(text) {
|
||||||
|
repo, _, ok := delivery.PackageRepository(path)
|
||||||
|
if !ok || have[repo] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
have[repo] = true
|
||||||
|
out = append(out, repo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
slices.Sort(out)
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// zeroVersionRequires are the module paths a go.mod's require lines (a block or single ones) name at the zero version.
|
||||||
|
func zeroVersionRequires(gomod string) []string {
|
||||||
|
var out []string
|
||||||
|
block := false
|
||||||
|
for _, line := range strings.Split(gomod, "\n") {
|
||||||
|
line, _, _ = strings.Cut(line, "//")
|
||||||
|
f := strings.Fields(line)
|
||||||
|
switch {
|
||||||
|
case len(f) == 0:
|
||||||
|
continue
|
||||||
|
case f[0] == "require" && len(f) > 1 && f[1] == "(":
|
||||||
|
block = true
|
||||||
|
continue
|
||||||
|
case block && f[0] == ")":
|
||||||
|
block = false
|
||||||
|
continue
|
||||||
|
case f[0] == "require":
|
||||||
|
f = f[1:]
|
||||||
|
case !block:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(f) >= 2 && f[1] == zeroVersion {
|
||||||
|
out = append(out, f[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// zeroVersion is the version a go.mod requires a mesh package at (ADR 0573 §4): the builder writes the version it builds.
|
||||||
|
const zeroVersion = "v0.0.0-00010101000000-000000000000"
|
||||||
|
|
||||||
func (c *Case) member(repository string) *Pull {
|
func (c *Case) member(repository string) *Pull {
|
||||||
for i := range c.Pulls {
|
for i := range c.Pulls {
|
||||||
if c.Pulls[i].Repository == repository {
|
if c.Pulls[i].Repository == repository {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package check
|
package check
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"maps"
|
"maps"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -199,8 +200,16 @@ func TestTheChangesOwnRepositoryIsBesideItAtItsTrunk(t *testing.T) {
|
|||||||
func TestACatalogueChangeHasTheCatalogueBesideItAtItsTrunk(t *testing.T) {
|
func TestACatalogueChangeHasTheCatalogueBesideItAtItsTrunk(t *testing.T) {
|
||||||
hostRuns := strings.Repeat("7", 40)
|
hostRuns := strings.Repeat("7", 40)
|
||||||
b := newBench(t)
|
b := newBench(t)
|
||||||
b.ex.Trunks = trunksOf(t, map[string]string{catalogueRepository: trunkA, SDKRepository: trunkS, "novox/mesh-controller": trunkA2, nodeEngineRepo: trunkA2})
|
b.ex.Trunks = trunksOf(t, map[string]string{catalogueRepository: trunkA, SDKRepository: trunkS, "novox/mesh-controller": trunkA2, nodeEngineRepo: trunkA2,
|
||||||
|
contractsRepository: trunkA2, deliveryRepository: trunkA2, networkingRepository: trunkA2, "novox/mesh-tools": trunkA2})
|
||||||
b.ex.Runs = runsAt{nodeEngineModule: hostRuns}
|
b.ex.Runs = runsAt{nodeEngineModule: hostRuns}
|
||||||
|
// The node-engine's go.mod at the commit the mesh runs: what it requires at the zero version is placed beside too,
|
||||||
|
// derived, not named in a rule (issue 1921); a version a build wrote, or a package outside the forge, is not.
|
||||||
|
b.ex.Forge = headFiles{nodeEngineRepo + " go.mod " + hostRuns: "module github.com/novox/mesh-host\n\ngo 1.26\n\nrequire (\n" +
|
||||||
|
"\tgit.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient v0.0.0-00010101000000-000000000000\n" +
|
||||||
|
"\tgit.novox.be/novox/mesh-delivery/sdk v0.0.0-00010101000000-000000000000 // indirect\n" +
|
||||||
|
"\tgit.novox.be/novox/mesh-tools/go v0.1.2\n\tgithub.com/nats-io/nats.go v1.40.0\n)\n\n" +
|
||||||
|
"require git.novox.be/novox/mesh-networking/modules/mesh-wireguard/go v0.0.0-00010101000000-000000000000\n"}
|
||||||
out := b.judge(t, caseOf(t, pullOf(catalogueRepository, 12, head1)))
|
out := b.judge(t, caseOf(t, pullOf(catalogueRepository, 12, head1)))
|
||||||
// ADR 0548 §6: the outcome names the siblings read at their trunk's head or the commit the mesh runs, never the
|
// ADR 0548 §6: the outcome names the siblings read at their trunk's head or the commit the mesh runs, never the
|
||||||
// repository it merges.
|
// repository it merges.
|
||||||
@@ -211,8 +220,15 @@ func TestACatalogueChangeHasTheCatalogueBesideItAtItsTrunk(t *testing.T) {
|
|||||||
if c := got[catalogueRepository]; c.Commit != trunkA || c.Ref != "" {
|
if c := got[catalogueRepository]; c.Commit != trunkA || c.Ref != "" {
|
||||||
t.Fatalf("the catalogue beside its own change = %+v, want its trunk %s", c, trunkA)
|
t.Fatalf("the catalogue beside its own change = %+v, want its trunk %s", c, trunkA)
|
||||||
}
|
}
|
||||||
if _, ok := got[SDKRepository]; !ok {
|
// Issue 1921: and every repository the node-engine's composer requires at the zero version, which mesh-build-go's
|
||||||
t.Errorf("mesh-sdk is no longer beside a catalogue change: %v", got)
|
// composer test builds from what lies beside, with nothing fetched.
|
||||||
|
for _, repo := range []string{SDKRepository, contractsRepository, deliveryRepository, networkingRepository} {
|
||||||
|
if _, ok := got[repo]; !ok {
|
||||||
|
t.Errorf("%s is not beside a catalogue change: %v", repo, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, ok := got["novox/mesh-tools"]; ok {
|
||||||
|
t.Errorf("mesh-tools, required at a version a build wrote, is beside a catalogue change: %v", got)
|
||||||
}
|
}
|
||||||
if h := got[nodeEngineRepo]; h.Commit != hostRuns {
|
if h := got[nodeEngineRepo]; h.Commit != hostRuns {
|
||||||
t.Errorf("the node-engine beside a catalogue change = %+v, want the commit the mesh runs %s", h, hostRuns)
|
t.Errorf("the node-engine beside a catalogue change = %+v, want the commit the mesh runs %s", h, hostRuns)
|
||||||
@@ -252,3 +268,13 @@ func TestAMeshLabHeadGetsACheck(t *testing.T) {
|
|||||||
t.Fatalf("mesh-lab was not fetched: %+v", fetched)
|
t.Fatalf("mesh-lab was not fetched: %+v", fetched)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// headFiles are the forge's files at a ref, by "<repository> <path> <ref>".
|
||||||
|
type headFiles map[string]string
|
||||||
|
|
||||||
|
func (h headFiles) File(_ context.Context, repository, path, ref string) (string, error) {
|
||||||
|
if text, ok := h[repository+" "+path+" "+ref]; ok {
|
||||||
|
return text, nil
|
||||||
|
}
|
||||||
|
return "", ErrNotFound
|
||||||
|
}
|
||||||
|
|||||||
@@ -711,7 +711,10 @@ func (tr *Tryer) run(ctx context.Context, key string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
tr.logf("%s: %s", key, t.State)
|
tr.logf("%s: %s", key, t.State)
|
||||||
tr.sendEnd(ctx, key)
|
// The end just written comes back on the key's watch, and Put then stops this run's context (it stops judging a try
|
||||||
|
// ended by hand) while this run says the end, which Put's own say leaves to it: the end is said on a context that
|
||||||
|
// stop does not cancel, or it is never said and the indexer keeps the candidate of the try's group.
|
||||||
|
tr.sendEnd(context.WithoutCancel(ctx), key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// sendEnd says the end of an ended try whose start was said (Told) with the state it ended in, then clears Told in the try's
|
// sendEnd says the end of an ended try whose start was said (Told) with the state it ended in, then clears Told in the try's
|
||||||
|
|||||||
@@ -188,7 +188,19 @@ func TestATryIsJudgedAsAGroupsCheckAndOwesTheForgeNothing(t *testing.T) { // ADR
|
|||||||
var waiting string
|
var waiting string
|
||||||
cands.during = func() { waiting = readTry(t, kv, id).Waiting } // ADR 0639 §5: the stored try says what it waits for
|
cands.during = func() { waiting = readTry(t, kv, id).Waiting } // ADR 0639 §5: the stored try says what it waits for
|
||||||
var said []string
|
var said []string
|
||||||
tr := &Tryer{Tries: written, Judge: b.ex, Say: func(_ context.Context, event string, e tryevent.Event) error {
|
var tr *Tryer
|
||||||
|
echoed := false
|
||||||
|
tr = &Tryer{Tries: written, Judge: b.ex, Say: func(ctx context.Context, event string, e tryevent.Event) error {
|
||||||
|
if event == tryevent.Ended && !echoed {
|
||||||
|
// The end the run wrote comes back on the key's watch while the run says it: Put stops the run's context and
|
||||||
|
// leaves the saying to the run, whose say must not be stopped with it (issue 1921).
|
||||||
|
echoed = true
|
||||||
|
raw, _, _, _ := kv.Get(context.Background(), id)
|
||||||
|
tr.Put(context.Background(), id, raw)
|
||||||
|
}
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return err // a publish on a stopped context is never acknowledged
|
||||||
|
}
|
||||||
said = append(said, fmt.Sprintf("%s %s %d %s", event, e.ID, len(e.Members), e.State))
|
said = append(said, fmt.Sprintf("%s %s %d %s", event, e.ID, len(e.Members), e.State))
|
||||||
return nil
|
return nil
|
||||||
}}
|
}}
|
||||||
|
|||||||
Reference in New Issue
Block a user