package utils import ( "bytes" "context" "io" "os" "os/exec" "strings" "testing" "github.com/jedib0t/go-pretty/v6/text" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestPrintErr(t *testing.T) { t.Run("PrintErr writes to stderr with color", func(t *testing.T) { // Capture stderr oldStderr := os.Stderr reader, writer, _ := os.Pipe() os.Stderr = writer // Call PrintErr PrintErr(text.FgRed, "Error: %s", "test error") // Restore stderr and read output writer.Close() os.Stderr = oldStderr var buf bytes.Buffer io.Copy(&buf, reader) output := buf.String() // The output should contain the message (color codes are included) assert.Contains(t, output, "test error") assert.Contains(t, output, "Error:") assert.True(t, strings.HasSuffix(output, "\n")) }) t.Run("PrintErr with multiple format arguments", func(t *testing.T) { oldStderr := os.Stderr reader, writer, _ := os.Pipe() os.Stderr = writer PrintErr(text.FgYellow, "Warning: %s at line %d", "issue", 42) writer.Close() os.Stderr = oldStderr var buf bytes.Buffer io.Copy(&buf, reader) output := buf.String() assert.Contains(t, output, "Warning: issue at line 42") }) t.Run("PrintErr with no format arguments", func(t *testing.T) { oldStderr := os.Stderr reader, writer, _ := os.Pipe() os.Stderr = writer PrintErr(text.FgGreen, "Simple message") writer.Close() os.Stderr = oldStderr var buf bytes.Buffer io.Copy(&buf, reader) output := buf.String() assert.Contains(t, output, "Simple message") assert.True(t, strings.HasSuffix(output, "\n")) }) t.Run("PrintErr with different colors", func(t *testing.T) { colors := []text.Color{ text.FgRed, text.FgGreen, text.FgYellow, text.FgBlue, text.FgMagenta, text.FgCyan, } for _, color := range colors { oldStderr := os.Stderr reader, writer, _ := os.Pipe() os.Stderr = writer PrintErr(color, "Message with color") writer.Close() os.Stderr = oldStderr var buf bytes.Buffer io.Copy(&buf, reader) output := buf.String() assert.Contains(t, output, "Message with color") } }) t.Run("PrintErr with empty string", func(t *testing.T) { oldStderr := os.Stderr reader, writer, _ := os.Pipe() os.Stderr = writer PrintErr(text.FgRed, "") writer.Close() os.Stderr = oldStderr var buf bytes.Buffer io.Copy(&buf, reader) output := buf.String() assert.Equal(t, "\n", strings.TrimPrefix(output, "\x1b[31m\x1b[0m")) // Just newline after color codes }) t.Run("PrintErr with special characters", func(t *testing.T) { oldStderr := os.Stderr reader, writer, _ := os.Pipe() os.Stderr = writer PrintErr(text.FgRed, "Special chars: %s", "!@#$%^&*()") writer.Close() os.Stderr = oldStderr var buf bytes.Buffer io.Copy(&buf, reader) output := buf.String() assert.Contains(t, output, "Special chars: !@#$%^&*()") }) t.Run("PrintErr with percent sign in message", func(t *testing.T) { oldStderr := os.Stderr reader, writer, _ := os.Pipe() os.Stderr = writer PrintErr(text.FgRed, "Progress: 100%% complete") writer.Close() os.Stderr = oldStderr var buf bytes.Buffer io.Copy(&buf, reader) output := buf.String() assert.Contains(t, output, "Progress: 100% complete") }) } func TestPrintErrAndExit(t *testing.T) { if os.Getenv("BE_CRASHER") == "1" { // This is the subprocess that will actually call PrintErrAndExit exitCode := 1 if code := os.Getenv("EXIT_CODE"); code != "" { switch code { case "0": exitCode = 0 case "1": exitCode = 1 case "2": exitCode = 2 } } PrintErrAndExit(text.FgRed, exitCode, "Error: %s", "fatal error") return } t.Run("PrintErrAndExit calls os.Exit with correct code", func(t *testing.T) { testCases := []struct { name string exitCode int }{ {"Exit with code 0", 0}, {"Exit with code 1", 1}, {"Exit with code 2", 2}, } for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { ctx := context.Background() cmd := exec.CommandContext(ctx, os.Args[0], "-test.run=TestPrintErrAndExit") cmd.Env = append(os.Environ(), "BE_CRASHER=1", "EXIT_CODE="+string(rune('0'+testCase.exitCode))) var stderr bytes.Buffer cmd.Stderr = &stderr err := cmd.Run() if testCase.exitCode == 0 { require.NoError(t, err) } else { require.Error(t, err) if exitErr, ok := err.(*exec.ExitError); ok { assert.Equal(t, testCase.exitCode, exitErr.ExitCode()) } } // Check that error message was printed to stderr assert.Contains(t, stderr.String(), "Error: fatal error") }) } }) t.Run("PrintErrAndExit prints before exiting", func(t *testing.T) { ctx := context.Background() cmd := exec.CommandContext(ctx, os.Args[0], "-test.run=TestPrintErrAndExit") cmd.Env = append(os.Environ(), "BE_CRASHER=1", "EXIT_CODE=1") var stderr bytes.Buffer cmd.Stderr = &stderr cmd.Run() // Ignore error since we expect non-zero exit output := stderr.String() assert.Contains(t, output, "Error: fatal error") assert.True(t, strings.HasSuffix(output, "\n")) }) } // Benchmarks for performance testing func BenchmarkPrintErr(b *testing.B) { // Redirect stderr to /dev/null for benchmarking oldStderr := os.Stderr devNull, _ := os.Open(os.DevNull) os.Stderr = devNull defer func() { os.Stderr = oldStderr devNull.Close() }() b.Run("Simple message", func(b *testing.B) { for range b.N { PrintErr(text.FgRed, "Error message") } }) b.Run("Formatted message", func(b *testing.B) { for range b.N { PrintErr(text.FgRed, "Error: %s at line %d", "issue", 42) } }) b.Run("Different colors", func(b *testing.B) { colors := []text.Color{text.FgRed, text.FgGreen, text.FgYellow} for idx := range b.N { PrintErr(colors[idx%len(colors)], "Message %d", idx) } }) }