Fingerprints computed again by hand run a merge's recompute; the tests join existing ones (issue 1966, review round 1)
mesh/delivery closed: a newer head of the same pull request
mesh/check failed (6): integration: mesh-delivery-indexer/cmd/mesh-delivery-indexer.TestIndexerWritesModule…; and 5 more | 3a63813b3d75 on fb69c8dee691
mesh/delivery closed: a newer head of the same pull request
mesh/check failed (6): integration: mesh-delivery-indexer/cmd/mesh-delivery-indexer.TestIndexerWritesModule…; and 5 more | 3a63813b3d75 on fb69c8dee691
Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
@@ -150,14 +150,15 @@ type Indexer struct {
|
||||
retrying atomic.Bool // a RetryUnread runs in the background (kickRetry)
|
||||
retryAgain atomic.Bool // an event asked for one more RetryUnread after the running one
|
||||
retryWG sync.WaitGroup // the background RetryUnread, for tests to wait on
|
||||
fresh map[string]bool // modules whose import form is computed again by hand (FingerprintsAgain), "*" for every one; under recompute
|
||||
recompute sync.Mutex // one Recompute at a time
|
||||
untaken map[string]string // repository to the latest merge applied while the taken state was missing (taken.go)
|
||||
filling atomic.Bool // a FillTaken runs in the background (KickFill)
|
||||
fillAgain atomic.Bool // a kick arrived while it ran
|
||||
atHeads map[string]map[string]meshcatalog.PackagesByModuleValue // a member head's published Go modules, by repository@head (candidatePublished)
|
||||
saidAt map[string]string // the head of a repository whose failed read was said, by repository: said once per head
|
||||
failedAt map[string]string // the head of a repository whose tree lacks a directory asked, by repository: not asked again until the head changes
|
||||
fresh map[string]bool // modules whose import form is computed again by hand (FingerprintsAgain), "*" for every one; under freshMu
|
||||
freshMu sync.Mutex
|
||||
recompute sync.Mutex // one Recompute at a time
|
||||
untaken map[string]string // repository to the latest merge applied while the taken state was missing (taken.go)
|
||||
filling atomic.Bool // a FillTaken runs in the background (KickFill)
|
||||
fillAgain atomic.Bool // a kick arrived while it ran
|
||||
atHeads map[string]map[string]meshcatalog.PackagesByModuleValue // a member head's published Go modules, by repository@head (candidatePublished)
|
||||
saidAt map[string]string // the head of a repository whose failed read was said, by repository: said once per head
|
||||
failedAt map[string]string // the head of a repository whose tree lacks a directory asked, by repository: not asked again until the head changes
|
||||
// OwnBuild answers the fingerprint of the build this program runs, from the node-engine's seat; nil knows none.
|
||||
OwnBuild func(ctx context.Context) (string, error)
|
||||
underSeats bool // a dry run inside judge.WithSeats: its judge passes do not hold the seat list (verdicts.go)
|
||||
|
||||
@@ -40,8 +40,6 @@ func (ix *Indexer) FingerprintsAgain(ctx context.Context, module string) TakenIn
|
||||
out.Answer = "the indexer writes no fingerprints"
|
||||
return out
|
||||
}
|
||||
ix.recompute.Lock()
|
||||
defer ix.recompute.Unlock()
|
||||
before, err := ix.fingerprintsHeld(ctx)
|
||||
if err != nil {
|
||||
out.Answer = "the modules state cannot be read: " + err.Error()
|
||||
@@ -57,13 +55,22 @@ func (ix *Indexer) FingerprintsAgain(ctx context.Context, module string) TakenIn
|
||||
if module == "all" {
|
||||
key = "*"
|
||||
}
|
||||
ix.freshMu.Lock()
|
||||
ix.fresh = map[string]bool{key: true}
|
||||
err = ix.recomputeLocked(ctx, nil, nil)
|
||||
ix.freshMu.Unlock()
|
||||
// The path of a merge's recompute: the modules, then the candidates their fingerprints reach (recomputeWith), then the
|
||||
// pending merges a changed fingerprint settles (PendingAgain, as after a toolchain report).
|
||||
err = ix.recomputeWith(ctx, nil, nil)
|
||||
ix.freshMu.Lock()
|
||||
ix.fresh = nil
|
||||
ix.freshMu.Unlock()
|
||||
if err != nil {
|
||||
out.Answer = "nothing was written: " + err.Error()
|
||||
return out
|
||||
}
|
||||
if err := ix.PendingAgain(ctx); err != nil {
|
||||
ix.logf("the pending merges are not evaluated again after the fingerprints were computed again: %v", err)
|
||||
}
|
||||
after, err := ix.fingerprintsHeld(ctx)
|
||||
if err != nil {
|
||||
out.Answer = "computed, but the modules state cannot be read back: " + err.Error()
|
||||
@@ -82,6 +89,13 @@ func (ix *Indexer) FingerprintsAgain(ctx context.Context, module string) TakenIn
|
||||
return out
|
||||
}
|
||||
|
||||
// isFresh says the module's import form is computed again by hand in this pass.
|
||||
func (ix *Indexer) isFresh(module string) bool {
|
||||
ix.freshMu.Lock()
|
||||
defer ix.freshMu.Unlock()
|
||||
return ix.fresh[module] || ix.fresh["*"]
|
||||
}
|
||||
|
||||
// fingerprintsHeld is each held module's fingerprint of the new form.
|
||||
func (ix *Indexer) fingerprintsHeld(ctx context.Context) (map[string]string, error) {
|
||||
keys, err := ix.Modules.Keys(ctx)
|
||||
@@ -113,7 +127,7 @@ func FingerprintsTools(ask func(key string, value []byte) (TakeInAsk, error), no
|
||||
"the import form is written for every module whose imports are all published, whatever fingerprint the module holds. For the " +
|
||||
"fingerprints a pass wrote while the packages state was unread (the version line over the import form: builds then fail on an " +
|
||||
"older package). Answers each module whose fingerprint moved, old and new; when the packages state cannot be read it writes " +
|
||||
"nothing and says why. Acts first, then records the hand act through mesh-hand-acts; when that cannot be recorded the answer says so.",
|
||||
"nothing and says why. Runs as a merge's recompute does (the modules, the candidates they reach, the pending merges they settle). Acts first, then records the hand act through mesh-hand-acts; when that cannot be recorded the answer says so.",
|
||||
Input: map[string]any{"type": "object", "required": []string{"why"}, "properties": map[string]any{
|
||||
"module": map[string]any{"type": "string", "description": "the module's name, or all (the default)"},
|
||||
"why": map[string]any{"type": "string", "description": "why it is computed again now, in plain words"}}},
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
package index
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// indexer_fingerprints_again acts first and records the hand act after: a record that fails, or never answers, is said in
|
||||
// the answer and never refuses or holds the recovery (mesh-hand-acts on a mesh with no ledger answers nothing).
|
||||
func TestFingerprintsAgainActsBeforeItRecordsAndSaysAFailedRecord(t *testing.T) {
|
||||
for _, c := range []struct {
|
||||
name string
|
||||
record func(what, why string) error
|
||||
said string
|
||||
}{
|
||||
{"recorded", func(string, string) error { return nil }, ""},
|
||||
{"record refused", func(string, string) error { return errors.New("no ledger") }, "not recorded: no ledger"},
|
||||
{"record never answers", func(string, string) error { select {} }, "not recorded within"},
|
||||
} {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
var asked []string
|
||||
ask := func(key string, _ []byte) (TakeInAsk, error) {
|
||||
asked = append(asked, key)
|
||||
return TakeInAsk{Result: &TakenIn{Module: "all", Stored: true}}, nil
|
||||
}
|
||||
tool := FingerprintsTools(ask, time.Now, c.record, 10*time.Millisecond)[0]
|
||||
if _, err := tool.Run(map[string]any{"module": "avahi"}); err == nil {
|
||||
t.Fatal("a hand act with no why was done")
|
||||
}
|
||||
if len(asked) != 0 {
|
||||
t.Fatalf("asked the indexer without a why: %v", asked)
|
||||
}
|
||||
got, err := tool.Run(map[string]any{"module": "avahi", "why": "the packages read came back empty"})
|
||||
if err != nil || len(asked) != 1 || !strings.HasPrefix(asked[0], "fingerprints.avahi.") {
|
||||
t.Fatalf("run: %v, asked %v", err, asked)
|
||||
}
|
||||
said, _ := got.(map[string]any)["hand-act"].(string)
|
||||
if !strings.Contains(said, c.said) || (c.said == "") != (said == "") {
|
||||
t.Fatalf("hand-act %q, want one saying %q", said, c.said)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||
)
|
||||
|
||||
@@ -52,6 +53,7 @@ func (m *missingDir) Error() string { return "no directory " + m.dir }
|
||||
func TestImportsReportedMoveTheFingerprintOnlyWithAnImportedPackage(t *testing.T) {
|
||||
t.Run("a build of an older fingerprint keeps no imports", buildOfAnOlderFingerprintKeepsNoImports)
|
||||
t.Run("a change of a sibling directory alone moves its importer", aSiblingDirectoryAloneMovesItsImporter)
|
||||
t.Run("an unread packages state writes no fingerprint, and they are computed again by hand (issue 1966)", anUnreadPackagesStateWritesNoFingerprintAndFingerprintsAreComputedAgain)
|
||||
t.Run("an accepted series' newest published version pins it, by that version's contents", anAcceptedSeriesPinsItsNewest)
|
||||
ctx := context.Background()
|
||||
ix, mods := newFormIndexer(t, mod{"avahi", catalogueManifest(t, "avahi")})
|
||||
@@ -276,7 +278,7 @@ func anAcceptedSeriesPinsItsNewest(t *testing.T) {
|
||||
// answered no published package where held modules were computed from published ones, writes no fingerprint (the version
|
||||
// line over every import form is what an empty state computes); a version line written leaves no import form behind
|
||||
// that a later pass takes for the build's; and indexer_fingerprints_again computes the import form again by hand.
|
||||
func TestAnUnreadPackagesStateWritesNoFingerprintAndFingerprintsAreComputedAgain(t *testing.T) {
|
||||
func anUnreadPackagesStateWritesNoFingerprintAndFingerprintsAreComputedAgain(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
// avahi at the import form, pinned to mesh-sdk at c3, as the test above leaves it.
|
||||
setup := func(t *testing.T) (*Indexer, *memStore, meshcatalog.ModulesByModuleValue) {
|
||||
@@ -359,6 +361,11 @@ func TestAnUnreadPackagesStateWritesNoFingerprintAndFingerprintsAreComputedAgain
|
||||
if now, _ := moduleOf(t, mods, "avahi"); now.Fp2 != good.Fp2 || ix.fresh != nil {
|
||||
t.Fatalf("avahi holds %q after it, want %q (fresh %v)", now.Fp2, good.Fp2, ix.fresh)
|
||||
}
|
||||
// It runs a merge's recompute, candidates included: a candidate store that cannot be listed fails it, said.
|
||||
ix.Store, ix.previews = &memStore{keysErr: errors.New("no bus")}, map[string]meshdeliverychecker.PreviewsByRepositoryHeadTrunkValue{"k": {}}
|
||||
if r := ix.FingerprintsAgain(ctx, "avahi"); r.Stored || !strings.Contains(r.Answer, "listing the candidates") {
|
||||
t.Fatalf("fingerprints again over candidates that cannot be listed: %+v", r)
|
||||
}
|
||||
if r := ix.FingerprintsAgain(ctx, "nothing"); r.Stored {
|
||||
t.Fatalf("fingerprints again of a module not held answered stored: %+v", r)
|
||||
}
|
||||
|
||||
@@ -135,6 +135,40 @@ func sha(c byte) string { return strings.Repeat(string(c), 40) }
|
||||
|
||||
func TestTakeIn(t *testing.T) { // 1585/T3-T13 (ADR 0633)
|
||||
ctx := context.Background()
|
||||
t.Run("indexer_fingerprints_again acts before it records the hand act and says a failed record", func(t *testing.T) { // issue 1966
|
||||
for _, c := range []struct {
|
||||
name string
|
||||
record func(what, why string) error
|
||||
said string
|
||||
}{
|
||||
{"recorded", func(string, string) error { return nil }, ""},
|
||||
{"record refused", func(string, string) error { return errors.New("no ledger") }, "not recorded: no ledger"},
|
||||
{"record never answers", func(string, string) error { select {} }, "not recorded within"},
|
||||
} {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
var asked []string
|
||||
ask := func(key string, _ []byte) (TakeInAsk, error) {
|
||||
asked = append(asked, key)
|
||||
return TakeInAsk{Result: &TakenIn{Module: "all", Stored: true}}, nil
|
||||
}
|
||||
tool := FingerprintsTools(ask, time.Now, c.record, 10*time.Millisecond)[0]
|
||||
if _, err := tool.Run(map[string]any{"module": "avahi"}); err == nil {
|
||||
t.Fatal("a hand act with no why was done")
|
||||
}
|
||||
if len(asked) != 0 {
|
||||
t.Fatalf("asked the indexer without a why: %v", asked)
|
||||
}
|
||||
got, err := tool.Run(map[string]any{"module": "avahi", "why": "the packages read came back empty"})
|
||||
if err != nil || len(asked) != 1 || !strings.HasPrefix(asked[0], "fingerprints.avahi.") {
|
||||
t.Fatalf("run: %v, asked %v", err, asked)
|
||||
}
|
||||
said, _ := got.(map[string]any)["hand-act"].(string)
|
||||
if !strings.Contains(said, c.said) || (c.said == "") != (said == "") {
|
||||
t.Fatalf("hand-act %q, want one saying %q", said, c.said)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
t.Run("T6 a merge while the own build is unknown writes nothing", func(t *testing.T) {
|
||||
b := newTakeInBed(t)
|
||||
b.ix.ownFP, b.ix.ownFixed = "", false // this process is started again and asks the engine, which does not answer
|
||||
|
||||
@@ -264,7 +264,7 @@ func (ix *Indexer) recomputeLocked(ctx context.Context, overlay map[string]meshc
|
||||
chain = append(chain, module)
|
||||
kept := *k.cur.Inputs2
|
||||
kept.Bases, kept.Builder = nil, ""
|
||||
if ix.fresh[module] || ix.fresh["*"] { // indexer_fingerprints_again: the import form is written again, the held one stands for nothing
|
||||
if ix.isFresh(module) { // indexer_fingerprints_again: the import form is written again, the held one stands for nothing
|
||||
kept.ImportForm = ""
|
||||
}
|
||||
var missing []string
|
||||
|
||||
Reference in New Issue
Block a user