This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / spindle / mill / executor / capability_test.go
3.3 kB 119 lines
1package executor 2 3import ( 4 "context" 5 "encoding/json" 6 "errors" 7 "io" 8 "log/slog" 9 "testing" 10 11 "tangled.org/core/api/tangled" 12 millv1 "tangled.org/core/spindle/mill/proto/gen" 13 "tangled.org/core/spindle/models" 14) 15 16type placementEngine struct { 17 *fakeEngine 18 validationErr error 19 validated bool 20} 21 22func (e *placementEngine) ValidateWorkflowPlacement(*models.Workflow) error { 23 e.validated = true 24 return e.validationErr 25} 26 27func TestHandleReserveRejectsMissingTriggerMetadata(t *testing.T) { 28 enc := newCaptureEncoder() 29 e := &Executor{ 30 l: slog.New(slog.NewTextHandler(io.Discard, nil)), 31 enc: enc, 32 seats: 1, 33 engines: map[string]models.Engine{"microvm": &fakeEngine{}}, 34 active: make(map[string]*reservation), 35 } 36 twf, err := json.Marshal(tangled.Pipeline_Workflow{Name: "build"}) 37 if err != nil { 38 t.Fatal(err) 39 } 40 tpl, err := json.Marshal(tangled.Pipeline{}) 41 if err != nil { 42 t.Fatal(err) 43 } 44 45 // engines deref TriggerMetadata unconditionally. this must be a reject, 46 // not a panic that takes the whole executor down 47 e.handleReserve(context.Background(), &millv1.ReserveSeat{ 48 LeaseId: "lease-1", 49 TargetEngine: "microvm", 50 RawWorkflowJson: string(twf), 51 RawPipelineJson: string(tpl), 52 Knot: "k", 53 Rkey: "r", 54 }) 55 56 result := (<-enc.messages).GetReserveResult() 57 if result == nil { 58 t.Fatal("handleReserve() did not send ReserveResult") 59 } 60 if result.GetAccepted() { 61 t.Fatal("handleReserve() accepted a pipeline without trigger metadata") 62 } 63 if result.GetRejectClass() != millv1.RejectClass_REJECT_CLASS_INCOMPATIBLE { 64 t.Fatalf("reject class = %v, want incompatible", result.GetRejectClass()) 65 } 66 if len(e.active) != 0 { 67 t.Fatalf("active reservations = %d, want 0", len(e.active)) 68 } 69} 70 71func TestHandleReserveValidatesPlacementBeforeAcquiringSlot(t *testing.T) { 72 enc := newCaptureEncoder() 73 validationErr := errors.New("image architecture is not native") 74 eng := &placementEngine{fakeEngine: &fakeEngine{}, validationErr: validationErr} 75 e := &Executor{ 76 l: slog.New(slog.NewTextHandler(io.Discard, nil)), 77 enc: enc, 78 seats: 1, 79 engines: map[string]models.Engine{"microvm": eng}, 80 active: make(map[string]*reservation), 81 } 82 twf, err := json.Marshal(tangled.Pipeline_Workflow{Name: "build"}) 83 if err != nil { 84 t.Fatal(err) 85 } 86 tpl, err := json.Marshal(tangled.Pipeline{TriggerMetadata: &tangled.Pipeline_TriggerMetadata{}}) 87 if err != nil { 88 t.Fatal(err) 89 } 90 91 e.handleReserve(context.Background(), &millv1.ReserveSeat{ 92 LeaseId: "lease-1", 93 TargetEngine: "microvm", 94 RawWorkflowJson: string(twf), 95 RawPipelineJson: string(tpl), 96 Knot: "k", 97 Rkey: "r", 98 }) 99 100 result := (<-enc.messages).GetReserveResult() 101 if result == nil { 102 t.Fatal("handleReserve() did not send ReserveResult") 103 } 104 if result.GetAccepted() { 105 t.Fatal("handleReserve() accepted placement validation failure") 106 } 107 if result.GetRejectClass() != millv1.RejectClass_REJECT_CLASS_INCOMPATIBLE { 108 t.Fatalf("reject class = %v, want incompatible", result.GetRejectClass()) 109 } 110 if !eng.validated { 111 t.Fatal("placement validator was not called") 112 } 113 if eng.acquireCalled { 114 t.Fatal("slot acquisition ran after placement validation failed") 115 } 116 if len(e.active) != 0 { 117 t.Fatalf("active reservations = %d, want 0", len(e.active)) 118 } 119}