From 5b79aa03be5f1d75bc47e32f5ec0ce1da691c0a3 Mon Sep 17 00:00:00 2001 From: Dejan K Date: Tue, 23 Dec 2025 18:37:58 +0100 Subject: [PATCH 1/2] feat(test-results): add --no-trim-output flag and allow 0 to disable trimming --- test-results/DOCUMENTATION.md | 2 +- test-results/cmd/root.go | 3 +- test-results/pkg/cli/cli.go | 12 ++- test-results/pkg/cli/cli_test.go | 163 +++++++++++++++++++++++++++++++ 4 files changed, 175 insertions(+), 5 deletions(-) diff --git a/test-results/DOCUMENTATION.md b/test-results/DOCUMENTATION.md index 6d25c30d..edd9c8b9 100644 --- a/test-results/DOCUMENTATION.md +++ b/test-results/DOCUMENTATION.md @@ -43,7 +43,7 @@ ## Configuration & Environment - Viper auto-loads `$HOME/.test-results.yaml` when present; command flags override config. - Key env vars: `SEMAPHORE_PIPELINE_ID`, `SEMAPHORE_WORKFLOW_ID`, `SEMAPHORE_JOB_ID`, `SEMAPHORE_AGENT_MACHINE_TYPE`, etc.; parsers read them to enrich metadata. -- Flags to know: `--parser`, `--ignore-missing`, `--no-compress`, `--suite-prefix`, `--omit-output-for-passed`, `--trim-output-to`, `--name`. +- Flags to know: `--parser`, `--ignore-missing`, `--no-compress`, `--suite-prefix`, `--omit-output-for-passed`, `--trim-output-to`, `--no-trim-output`, `--name`. ## Development Workflow - Requires Go 1.24+. Use `make test.setup` once to build the `cli` Docker image and fetch module deps. diff --git a/test-results/cmd/root.go b/test-results/cmd/root.go index 47d1198b..36a4c694 100644 --- a/test-results/cmd/root.go +++ b/test-results/cmd/root.go @@ -44,7 +44,8 @@ func init() { rootCmd.PersistentFlags().StringP("suite-prefix", "S", "", "prefix for each suite") rootCmd.PersistentFlags().StringP("parser", "p", "auto", "override parser to be used") rootCmd.PersistentFlags().Bool("no-compress", false, "skip gzip compression for the output") - rootCmd.PersistentFlags().IntP("trim-output-to", "s", 1000, "trim stdout/stderr to last N characters (max 10000), defaults to 1000") + rootCmd.PersistentFlags().IntP("trim-output-to", "s", 1000, "trim stdout/stderr to last N characters, defaults to 1000 (use 0 or --no-trim-output to disable)") + rootCmd.PersistentFlags().Bool("no-trim-output", false, "disable output trimming entirely") // Cobra also supports local flags, which will only run // when this action is called directly. diff --git a/test-results/pkg/cli/cli.go b/test-results/pkg/cli/cli.go index b9d1ecc5..18c00e5d 100644 --- a/test-results/pkg/cli/cli.go +++ b/test-results/pkg/cli/cli.go @@ -466,16 +466,22 @@ func ApplyOutputTrimming(result *parser.Result, cmd *cobra.Command) { return } + // Check if trimming is disabled via --no-trim-output flag + noTrim, err := cmd.Flags().GetBool("no-trim-output") + if err == nil && noTrim { + return + } + trimTo := 1000 - maxTrimLength := 10000 trimToFlag, err := cmd.Flags().GetInt("trim-output-to") if err == nil { trimTo = trimToFlag } - if trimTo > maxTrimLength || trimTo <= 0 { - trimTo = maxTrimLength + // If trimTo is 0 or negative, disable trimming + if trimTo <= 0 { + return } for i := range result.TestResults { diff --git a/test-results/pkg/cli/cli_test.go b/test-results/pkg/cli/cli_test.go index 6fa79ca6..623ceb0f 100644 --- a/test-results/pkg/cli/cli_test.go +++ b/test-results/pkg/cli/cli_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/semaphoreci/toolbox/test-results/pkg/parser" + "github.com/spf13/cobra" "github.com/semaphoreci/toolbox/test-results/pkg/cli" "github.com/stretchr/testify/assert" @@ -151,6 +152,168 @@ func TestWriteToTmpFile(t *testing.T) { }) } +func TestApplyOutputTrimming(t *testing.T) { + longText := func(n int) string { + s := "" + for i := 0; i < n; i++ { + s += "x" + } + return s + } + + createTestResult := func(text string) *parser.Result { + return &parser.Result{ + TestResults: []parser.TestResults{ + { + ID: "test-1", + Name: "Test", + Suites: []parser.Suite{ + { + Name: "Suite1", + SystemOut: text, + SystemErr: text, + Tests: []parser.Test{ + { + Name: "Test1", + SystemOut: text, + SystemErr: text, + Failure: &parser.Failure{ + Message: text, + Body: text, + }, + }, + }, + }, + }, + }, + }, + } + } + + createCmd := func(trimTo int, noTrim bool) *cobra.Command { + cmd := &cobra.Command{} + cmd.Flags().Int("trim-output-to", trimTo, "") + cmd.Flags().Bool("no-trim-output", noTrim, "") + return cmd + } + + t.Run("default trimming to 1000 characters", func(t *testing.T) { + result := createTestResult(longText(2000)) + cmd := createCmd(1000, false) + + cli.ApplyOutputTrimming(result, cmd) + + suite := result.TestResults[0].Suites[0] + assert.True(t, len(suite.SystemOut) <= 1000+len("...[truncated]...\n")) + assert.True(t, len(suite.SystemErr) <= 1000+len("...[truncated]...\n")) + assert.Contains(t, suite.SystemOut, "...[truncated]...") + }) + + t.Run("custom trim length", func(t *testing.T) { + result := createTestResult(longText(5000)) + cmd := createCmd(3000, false) + + cli.ApplyOutputTrimming(result, cmd) + + suite := result.TestResults[0].Suites[0] + assert.True(t, len(suite.SystemOut) <= 3000+len("...[truncated]...\n")) + assert.Contains(t, suite.SystemOut, "...[truncated]...") + }) + + t.Run("no trimming when --no-trim-output is set", func(t *testing.T) { + originalText := longText(5000) + result := createTestResult(originalText) + cmd := createCmd(1000, true) + + cli.ApplyOutputTrimming(result, cmd) + + suite := result.TestResults[0].Suites[0] + assert.Equal(t, originalText, suite.SystemOut) + assert.Equal(t, originalText, suite.SystemErr) + assert.Equal(t, originalText, suite.Tests[0].SystemOut) + assert.Equal(t, originalText, suite.Tests[0].Failure.Message) + }) + + t.Run("no trimming when --trim-output-to is 0", func(t *testing.T) { + originalText := longText(5000) + result := createTestResult(originalText) + cmd := createCmd(0, false) + + cli.ApplyOutputTrimming(result, cmd) + + suite := result.TestResults[0].Suites[0] + assert.Equal(t, originalText, suite.SystemOut) + assert.Equal(t, originalText, suite.SystemErr) + }) + + t.Run("no trimming when --trim-output-to is negative", func(t *testing.T) { + originalText := longText(5000) + result := createTestResult(originalText) + cmd := createCmd(-1, false) + + cli.ApplyOutputTrimming(result, cmd) + + suite := result.TestResults[0].Suites[0] + assert.Equal(t, originalText, suite.SystemOut) + }) + + t.Run("text shorter than trim limit is not modified", func(t *testing.T) { + originalText := longText(500) + result := createTestResult(originalText) + cmd := createCmd(1000, false) + + cli.ApplyOutputTrimming(result, cmd) + + suite := result.TestResults[0].Suites[0] + assert.Equal(t, originalText, suite.SystemOut) + assert.NotContains(t, suite.SystemOut, "...[truncated]...") + }) + + t.Run("nil result does not panic", func(t *testing.T) { + cmd := createCmd(1000, false) + assert.NotPanics(t, func() { + cli.ApplyOutputTrimming(nil, cmd) + }) + }) + + t.Run("trims failure and error fields", func(t *testing.T) { + result := &parser.Result{ + TestResults: []parser.TestResults{ + { + ID: "test-1", + Suites: []parser.Suite{ + { + Tests: []parser.Test{ + { + Failure: &parser.Failure{ + Message: longText(2000), + Type: longText(2000), + Body: longText(2000), + }, + Error: &parser.Error{ + Message: longText(2000), + Type: longText(2000), + Body: longText(2000), + }, + }, + }, + }, + }, + }, + }, + } + cmd := createCmd(1000, false) + + cli.ApplyOutputTrimming(result, cmd) + + test := result.TestResults[0].Suites[0].Tests[0] + assert.Contains(t, test.Failure.Message, "...[truncated]...") + assert.Contains(t, test.Failure.Body, "...[truncated]...") + assert.Contains(t, test.Error.Message, "...[truncated]...") + assert.Contains(t, test.Error.Body, "...[truncated]...") + }) +} + func TestWriteToFilePath(t *testing.T) { tr := parser.TestResults{ ID: "1234", From b03895c47321d2cb44b539284d41214ced607435 Mon Sep 17 00:00:00 2001 From: Dejan K Date: Thu, 25 Dec 2025 11:16:25 +0100 Subject: [PATCH 2/2] docs(test-results): add section on trimming test output options --- test-results/README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test-results/README.md b/test-results/README.md index 1192409d..145a0f2a 100644 --- a/test-results/README.md +++ b/test-results/README.md @@ -75,6 +75,21 @@ The generated tests in a report will sometimes contain a prefix in the name. For test-results publish --suite-prefix "Elixir." results.xml ``` +## Trimming test output + +By default, the CLI trims stdout/stderr fields to the last 1000 characters and prepends `...[truncated]...` when trimming occurs. You can change the limit or disable trimming entirely: + +```bash +# Keep the last 5000 characters +test-results publish --trim-output-to 5000 results.xml + +# Disable trimming +test-results publish --no-trim-output results.xml + +# Also disables trimming +test-results publish --trim-output-to 0 results.xml +``` + ## Multiple reports from one job If your job generates multiple reports: `integration.xml`, `unit.xml` you can use this command to merge and publish them