package update import ( "context" "fmt" "time" ) // Verification is "does this tree build and does it pass its own tests", run // before a single byte is written to the install dir. // // It is `make build` and `make test`, not `go build`: the CGO daemons need the // vendored toolchain and the whisper/piper include and library paths wired // through the Makefile, and a bare `go build` on them fails in a way that has // nothing to do with the change being deployed. `make test` is the -race suite // with the CGO env set, and it is the only evidence available on a single box // that the new code does what the old code did. // // This is not a substitute for a second environment. A test suite that passes // says the code is self-consistent; it does not say the new build will start // against this machine's actual models, sockets and encrypted store. That is // what the post-restart health check is for, and it is why the install is // reversible rather than merely careful. // Step — one verification or orchestration step and how it went. Kept so the CLI // can print a truthful account of what was done, including on the failure path. type Step struct { Name string Argv []string Took time.Duration Err error Output string // combined output, only retained for failures } // Verify runs the build and the test suite in SourceDir. func (u *Updater) Verify(ctx context.Context) ([]Step, error) { ctx, cancel := context.WithTimeout(ctx, u.cfg.verifyTimeout()) defer cancel() var steps []Step for _, argv := range [][]string{{"make", "build"}, {"make", "test"}} { u.log("verify: %v (this takes a while)", argv) start := u.now() out, err := u.run(ctx, u.cfg.SourceDir, argv) st := Step{Name: argv[len(argv)-1], Argv: argv, Took: u.now().Sub(start), Err: err} if err != nil { st.Output = tail(out, 4000) } steps = append(steps, st) if err != nil { u.log("verify: %v FAILED after %s", argv, st.Took.Round(time.Second)) return steps, fmt.Errorf("%w: %v: %v", ErrVerifyFailed, argv, err) } u.log("verify: %v ok in %s", argv, st.Took.Round(time.Second)) } return steps, nil } // tail keeps the last n bytes — a failing `make test` prints far more than is // useful, and the failure is always at the end. func tail(s string, n int) string { if len(s) <= n { return s } return "…" + s[len(s)-n:] }