package rules import ( "fmt" "strings" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshdeliverycheckerclient" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshdeliverychecker" "git.novox.be/novox/mesh-delivery/modules/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, " with mesh-sdk "); 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 ", "FAIL "), 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\t