Skip to content

Commit c229484

Browse files
joanlopezoleiadeolegbespalov
authored
Revamp the end-of-test summary (#4089)
* Start collecting metrics on summary output Co-authored-by: oleiade <[email protected]> * Include groups & scenarios metrics on summary Co-authored-by: oleiade <[email protected]> * Display nested groups in the summary Co-authored-by: oleiade <[email protected]> * Include nested checks to the end-of-test summary Co-authored-by: oleiade <[email protected]> * Store Threholds to Summary output and its report * Print Threholds as part of the summary output * Add JSDoc documentation to summary.js * Apply JS linter recommendations to summary.js * Factor decoration in a ANSIFormatter class * Reorganize the summary.js file for easier maintenance * Fulfill JSDoc documentation of summary.js * Clean up the old summary data (except for the user-defined handler) * Par the end-of-test summary with the design * Align metrics sections within the same block * Move full-summary example to internal/cmd/testdata * Pass render options missing to ANSIFormatter * Accommodate tests to the new summary format * Refine flaky test with undetermined amount of iterations * Apply suggestions from code review Co-authored-by: Oleg Bespalov <[email protected]> * Skip some HTTP metrics when on 'compact' mode * Move 'summary' output to 'internal' * Stop holding *metrics.Metric on summary.Output * Move lib.Summary and related types and logic into internal/lib/summary pkg * Fix duplicated thresholds on submetrics --------- Co-authored-by: oleiade <[email protected]> Co-authored-by: Oleg Bespalov <[email protected]>
1 parent a4939a8 commit c229484

34 files changed

+3506
-506
lines changed

.github/workflows/xk6-tests/xk6-test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export let options = {
1111

1212
export function handleSummary(data) {
1313
return {
14-
'summary-results.txt': data.metrics.foos.values.count.toString(),
14+
'summary-results.txt': data.metrics.custom.foos.values.count.toString(),
1515
};
1616
}
1717

internal/cmd/builtin_output_gen.go

+8-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/cmd/outputs.go

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ const (
3737
builtinOutputKafka
3838
builtinOutputStatsd
3939
builtinOutputExperimentalOpentelemetry
40+
builtinOutputSummary
4041
)
4142

4243
// TODO: move this to an output sub-module after we get rid of the old collectors?

internal/cmd/outputs_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ func TestBuiltinOutputString(t *testing.T) {
1111
exp := []string{
1212
"cloud", "csv", "datadog", "experimental-prometheus-rw",
1313
"influxdb", "json", "kafka", "statsd", "experimental-opentelemetry",
14+
"summary",
1415
}
1516
assert.Equal(t, exp, builtinOutputStrings())
1617
}

internal/cmd/run.go

+70-18
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@ import (
2323
"go.k6.io/k6/internal/event"
2424
"go.k6.io/k6/internal/execution"
2525
"go.k6.io/k6/internal/execution/local"
26+
"go.k6.io/k6/internal/lib/summary"
2627
"go.k6.io/k6/internal/lib/trace"
2728
"go.k6.io/k6/internal/metrics/engine"
29+
summaryoutput "go.k6.io/k6/internal/output/summary"
2830
"go.k6.io/k6/internal/ui/pb"
2931
"go.k6.io/k6/js/common"
3032
"go.k6.io/k6/lib"
@@ -189,26 +191,76 @@ func (c *cmdRun) run(cmd *cobra.Command, args []string) (err error) {
189191
}
190192

191193
executionState := execScheduler.GetState()
192-
if !testRunState.RuntimeOptions.NoSummary.Bool {
193-
defer func() {
194-
logger.Debug("Generating the end-of-test summary...")
195-
summaryResult, hsErr := test.initRunner.HandleSummary(globalCtx, &lib.Summary{
196-
Metrics: metricsEngine.ObservedMetrics,
197-
RootGroup: testRunState.GroupSummary.Group(),
198-
TestRunDuration: executionState.GetCurrentTestRunDuration(),
199-
NoColor: c.gs.Flags.NoColor,
200-
UIState: lib.UIState{
201-
IsStdOutTTY: c.gs.Stdout.IsTTY,
202-
IsStdErrTTY: c.gs.Stderr.IsTTY,
203-
},
194+
if !testRunState.RuntimeOptions.NoSummary.Bool { //nolint:nestif
195+
sm, err := summary.ValidateMode(testRunState.RuntimeOptions.SummaryMode.String)
196+
if err != nil {
197+
logger.WithError(err).Warnf(
198+
"invalid summary mode %q, falling back to \"compact\" (default)",
199+
testRunState.RuntimeOptions.SummaryMode.String,
200+
)
201+
}
202+
203+
switch sm {
204+
// TODO: Remove this code block once we stop supporting the legacy summary, and just leave the default.
205+
case summary.ModeLegacy:
206+
// At the end of the test run
207+
defer func() {
208+
logger.Debug("Generating the end-of-test summary...")
209+
210+
legacySummary := &lib.LegacySummary{
211+
Metrics: metricsEngine.ObservedMetrics,
212+
RootGroup: testRunState.GroupSummary.Group(),
213+
TestRunDuration: executionState.GetCurrentTestRunDuration(),
214+
NoColor: c.gs.Flags.NoColor,
215+
UIState: lib.UIState{
216+
IsStdOutTTY: c.gs.Stdout.IsTTY,
217+
IsStdErrTTY: c.gs.Stderr.IsTTY,
218+
},
219+
}
220+
221+
summaryResult, hsErr := test.initRunner.HandleSummary(globalCtx, legacySummary, nil)
222+
if hsErr == nil {
223+
hsErr = handleSummaryResult(c.gs.FS, c.gs.Stdout, c.gs.Stderr, summaryResult)
224+
}
225+
if hsErr != nil {
226+
logger.WithError(hsErr).Error("failed to handle the end-of-test summary")
227+
}
228+
}()
229+
default:
230+
// Instantiates the summary output
231+
summaryOutput, err := summaryoutput.New(output.Params{
232+
RuntimeOptions: testRunState.RuntimeOptions,
233+
Logger: c.gs.Logger,
204234
})
205-
if hsErr == nil {
206-
hsErr = handleSummaryResult(c.gs.FS, c.gs.Stdout, c.gs.Stderr, summaryResult)
235+
if err != nil {
236+
logger.WithError(err).Error("failed to initialize the end-of-test summary output")
207237
}
208-
if hsErr != nil {
209-
logger.WithError(hsErr).Error("failed to handle the end-of-test summary")
210-
}
211-
}()
238+
outputs = append(outputs, summaryOutput)
239+
240+
// At the end of the test run
241+
defer func() {
242+
logger.Debug("Generating the end-of-test summary...")
243+
244+
summary := summaryOutput.Summary(
245+
executionState.GetCurrentTestRunDuration(),
246+
metricsEngine.ObservedMetrics,
247+
test.initRunner.GetOptions(),
248+
)
249+
250+
// TODO: We should probably try to move these out of the summary,
251+
// likely as an additional argument like options.
252+
summary.NoColor = c.gs.Flags.NoColor
253+
summary.EnableColors = !summary.NoColor && c.gs.Stdout.IsTTY
254+
255+
summaryResult, hsErr := test.initRunner.HandleSummary(globalCtx, nil, summary)
256+
if hsErr == nil {
257+
hsErr = handleSummaryResult(c.gs.FS, c.gs.Stdout, c.gs.Stderr, summaryResult)
258+
}
259+
if hsErr != nil {
260+
logger.WithError(hsErr).Error("failed to handle the end-of-test summary")
261+
}
262+
}()
263+
}
212264
}
213265

214266
waitInitDone := emitEvent(&event.Event{Type: event.Init})

internal/cmd/run_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ func TestThresholdsRuntimeBehavior(t *testing.T) {
326326
name: "#2518: submetrics without values should be rendered under their parent metric #2518",
327327
testFilename: "thresholds/thresholds_on_submetric_without_samples.js",
328328
expExitCode: 0,
329-
expStdoutContains: " one..................: 0 0/s\n { tag:xyz }........: 0 0/s\n",
329+
expStdoutContains: " one....................................: 0 0/s\n { tag:xyz }..........................: 0 0/s\n",
330330
},
331331
{
332332
name: "#2512: parsing threshold names containing parsable tokens should be valid",
@@ -337,7 +337,7 @@ func TestThresholdsRuntimeBehavior(t *testing.T) {
337337
name: "#2520: thresholds over metrics without values should avoid division by zero and displaying NaN values",
338338
testFilename: "thresholds/empty_sink_no_nan.js",
339339
expExitCode: 0,
340-
expStdoutContains: "rate.................: 0.00%",
340+
expStdoutContains: "rate...................................: 0.00%",
341341
expStdoutNotContains: "NaN",
342342
},
343343
}

internal/cmd/runtime_options.go

+14-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"gopkg.in/guregu/null.v3"
1010

1111
"go.k6.io/k6/cmd/state"
12+
"go.k6.io/k6/internal/lib/summary"
1213
"go.k6.io/k6/lib"
1314
)
1415

@@ -31,6 +32,8 @@ extended: base + sets "global" as alias for "globalThis"
3132
flags.StringArrayP("env", "e", nil, "add/override environment variable with `VAR=value`")
3233
flags.Bool("no-thresholds", false, "don't run thresholds")
3334
flags.Bool("no-summary", false, "don't show the summary at the end of the test")
35+
flags.String("summary-mode", summary.ModeCompact.String(), "determine the summary mode,"+
36+
" \"compact\", \"full\" or \"legacy\"")
3437
flags.String(
3538
"summary-export",
3639
"",
@@ -76,6 +79,7 @@ func runtimeOptionsFromFlags(flags *pflag.FlagSet) lib.RuntimeOptions {
7679
CompatibilityMode: getNullString(flags, "compatibility-mode"),
7780
NoThresholds: getNullBool(flags, "no-thresholds"),
7881
NoSummary: getNullBool(flags, "no-summary"),
82+
SummaryMode: getNullString(flags, "summary-mode"),
7983
SummaryExport: getNullString(flags, "summary-export"),
8084
TracesOutput: getNullString(flags, "traces-output"),
8185
Env: make(map[string]string),
@@ -94,6 +98,11 @@ func populateRuntimeOptionsFromEnv(opts lib.RuntimeOptions, environment map[stri
9498
opts.CompatibilityMode = null.StringFrom(envVar)
9599
}
96100

101+
if _, err := lib.ValidateCompatibilityMode(opts.CompatibilityMode.String); err != nil {
102+
// some early validation
103+
return opts, err
104+
}
105+
97106
if err := saveBoolFromEnv(environment, "K6_INCLUDE_SYSTEM_ENV_VARS", &opts.IncludeSystemEnvVars); err != nil {
98107
return opts, err
99108
}
@@ -106,7 +115,11 @@ func populateRuntimeOptionsFromEnv(opts lib.RuntimeOptions, environment map[stri
106115
return opts, err
107116
}
108117

109-
if _, err := lib.ValidateCompatibilityMode(opts.CompatibilityMode.String); err != nil {
118+
if envVar, ok := environment["K6_SUMMARY_MODE"]; !opts.SummaryMode.Valid && ok {
119+
opts.SummaryMode = null.StringFrom(envVar)
120+
}
121+
122+
if _, err := summary.ValidateMode(opts.SummaryMode.String); err != nil {
110123
// some early validation
111124
return opts, err
112125
}

0 commit comments

Comments
 (0)