mirror of
https://github.com/docker/compose.git
synced 2025-04-08 17:05:13 +02:00
compact TUI to monitor layers download progress
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
parent
593c4263f3
commit
24ff098252
6
go.mod
6
go.mod
@ -86,8 +86,8 @@ require (
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/klauspost/compress v1.15.12 // indirect
|
||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
||||
@ -132,7 +132,7 @@ require (
|
||||
golang.org/x/crypto v0.2.0 // indirect
|
||||
golang.org/x/net v0.4.0 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect
|
||||
golang.org/x/sys v0.4.0 // indirect
|
||||
golang.org/x/sys v0.5.0 // indirect
|
||||
golang.org/x/term v0.3.0 // indirect
|
||||
golang.org/x/text v0.6.0 // indirect
|
||||
golang.org/x/time v0.1.0 // indirect
|
||||
|
13
go.sum
13
go.sum
@ -516,12 +516,12 @@ github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPK
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
|
||||
@ -1001,7 +1001,6 @@ golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@ -1021,8 +1020,8 @@ golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
|
||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
|
@ -336,23 +336,53 @@ func isServiceImageToBuild(service types.ServiceConfig, services []types.Service
|
||||
return false
|
||||
}
|
||||
|
||||
const (
|
||||
PreparingPhase = "Preparing"
|
||||
WaitingPhase = "Waiting"
|
||||
PullingFsPhase = "Pulling fs layer"
|
||||
DownloadingPhase = "Downloading"
|
||||
DownloadCompletePhase = "Download complete"
|
||||
ExtractingPhase = "Extracting"
|
||||
VerifyingChecksumPhase = "Verifying Checksum"
|
||||
AlreadyExistsPhase = "Already exists"
|
||||
PullCompletePhase = "Pull complete"
|
||||
)
|
||||
|
||||
func toPullProgressEvent(parent string, jm jsonmessage.JSONMessage, w progress.Writer) {
|
||||
if jm.ID == "" || jm.Progress == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
text string
|
||||
status = progress.Working
|
||||
text string
|
||||
total int64
|
||||
percent int
|
||||
current int64
|
||||
status = progress.Working
|
||||
)
|
||||
|
||||
text = jm.Progress.String()
|
||||
|
||||
if jm.Status == "Pull complete" ||
|
||||
jm.Status == "Already exists" ||
|
||||
strings.Contains(jm.Status, "Image is up to date") ||
|
||||
switch jm.Status {
|
||||
case PreparingPhase, WaitingPhase, PullingFsPhase:
|
||||
percent = 0
|
||||
case DownloadingPhase, ExtractingPhase, VerifyingChecksumPhase:
|
||||
if jm.Progress != nil {
|
||||
current = jm.Progress.Current
|
||||
total = jm.Progress.Total
|
||||
if jm.Progress.Total > 0 {
|
||||
percent = int(jm.Progress.Current * 100 / jm.Progress.Total)
|
||||
}
|
||||
}
|
||||
case DownloadCompletePhase, AlreadyExistsPhase, PullCompletePhase:
|
||||
status = progress.Done
|
||||
percent = 100
|
||||
}
|
||||
|
||||
if strings.Contains(jm.Status, "Image is up to date") ||
|
||||
strings.Contains(jm.Status, "Downloaded newer image") {
|
||||
status = progress.Done
|
||||
percent = 100
|
||||
}
|
||||
|
||||
if jm.Error != nil {
|
||||
@ -363,6 +393,9 @@ func toPullProgressEvent(parent string, jm jsonmessage.JSONMessage, w progress.W
|
||||
w.Event(progress.Event{
|
||||
ID: jm.ID,
|
||||
ParentID: parent,
|
||||
Current: current,
|
||||
Total: total,
|
||||
Percent: percent,
|
||||
Text: jm.Status,
|
||||
Status: status,
|
||||
StatusText: text,
|
||||
|
@ -139,11 +139,15 @@ func toPushProgressEvent(prefix string, jm jsonmessage.JSONMessage, w progress.W
|
||||
return
|
||||
}
|
||||
var (
|
||||
text string
|
||||
status = progress.Working
|
||||
text string
|
||||
status = progress.Working
|
||||
total int64
|
||||
current int64
|
||||
percent int
|
||||
)
|
||||
if jm.Status == "Pull complete" || jm.Status == "Already exists" {
|
||||
if jm.Status == "Pushed" || jm.Status == "Already exists" {
|
||||
status = progress.Done
|
||||
percent = 100
|
||||
}
|
||||
if jm.Error != nil {
|
||||
status = progress.Error
|
||||
@ -151,11 +155,22 @@ func toPushProgressEvent(prefix string, jm jsonmessage.JSONMessage, w progress.W
|
||||
}
|
||||
if jm.Progress != nil {
|
||||
text = jm.Progress.String()
|
||||
if jm.Progress.Total != 0 {
|
||||
current = jm.Progress.Current
|
||||
total = jm.Progress.Total
|
||||
if jm.Progress.Total > 0 {
|
||||
percent = int(jm.Progress.Current * 100 / jm.Progress.Total)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
w.Event(progress.Event{
|
||||
ID: fmt.Sprintf("Pushing %s: %s", prefix, jm.ID),
|
||||
Text: jm.Status,
|
||||
Status: status,
|
||||
Current: current,
|
||||
Total: total,
|
||||
Percent: percent,
|
||||
StatusText: text,
|
||||
})
|
||||
}
|
||||
|
@ -16,20 +16,37 @@
|
||||
|
||||
package progress
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/morikuni/aec"
|
||||
)
|
||||
|
||||
// EventStatus indicates the status of an action
|
||||
type EventStatus int
|
||||
|
||||
func (s EventStatus) color() aec.ANSI {
|
||||
switch s {
|
||||
case Done:
|
||||
return aec.GreenF
|
||||
case Warning:
|
||||
return aec.YellowF.With(aec.Bold)
|
||||
case Error:
|
||||
return aec.RedF.With(aec.Bold)
|
||||
default:
|
||||
return aec.DefaultF
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
// Working means that the current task is working
|
||||
Working EventStatus = iota
|
||||
// Done means that the current task is done
|
||||
Done
|
||||
// Error means that the current task has errored
|
||||
Error
|
||||
// Warning means that the current task has warning
|
||||
Warning
|
||||
// Error means that the current task has errored
|
||||
Error
|
||||
)
|
||||
|
||||
// Event represents a progress event.
|
||||
@ -39,7 +56,10 @@ type Event struct {
|
||||
Text string
|
||||
Status EventStatus
|
||||
StatusText string
|
||||
Current int64
|
||||
Percent int
|
||||
|
||||
Total int64
|
||||
startTime time.Time
|
||||
endTime time.Time
|
||||
spinner *spinner
|
||||
@ -148,3 +168,22 @@ func (e *Event) stop() {
|
||||
e.endTime = time.Now()
|
||||
e.spinner.Stop()
|
||||
}
|
||||
|
||||
var (
|
||||
spinnerDone = aec.Apply("✔", aec.GreenF)
|
||||
spinnerWarning = aec.Apply("!", aec.YellowF)
|
||||
spinnerError = aec.Apply("✘", aec.RedF)
|
||||
)
|
||||
|
||||
func (e *Event) Spinner() any {
|
||||
switch e.Status {
|
||||
case Done:
|
||||
return spinnerDone
|
||||
case Warning:
|
||||
return spinnerWarning
|
||||
case Error:
|
||||
return spinnerError
|
||||
default:
|
||||
return e.spinner.String()
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@ -29,19 +28,21 @@ import (
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
|
||||
"github.com/buger/goterm"
|
||||
"github.com/docker/go-units"
|
||||
"github.com/morikuni/aec"
|
||||
)
|
||||
|
||||
type ttyWriter struct {
|
||||
out io.Writer
|
||||
events map[string]Event
|
||||
eventIDs []string
|
||||
repeated bool
|
||||
numLines int
|
||||
done chan bool
|
||||
mtx *sync.Mutex
|
||||
tailEvents []string
|
||||
dryRun bool
|
||||
out io.Writer
|
||||
events map[string]Event
|
||||
eventIDs []string
|
||||
repeated bool
|
||||
numLines int
|
||||
done chan bool
|
||||
mtx *sync.Mutex
|
||||
tailEvents []string
|
||||
dryRun bool
|
||||
skipChildEvents bool
|
||||
}
|
||||
|
||||
func (w *ttyWriter) Start(ctx context.Context) error {
|
||||
@ -85,6 +86,9 @@ func (w *ttyWriter) Event(e Event) {
|
||||
last.Status = e.Status
|
||||
last.Text = e.Text
|
||||
last.StatusText = e.StatusText
|
||||
last.Total = e.Total
|
||||
last.Current = e.Current
|
||||
last.Percent = e.Percent
|
||||
// allow set/unset of parent, but not swapping otherwise prompt is flickering
|
||||
if last.ParentID == "" || e.ParentID == "" {
|
||||
last.ParentID = e.ParentID
|
||||
@ -163,9 +167,8 @@ func (w *ttyWriter) print() { //nolint:gocyclo
|
||||
}
|
||||
}
|
||||
|
||||
skipChildEvents := false
|
||||
if len(w.eventIDs) > goterm.Height()-2 {
|
||||
skipChildEvents = true
|
||||
w.skipChildEvents = true
|
||||
}
|
||||
numLines := 0
|
||||
for _, v := range w.eventIDs {
|
||||
@ -173,16 +176,16 @@ func (w *ttyWriter) print() { //nolint:gocyclo
|
||||
if event.ParentID != "" {
|
||||
continue
|
||||
}
|
||||
line := lineText(event, "", terminalWidth, statusPadding, runtime.GOOS != "windows", w.dryRun)
|
||||
line := w.lineText(event, "", terminalWidth, statusPadding, w.dryRun)
|
||||
fmt.Fprint(w.out, line)
|
||||
numLines++
|
||||
for _, v := range w.eventIDs {
|
||||
ev := w.events[v]
|
||||
if ev.ParentID == event.ID {
|
||||
if skipChildEvents {
|
||||
if w.skipChildEvents {
|
||||
continue
|
||||
}
|
||||
line := lineText(ev, " ", terminalWidth, statusPadding, runtime.GOOS != "windows", w.dryRun)
|
||||
line := w.lineText(ev, " ", terminalWidth, statusPadding, w.dryRun)
|
||||
fmt.Fprint(w.out, line)
|
||||
numLines++
|
||||
}
|
||||
@ -197,7 +200,7 @@ func (w *ttyWriter) print() { //nolint:gocyclo
|
||||
w.numLines = numLines
|
||||
}
|
||||
|
||||
func lineText(event Event, pad string, terminalWidth, statusPadding int, color bool, dryRun bool) string {
|
||||
func (w *ttyWriter) lineText(event Event, pad string, terminalWidth, statusPadding int, dryRun bool) string {
|
||||
endTime := time.Now()
|
||||
if event.Status != Working {
|
||||
endTime = event.startTime
|
||||
@ -207,12 +210,38 @@ func lineText(event Event, pad string, terminalWidth, statusPadding int, color b
|
||||
}
|
||||
prefix := ""
|
||||
if dryRun {
|
||||
prefix = api.DRYRUN_PREFIX
|
||||
prefix = aec.Apply(api.DRYRUN_PREFIX, aec.CyanF)
|
||||
}
|
||||
|
||||
elapsed := endTime.Sub(event.startTime).Seconds()
|
||||
|
||||
textLen := len(fmt.Sprintf("%s %s", event.ID, event.Text))
|
||||
var (
|
||||
total int64
|
||||
current int64
|
||||
completion []string
|
||||
)
|
||||
|
||||
for _, v := range w.eventIDs {
|
||||
ev := w.events[v]
|
||||
if ev.ParentID == event.ID {
|
||||
total += ev.Total
|
||||
current += ev.Current
|
||||
completion = append(completion, percentChars[(len(percentChars)-1)*ev.Percent/100])
|
||||
}
|
||||
}
|
||||
|
||||
var txt string
|
||||
if len(completion) > 0 {
|
||||
txt = fmt.Sprintf("%s %s [%s] %7s/%-7s %s",
|
||||
event.ID,
|
||||
aec.Apply(fmt.Sprintf("%d layers", len(completion)), aec.YellowF),
|
||||
aec.Apply(strings.Join(completion, ""), aec.GreenF, aec.Bold),
|
||||
units.HumanSize(float64(current)), units.HumanSize(float64(total)),
|
||||
event.Text)
|
||||
} else {
|
||||
txt = fmt.Sprintf("%s %s", event.ID, event.Text)
|
||||
}
|
||||
textLen := len(txt)
|
||||
padding := statusPadding - textLen
|
||||
if padding < 0 {
|
||||
padding = 0
|
||||
@ -225,31 +254,16 @@ func lineText(event Event, pad string, terminalWidth, statusPadding int, color b
|
||||
if maxStatusLen > 0 && len(status) > maxStatusLen {
|
||||
status = status[:maxStatusLen] + "..."
|
||||
}
|
||||
text := fmt.Sprintf("%s %s%s %s %s%s %s",
|
||||
text := fmt.Sprintf("%s %s%s %s%s %s",
|
||||
pad,
|
||||
event.spinner.String(),
|
||||
event.Spinner(),
|
||||
prefix,
|
||||
event.ID,
|
||||
event.Text,
|
||||
txt,
|
||||
strings.Repeat(" ", padding),
|
||||
status,
|
||||
aec.Apply(status, event.Status.color()),
|
||||
)
|
||||
timer := fmt.Sprintf("%.1fs\n", elapsed)
|
||||
o := align(text, timer, terminalWidth)
|
||||
|
||||
if color {
|
||||
color := aec.WhiteF
|
||||
if event.Status == Done {
|
||||
color = aec.BlueF
|
||||
}
|
||||
if event.Status == Error {
|
||||
color = aec.RedF
|
||||
}
|
||||
if event.Status == Warning {
|
||||
color = aec.YellowF
|
||||
}
|
||||
return aec.Apply(o, color)
|
||||
}
|
||||
timer := fmt.Sprintf("%.1fs ", elapsed)
|
||||
o := align(text, aec.Apply(timer, aec.BlueF), terminalWidth)
|
||||
|
||||
return o
|
||||
}
|
||||
@ -257,7 +271,7 @@ func lineText(event Event, pad string, terminalWidth, statusPadding int, color b
|
||||
func numDone(events map[string]Event) int {
|
||||
i := 0
|
||||
for _, e := range events {
|
||||
if e.Status == Done {
|
||||
if e.Status != Working {
|
||||
i++
|
||||
}
|
||||
}
|
||||
@ -265,5 +279,32 @@ func numDone(events map[string]Event) int {
|
||||
}
|
||||
|
||||
func align(l, r string, w int) string {
|
||||
return fmt.Sprintf("%-[2]*[1]s %[3]s", l, w-len(r)-1, r)
|
||||
ll := lenAnsi(l)
|
||||
lr := lenAnsi(r)
|
||||
pad := strings.Repeat(" ", w-ll-lr)
|
||||
return fmt.Sprintf("%s%s%s\n", l, pad, r)
|
||||
}
|
||||
|
||||
// lenAnsi count of user-perceived characters in ANSI string.
|
||||
func lenAnsi(s string) int {
|
||||
length := 0
|
||||
ansiCode := false
|
||||
for _, r := range s {
|
||||
if r == '\x1b' {
|
||||
ansiCode = true
|
||||
continue
|
||||
}
|
||||
if ansiCode && r == 'm' {
|
||||
ansiCode = false
|
||||
continue
|
||||
}
|
||||
if !ansiCode {
|
||||
length++
|
||||
}
|
||||
}
|
||||
return length
|
||||
}
|
||||
|
||||
var (
|
||||
percentChars = strings.Split("⠀⡀⣀⣄⣤⣦⣶⣷⣿", "")
|
||||
)
|
||||
|
@ -41,23 +41,20 @@ func TestLineText(t *testing.T) {
|
||||
|
||||
lineWidth := len(fmt.Sprintf("%s %s", ev.ID, ev.Text))
|
||||
|
||||
out := lineText(ev, "", 50, lineWidth, true, false)
|
||||
assert.Equal(t, out, "\x1b[37m . id Text Status 0.0s\n\x1b[0m")
|
||||
|
||||
out = lineText(ev, "", 50, lineWidth, false, false)
|
||||
assert.Equal(t, out, " . id Text Status 0.0s\n")
|
||||
out := tty().lineText(ev, "", 50, lineWidth, false)
|
||||
assert.Equal(t, out, " . id Text \x1b[39mStatus\x1b[0m \x1b[34m0.0s \x1b[0m\n")
|
||||
|
||||
ev.Status = Done
|
||||
out = lineText(ev, "", 50, lineWidth, true, false)
|
||||
assert.Equal(t, out, "\x1b[34m . id Text Status 0.0s\n\x1b[0m")
|
||||
out = tty().lineText(ev, "", 50, lineWidth, false)
|
||||
assert.Equal(t, out, " \x1b[32m✔\x1b[0m id Text \x1b[32mStatus\x1b[0m \x1b[34m0.0s \x1b[0m\n")
|
||||
|
||||
ev.Status = Error
|
||||
out = lineText(ev, "", 50, lineWidth, true, false)
|
||||
assert.Equal(t, out, "\x1b[31m . id Text Status 0.0s\n\x1b[0m")
|
||||
out = tty().lineText(ev, "", 50, lineWidth, false)
|
||||
assert.Equal(t, out, " \x1b[31m✘\x1b[0m id Text \x1b[31m\x1b[1mStatus\x1b[0m \x1b[34m0.0s \x1b[0m\n")
|
||||
|
||||
ev.Status = Warning
|
||||
out = lineText(ev, "", 50, lineWidth, true, false)
|
||||
assert.Equal(t, out, "\x1b[33m . id Text Status 0.0s\n\x1b[0m")
|
||||
out = tty().lineText(ev, "", 50, lineWidth, false)
|
||||
assert.Equal(t, out, " \x1b[33m!\x1b[0m id Text \x1b[33m\x1b[1mStatus\x1b[0m \x1b[34m0.0s \x1b[0m\n")
|
||||
}
|
||||
|
||||
func TestLineTextSingleEvent(t *testing.T) {
|
||||
@ -75,8 +72,8 @@ func TestLineTextSingleEvent(t *testing.T) {
|
||||
|
||||
lineWidth := len(fmt.Sprintf("%s %s", ev.ID, ev.Text))
|
||||
|
||||
out := lineText(ev, "", 50, lineWidth, true, false)
|
||||
assert.Equal(t, out, "\x1b[34m . id Text Status 0.0s\n\x1b[0m")
|
||||
out := tty().lineText(ev, "", 50, lineWidth, false)
|
||||
assert.Equal(t, out, " \x1b[32m✔\x1b[0m id Text \x1b[32mStatus\x1b[0m \x1b[34m0.0s \x1b[0m\n")
|
||||
}
|
||||
|
||||
func TestErrorEvent(t *testing.T) {
|
||||
@ -136,3 +133,13 @@ func TestWarningEvent(t *testing.T) {
|
||||
assert.Assert(t, ok)
|
||||
assert.Assert(t, event.endTime.After(time.Now().Add(-10*time.Second)))
|
||||
}
|
||||
|
||||
func tty() *ttyWriter {
|
||||
tty := &ttyWriter{
|
||||
eventIDs: []string{},
|
||||
events: map[string]Event{},
|
||||
done: make(chan bool),
|
||||
mtx: &sync.Mutex{},
|
||||
}
|
||||
return tty
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user