Files
mesh-delivery/modules/mesh-delivery-checker/rules/words.go
T
jschoubben 84bcdb687a BL18: build modules found through their build seats' holders on the seat list
The builder finds a build module as the indexer's seed does: the build seat whose acting rule
`build` has an `on` matching the artifact's language or kind:image, and that seat's holder, read
from the seat list's definitions and the claims of the catalog half's manifests. Task.Module, every
lane key, the unpublished modules, the toolchain sources, the ends watched, the sayer and
builder_stuck (through `<seat>.steps`) derive from it; the floor's sources are the holders of the
seats git and mesh-catalog. buildrun.GoBuildModule, NodeBuildModule, LanguageOf,
buildstep.BuildModuleOf use, build.ImageBuildModule and StuckBuildModules are gone from the builder
(the checker keeps its own names in two small files until its part reads them from the list).

Builds: design 47 G §7, §428, §704, §204, A-82
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 00:04:34 +01:00

282 lines
12 KiB
Go

package rules
import (
"fmt"
"strings"
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
)
// The words rules: how a failed test, a failed package and a list of failures are worded in a verdict (issue 1901, issue
// 1921). Moved whole from the running checker's package (milestone 47/B0): they are the checking program's.
// FailedName names a failure: its package and test.
func FailedName(f Failure) string {
if f.Test == "" {
return f.Package
}
return f.Package + "." + f.Test
}
// MaxWords is the most a failed test's words in a verdict hold (issue 1901). **The words keep the failure's last lines**,
// where a test says why it failed: the first line is often something it logged before (the group r4g verdict kept a
// "judged against" line and lost the t.Fatalf after it), and the 120 characters kept before cut a docker command's echo
// before docker's own error. Four kilobytes holds a long message and is small beside a verdict's other parts.
const MaxWords = 4096
// FailureWords is a failure's words: its output's last lines up to MaxWords, with the line the build module told apart as
// the test's error first when the output does not start with it, so the first line of the words is the cause and the
// status line, which is cut short, names it. **A failure's words are never "PASS" or "ok"** (issue 1901, its third
// meeting: a package that failed after its tests passed was worded "PASS"): go test's summary lines are dropped, and with
// no cause told apart (an older build module) the cause is the output's first "panic:", "fatal error:" or "signal:"
// line, read before the output is cut, or the words say the tests passed and the program failed after them.
func FailureWords(f Failure) string {
return FailureWordsAfter(f, "")
}
// FailureWordsAfter is FailureWords with the line loud put first, within the same MaxWords: what the checker itself says of
// the failure (that the build module hid it), which the status line then names.
func FailureWordsAfter(f Failure, loud string) string {
out, passed := DropSummary(strings.TrimSpace(f.Output))
cause := strings.TrimSpace(f.Cause)
if cause == "" {
cause = CauseIn(out)
}
switch {
case cause == "" && out == "" && passed:
cause = "the tests passed, then the test program exited with a failure: an os.Exit or a panic in TestMain or after the tests"
case cause == "" && out == "":
// The build module on main sends a package that stopped (a timeout, an exit, a kill) as its "FAIL" line alone.
cause = "the package failed with no test to name and no panic, signal or build error in its output (an older build module sent no cause)"
}
if len(cause) > MaxCause {
cause = strings.ToValidUTF8(cause[:MaxCause], "") + "..."
}
// The words keep MaxWords exactly: the loud line, the cause and their newlines, the marker and the kept lines.
budget := MaxWords
if cause != "" {
budget -= len(cause) + 1
}
if loud != "" {
budget -= len(loud) + 1
}
if len(out) > budget {
const marker = "... "
tail := out[len(out)-(budget-len(marker)):]
// Start on a whole line, and a whole character.
if i := strings.IndexByte(tail, '\n'); i >= 0 {
tail = strings.TrimLeft(tail[i:], "\n")
}
out = marker + strings.ToValidUTF8(tail, "")
}
if cause != "" && !strings.HasPrefix(FirstLine(out), cause) {
out = strings.TrimRight(cause+"\n"+out, "\n")
}
if loud != "" {
out = strings.TrimRight(loud+"\n"+out, "\n")
}
return out
}
// HidWords is the checker's loud line for a failure the build module sent hiding its cause (issue 1901, its thorough
// fix): with no cause, or a passing line as its cause, under the failure contract (buildstep.Hidden). It names the build
// module and the version it says it is (Tested.By, "<module> with mesh-sdk <version>"); one from before the contract
// says nothing of itself. "" when the failure keeps the contract. The check fails either way: only its words change.
func HidWords(module, by string, f Failure) string {
why := buildstep.Hidden(f)
if why == "" {
return ""
}
version := "a version before the failure contract"
if module == "" {
module = "an unnamed build module"
}
if m, v, ok := strings.Cut(by, " with "); ok && m != "" {
module, version = m, v
}
return fmt.Sprintf("the build module hid the failure (%s at %s): %s", module, version, why)
}
// MaxCause is the most a cause in a failure's words holds: one long line (the 1901 review: a cause of a kilobyte could
// push the words past MaxWords).
const MaxCause = 1024
// DropSummary drops go test's summary lines from a failure's output ("PASS", "FAIL", "ok <pkg>", "FAIL <pkg>"), and says
// whether a "PASS" was among them: the package's tests passed before it failed.
func DropSummary(out string) (string, bool) {
passed := false
lines := strings.Split(out, "\n")
kept := lines[:0]
for _, l := range lines {
// go's exact forms only (review round 1): "PASS" or "FAIL" alone, "ok \t<pkg>\t<time>", "FAIL\t<pkg>\t<time>".
switch t := strings.TrimRight(l, "\r"); {
case t == "PASS":
passed = true
case t == "FAIL", strings.HasPrefix(t, "ok \t"), strings.HasPrefix(t, "FAIL\t"):
default:
kept = append(kept, l)
}
}
return strings.TrimSpace(strings.Join(kept, "\n")), passed
}
// CauseIn is a race the race detector reported, as its heading, its access and the frame that made it ("WARNING: DATA
// RACE: Write at 0x... by goroutine 9 in pkg.F.func1()"); else the first "panic:", "fatal error:" or "signal:" line of a
// failure's output, in that order; or "". **Only lines at column 0**, where go prints the runtime's: a test's indented log
// line holding the same words is not the cause (review round 1).
func CauseIn(out string) string {
lines := strings.Split(out, "\n")
for i, l := range lines {
if strings.TrimRight(l, "\r") != "WARNING: DATA RACE" {
continue
}
words := "WARNING: DATA RACE"
if i+1 < len(lines) && strings.TrimSpace(lines[i+1]) != "" {
words += ": " + strings.TrimSuffix(strings.TrimSpace(lines[i+1]), ":")
if i+2 < len(lines) && strings.HasPrefix(lines[i+2], " ") {
words += " in " + strings.TrimSpace(lines[i+2])
}
}
return words
}
for _, prefix := range []string{"panic: ", "fatal error: ", "signal: "} {
for _, l := range lines {
if l = strings.TrimRight(l, "\r"); strings.HasPrefix(l, prefix) {
return l
}
}
}
return ""
}
// FirstLine is the first non-empty line of a failure's words.
func FirstLine(s string) string {
for _, l := range strings.Split(s, "\n") {
if l = strings.TrimSpace(l); l != "" {
return l
}
}
return ""
}
// MaxFailureLine is the most bytes of a failure's line in a verdict's Failures: the first line of its words, which is
// its cause where the build module told one apart (issue 1901). Failed keeps the first failure's words whole.
const MaxFailureLine = 300
// LevelFailure turns a level that did not pass into a verdict: Failed names what failed first with its words whole,
// and Failures every failed test of the level's results with its first line, at most meshdeliverycheckerclient.MaxFailures and an entry
// saying how many more (issue 1921).
func LevelFailure(repository, module, level string, results ...Tested) *meshdeliverychecker.VerdictsByGroupHeadsValue {
v := meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictFail}
var all []FailedFirst
for _, res := range results {
if len(res.Failed) == 0 {
why := res.Why
if why == "" {
why = "the " + level + " level did not pass"
}
all = append(all, FailedFirst{Test: level + ": " + repository, Words: why})
continue
}
for _, f := range res.Failed {
name := FailedName(f)
if module == nodeBuildModule && f.Package == "" {
name = res.Module
}
all = append(all, FailedFirst{Test: level + ": " + name, Words: FailureWordsAfter(f, HidWords(module, res.By, f))})
}
}
if len(all) == 0 { // no result: a level said failed with nothing to name
all = append(all, FailedFirst{Test: level + ": " + repository, Words: "the " + level + " level did not pass"})
}
first := all[0]
v.Failed = &first
v.Failures = failureItems(all) // whole: Settled cuts them to their first lines once every failure of the check is joined
return &v
}
// JoinFailures is one failed verdict of every failure of vs, in the order given (a nil one passed), unsettled: Failed is
// the first whole, and Failures every failure whole, or none when the one failure is no level's (a conflict, a refused
// step: one line, as before issue 1921). Nil when none failed.
func JoinFailures(vs ...*meshdeliverychecker.VerdictsByGroupHeadsValue) *meshdeliverychecker.VerdictsByGroupHeadsValue {
var out *meshdeliverychecker.VerdictsByGroupHeadsValue
var all []FailedFirst
listed := false
for _, v := range vs {
if v == nil {
continue
}
if out == nil {
out = &meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictFail, Failed: v.Failed}
}
if len(v.Failures) > 0 {
all, listed = append(all, failedList(v.Failures)...), true
continue
}
all = append(all, failedOf(*v))
}
if out != nil && (listed || len(all) > 1) {
out.Failures = failureItems(all)
}
return out
}
// Settled is a joined verdict as it is written: each failure of its list cut to its first line, at most
// meshdeliverycheckerclient.MaxFailures and then how many more (FailureLines).
func Settled(v *meshdeliverychecker.VerdictsByGroupHeadsValue) *meshdeliverychecker.VerdictsByGroupHeadsValue {
if v != nil && len(v.Failures) > 0 {
v.Failures = failureItems(FailureLines(failedList(v.Failures)))
}
return v
}
// AddFailure is a Settled list of failures with one more at its end: the first failure is put first when the list is
// empty (a verdict of one failure keeps none), and past meshdeliverycheckerclient.MaxFailures the last entry counts one more.
func AddFailure(v *meshdeliverychecker.VerdictsByGroupHeadsValue, f FailedFirst) {
if len(v.Failures) == 0 {
v.Failures = failureItems(FailureLines([]FailedFirst{failedOf(*v)}))
}
if n := len(v.Failures); n > meshdeliverycheckerclient.MaxFailures {
v.Failures[n-1].Test = fmt.Sprintf("and %d more", FailedCount(*v)-meshdeliverycheckerclient.MaxFailures+1)
return
}
v.Failures = append(v.Failures, failureItems(FailureLines([]FailedFirst{f}))...)
}
// FailureLines is every failure with the first line of its words, cut to MaxFailureLine, at most meshdeliverycheckerclient.MaxFailures
// and then one entry saying how many more failed.
func FailureLines(all []FailedFirst) []FailedFirst {
n := min(len(all), meshdeliverycheckerclient.MaxFailures)
out := make([]FailedFirst, 0, n+1)
for _, f := range all[:n] {
out = append(out, FailedFirst{Test: f.Test, Machine: f.Machine, Words: ClipLine(FirstLine(f.Words), MaxFailureLine)})
}
if more := len(all) - n; more > 0 {
out = append(out, FailedFirst{Test: fmt.Sprintf("and %d more", more)})
}
return out
}
// ClipLine cuts a line to at most n bytes on a whole character, marking the cut.
func ClipLine(s string, n int) string {
if len(s) <= n {
return s
}
s = strings.ToValidUTF8(s[:n-3], "")
return s + "..."
}
// FailedCount is how many failures a verdict names, the ones after meshdeliverycheckerclient.MaxFailures counted too.
func FailedCount(v meshdeliverychecker.VerdictsByGroupHeadsValue) int {
n := len(v.Failures)
if n > meshdeliverycheckerclient.MaxFailures {
var more int
if _, err := fmt.Sscanf(v.Failures[n-1].Test, "and %d more", &more); err == nil {
return meshdeliverycheckerclient.MaxFailures + more
}
}
return n
}