1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
|
package testjson
import (
"fmt"
"io"
"os"
"sort"
"strings"
"time"
"golang.org/x/term"
"gotest.tools/gotestsum/internal/dotwriter"
"gotest.tools/gotestsum/internal/log"
)
func dotsFormatV1(event TestEvent, exec *Execution) string {
pkg := exec.Package(event.Package)
switch {
case event.PackageEvent():
return ""
case event.Action == ActionRun && pkg.Total == 1:
return "[" + RelativePackagePath(event.Package) + "]"
}
return fmtDot(event)
}
func fmtDot(event TestEvent) string {
withColor := colorEvent(event)
switch event.Action {
case ActionPass:
return withColor("·")
case ActionFail:
return withColor("✖")
case ActionSkip:
return withColor("↷")
}
return ""
}
type dotFormatter struct {
pkgs map[string]*dotLine
order []string
writer *dotwriter.Writer
termWidth int
}
type dotLine struct {
runes int
builder *strings.Builder
lastUpdate time.Time
}
func (l *dotLine) update(dot string) {
if dot == "" {
return
}
l.builder.WriteString(dot)
l.runes++
}
// checkWidth marks the line as full when the width of the line hits the
// terminal width.
func (l *dotLine) checkWidth(prefix, terminal int) {
if prefix+l.runes >= terminal {
l.builder.WriteString("\n" + strings.Repeat(" ", prefix))
l.runes = 0
}
}
func newDotFormatter(out io.Writer) EventFormatter {
w, _, err := term.GetSize(int(os.Stdout.Fd()))
if err != nil || w == 0 {
log.Warnf("Failed to detect terminal width for dots format, error: %v", err)
return &formatAdapter{format: dotsFormatV1, out: out}
}
return &dotFormatter{
pkgs: make(map[string]*dotLine),
writer: dotwriter.New(out),
termWidth: w,
}
}
func (d *dotFormatter) Format(event TestEvent, exec *Execution) error {
if d.pkgs[event.Package] == nil {
d.pkgs[event.Package] = &dotLine{builder: new(strings.Builder)}
d.order = append(d.order, event.Package)
}
line := d.pkgs[event.Package]
line.lastUpdate = event.Time
if !event.PackageEvent() {
line.update(fmtDot(event))
}
switch event.Action {
case ActionOutput, ActionBench:
return nil
}
// Add an empty header to work around incorrect line counting
fmt.Fprint(d.writer, "\n\n")
sort.Slice(d.order, d.orderByLastUpdated)
for _, pkg := range d.order {
line := d.pkgs[pkg]
pkgname := RelativePackagePath(pkg) + " "
prefix := fmtDotElapsed(exec.Package(pkg))
line.checkWidth(len(prefix+pkgname), d.termWidth)
fmt.Fprintf(d.writer, prefix+pkgname+line.builder.String()+"\n")
}
PrintSummary(d.writer, exec, SummarizeNone)
return d.writer.Flush()
}
// orderByLastUpdated so that the most recently updated packages move to the
// bottom of the list, leaving completed package in the same order at the top.
func (d *dotFormatter) orderByLastUpdated(i, j int) bool {
return d.pkgs[d.order[i]].lastUpdate.Before(d.pkgs[d.order[j]].lastUpdate)
}
func fmtDotElapsed(p *Package) string {
f := func(v string) string {
return fmt.Sprintf(" %5s ", v)
}
elapsed := p.Elapsed()
switch {
case p.cached:
return f("🖴 ")
case elapsed <= 0:
return f("")
case elapsed >= time.Hour:
return f("⏳ ")
case elapsed < time.Second:
return f(elapsed.String())
}
const maxWidth = 7
var steps = []time.Duration{
time.Millisecond,
10 * time.Millisecond,
100 * time.Millisecond,
time.Second,
10 * time.Second,
time.Minute,
10 * time.Minute,
}
for _, trunc := range steps {
r := f(elapsed.Truncate(trunc).String())
if len(r) <= maxWidth {
return r
}
}
return f("")
}
|