mirror of
https://github.com/coder/coder.git
synced 2026-09-22 21:22:17 +08:00
Adds a bund of optimizations to chatdebug:
In `coderd/x/chatd/chatdebug`:
- Adds a benchmark (excluding LLM and database stuff)
- Replaces string concatenation with strings.Builder when accumulating
stream parts (~105,000ns -> ~50,00ns)
- Removes double JSON encode in RecordingTransport (114,000ns ->
64,000ns)
In `coderd/util/strings`:
- Adds a benchmark for Truncate
- Removes unnecessary allocations in Truncate (~110,000ns -> 1,550ns in
truncation case, 1 alloc -> 0 allocs in no truncation case)
> 🤖 Claude helped with this.
166 lines
6.4 KiB
Go
166 lines
6.4 KiB
Go
package strings_test
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/coder/coder/v2/coderd/util/strings"
|
|
)
|
|
|
|
func TestJoinWithConjunction(t *testing.T) {
|
|
t.Parallel()
|
|
require.Equal(t, "foo", strings.JoinWithConjunction([]string{"foo"}))
|
|
require.Equal(t, "foo and bar", strings.JoinWithConjunction([]string{"foo", "bar"}))
|
|
require.Equal(t, "foo, bar and baz", strings.JoinWithConjunction([]string{"foo", "bar", "baz"}))
|
|
}
|
|
|
|
func TestTruncate(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
for _, tt := range []struct {
|
|
s string
|
|
n int
|
|
expected string
|
|
options []strings.TruncateOption
|
|
}{
|
|
{"foo", 4, "foo", nil},
|
|
{"foo", 3, "foo", nil},
|
|
{"foo", 2, "fo", nil},
|
|
{"foo", 1, "f", nil},
|
|
{"foo", 0, "", nil},
|
|
{"foo", -1, "", nil},
|
|
{"", 5, "", nil},
|
|
{"foo bar", 7, "foo bar", []strings.TruncateOption{strings.TruncateWithEllipsis}},
|
|
{"foo bar", 6, "foo b…", []strings.TruncateOption{strings.TruncateWithEllipsis}},
|
|
{"foo bar", 5, "foo …", []strings.TruncateOption{strings.TruncateWithEllipsis}},
|
|
{"foo bar", 4, "foo…", []strings.TruncateOption{strings.TruncateWithEllipsis}},
|
|
{"foo bar", 3, "fo…", []strings.TruncateOption{strings.TruncateWithEllipsis}},
|
|
{"foo bar", 2, "f…", []strings.TruncateOption{strings.TruncateWithEllipsis}},
|
|
{"foo bar", 1, "…", []strings.TruncateOption{strings.TruncateWithEllipsis}},
|
|
{"foo bar", 0, "", []strings.TruncateOption{strings.TruncateWithEllipsis}},
|
|
{"foo bar", 7, "foo bar", []strings.TruncateOption{strings.TruncateWithFullWords}},
|
|
{"foo bar", 6, "foo", []strings.TruncateOption{strings.TruncateWithFullWords}},
|
|
{"foo bar", 5, "foo", []strings.TruncateOption{strings.TruncateWithFullWords}},
|
|
{"foo bar", 4, "foo", []strings.TruncateOption{strings.TruncateWithFullWords}},
|
|
{"foo bar", 3, "foo", []strings.TruncateOption{strings.TruncateWithFullWords}},
|
|
{"foo bar", 2, "fo", []strings.TruncateOption{strings.TruncateWithFullWords}},
|
|
{"foo bar", 1, "f", []strings.TruncateOption{strings.TruncateWithFullWords}},
|
|
{"foo bar", 0, "", []strings.TruncateOption{strings.TruncateWithFullWords}},
|
|
{"foo bar", 7, "foo bar", []strings.TruncateOption{strings.TruncateWithFullWords, strings.TruncateWithEllipsis}},
|
|
{"foo bar", 6, "foo…", []strings.TruncateOption{strings.TruncateWithFullWords, strings.TruncateWithEllipsis}},
|
|
{"foo bar", 5, "foo…", []strings.TruncateOption{strings.TruncateWithFullWords, strings.TruncateWithEllipsis}},
|
|
{"foo bar", 4, "foo…", []strings.TruncateOption{strings.TruncateWithFullWords, strings.TruncateWithEllipsis}},
|
|
{"foo bar", 3, "fo…", []strings.TruncateOption{strings.TruncateWithFullWords, strings.TruncateWithEllipsis}},
|
|
{"foo bar", 2, "f…", []strings.TruncateOption{strings.TruncateWithFullWords, strings.TruncateWithEllipsis}},
|
|
{"foo bar", 1, "…", []strings.TruncateOption{strings.TruncateWithFullWords, strings.TruncateWithEllipsis}},
|
|
{"foo bar", 0, "", []strings.TruncateOption{strings.TruncateWithFullWords, strings.TruncateWithEllipsis}},
|
|
{"This is a very long task prompt that should be truncated to 160 characters. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", 160, "This is a very long task prompt that should be truncated to 160 characters. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor…", []strings.TruncateOption{strings.TruncateWithFullWords, strings.TruncateWithEllipsis}},
|
|
// Multi-byte rune handling.
|
|
{"日本語テスト", 3, "日本語", nil},
|
|
{"日本語テスト", 4, "日本語テ", nil},
|
|
{"日本語テスト", 6, "日本語テスト", nil},
|
|
{"日本語テスト", 4, "日本語…", []strings.TruncateOption{strings.TruncateWithEllipsis}},
|
|
{"🎉🎊🎈🎁", 2, "🎉🎊", nil},
|
|
{"🎉🎊🎈🎁", 3, "🎉🎊…", []strings.TruncateOption{strings.TruncateWithEllipsis}},
|
|
// Multi-byte with full-word truncation.
|
|
{"hello 日本語", 7, "hello…", []strings.TruncateOption{strings.TruncateWithFullWords, strings.TruncateWithEllipsis}},
|
|
{"hello 日本語", 8, "hello 日…", []strings.TruncateOption{strings.TruncateWithEllipsis}},
|
|
{"日本語 テスト", 4, "日本語", []strings.TruncateOption{strings.TruncateWithFullWords}},
|
|
} {
|
|
tName := fmt.Sprintf("%s_%d", tt.s, tt.n)
|
|
for _, opt := range tt.options {
|
|
tName += fmt.Sprintf("_%v", opt)
|
|
}
|
|
t.Run(tName, func(t *testing.T) {
|
|
t.Parallel()
|
|
actual := strings.Truncate(tt.s, tt.n, tt.options...)
|
|
require.Equal(t, tt.expected, actual)
|
|
})
|
|
}
|
|
}
|
|
|
|
func BenchmarkTruncate(b *testing.B) {
|
|
b.Run("NoTruncationNeeded", func(b *testing.B) {
|
|
s := "a short string well under the limit"
|
|
b.ReportAllocs()
|
|
for b.Loop() {
|
|
strings.Truncate(s, 1000)
|
|
}
|
|
})
|
|
|
|
b.Run("ActualTruncation", func(b *testing.B) {
|
|
var buf bytes.Buffer
|
|
for range 2000 {
|
|
buf.WriteString("日本語テスト word ")
|
|
}
|
|
s := buf.String()
|
|
b.ReportAllocs()
|
|
for b.Loop() {
|
|
strings.Truncate(s, 100, strings.TruncateWithEllipsis, strings.TruncateWithFullWords)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestUISanitize(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
for _, tt := range []struct {
|
|
s string
|
|
expected string
|
|
}{
|
|
{"normal text", "normal text"},
|
|
{"\tfoo \r\\nbar ", "foo bar"},
|
|
{"通常のテキスト", "通常のテキスト"},
|
|
{"foo\nbar", "foo bar"},
|
|
{"foo\tbar", "foo bar"},
|
|
{"foo\rbar", "foo bar"},
|
|
{"foo\x00bar", "foobar"},
|
|
{"\u202Eabc", "abc"},
|
|
{"\u200Bzero width", "zero width"},
|
|
{"foo\x1b[31mred\x1b[0mbar", "fooredbar"},
|
|
{"foo\u0008bar", "foobar"},
|
|
{"foo\x07bar", "foobar"},
|
|
{"foo\uFEFFbar", "foobar"},
|
|
{"<a href='javascript:alert(1)'>link</a>", "link"},
|
|
{"<style>body{display:none}</style>", ""},
|
|
{"<html>HTML</html>", "HTML"},
|
|
{"<br>line break", "line break"},
|
|
{"<link rel='stylesheet' href='evil.css'>", ""},
|
|
{"<img src=1 onerror=alert(1)>", ""},
|
|
{"<!-- comment -->visible", "visible"},
|
|
{"<script>alert('xss')</script>", ""},
|
|
{"<iframe src='evil.com'></iframe>", ""},
|
|
} {
|
|
t.Run(tt.expected, func(t *testing.T) {
|
|
t.Parallel()
|
|
actual := strings.UISanitize(tt.s)
|
|
assert.Equal(t, tt.expected, actual)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCapitalize(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
input string
|
|
expected string
|
|
}{
|
|
{"", ""},
|
|
{"hello", "Hello"},
|
|
{"über", "Über"},
|
|
{"Hello", "Hello"},
|
|
{"a", "A"},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(fmt.Sprintf("%q", tt.input), func(t *testing.T) {
|
|
t.Parallel()
|
|
assert.Equal(t, tt.expected, strings.Capitalize(tt.input))
|
|
})
|
|
}
|
|
}
|