fix(landscape): bound node fan-out, cover override resolution, test the run consumer
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -356,6 +356,10 @@ func (j *stubLPJobs) EnqueueRun(_ context.Context, runID uuid.UUID) error {
|
||||
type stubComputeCall struct {
|
||||
node string
|
||||
inputs map[string]string
|
||||
// bodies holds each input file's content at call time; seedDataset stores
|
||||
// each object under its own storage key as its body, so a test can tell
|
||||
// which dataset a node actually downloaded.
|
||||
bodies map[string]string
|
||||
params landscape.Params
|
||||
}
|
||||
|
||||
@@ -370,12 +374,19 @@ type stubCompute struct {
|
||||
func newStubCompute() *stubCompute { return &stubCompute{errs: map[string]error{}} }
|
||||
|
||||
func (c *stubCompute) fn(_ context.Context, _ raster.IO, node landscape.Node, inputs map[string]string, outDir string, p landscape.Params) (map[string]string, error) {
|
||||
c.mu.Lock()
|
||||
in := map[string]string{}
|
||||
bodies := map[string]string{}
|
||||
for k, v := range inputs {
|
||||
in[k] = v
|
||||
body, err := os.ReadFile(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bodies[k] = string(body)
|
||||
}
|
||||
c.calls = append(c.calls, stubComputeCall{node: node.Code, inputs: in, params: p})
|
||||
|
||||
c.mu.Lock()
|
||||
c.calls = append(c.calls, stubComputeCall{node: node.Code, inputs: in, bodies: bodies, params: p})
|
||||
err := c.errs[node.Code]
|
||||
c.mu.Unlock()
|
||||
if err != nil {
|
||||
@@ -512,7 +523,7 @@ func (f *lpFixture) seedDataset(t *testing.T, code, status string, fileType doma
|
||||
if err != nil {
|
||||
t.Fatalf("seed dataset %s: %v", code, err)
|
||||
}
|
||||
if err := f.store.Put(ctx, key, bytes.NewReader([]byte("TIF")), 3, "image/tiff"); err != nil {
|
||||
if err := f.store.Put(ctx, key, bytes.NewReader([]byte(key)), int64(len(key)), "image/tiff"); err != nil {
|
||||
t.Fatalf("seed object %s: %v", key, err)
|
||||
}
|
||||
return d
|
||||
@@ -1159,3 +1170,49 @@ func TestLandscapeService_Execute_NodeFailureFailsRunAndCleansUp(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLandscapeService_Execute_OverridePinsInputOverFresherDataset(t *testing.T) {
|
||||
f := newLPFixture(t)
|
||||
f.seedAllSlots(t)
|
||||
|
||||
// Two ready rasters in the same slot group: the pinned one is the older of
|
||||
// the two, so resolving by freshness alone would pick the other.
|
||||
pinned := f.seedDataset(t, landscape.SlotZLCR, domain.DatasetStatusReady, domain.FileTypeRaster, "2023-01-31")
|
||||
fresher := f.seedDataset(t, landscape.SlotZLCR, domain.DatasetStatusReady, domain.FileTypeRaster, "2026-01-31")
|
||||
|
||||
run := f.runOnce(t, RunInput{Overrides: map[string]uuid.UUID{landscape.SlotZLCR: pinned.ID}})
|
||||
if run.Status != domain.LPRunSucceeded {
|
||||
t.Fatalf("status = %s (%v), want succeeded", run.Status, run.Error)
|
||||
}
|
||||
|
||||
// Both Z nodes consume the slot, and both must record the pinned dataset.
|
||||
for _, code := range []string{"00", "02"} {
|
||||
got := refsByGroup(nodeByCode(t, run, code).Inputs)[landscape.SlotZLCR]
|
||||
if got != pinned.ID {
|
||||
if got == fresher.ID {
|
||||
t.Fatalf("node %s resolved the fresher dataset %v, want the pinned %v", code, got, pinned.ID)
|
||||
}
|
||||
t.Fatalf("node %s input for %s = %v, want %v", code, landscape.SlotZLCR, got, pinned.ID)
|
||||
}
|
||||
}
|
||||
|
||||
// The compute stub was handed the pinned object's bytes, not the fresher one's.
|
||||
call, ok := f.compute.callFor("00")
|
||||
if !ok {
|
||||
t.Fatal("compute was not called for node 00")
|
||||
}
|
||||
path := call.inputs[landscape.SlotZLCR]
|
||||
if path == "" {
|
||||
t.Fatalf("node 00 got no path for %s (%v)", landscape.SlotZLCR, call.inputs)
|
||||
}
|
||||
if got := call.bodies[landscape.SlotZLCR]; got != pinned.StorageKey {
|
||||
t.Fatalf("node 00 downloaded %q, want the pinned dataset's object %q", got, pinned.StorageKey)
|
||||
}
|
||||
|
||||
// A group with no override still resolves the freshest ready dataset.
|
||||
newest := f.seedDataset(t, landscape.SlotUVeg, domain.DatasetStatusReady, domain.FileTypeRaster, "2026-02-28")
|
||||
second := f.runOnce(t, RunInput{Overrides: map[string]uuid.UUID{landscape.SlotZLCR: pinned.ID}})
|
||||
if got := refsByGroup(nodeByCode(t, second, "01").Inputs)[landscape.SlotUVeg]; got != newest.ID {
|
||||
t.Fatalf("node 01 input for %s = %v, want the freshest %v", landscape.SlotUVeg, got, newest.ID)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user