215 lines
6.6 KiB
Go
215 lines
6.6 KiB
Go
package check
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"unicode/utf8"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
)
|
|
|
|
// MaxLine is the most characters a status's line has: the forge shows a status's description in a short space, and the full
|
|
// report is linked (ADR 0447 clause 3, ADR 0484 clause 7). A line that would be longer drops its lesser parts in a fixed
|
|
// order, and is cut last.
|
|
const MaxLine = 140
|
|
|
|
// seconds is a time in milliseconds in the unit a person reads: 350ms, 4.1s, 72s.
|
|
func seconds(ms int64) string {
|
|
switch {
|
|
case ms < 1000:
|
|
return fmt.Sprintf("%dms", ms)
|
|
case ms < 10000:
|
|
return fmt.Sprintf("%.1fs", float64(ms)/1000)
|
|
}
|
|
return fmt.Sprintf("%ds", (ms+500)/1000)
|
|
}
|
|
|
|
func short12(s string) string {
|
|
if len(s) > 12 {
|
|
return s[:12]
|
|
}
|
|
return s
|
|
}
|
|
|
|
// firstFailed is what failed first, in words: a conflict, a refused step or a test, named before anything else.
|
|
func firstFailed(v delivery.Verdict) string {
|
|
f := v.Failed
|
|
switch {
|
|
case f.Test != "":
|
|
if f.Words != "" {
|
|
return f.Test + ": " + f.Words
|
|
}
|
|
return f.Test
|
|
case f.Machine != "" && f.Words != "":
|
|
return f.Machine + ": " + f.Words
|
|
case f.Machine != "":
|
|
return f.Machine
|
|
}
|
|
return f.Words
|
|
}
|
|
|
|
// pieces are the parts of a line, from the most to the least needed.
|
|
type pieces struct {
|
|
head, commits, counts, shortCounts, times, shortTimes, over, builds, composition, group string
|
|
}
|
|
|
|
func (p pieces) join(drop int) string {
|
|
parts := []string{p.head, p.commits, p.counts}
|
|
switch {
|
|
case drop < 3 && p.times != "":
|
|
parts = append(parts, p.times)
|
|
case drop == 3 && p.shortTimes != "":
|
|
parts = []string{p.head, p.commits, p.shortCounts, p.shortTimes}
|
|
}
|
|
if p.over != "" {
|
|
parts = append(parts, p.over)
|
|
}
|
|
if drop < 1 && p.builds != "" {
|
|
parts = append(parts, p.builds)
|
|
}
|
|
if drop < 2 && p.group != "" {
|
|
parts = append(parts, p.group)
|
|
}
|
|
if p.composition != "" {
|
|
parts = append(parts, p.composition)
|
|
}
|
|
out := make([]string, 0, len(parts))
|
|
for _, s := range parts {
|
|
if s != "" {
|
|
out = append(out, s)
|
|
}
|
|
}
|
|
return strings.Join(out, " | ")
|
|
}
|
|
|
|
func levelWords(name string, r delivery.LevelResult) string {
|
|
s := fmt.Sprintf("%s %d run, %d kept", name, r.Run, r.Kept)
|
|
if r.Skipped > 0 {
|
|
s += fmt.Sprintf(", %d skipped", r.Skipped)
|
|
}
|
|
return s
|
|
}
|
|
|
|
func compositionWords(c *delivery.Composition) string {
|
|
switch {
|
|
case c == nil, c.State == delivery.CompositionNotRun:
|
|
return "composition not yet run"
|
|
case c.State == delivery.CompositionPass:
|
|
return "composition passed with " + short12(c.SDK)
|
|
}
|
|
return "composition failed: " + c.Words
|
|
}
|
|
|
|
func buildWords(builds []BuildNote) string {
|
|
if len(builds) == 0 {
|
|
return ""
|
|
}
|
|
parts := make([]string, len(builds))
|
|
for i, b := range builds {
|
|
parts[i] = b.Module + " " + b.State
|
|
}
|
|
return "builds: " + strings.Join(parts, ", ")
|
|
}
|
|
|
|
// parts makes the pieces of the line of a pull request of a case.
|
|
func parts(v delivery.Verdict, cs *Case, p Pull, builds []BuildNote, slowest []string) pieces {
|
|
var pc pieces
|
|
commits := short12(p.Head) + " on " + short12(p.Trunk)
|
|
switch v.State {
|
|
case delivery.VerdictRunning:
|
|
pc.head = "checking " + commits
|
|
case delivery.VerdictPass:
|
|
pc.head = "pass " + commits
|
|
default:
|
|
pc.head = "failed: " + firstFailed(v)
|
|
pc.commits = commits
|
|
}
|
|
var counts []string
|
|
for _, level := range []string{delivery.LevelUnit, delivery.LevelIntegration} {
|
|
if r, ok := v.Levels[level]; ok {
|
|
counts = append(counts, levelWords(level, r))
|
|
}
|
|
}
|
|
pc.counts = strings.Join(counts, " | ")
|
|
var short []string
|
|
for _, level := range []string{delivery.LevelUnit, delivery.LevelIntegration} {
|
|
if r, ok := v.Levels[level]; ok {
|
|
name := map[string]string{delivery.LevelUnit: "unit", delivery.LevelIntegration: "int"}[level]
|
|
w := fmt.Sprintf("%s %d run %d kept", name, r.Run, r.Kept)
|
|
if r.Skipped > 0 {
|
|
w += fmt.Sprintf(" %d skipped", r.Skipped)
|
|
}
|
|
short = append(short, w)
|
|
}
|
|
}
|
|
pc.shortCounts = strings.Join(short, " | ")
|
|
if v.State != delivery.VerdictRunning {
|
|
t := v.Took
|
|
var times []string
|
|
for _, x := range []struct {
|
|
name string
|
|
ms int64
|
|
}{{"queue", t.Queue}, {"clone", t.Clone}, {"unit", t.Unit}, {"int setup", t.IntegrationSetup}, {"int run", t.IntegrationRun}, {"composition", t.Composition}} {
|
|
if x.ms > 0 {
|
|
times = append(times, x.name+" "+seconds(x.ms))
|
|
}
|
|
}
|
|
if len(times) > 0 {
|
|
pc.times = strings.Join(times, ", ")
|
|
}
|
|
// The compact form for a line that is too long: the parts that dominate, the integration level's setup and run as one.
|
|
short = short[:0:0]
|
|
if t.Clone > 0 {
|
|
short = append(short, "clone "+seconds(t.Clone))
|
|
}
|
|
if t.Unit > 0 {
|
|
short = append(short, "unit "+seconds(t.Unit))
|
|
}
|
|
switch {
|
|
case t.IntegrationSetup > 0 && t.IntegrationRun > 0:
|
|
short = append(short, "int "+seconds(t.IntegrationSetup)+"+"+seconds(t.IntegrationRun))
|
|
case t.IntegrationRun > 0:
|
|
short = append(short, "int "+seconds(t.IntegrationRun))
|
|
}
|
|
pc.shortTimes = strings.Join(short, " ")
|
|
}
|
|
if len(v.OverBound) > 0 {
|
|
pc.over = "over bound: " + strings.Join(v.OverBound, ", ")
|
|
if len(slowest) > 0 {
|
|
pc.over += " (slowest " + strings.Join(slowest, ", ") + ")"
|
|
}
|
|
}
|
|
pc.builds = buildWords(builds)
|
|
if v.State != delivery.VerdictRunning {
|
|
pc.composition = compositionWords(v.Composition)
|
|
}
|
|
if cs != nil && len(cs.Pulls) > 1 {
|
|
pc.group = fmt.Sprintf("group %s of %d pull requests", cs.Group, len(cs.Pulls))
|
|
if v.State != delivery.VerdictPass {
|
|
pc.group = fmt.Sprintf("%s#%d in group %s", p.Repository, p.Number, cs.Group)
|
|
}
|
|
}
|
|
return pc
|
|
}
|
|
|
|
// Report is the full line of a pull request's status, with no limit: what the read tools and the full report say.
|
|
func Report(v delivery.Verdict, cs *Case, p Pull, builds []BuildNote, slowest []string) string {
|
|
return parts(v, cs, p, builds, slowest).join(0)
|
|
}
|
|
|
|
// Line is the words of a status for one pull request of a case (ADR 0447 clause 3, ADR 0484 clause 7): the verdict first (a
|
|
// failure names the conflict, the refused step or the test before anything), the head and the trunk commit as twelve
|
|
// characters each, the tests run, kept and skipped per level, the time of each part, a level over its bound with its slowest
|
|
// tests, the builds of the touched modules, and that the composition was not yet run. It is at most MaxLine characters: the
|
|
// builds, then the group drop first, then the times are given in their compact form, then dropped, and the line is cut at last.
|
|
func Line(v delivery.Verdict, cs *Case, p Pull, builds []BuildNote, slowest []string) string {
|
|
pc := parts(v, cs, p, builds, slowest)
|
|
for drop := 0; drop <= 4; drop++ {
|
|
if s := pc.join(drop); utf8.RuneCountInString(s) <= MaxLine {
|
|
return s
|
|
}
|
|
}
|
|
runes := []rune(pc.join(4))
|
|
return string(runes[:MaxLine-1]) + "…"
|
|
}
|