Skip to content

Commit

Permalink
Move CLI UI to ui/console package
Browse files Browse the repository at this point in the history
  • Loading branch information
Ivan Mirić authored and imiric committed Dec 20, 2022
1 parent f5e670c commit b0e0243
Show file tree
Hide file tree
Showing 28 changed files with 756 additions and 603 deletions.
54 changes: 32 additions & 22 deletions cmd/cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"sync"
"time"

"github.com/fatih/color"
"github.com/spf13/cobra"
"github.com/spf13/pflag"

Expand Down Expand Up @@ -64,13 +63,13 @@ func (c *cmdCloud) preRun(cmd *cobra.Command, args []string) error {
// TODO: split apart some more
//nolint:funlen,gocognit,cyclop
func (c *cmdCloud) run(cmd *cobra.Command, args []string) error {
printBanner(c.gs)
maybePrintBanner(c.gs)

progressBar := pb.New(
pb.WithConstLeft("Init"),
pb.WithConstProgress(0, "Loading test script..."),
)
printBar(c.gs, progressBar)
maybePrintBar(c.gs, progressBar)

test, err := loadAndConfigureTest(c.gs, cmd, args, getPartialConfig)
if err != nil {
Expand All @@ -91,7 +90,8 @@ func (c *cmdCloud) run(cmd *cobra.Command, args []string) error {
// TODO: validate for externally controlled executor (i.e. executors that aren't distributable)
// TODO: move those validations to a separate function and reuse validateConfig()?

modifyAndPrintBar(c.gs, progressBar, pb.WithConstProgress(0, "Building the archive..."))
progressBar.Modify(pb.WithConstProgress(0, "Building the archive..."))
maybePrintBar(c.gs, progressBar)
arc := testRunState.Runner.MakeArchive()

// TODO: Fix this
Expand Down Expand Up @@ -152,14 +152,16 @@ func (c *cmdCloud) run(cmd *cobra.Command, args []string) error {
logger := c.gs.logger

// Start cloud test run
modifyAndPrintBar(c.gs, progressBar, pb.WithConstProgress(0, "Validating script options"))
progressBar.Modify(pb.WithConstProgress(0, "Validating script options"))
maybePrintBar(c.gs, progressBar)
client := cloudapi.NewClient(
logger, cloudConfig.Token.String, cloudConfig.Host.String, consts.Version, cloudConfig.Timeout.TimeDuration())
if err = client.ValidateOptions(arc.Options); err != nil {
return err
}

modifyAndPrintBar(c.gs, progressBar, pb.WithConstProgress(0, "Uploading archive"))
progressBar.Modify(pb.WithConstProgress(0, "Uploading archive"))
maybePrintBar(c.gs, progressBar)
refID, err := client.StartCloudTestRun(name, cloudConfig.ProjectID.Int64, arc)
if err != nil {
return err
Expand Down Expand Up @@ -192,24 +194,34 @@ func (c *cmdCloud) run(cmd *cobra.Command, args []string) error {
}
testURL := cloudapi.URLForResults(refID, cloudConfig)
executionPlan := test.derivedConfig.Scenarios.GetFullExecutionRequirements(et)
printExecutionDescription(
c.gs, "cloud", test.sourceRootPath, testURL, test.derivedConfig, et, executionPlan, nil,

execDesc := getExecutionDescription(
c.gs.console.ApplyTheme, "cloud", test.sourceRootPath, testURL, test.derivedConfig,
et, executionPlan, nil,
)
if c.gs.flags.quiet {
c.gs.logger.Debug(execDesc)
} else {
c.gs.console.Print(execDesc)
}

modifyAndPrintBar(
c.gs, progressBar,
pb.WithConstLeft("Run "), pb.WithConstProgress(0, "Initializing the cloud test"),
progressBar.Modify(
pb.WithConstLeft("Run "),
pb.WithConstProgress(0, "Initializing the cloud test"),
)
maybePrintBar(c.gs, progressBar)

progressCtx, progressCancel := context.WithCancel(globalCtx)
progressBarWG := &sync.WaitGroup{}
progressBarWG.Add(1)
defer progressBarWG.Wait()
defer progressCancel()
go func() {
showProgress(progressCtx, c.gs, []*pb.ProgressBar{progressBar}, logger)
progressBarWG.Done()
}()
if !c.gs.flags.quiet {
progressBarWG := &sync.WaitGroup{}
progressBarWG.Add(1)
defer progressBarWG.Wait()
go func() {
c.gs.console.ShowProgress(progressCtx, []*pb.ProgressBar{progressBar})
progressBarWG.Done()
}()
}

var (
startTime time.Time
Expand Down Expand Up @@ -282,10 +294,8 @@ func (c *cmdCloud) run(cmd *cobra.Command, args []string) error {
}

if !c.gs.flags.quiet {
valueColor := getColor(c.gs.flags.noColor || !c.gs.stdOut.isTTY, color.FgCyan)
printToStdout(c.gs, fmt.Sprintf(
" test status: %s\n", valueColor.Sprint(testProgress.RunStatusText),
))
c.gs.console.Printf(" test status: %s\n",
c.gs.console.ApplyTheme(testProgress.RunStatusText))
} else {
logger.WithField("run_status", testProgress.RunStatusText).Debug("Test finished")
}
Expand Down
58 changes: 52 additions & 6 deletions cmd/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,18 @@ package cmd
import (
"fmt"
"os"
"strings"
"syscall"
"time"

"github.com/spf13/cobra"
"github.com/spf13/pflag"
"gopkg.in/guregu/null.v3"

"go.k6.io/k6/errext/exitcodes"
"go.k6.io/k6/lib"
"go.k6.io/k6/lib/types"
"go.k6.io/k6/output"
)

// Panic if the given error is not nil.
Expand Down Expand Up @@ -65,12 +69,6 @@ func exactArgsWithMsg(n int, msg string) cobra.PositionalArgs {
}
}

func printToStdout(gs *globalState, s string) {
if _, err := fmt.Fprint(gs.stdOut, s); err != nil {
gs.logger.Errorf("could not print '%s' to stdout: %s", s, err.Error())
}
}

// Trap Interrupts, SIGINTs and SIGTERMs and call the given.
func handleTestAbortSignals(gs *globalState, gracefulStopHandler, onHardStop func(os.Signal)) (stop func()) {
sigC := make(chan os.Signal, 2)
Expand Down Expand Up @@ -103,3 +101,51 @@ func handleTestAbortSignals(gs *globalState, gracefulStopHandler, onHardStop fun
gs.signalStop(sigC)
}
}

// Generate execution description for both cloud and local execution.
// TODO: Clean this up as part of #1499 or #1427
func getExecutionDescription(
applyTheme func(string) string, execution, filename, outputOverride string,
conf Config, et *lib.ExecutionTuple, execPlan []lib.ExecutionStep,
outputs []output.Output,
) string {
buf := &strings.Builder{}
fmt.Fprintf(buf, " execution: %s\n", applyTheme(execution))
fmt.Fprintf(buf, " script: %s\n", applyTheme(filename))

var outputDescriptions []string
switch {
case outputOverride != "":
outputDescriptions = []string{outputOverride}
case len(outputs) == 0:
outputDescriptions = []string{"-"}
default:
for _, out := range outputs {
outputDescriptions = append(outputDescriptions, out.Description())
}
}

fmt.Fprintf(buf, " output: %s\n", applyTheme(strings.Join(outputDescriptions, ", ")))
fmt.Fprintf(buf, "\n")

maxDuration, _ := lib.GetEndOffset(execPlan)
executorConfigs := conf.Scenarios.GetSortedConfigs()

scenarioDesc := "1 scenario"
if len(executorConfigs) > 1 {
scenarioDesc = fmt.Sprintf("%d scenarios", len(executorConfigs))
}

fmt.Fprintf(buf, " scenarios: %s\n", applyTheme(fmt.Sprintf(
"(%.2f%%) %s, %d max VUs, %s max duration (incl. graceful stop):",
conf.ExecutionSegment.FloatLength()*100, scenarioDesc,
lib.GetMaxPossibleVUs(execPlan), maxDuration.Round(100*time.Millisecond)),
))
for _, ec := range executorConfigs {
fmt.Fprintf(buf, " * %s: %s\n",
ec.GetName(), ec.GetDescription(et))
}
fmt.Fprintf(buf, "\n")

return buf.String()
}
2 changes: 1 addition & 1 deletion cmd/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ func getCmdConvert(globalState *globalState) *cobra.Command {

// Write script content to stdout or file
if convertOutput == "" || convertOutput == "-" { //nolint:nestif
if _, err := io.WriteString(globalState.stdOut, script); err != nil {
if _, err := io.WriteString(globalState.console.Stdout, script); err != nil {
return err
}
} else {
Expand Down
2 changes: 1 addition & 1 deletion cmd/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func getCmdInspect(gs *globalState) *cobra.Command {
if err != nil {
return err
}
printToStdout(gs, string(data))
gs.console.Print(string(data))

return nil
},
Expand Down
51 changes: 16 additions & 35 deletions cmd/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func TestSimpleTestStdin(t *testing.T) {

ts := newGlobalTestState(t)
ts.args = []string{"k6", "run", "-"}
ts.stdIn = bytes.NewBufferString(`export default function() {};`)
ts.console.Stdin = &testOSFileR{bytes.NewBufferString(`export default function() {};`)}
newRootCommand(ts.globalState).execute()

stdOut := ts.stdOut.String()
Expand All @@ -67,17 +67,12 @@ func TestStdoutAndStderrAreEmptyWithQuietAndHandleSummary(t *testing.T) {

ts := newGlobalTestState(t)
ts.args = []string{"k6", "--quiet", "run", "-"}
ts.stdIn = bytes.NewBufferString(`
ts.console.Stdin = &testOSFileR{bytes.NewBufferString(`
export default function() {};
export function handleSummary(data) {
return {}; // silence the end of test summary
};
`)
newRootCommand(ts.globalState).execute()

assert.Empty(t, ts.stdErr.Bytes())
assert.Empty(t, ts.stdOut.Bytes())
assert.Empty(t, ts.loggerHook.Drain())
`)}
}

func TestStdoutAndStderrAreEmptyWithQuietAndLogsForwarded(t *testing.T) {
Expand All @@ -92,10 +87,10 @@ func TestStdoutAndStderrAreEmptyWithQuietAndLogsForwarded(t *testing.T) {
"k6", "--quiet", "--log-output", "file=" + logFilePath,
"--log-format", "raw", "run", "--no-summary", "-",
}
ts.stdIn = bytes.NewBufferString(`
ts.console.Stdin = &testOSFileR{bytes.NewBufferString(`
console.log('init');
export default function() { console.log('foo'); };
`)
`)}
newRootCommand(ts.globalState).execute()

// The test state hook still catches this message
Expand All @@ -117,12 +112,12 @@ func TestRelativeLogPathWithSetupAndTeardown(t *testing.T) {
ts := newGlobalTestState(t)

ts.args = []string{"k6", "--log-output", "file=test.log", "--log-format", "raw", "run", "-i", "2", "-"}
ts.stdIn = bytes.NewBufferString(`
ts.console.Stdin = &testOSFileR{bytes.NewBufferString(`
console.log('init');
export default function() { console.log('foo'); };
export function setup() { console.log('bar'); };
export function teardown() { console.log('baz'); };
`)
`)}
newRootCommand(ts.globalState).execute()

// The test state hook still catches these messages
Expand All @@ -142,7 +137,7 @@ func TestWrongCliFlagIterations(t *testing.T) {

ts := newGlobalTestState(t)
ts.args = []string{"k6", "run", "--iterations", "foo", "-"}
ts.stdIn = bytes.NewBufferString(`export default function() {};`)
ts.console.Stdin = &testOSFileR{bytes.NewBufferString(`export default function() {};`)}
// TODO: check for exitcodes.InvalidConfig after https://github.com/loadimpact/k6/issues/883 is done...
ts.expectedExitCode = -1
newRootCommand(ts.globalState).execute()
Expand All @@ -155,7 +150,7 @@ func TestWrongEnvVarIterations(t *testing.T) {
ts := newGlobalTestState(t)
ts.args = []string{"k6", "run", "--vus", "2", "-"}
ts.envVars["K6_ITERATIONS"] = "4"
ts.stdIn = bytes.NewBufferString(`export default function() {};`)
ts.console.Stdin = &testOSFileR{bytes.NewBufferString(`export default function() {};`)}

newRootCommand(ts.globalState).execute()

Expand Down Expand Up @@ -281,7 +276,7 @@ func testSSLKEYLOGFILE(t *testing.T, ts *globalTestState, filePath string) {
tb := httpmultibin.NewHTTPMultiBin(t)
ts.args = []string{"k6", "run", "-"}
ts.envVars["SSLKEYLOGFILE"] = filePath
ts.stdIn = bytes.NewReader([]byte(tb.Replacer.Replace(`
ts.console.Stdin = &testOSFileR{bytes.NewReader([]byte(tb.Replacer.Replace(`
import http from "k6/http"
export const options = {
hosts: {
Expand All @@ -293,7 +288,7 @@ func testSSLKEYLOGFILE(t *testing.T, ts *globalTestState, filePath string) {
export default () => {
http.get("HTTPSBIN_URL/get");
}
`)))
`)))}

newRootCommand(ts.globalState).execute()

Expand All @@ -310,7 +305,7 @@ func TestThresholdDeprecationWarnings(t *testing.T) {

ts := newGlobalTestState(t)
ts.args = []string{"k6", "run", "--system-tags", "url,error,vu,iter,scenario", "-"}
ts.stdIn = bytes.NewReader([]byte(`
ts.console.Stdin = &testOSFileR{bytes.NewReader([]byte(`
export const options = {
thresholds: {
'http_req_duration{url:https://test.k6.io}': ['p(95)<500', 'p(99)<1000'],
Expand All @@ -321,7 +316,7 @@ func TestThresholdDeprecationWarnings(t *testing.T) {
};
export default function () { }`,
))
))}

newRootCommand(ts.globalState).execute()

Expand Down Expand Up @@ -699,9 +694,7 @@ func TestAbortedByUserWithRestAPI(t *testing.T) {
reachedIteration := false
for i := 0; i <= 10 && reachedIteration == false; i++ {
time.Sleep(1 * time.Second)
ts.outMutex.Lock()
stdOut := ts.stdOut.String()
ts.outMutex.Unlock()

if !strings.Contains(stdOut, "a simple iteration") {
t.Logf("did not see an iteration on try %d at t=%s", i, time.Now())
Expand Down Expand Up @@ -811,9 +804,7 @@ func runTestWithLinger(t *testing.T, ts *globalTestState) {
testFinished := false
for i := 0; i <= 15 && testFinished == false; i++ {
time.Sleep(1 * time.Second)
ts.outMutex.Lock()
stdOut := ts.stdOut.String()
ts.outMutex.Unlock()

if !strings.Contains(stdOut, "Linger set; waiting for Ctrl+C") {
t.Logf("test wasn't finished on try %d at t=%s", i, time.Now())
Expand Down Expand Up @@ -973,9 +964,7 @@ func TestAbortedByTestAbortInNonFirstInitCode(t *testing.T) {
)
newRootCommand(ts.globalState).execute()

ts.outMutex.Lock()
stdOut := ts.stdOut.String()
ts.outMutex.Unlock()
t.Log(stdOut)
assert.Contains(t, stdOut, "test aborted: foo")
assert.Contains(t, stdOut, `level=debug msg="Sending test finished" output=cloud ref=111 run_status=5 tainted=false`)
Expand Down Expand Up @@ -1090,13 +1079,7 @@ func TestAbortedByScriptInitError(t *testing.T) {
)
newRootCommand(ts.globalState).execute()

// FIXME: remove this locking after VU initialization accepts a context and
// is properly synchronized: currently when a test is aborted during the
// init phase, some logs might be emitted after the above command returns...
// see: https://github.com/grafana/k6/issues/2790
ts.outMutex.Lock()
stdOut := ts.stdOut.String()
ts.outMutex.Unlock()

t.Log(stdOut)
assert.Contains(t, stdOut, `level=error msg="Error: oops in 2\n\tat file:///`)
Expand Down Expand Up @@ -1494,15 +1477,13 @@ func TestPrometheusRemoteWriteOutput(t *testing.T) {

ts := newGlobalTestState(t)
ts.args = []string{"k6", "run", "--out", "experimental-prometheus-rw", "-"}
ts.stdIn = bytes.NewBufferString(`
ts.console.Stdin = &testOSFileR{bytes.NewBufferString(`
import exec from 'k6/execution';
export default function () {};
`)
`)}

newRootCommand(ts.globalState).execute()
ts.outMutex.Lock()
stdOut := ts.stdOut.String()
ts.outMutex.Unlock()

stdOut := ts.stdOut.String()
assert.Contains(t, stdOut, "output: Prometheus remote write")
}
Loading

0 comments on commit b0e0243

Please sign in to comment.