1
0
buffer/readutf8_test.go

236 lines
5.2 KiB
Go
Raw Permalink Normal View History

2026-02-17 16:58:00 +01:00
package buffer_test
import (
"code.squareroundforest.org/arpio/buffer"
"errors"
"io"
2026-02-22 16:30:37 +01:00
"testing"
2026-02-17 16:58:00 +01:00
)
func TestReadUTF8(t *testing.T) {
2026-02-22 16:30:37 +01:00
for title, cr := range map[string]createReader{"reader": buffer.BufferedReader, "content": testContent} {
t.Run(title, func(t *testing.T) {
t.Run("read all after error", func(t *testing.T) {
g := &gen{
rng: utf8Range,
max: 24,
fastErr: true,
}
o := buffer.Options{Pool: buffer.NoPool(1 << 12)}
r := cr(g, o)
runes, n0, err := r.ReadUTF8(12)
if err != nil {
t.Fatal(err)
}
if len(runes) != 12 {
t.Fatal("short read", len(runes))
}
if string(runes) != string(generateFrom(utf8Range, n0)) {
t.Fatal("invalid content")
}
runes, n1, err := r.ReadUTF8(12)
if err != nil {
t.Fatal(err)
}
if len(runes) != 3 {
t.Fatal("short read", len(runes))
}
if string(runes) != string(generateFrom(utf8Range, n0+n1)[n0:]) {
t.Fatal("invalid content", string(runes), string(generateFrom(utf8Range, n0+n1)[n0:]))
}
runes, n2, err := r.ReadUTF8(12)
if !errors.Is(err, io.EOF) {
t.Fatal(err)
}
if n2 != 0 {
t.Fatal("unexpected consumption")
}
if len(runes) != 0 {
t.Fatal("unexpected read", len(runes))
}
})
t.Run("ascii", func(t *testing.T) {
g := &gen{max: 1 << 15}
o := buffer.Options{Pool: buffer.NoPool(1 << 12)}
r := cr(g, o)
runes, n, err := r.ReadUTF8(12)
if err != nil {
t.Fatal(err)
}
if len(runes) != 12 {
t.Fatal("short read", len(runes))
}
if string(runes) != string(generate(n)) {
t.Fatal("invalid content")
}
})
t.Run("long within segment", func(t *testing.T) {
g := &gen{
rng: utf8W2Range,
max: 1 << 15,
}
o := buffer.Options{Pool: buffer.NoPool(1 << 12)}
r := cr(g, o)
runes, n, err := r.ReadUTF8(12)
if err != nil {
t.Fatal(err)
}
if len(runes) != 12 {
t.Fatal("short read", len(runes))
}
if string(runes) != string(generateFrom(utf8W2Range, n)) {
t.Fatal("invalid content")
}
})
t.Run("long across segments", func(t *testing.T) {
g := &gen{
rng: utf8W2Range,
max: 1 << 15,
}
o := buffer.Options{Pool: buffer.NoPool(9)}
r := cr(g, o)
runes, n, err := r.ReadUTF8(12)
if err != nil {
t.Fatal(err)
}
if len(runes) != 12 {
t.Fatal("short read", len(runes))
}
if string(runes) != string(generateFrom(utf8W2Range, n)) {
t.Fatal("invalid content")
}
})
t.Run("broken unicode at the end", func(t *testing.T) {
brokenRange := []byte{0xc3, 0xc3, 0xc3}
g := &gen{
rng: utf8Range,
max: len(utf8Range) + len(brokenRange),
customContentAfter: []int{len(utf8Range)},
customContent: map[int][]byte{len(utf8Range): brokenRange},
}
o := buffer.Options{Pool: buffer.NoPool(1 << 12)}
r := cr(g, o)
runes, n, err := r.ReadUTF8(24)
if err != nil {
t.Fatal(err)
}
if string(runes) != string(generateFrom(utf8Range, n)) {
t.Fatal("invalid content", string(runes), string(generateFrom(utf8Range, n)))
}
if len([]byte(string(runes))) != len(utf8Range) {
t.Fatal("invalid number of bytes")
}
for i := 0; i < 3; i++ {
runes, n, err = r.ReadUTF8(24)
if len(runes) != 0 || n != 1 || err != nil {
t.Fatal("failed to read out broken end")
}
}
runes, n, err = r.ReadUTF8(24)
if len(runes) != 0 || n != 0 || !errors.Is(err, io.EOF) {
t.Fatal("failed to read EOF", len(runes), n, err)
}
})
t.Run("immediate err", func(t *testing.T) {
g := &gen{rng: utf8Range}
o := buffer.Options{Pool: buffer.NoPool(1 << 12)}
r := cr(g, o)
runes, n, err := r.ReadUTF8(12)
if !errors.Is(err, io.EOF) {
t.Fatal(err)
}
if n != 0 {
t.Fatal("unexpected read", n)
}
if len(runes) != 0 {
t.Fatal("unexpected content")
}
})
})
}
t.Run("reader only", func(t *testing.T) {
cr := buffer.BufferedReader
t.Run("null read", func(t *testing.T) {
const numRunes = 6
nullReadAfter := len(string([]rune(string(utf8W2Range))[:numRunes]))
g := &gen{
rng: utf8W2Range,
max: 1 << 15,
nullReadAfter: []int{nullReadAfter, nullReadAfter},
}
2026-02-17 16:58:00 +01:00
2026-02-22 16:30:37 +01:00
o := buffer.Options{Pool: buffer.NoPool(nullReadAfter)}
r := cr(g, o)
runes, _, err := r.ReadUTF8(numRunes - 2) // -2 for min read in readUTF8
if err != nil {
t.Fatal(err)
}
2026-02-17 16:58:00 +01:00
2026-02-22 16:30:37 +01:00
if len(runes) != numRunes-2 {
t.Fatal("short read", len(runes))
}
2026-02-17 16:58:00 +01:00
2026-02-22 16:30:37 +01:00
if string(runes) != string(generateFrom(utf8W2Range, nullReadAfter-4)) {
t.Fatal("invalid content")
}
2026-02-17 16:58:00 +01:00
2026-02-22 16:30:37 +01:00
runes, _, err = r.ReadUTF8(12)
if err != nil {
t.Fatal(err)
}
2026-02-17 16:58:00 +01:00
2026-02-22 16:30:37 +01:00
if len(runes) != 2 {
t.Fatal("short read 2", len(runes))
}
2026-02-17 16:58:00 +01:00
2026-02-22 16:30:37 +01:00
if string(runes) != string(generateFrom(utf8W2Range, nullReadAfter)[nullReadAfter-4:]) {
t.Fatal("invalid content 2")
}
2026-02-17 16:58:00 +01:00
2026-02-22 16:30:37 +01:00
runes, _, err = r.ReadUTF8(12)
if err != nil {
t.Fatal(err)
2026-02-17 16:58:00 +01:00
}
2026-02-22 16:30:37 +01:00
if len(runes) != 12 {
t.Fatal("short read 3", len(runes))
}
2026-02-17 16:58:00 +01:00
2026-02-22 16:30:37 +01:00
if string(runes) != string(generateFrom(utf8W2Range, 3*nullReadAfter)[nullReadAfter:]) {
t.Fatal("invalid content 3")
}
})
2026-02-17 16:58:00 +01:00
})
}