117 lines
4.5 KiB
Go
117 lines
4.5 KiB
Go
package build
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
)
|
|
|
|
type call struct {
|
|
node, key string
|
|
body any
|
|
}
|
|
|
|
// fakeCaller answers a long tool as the build modules do: at once, with the step and its key.
|
|
type fakeCaller struct {
|
|
calls []call
|
|
answer string
|
|
err error
|
|
}
|
|
|
|
func (f *fakeCaller) CallOn(node, key string, body any) (json.RawMessage, error) {
|
|
f.calls = append(f.calls, call{node, key, body})
|
|
if f.err != nil {
|
|
return nil, f.err
|
|
}
|
|
return json.RawMessage(f.answer), nil
|
|
}
|
|
|
|
// fakeSteps holds the steps values by `<module>/<key>`; Wait on a key it does not hold blocks until the context ends.
|
|
type fakeSteps struct {
|
|
mu sync.Mutex
|
|
steps map[string]delivery.Step
|
|
// during, when set, is called as a wait starts and the function it answers as the wait ends.
|
|
during func(key string) func()
|
|
}
|
|
|
|
func (f *fakeSteps) Wait(ctx context.Context, module, key string) (delivery.Step, error) {
|
|
f.mu.Lock()
|
|
s, ok := f.steps[module+"/"+key]
|
|
during := f.during
|
|
f.mu.Unlock()
|
|
if ok {
|
|
if during != nil {
|
|
defer during(key)()
|
|
}
|
|
return s, nil
|
|
}
|
|
<-ctx.Done()
|
|
return delivery.Step{}, context.Cause(ctx)
|
|
}
|
|
|
|
func stepAt(node, id, state, reason string, result string) delivery.Step {
|
|
s := delivery.Step{ID: id, Tool: "build_go_fetch", Node: node, State: state, Reason: reason}
|
|
if result != "" {
|
|
s.Result = json.RawMessage(result)
|
|
}
|
|
return s
|
|
}
|
|
|
|
func TestRunCallsTheToolOnTheNodeAndReadsTheStepsKey(t *testing.T) { // 1607/AC5
|
|
caller := &fakeCaller{answer: `{"step":"abc1","key":"ace.abc1","state":"running"}`}
|
|
steps := &fakeSteps{steps: map[string]delivery.Step{"mesh-build-go/ace.abc1": stepAt("ace", "abc1", delivery.StepPassed, "", `{"workspace":"w1","commit":"c"}`)}}
|
|
res, err := Runner{Caller: caller, Steps: steps, Bound: time.Second}.Run(context.Background(), "mesh-build-go", "ace", "build_go_fetch", map[string]any{"repository": "novox/r", "commit": "c"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(caller.calls) != 1 || caller.calls[0].node != "ace" || caller.calls[0].key != "mesh-build-go.build_go_fetch" {
|
|
t.Errorf("calls = %+v, want one on ace for mesh-build-go.build_go_fetch", caller.calls)
|
|
}
|
|
var fr struct{ Workspace string }
|
|
if err := json.Unmarshal(res, &fr); err != nil || fr.Workspace != "w1" {
|
|
t.Errorf("result = %s (%v), want the step's result", res, err)
|
|
}
|
|
}
|
|
|
|
func TestRunFailsOnAFailedOrRefusedStepNamingItsReason(t *testing.T) { // 1607/AC5
|
|
for _, state := range []string{delivery.StepFailed, delivery.StepRefused} {
|
|
caller := &fakeCaller{answer: `{"step":"abc1","key":"ace.abc1","state":"running"}`}
|
|
steps := &fakeSteps{steps: map[string]delivery.Step{"mesh-build-go/ace.abc1": stepAt("ace", "abc1", state, "go is go1.26.0, the recipe builds with go1.27.1", "")}}
|
|
_, err := Runner{Caller: caller, Steps: steps, Bound: time.Second}.Run(context.Background(), "mesh-build-go", "ace", "build_go_build", nil)
|
|
if err == nil || !strings.Contains(err.Error(), "go1.27.1") || !strings.Contains(err.Error(), state) || !strings.Contains(err.Error(), "ace") {
|
|
t.Errorf("%s: error = %v, want the node, the state and the reason", state, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRunStopsWaitingAtTheBound(t *testing.T) { // 1607/AC5
|
|
caller := &fakeCaller{answer: `{"step":"abc1","key":"ace.abc1","state":"running"}`}
|
|
_, err := Runner{Caller: caller, Steps: &fakeSteps{}, Bound: 20 * time.Millisecond}.Run(context.Background(), "mesh-build-go", "ace", "build_go_build", nil)
|
|
if err == nil || !strings.Contains(err.Error(), "ace.abc1") || !strings.Contains(err.Error(), "did not end") {
|
|
t.Errorf("error = %v, want the step key and that it did not end within its bound", err)
|
|
}
|
|
}
|
|
|
|
func TestRunRefusesAnAnswerWithNoKeyAndAKeyOfAnotherNode(t *testing.T) { // 1607/AC5
|
|
for _, answer := range []string{`{"state":"running"}`, `{"step":"abc1","key":"novox.abc1","state":"running"}`, `not json`} {
|
|
_, err := Runner{Caller: &fakeCaller{answer: answer}, Steps: &fakeSteps{}, Bound: time.Second}.Run(context.Background(), "mesh-build-go", "ace", "build_go_fetch", nil)
|
|
if err == nil {
|
|
t.Errorf("answer %s: no error", answer)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRunReturnsTheCallsOwnError(t *testing.T) { // 1607/AC5
|
|
boom := errors.New("no responders")
|
|
_, err := Runner{Caller: &fakeCaller{err: boom}, Steps: &fakeSteps{}, Bound: time.Second}.Run(context.Background(), "mesh-build-go", "ace", "build_go_fetch", nil)
|
|
if !errors.Is(err, boom) || !strings.Contains(err.Error(), "ace") {
|
|
t.Errorf("error = %v, want the call's error naming the node", err)
|
|
}
|
|
}
|