Skip to content
Merged
42 changes: 31 additions & 11 deletions cmd/src/campaign_progress_printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"fmt"
"sort"
"strings"

"github.com/sourcegraph/go-diff/diff"
Expand Down Expand Up @@ -29,6 +30,9 @@ func newCampaignProgressPrinter(out *output.Output, verbose bool, numParallelism
}

type campaignProgressPrinter struct {
// Used in tests only
forceNoSpinner bool

out *output.Output

sem *semaphore.Weighted
Expand Down Expand Up @@ -59,10 +63,15 @@ func (p *campaignProgressPrinter) initProgressBar(statuses []*campaigns.TaskStat
statusBars = append(statusBars, output.NewStatusBar())
}

p.progress = p.out.ProgressWithStatusBars([]output.ProgressBar{{
Label: fmt.Sprintf("Executing ... (0/%d, 0 errored)", len(statuses)),
Max: float64(len(statuses)),
}}, statusBars, nil)
progressBars := []output.ProgressBar{
{
Label: fmt.Sprintf("Executing ... (0/%d, 0 errored)", len(statuses)),
Max: float64(len(statuses)),
},
}

opts := output.DefaultProgressTTYOpts.WithoutSpinner()
p.progress = p.out.ProgressWithStatusBars(progressBars, statusBars, opts)

return numStatusBars
}
Expand Down Expand Up @@ -298,12 +307,12 @@ func verboseDiffSummary(fileDiffs []*diff.FileDiff) ([]string, error) {
)

fileStats := make(map[string]string, len(fileDiffs))
fileNames := make([]string, len(fileDiffs))

for _, f := range fileDiffs {
name := f.NewName
if name == "/dev/null" {
name = f.OrigName
}
for i, f := range fileDiffs {
name := diffDisplayName(f)

fileNames[i] = name

if len(name) > maxFilenameLen {
maxFilenameLen = len(name)
Expand All @@ -319,8 +328,11 @@ func verboseDiffSummary(fileDiffs []*diff.FileDiff) ([]string, error) {
fileStats[name] = fmt.Sprintf("%d %s", num, diffStatDiagram(stat))
}

for file, stats := range fileStats {
lines = append(lines, fmt.Sprintf("\t%-*s | %s", maxFilenameLen, file, stats))
sort.Slice(fileNames, func(i, j int) bool { return fileNames[i] < fileNames[j] })

for _, name := range fileNames {
stats := fileStats[name]
lines = append(lines, fmt.Sprintf("\t%-*s | %s", maxFilenameLen, name, stats))
}

var insertionsPlural string
Expand All @@ -341,3 +353,11 @@ func verboseDiffSummary(fileDiffs []*diff.FileDiff) ([]string, error) {

return lines, nil
}

func diffDisplayName(f *diff.FileDiff) string {
name := f.NewName
if name == "/dev/null" {
name = f.OrigName
}
return name
}
263 changes: 263 additions & 0 deletions cmd/src/campaign_progress_printer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
package main

import (
"runtime"
"strconv"
"testing"
"time"

"github.com/google/go-cmp/cmp"
"github.com/sourcegraph/src-cli/internal/campaigns"
"github.com/sourcegraph/src-cli/internal/output"
)

const progressPrinterDiff = `diff --git README.md README.md
new file mode 100644
index 0000000..3363c39
--- /dev/null
+++ README.md
@@ -0,0 +1,3 @@
+# README
+
+This is the readme
diff --git a/b/c/c.txt a/b/c/c.txt
deleted file mode 100644
index 5da75cf..0000000
--- a/b/c/c.txt
+++ /dev/null
@@ -1 +0,0 @@
-this is c
diff --git x/x.txt x/x.txt
index 627c2ae..88f1836 100644
--- x/x.txt
+++ x/x.txt
@@ -1 +1 @@
-this is x
+this is x (or is it?)
`

func TestCampaignProgressPrinterIntegration(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("Something emits different escape codes on windows.")
}

buf := &ttyBuf{}

out := output.NewOutput(buf, output.OutputOpts{
ForceTTY: true,
ForceColor: true,
ForceHeight: 25,
ForceWidth: 80,
Verbose: true,
})

now := time.Now()
statuses := []*campaigns.TaskStatus{
{
RepoName: "github.com/sourcegraph/sourcegraph",
StartedAt: now,
CurrentlyExecuting: "echo Hello World > README.md",
},
{
RepoName: "github.com/sourcegraph/src-cli",
StartedAt: now.Add(time.Duration(-5) * time.Second),
CurrentlyExecuting: "Downloading archive",
},
{
RepoName: "github.com/sourcegraph/automation-testing",
StartedAt: now.Add(time.Duration(-5) * time.Second),
CurrentlyExecuting: "echo Hello World > README.md",
},
}

printer := newCampaignProgressPrinter(out, true, 4)
printer.forceNoSpinner = true

// Print with all three tasks running
printer.PrintStatuses(statuses)
have := buf.Lines()
want := []string{
"⠋ Executing... (0/3, 0 errored) ",
"│ ",
"├── github.com/sourcegraph/sourcegraph echo Hello World > README.md 0s",
"├── github.com/sourcegraph/src-cli Downloading archive 0s",
"└── github.com/sourcegraph/automati... echo Hello World > README.md 0s",
"",
}
if !cmp.Equal(want, have) {
t.Fatalf("wrong output:\n%s", cmp.Diff(want, have))
}

// Now mark the last task as completed
statuses[len(statuses)-1] = &campaigns.TaskStatus{
RepoName: "github.com/sourcegraph/automation-testing",
StartedAt: now.Add(time.Duration(-5) * time.Second),
FinishedAt: now.Add(time.Duration(5) * time.Second),
CurrentlyExecuting: "",
Err: nil,
ChangesetSpec: &campaigns.ChangesetSpec{
BaseRepository: "graphql-id",
CreatedChangeset: &campaigns.CreatedChangeset{
BaseRef: "refs/heads/main",
BaseRev: "d34db33f",
HeadRepository: "graphql-id",
HeadRef: "refs/heads/my-campaign",
Title: "This is my campaign",
Body: "This is my campaign",
Commits: []campaigns.GitCommitDescription{
{
Message: "This is my campaign",
Diff: progressPrinterDiff,
},
},
Published: false,
},
},
}

printer.PrintStatuses(statuses)
have = buf.Lines()
want = []string{
"github.com/sourcegraph/automation-testing",
"\tREADME.md | 3 +++",
"\ta/b/c/c.txt | 1 -",
"\tx/x.txt | 2 +-",
" 3 files changed, 4 insertions, 2 deletions",
" Execution took 10s",
"",
"⠋ Executing... (1/3, 0 errored) ███████████████▍",
"│ ",
"├── github.com/sourcegraph/sourcegraph echo Hello World > README.md 0s",
"├── github.com/sourcegraph/src-cli Downloading archive 0s",
"└── github.com/sourcegraph/automati... 3 files changed ++++ 0s",
"",
}
if !cmp.Equal(want, have) {
t.Fatalf("wrong output:\n%s", cmp.Diff(want, have))
}

// Print again to make sure we get the same result
printer.PrintStatuses(statuses)
have = buf.Lines()
if !cmp.Equal(want, have) {
t.Fatalf("wrong output:\n%s", cmp.Diff(want, have))
}
}

type ttyBuf struct {
lines [][]byte

line int
column int
}

func (t *ttyBuf) Write(b []byte) (int, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice!

var cur int

for cur < len(b) {
switch b[cur] {
case '\n':
t.line++
t.column = 0

if len(t.lines) == t.line {
t.lines = append(t.lines, []byte{})
}

case '\x1b':
// Check if we're looking at a VT100 escape code.
if len(b) <= cur || b[cur+1] != '[' {
t.writeToCurrentLine(b[cur])
cur++
continue
}

// First of all: forgive me.
//
// Now. Looks like we ran into a VT100 escape code.
// They follow this structure:
//
// \x1b [ <digit> <command>
//
// So we jump over the \x1b[ and try to parse the digit.

cur = cur + 2 // cur == '\x1b', cur + 1 == '['

digitStart := cur
for isDigit(b[cur]) {
cur++
}

rawDigit := string(b[digitStart:cur])
digit, err := strconv.ParseInt(rawDigit, 0, 64)
if err != nil {
return 0, err
}

command := b[cur]

// Debug helper:
// fmt.Printf("command=%q, digit=%d (t.line=%d, t.column=%d)\n", command, digit, t.line, t.column)

switch command {
case 'K':
// reset current line
if len(t.lines) > t.line {
t.lines[t.line] = []byte{}
t.column = 0
}
case 'A':
// move line up by <digit>
t.line = t.line - int(digit)

case 'D':
// *d*elete cursor by <digit> amount
t.column = t.column - int(digit)
if t.column < 0 {
t.column = 0
}

case 'm':
// noop

case ';':
// color, skip over until end of color command
for b[cur] != 'm' {
cur++
}
}

default:
t.writeToCurrentLine(b[cur])
}

cur++
}

return len(b), nil
}

func (t *ttyBuf) writeToCurrentLine(b byte) {
if len(t.lines) == t.line {
t.lines = append(t.lines, []byte{})
}

if len(t.lines[t.line]) <= t.column {
t.lines[t.line] = append(t.lines[t.line], b)
} else {
t.lines[t.line][t.column] = b
}
t.column++
}

func (t *ttyBuf) Lines() []string {
var lines []string
for _, l := range t.lines {
lines = append(lines, string(l))
}
return lines
}

func isDigit(ch byte) bool {
return '0' <= ch && ch <= '9'
}
19 changes: 18 additions & 1 deletion internal/output/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,15 @@ type Output struct {
type OutputOpts struct {
// ForceColor ignores all terminal detection and enabled coloured output.
ForceColor bool
Verbose bool
// ForceTTY ignores all terminal detection and enables TTY output.
ForceTTY bool

// ForceHeight ignores all terminal detection and sets the height to this value.
ForceHeight int
// ForceWidth ignores all terminal detection and sets the width to this value.
ForceWidth int

Verbose bool
}

// newOutputPlatformQuirks provides a way for conditionally compiled code to
Expand All @@ -62,6 +70,15 @@ func NewOutput(w io.Writer, opts OutputOpts) *Output {
if opts.ForceColor {
caps.Color = true
}
if opts.ForceTTY {
caps.Isatty = true
}
if opts.ForceHeight != 0 {
caps.Height = opts.ForceHeight
}
if opts.ForceWidth != 0 {
caps.Width = opts.ForceWidth
}

o := &Output{caps: caps, opts: opts, w: w}
if newOutputPlatformQuirks != nil {
Expand Down
Loading