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