92 lines
4.1 KiB
Go
92 lines
4.1 KiB
Go
package pipeline
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// eagerclients_test.go pins that the RUNNER pre-builds a client for every model a run can call — including
|
|
// the models only a GATE calls.
|
|
//
|
|
// ⚠ THE HOLE AND WHY NO TEST SAW IT. `reachableModels()` feeds buildClients, and the clients map is
|
|
// read-only during the waves: a miss there is not a fallback, it is the run dying at the moment the gate
|
|
// fires — after the draft wave has been bought. Planted, the runner's list was narrowed to stage models
|
|
// only (no repair model, no bank roles) and the ENTIRE pipeline package stayed green, mining-stop and
|
|
// repair integration tests included. The reason is a coincidence reproduced in fixture form: every fixture
|
|
// that enables the terminology gate points it at `fake-model`, which is also the stages' model, exactly as
|
|
// pipeline-c1.yaml points its bank role at the draft's model. A test can only see this hole on a book whose
|
|
// gate model is its OWN, so this one gives it one.
|
|
func TestTheEagerClientMapCoversTheGateModels(t *testing.T) {
|
|
srv := newJSONProvider(&reqRec{}, draftEdit)
|
|
defer srv.Close()
|
|
bookPath := setupMiningStopProject(t, srv.URL, miningStopOpts{terminology: true, classify: true})
|
|
dir := filepath.Dir(bookPath)
|
|
|
|
// A second model on the same fake provider, and the two bank roles pointed at it. Everything else about
|
|
// the fixture is untouched.
|
|
models := filepath.Join(dir, "models.yaml")
|
|
raw, err := os.ReadFile(models)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
body := string(raw) + " bank-model:\n provider: fake\n price: { input_per_m: 1.0, cached_per_m: 0.1, cache_write_per_m: 0, output_per_m: 2.0 }\n"
|
|
if err := os.WriteFile(models, []byte(body), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
pipePath := filepath.Join(dir, "pipeline.yaml")
|
|
praw, err := os.ReadFile(pipePath)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
const gateAnchor = " terminology:\n enabled: true\n model: fake-model\n"
|
|
ptxt := string(praw)
|
|
if !strings.Contains(ptxt, gateAnchor) {
|
|
t.Fatalf("premise broken: the fixture's terminology block moved, so this test is no longer pointing "+
|
|
"the gate anywhere:\n%s", ptxt)
|
|
}
|
|
ptxt = strings.Replace(ptxt, gateAnchor, " terminology:\n enabled: true\n model: bank-model\n", 1)
|
|
ptxt = strings.Replace(ptxt, " classify_types: true\n", " classify_types: true\n classify_model: bank-model\n", 1)
|
|
if err := os.WriteFile(pipePath, []byte(ptxt), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
r := newRunner(t, bookPath)
|
|
defer r.Close()
|
|
|
|
// Premise: the bank role really does run a model no stage runs. Without this the assertion below is the
|
|
// coincidence again, dressed as a test.
|
|
stageModels := map[string]bool{}
|
|
for _, st := range r.Pipeline.Stages {
|
|
stageModels[st.ResolvedModel] = true
|
|
}
|
|
if r.Pipeline.Gates.Terminology.Model != "bank-model" || stageModels["bank-model"] {
|
|
t.Fatalf("premise broken: the bank role must run a model of its own, got gate=%q stages=%v",
|
|
r.Pipeline.Gates.Terminology.Model, stageModels)
|
|
}
|
|
|
|
// The precompute pass, run explicitly — it is what TranslateBook does before any wave goroutine starts,
|
|
// and running it here keeps the test at $0 while judging the exact map the waves read.
|
|
if err := r.buildClients(); err != nil {
|
|
t.Fatalf("the precompute pass must build every reachable client: %v", err)
|
|
}
|
|
for _, m := range r.Pipeline.ReachableModels() {
|
|
if _, ok := r.clients[m]; !ok {
|
|
t.Errorf("no client was pre-built for %q, which this book can call: the clients map is read-only "+
|
|
"during the waves, so the run does not fall back — it dies when that model is first needed, "+
|
|
"which for a bank role is after the whole draft wave is paid for", m)
|
|
}
|
|
}
|
|
// And the two bank roles are IN the reachable set to begin with — the list and its consumer, asserted
|
|
// together, because each was green while the other was wrong.
|
|
reach := map[string]bool{}
|
|
for _, m := range r.Pipeline.ReachableModels() {
|
|
reach[m] = true
|
|
}
|
|
for _, m := range []string{r.Pipeline.Gates.Terminology.Model, r.Pipeline.Gates.Terminology.ClassifierModel()} {
|
|
if !reach[m] {
|
|
t.Errorf("%q is called by a bank role and is not in the reachable set", m)
|
|
}
|
|
}
|
|
}
|