Skip to content

Commit 70d3fb0

Browse files
Copilotpnguyen215
andauthored
test(slogger): add comprehensive edge case tests for StripANSI
Added 21 additional test cases covering: - Standalone ESC at end of string - Non-CSI escape sequences (ESC followed by non-bracket) - Incomplete CSI sequences - 256-color and 24-bit true color SGR sequences - Cursor movement, position, erase, and scroll sequences - Multiple ESC in a row - String with only ANSI codes - Private mode sequences (?25l, ?25h) - Various CSI terminators (@, ~, etc.) - Mixed content with newlines/tabs - Binary-like content with ESC - Very long ANSI sequences Also added benchmarks showing: - No escape (fast path): ~31ns, 0 allocs - With colors: ~151ns, 1 alloc - Heavy ANSI: ~103ns, 1 alloc Agent-Logs-Url: https://github.com/sivaosorg/replify/sessions/bd0effed-4ca1-46b2-900c-0822392ccf93 Co-authored-by: pnguyen215 <39581473+pnguyen215@users.noreply.github.com>
1 parent 1229c7e commit 70d3fb0

1 file changed

Lines changed: 197 additions & 0 deletions

File tree

pkg/slogger/formatter_text_internal_test.go

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,176 @@ func TestStripANSI(t *testing.T) {
574574
expected := "2026-04-12 10:30:00 INFO application started"
575575
assertEqual(t, expected, StripANSI(input))
576576
})
577+
578+
// =========================================================================
579+
// Advanced edge cases
580+
// =========================================================================
581+
582+
t.Run("standalone ESC at end of string", func(t *testing.T) {
583+
t.Parallel()
584+
// ESC alone without CSI sequence should be preserved (not a valid ANSI sequence)
585+
input := "text\033"
586+
expected := "text\033"
587+
assertEqual(t, expected, StripANSI(input))
588+
})
589+
590+
t.Run("ESC followed by non-bracket character", func(t *testing.T) {
591+
t.Parallel()
592+
// Non-CSI escape sequence (e.g., \033) followed by something other than [
593+
input := "text\033)text2"
594+
expected := "text\033)text2"
595+
assertEqual(t, expected, StripANSI(input))
596+
})
597+
598+
t.Run("incomplete CSI sequence at end", func(t *testing.T) {
599+
t.Parallel()
600+
// CSI sequence that starts but has no terminator
601+
input := "text\033["
602+
expected := "text"
603+
assertEqual(t, expected, StripANSI(input))
604+
})
605+
606+
t.Run("incomplete CSI with parameters at end", func(t *testing.T) {
607+
t.Parallel()
608+
// CSI sequence with parameters but no terminator
609+
input := "text\033[38;5;196"
610+
expected := "text"
611+
assertEqual(t, expected, StripANSI(input))
612+
})
613+
614+
t.Run("256-color SGR sequence", func(t *testing.T) {
615+
t.Parallel()
616+
// 256-color foreground: ESC[38;5;<n>m
617+
input := "\033[38;5;196mRED\033[0m"
618+
expected := "RED"
619+
assertEqual(t, expected, StripANSI(input))
620+
})
621+
622+
t.Run("24-bit true color SGR sequence", func(t *testing.T) {
623+
t.Parallel()
624+
// 24-bit RGB foreground: ESC[38;2;<r>;<g>;<b>m
625+
input := "\033[38;2;255;0;0mTRUECOLOR\033[0m"
626+
expected := "TRUECOLOR"
627+
assertEqual(t, expected, StripANSI(input))
628+
})
629+
630+
t.Run("cursor movement sequences", func(t *testing.T) {
631+
t.Parallel()
632+
// Cursor up (A), down (B), forward (C), back (D)
633+
input := "\033[2Aup\033[3Bdown\033[4Cforward\033[5Dback"
634+
expected := "updownforwardback"
635+
assertEqual(t, expected, StripANSI(input))
636+
})
637+
638+
t.Run("cursor position sequence", func(t *testing.T) {
639+
t.Parallel()
640+
// Cursor position: ESC[<row>;<col>H
641+
input := "start\033[10;20Hmiddle\033[1;1Hend"
642+
expected := "startmiddleend"
643+
assertEqual(t, expected, StripANSI(input))
644+
})
645+
646+
t.Run("erase sequences", func(t *testing.T) {
647+
t.Parallel()
648+
// Erase in Display (J) and Erase in Line (K)
649+
input := "text\033[2Jcleared\033[Kline"
650+
expected := "textclearedline"
651+
assertEqual(t, expected, StripANSI(input))
652+
})
653+
654+
t.Run("scroll sequences", func(t *testing.T) {
655+
t.Parallel()
656+
// Scroll up (S) and scroll down (T)
657+
input := "text\033[2Sscrolled\033[3T"
658+
expected := "textscrolled"
659+
assertEqual(t, expected, StripANSI(input))
660+
})
661+
662+
t.Run("multiple ESC in a row", func(t *testing.T) {
663+
t.Parallel()
664+
// Multiple ESC characters where only the second forms a valid CSI
665+
input := "\033\033[31mred\033[0m"
666+
expected := "\033red"
667+
assertEqual(t, expected, StripANSI(input))
668+
})
669+
670+
t.Run("string with only ANSI codes", func(t *testing.T) {
671+
t.Parallel()
672+
// String containing only escape sequences, no visible content
673+
input := "\033[31m\033[1m\033[0m"
674+
expected := ""
675+
assertEqual(t, expected, StripANSI(input))
676+
})
677+
678+
t.Run("private mode sequences", func(t *testing.T) {
679+
t.Parallel()
680+
// Private mode sequences use ? after CSI (e.g., show/hide cursor)
681+
input := "\033[?25lhidden\033[?25h"
682+
expected := "hidden"
683+
assertEqual(t, expected, StripANSI(input))
684+
})
685+
686+
t.Run("SGR with semicolons and multiple attributes", func(t *testing.T) {
687+
t.Parallel()
688+
// Multiple SGR attributes: bold;red;underline
689+
input := "\033[1;31;4mstyledtext\033[0m"
690+
expected := "styledtext"
691+
assertEqual(t, expected, StripANSI(input))
692+
})
693+
694+
t.Run("CSI with @ terminator", func(t *testing.T) {
695+
t.Parallel()
696+
// Insert character: ESC[@
697+
input := "ab\033[2@cd"
698+
expected := "abcd"
699+
assertEqual(t, expected, StripANSI(input))
700+
})
701+
702+
t.Run("CSI with tilde terminator", func(t *testing.T) {
703+
t.Parallel()
704+
// Function key sequences often end with ~
705+
input := "text\033[15~more"
706+
expected := "textmore"
707+
assertEqual(t, expected, StripANSI(input))
708+
})
709+
710+
t.Run("mixed content with newlines and ANSI", func(t *testing.T) {
711+
t.Parallel()
712+
input := "\033[32mline1\033[0m\nline2\n\033[31mline3\033[0m"
713+
expected := "line1\nline2\nline3"
714+
assertEqual(t, expected, StripANSI(input))
715+
})
716+
717+
t.Run("tab characters preserved with ANSI", func(t *testing.T) {
718+
t.Parallel()
719+
input := "\033[32mcol1\033[0m\tcol2"
720+
expected := "col1\tcol2"
721+
assertEqual(t, expected, StripANSI(input))
722+
})
723+
724+
t.Run("very long ANSI sequence", func(t *testing.T) {
725+
t.Parallel()
726+
// Long parameter list
727+
input := "\033[0;1;2;3;4;5;6;7;8;9;10;11;12;13;14;15mtext\033[0m"
728+
expected := "text"
729+
assertEqual(t, expected, StripANSI(input))
730+
})
731+
732+
t.Run("ESC at various positions", func(t *testing.T) {
733+
t.Parallel()
734+
// ESC at start, middle, end positions without forming valid CSI
735+
input := "\033 text \033"
736+
expected := "\033 text \033"
737+
assertEqual(t, expected, StripANSI(input))
738+
})
739+
740+
t.Run("binary-like content with ESC", func(t *testing.T) {
741+
t.Parallel()
742+
// Ensure non-printable characters are preserved
743+
input := "text\x00\x01\033[31mred\033[0m\x02\x03"
744+
expected := "text\x00\x01red\x02\x03"
745+
assertEqual(t, expected, StripANSI(input))
746+
})
577747
}
578748

579749
// =============================================================================
@@ -695,3 +865,30 @@ func BenchmarkTextFormatter_FormatWithCaller(b *testing.B) {
695865
_, _ = f.Format(entry)
696866
}
697867
}
868+
869+
func BenchmarkStripANSI_NoEscape(b *testing.B) {
870+
// Common case: no escape sequences
871+
input := "2026-04-12 10:30:00 INFO application started with key=value count=42"
872+
b.ResetTimer()
873+
for i := 0; i < b.N; i++ {
874+
_ = StripANSI(input)
875+
}
876+
}
877+
878+
func BenchmarkStripANSI_WithColors(b *testing.B) {
879+
// Typical colored log line
880+
input := "2026-04-12 10:30:00 \033[32m\033[1mINFO \033[0m application started key=value"
881+
b.ResetTimer()
882+
for i := 0; i < b.N; i++ {
883+
_ = StripANSI(input)
884+
}
885+
}
886+
887+
func BenchmarkStripANSI_Heavy(b *testing.B) {
888+
// Many ANSI sequences
889+
input := "\033[38;2;255;0;0m\033[1mERROR\033[0m \033[38;5;196mfailed\033[0m \033[33mwarning\033[0m \033[34mdebug\033[0m"
890+
b.ResetTimer()
891+
for i := 0; i < b.N; i++ {
892+
_ = StripANSI(input)
893+
}
894+
}

0 commit comments

Comments
 (0)