Merge pull request 'A try accepts a mesh-sdk pin that is the head of mesh-sdk's branch of the same name, so a group's members can be tried before its pull requests (issue 1921)' (#37) from fix/1921-try-same-branch-pin into main
This commit was merged in pull request #37.
This commit is contained in:
@@ -20,6 +20,8 @@ const (
|
|||||||
getFileTool = "gitea.gitea_get_file"
|
getFileTool = "gitea.gitea_get_file"
|
||||||
compareTool = "gitea.gitea_compare"
|
compareTool = "gitea.gitea_compare"
|
||||||
commitsTool = "gitea.gitea_commits"
|
commitsTool = "gitea.gitea_commits"
|
||||||
|
// gitea_list_branches answers {branches: [{name, commit}]}, every branch with the commit it points at.
|
||||||
|
branchesTool = "gitea.gitea_list_branches"
|
||||||
)
|
)
|
||||||
|
|
||||||
// notFoundStatus is the 404 of the forge's error, `Gitea API <path>: <status> <body>`.
|
// notFoundStatus is the 404 of the forge's error, `Gitea API <path>: <status> <body>`.
|
||||||
@@ -102,3 +104,26 @@ func (f forgeReads) FullSHA(_ context.Context, repository, commit string) (strin
|
|||||||
}
|
}
|
||||||
return a.Commits[0].SHA, nil
|
return a.Commits[0].SHA, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BranchHeads is the commit each branch of the repository points at.
|
||||||
|
func (f forgeReads) BranchHeads(_ context.Context, repository string) (map[string]string, error) {
|
||||||
|
owner, repo := ownerRepo(repository)
|
||||||
|
raw, err := f.ask(branchesTool, map[string]string{"owner": owner, "repo": repo})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var a struct {
|
||||||
|
Branches *[]struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Commit string `json:"commit"`
|
||||||
|
} `json:"branches"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(raw, &a); err != nil || a.Branches == nil {
|
||||||
|
return nil, fmt.Errorf("the answer of %s is not {branches: [{name, commit}]}: %v", branchesTool, err)
|
||||||
|
}
|
||||||
|
heads := map[string]string{}
|
||||||
|
for _, b := range *a.Branches {
|
||||||
|
heads[b.Name] = b.Commit
|
||||||
|
}
|
||||||
|
return heads, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package check
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"maps"
|
"maps"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -40,6 +41,9 @@ type Case struct {
|
|||||||
Queued time.Time
|
Queued time.Time
|
||||||
// Try narrows a try's check (its level, its tests); nil for a pull request's.
|
// Try narrows a try's check (its level, its tests); nil for a pull request's.
|
||||||
Try *TryOptions
|
Try *TryOptions
|
||||||
|
|
||||||
|
notesMu sync.Mutex
|
||||||
|
notes []string
|
||||||
// Progress says what the check is doing, so a check that ran past its bound names the step it was stuck at.
|
// Progress says what the check is doing, so a check that ran past its bound names the step it was stuck at.
|
||||||
Progress *Progress
|
Progress *Progress
|
||||||
// Kind is what asked the check (KindNew, KindNodes, KindTrunk): a re-judge after a trunk move is asked on the build
|
// Kind is what asked the check (KindNew, KindNodes, KindTrunk): a re-judge after a trunk move is asked on the build
|
||||||
@@ -148,3 +152,19 @@ func (t *Trunks) Heads() map[string]string {
|
|||||||
defer t.mu.Unlock()
|
defer t.mu.Unlock()
|
||||||
return maps.Clone(t.heads)
|
return maps.Clone(t.heads)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// note puts a word of the check's way on the case, for a try's line (issue 1921).
|
||||||
|
func (c *Case) note(s string) {
|
||||||
|
c.notesMu.Lock()
|
||||||
|
defer c.notesMu.Unlock()
|
||||||
|
if !slices.Contains(c.notes, s) {
|
||||||
|
c.notes = append(c.notes, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notes are the words noted on the case, in the order they were noted.
|
||||||
|
func (c *Case) Notes() []string {
|
||||||
|
c.notesMu.Lock()
|
||||||
|
defer c.notesMu.Unlock()
|
||||||
|
return slices.Clone(c.notes)
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"path"
|
"path"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -36,6 +37,8 @@ type Forge interface {
|
|||||||
OnTrunk(ctx context.Context, repository, commit string) (bool, error)
|
OnTrunk(ctx context.Context, repository, commit string) (bool, error)
|
||||||
// FullSHA is the 40-digit sha of a commit named by its first 12 digits.
|
// FullSHA is the 40-digit sha of a commit named by its first 12 digits.
|
||||||
FullSHA(ctx context.Context, repository, commit string) (string, error)
|
FullSHA(ctx context.Context, repository, commit string) (string, error)
|
||||||
|
// BranchHeads is the commit every branch of a repository points at, by branch name (issue 1921).
|
||||||
|
BranchHeads(ctx context.Context, repository string) (map[string]string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
var pseudoVersion = regexp.MustCompile(`-([0-9a-f]{12})$`)
|
var pseudoVersion = regexp.MustCompile(`-([0-9a-f]{12})$`)
|
||||||
@@ -93,6 +96,18 @@ func (st *state) checkPinsOf(ctx context.Context, t *target) verdictPtr {
|
|||||||
if onTrunk {
|
if onTrunk {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if st.c.Try != nil {
|
||||||
|
// A try only (issue 1921): a repository tried on a branch that mesh-sdk shares by name (a milestone's branch across
|
||||||
|
// repositories) is built with that branch's mesh-sdk head, which no group pull request holds before the pull requests exist.
|
||||||
|
branch, err := st.sameBranchPin(ctx, t, pin)
|
||||||
|
if err != nil {
|
||||||
|
return failed(forgeMachine(err, t.node), fmt.Sprintf("%s#%d: whether mesh-sdk %s is the head of a branch cannot be told: %v%s", t.pull.Repository, t.pull.Number, pin, err, unheardWords(err)))
|
||||||
|
}
|
||||||
|
if branch != "" {
|
||||||
|
st.c.note(fmt.Sprintf("pin accepted: head of mesh-sdk %s", branch))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
nor := "the group has no mesh-sdk pull request"
|
nor := "the group has no mesh-sdk pull request"
|
||||||
if sdk != nil {
|
if sdk != nil {
|
||||||
nor = "the head of the group's mesh-sdk pull request is " + short12(sdk.Head)
|
nor = "the head of the group's mesh-sdk pull request is " + short12(sdk.Head)
|
||||||
@@ -103,6 +118,35 @@ func (st *state) checkPinsOf(ctx context.Context, t *target) verdictPtr {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sameBranchPin names the branch of the tried repository that points at the tried head and that mesh-sdk also has, at the
|
||||||
|
// commit the pin names; empty when there is none.
|
||||||
|
func (st *state) sameBranchPin(ctx context.Context, t *target, pin string) (string, error) {
|
||||||
|
mine, err := st.j.Forge.BranchHeads(ctx, t.pull.Repository)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
var same []string
|
||||||
|
for name, head := range mine {
|
||||||
|
if head == t.pull.Head && name != "main" {
|
||||||
|
same = append(same, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(same) == 0 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
sdk, err := st.j.Forge.BranchHeads(ctx, SDKRepository)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
sort.Strings(same)
|
||||||
|
for _, name := range same {
|
||||||
|
if h, ok := sdk[name]; ok && strings.HasPrefix(h, pin) {
|
||||||
|
return name, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
// failureVerdict is a verdict of failure the check ends with.
|
// failureVerdict is a verdict of failure the check ends with.
|
||||||
|
|
||||||
// pinHint is added to the failure of a Go member's fetch that names mesh-sdk while the group holds a mesh-sdk pull request:
|
// pinHint is added to the failure of a Go member's fetch that names mesh-sdk while the group holds a mesh-sdk pull request:
|
||||||
|
|||||||
@@ -18,6 +18,15 @@ type fakeForge struct {
|
|||||||
onTrunk map[string]bool // by commit prefix
|
onTrunk map[string]bool // by commit prefix
|
||||||
err error
|
err error
|
||||||
errAt map[string]error // by ref: a read at that ref fails with it
|
errAt map[string]error // by ref: a read at that ref fails with it
|
||||||
|
// branches are the branches of a repository with the commit each points at.
|
||||||
|
branches map[string]map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeForge) BranchHeads(_ context.Context, repository string) (map[string]string, error) {
|
||||||
|
if f.err != nil {
|
||||||
|
return nil, f.err
|
||||||
|
}
|
||||||
|
return f.branches[repository], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fakeForge) File(_ context.Context, repository, path, ref string) (string, error) {
|
func (f *fakeForge) File(_ context.Context, repository, path, ref string) (string, error) {
|
||||||
@@ -346,3 +355,44 @@ func TestAMeshLabHeadGetsACheck(t *testing.T) {
|
|||||||
t.Fatalf("mesh-lab was not fetched: %+v", fetched)
|
t.Fatalf("mesh-lab was not fetched: %+v", fetched)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sameBranchBench is a try of mesh-delivery's feat/r6 at head1 whose go.mod pins the SDK at pin, with the SDK's feat/r6 at head2.
|
||||||
|
func sameBranchBench(t *testing.T, pin string) (*bench, *Case) {
|
||||||
|
b := groupBench(t, pin)
|
||||||
|
b.ex.Forge.(*fakeForge).branches = map[string]map[string]string{
|
||||||
|
repoA: {"main": trunkA, "feat/r6": head1},
|
||||||
|
repoSDK: {"main": "cb54756c3865" + strings.Repeat("0", 28), "feat/r6": head2},
|
||||||
|
}
|
||||||
|
c := caseOf(t, pullOf(repoA, 7, head1))
|
||||||
|
c.Try = &TryOptions{}
|
||||||
|
return b, c
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestATryPinnedToTheHeadOfTheSDKBranchOfTheTriedBranchPasses(t *testing.T) { // 1921/AC1
|
||||||
|
b, c := sameBranchBench(t, head2[:12])
|
||||||
|
if v := b.judge(t, c).Verdict; v.State != delivery.VerdictPass {
|
||||||
|
t.Fatalf("verdict = %+v", v)
|
||||||
|
}
|
||||||
|
if n := c.Notes(); len(n) != 1 || n[0] != "pin accepted: head of mesh-sdk feat/r6" {
|
||||||
|
t.Errorf("notes = %q", n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestATryPinnedToAnotherCommitIsStillRefused(t *testing.T) { // 1921/AC2
|
||||||
|
b, c := sameBranchBench(t, "9aaa5c80ac5c")
|
||||||
|
v := b.judge(t, c).Verdict
|
||||||
|
if v.State != delivery.VerdictFail || !strings.Contains(v.Failed.Words, "9aaa5c80ac5c") || !strings.Contains(v.Failed.Words, "neither on mesh-sdk's trunk nor the group's head") {
|
||||||
|
t.Fatalf("verdict = %+v", v)
|
||||||
|
}
|
||||||
|
if len(c.Notes()) != 0 {
|
||||||
|
t.Errorf("notes = %q", c.Notes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAMergeCheckWithTheSameBranchPinIsStillRefusedWithoutAGroupPullRequest(t *testing.T) { // 1921/AC3
|
||||||
|
b, _ := sameBranchBench(t, head2[:12])
|
||||||
|
v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))).Verdict // no Try: a pull request's check
|
||||||
|
if v.State != delivery.VerdictFail || !strings.Contains(v.Failed.Words, "the group has no mesh-sdk pull request") {
|
||||||
|
t.Fatalf("verdict = %+v", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -329,6 +329,9 @@ func (tr *Tryer) run(ctx context.Context, key string) {
|
|||||||
t.Took = t.Ended.Sub(t.Started).Round(time.Second).String()
|
t.Took = t.Ended.Sub(t.Started).Round(time.Second).String()
|
||||||
t.Levels, t.Machines = v.Levels, v.Machines
|
t.Levels, t.Machines = v.Levels, v.Machines
|
||||||
t.Said = Report(v, cs, cs.Pulls[0], out.Builds, out.Slowest, out.Judged)
|
t.Said = Report(v, cs, cs.Pulls[0], out.Builds, out.Slowest, out.Judged)
|
||||||
|
for _, n := range cs.Notes() {
|
||||||
|
t.Said += " | " + n
|
||||||
|
}
|
||||||
switch {
|
switch {
|
||||||
case !judged || IsNodesFailure(v):
|
case !judged || IsNodesFailure(v):
|
||||||
t.State = TryError
|
t.State = TryError
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ func TestManifestDeclarations(t *testing.T) { // 1678/AC1
|
|||||||
}
|
}
|
||||||
// 1887/A5b: the checker reads nothing of the controller's plans-building (ADR 0483 superseded by ADR 0525 §7), calls no
|
// 1887/A5b: the checker reads nothing of the controller's plans-building (ADR 0483 superseded by ADR 0525 §7), calls no
|
||||||
// build module's tool and holds no capacity: it puts check asks on the Go and Node build seats (ADR 0525 §1, §4).
|
// build module's tool and holds no capacity: it puts check asks on the Go and Node build seats (ADR 0525 §1, §4).
|
||||||
if want := []string{"gitea.gitea_commits", "gitea.gitea_compare", "gitea.gitea_get_file"}; !reflect.DeepEqual(sorted(m.Invokes), want) {
|
if want := []string{"gitea.gitea_commits", "gitea.gitea_compare", "gitea.gitea_get_file", "gitea.gitea_list_branches"}; !reflect.DeepEqual(sorted(m.Invokes), want) {
|
||||||
t.Errorf("invokes = %v, want only the forge's reads for a mesh-sdk pin (ADR 0484 clause 9)", m.Invokes)
|
t.Errorf("invokes = %v, want only the forge's reads for a mesh-sdk pin (ADR 0484 clause 9)", m.Invokes)
|
||||||
}
|
}
|
||||||
if want := []string{"node-build-go", "node-build-node"}; !reflect.DeepEqual(sorted(m.Uses), want) {
|
if want := []string{"node-build-go", "node-build-node"}; !reflect.DeepEqual(sorted(m.Uses), want) {
|
||||||
|
|||||||
@@ -47,7 +47,8 @@
|
|||||||
"invokes": [
|
"invokes": [
|
||||||
"gitea.gitea_commits",
|
"gitea.gitea_commits",
|
||||||
"gitea.gitea_compare",
|
"gitea.gitea_compare",
|
||||||
"gitea.gitea_get_file"
|
"gitea.gitea_get_file",
|
||||||
|
"gitea.gitea_list_branches"
|
||||||
],
|
],
|
||||||
|
|
||||||
"own-secrets": {
|
"own-secrets": {
|
||||||
|
|||||||
Reference in New Issue
Block a user