mirror of
https://github.com/junegunn/fzf
synced 2026-07-31 18:21:42 +00:00
f3ca0b1365
Fix #4707
260 lines
6.9 KiB
Go
260 lines
6.9 KiB
Go
package algo
|
|
|
|
import (
|
|
"bytes"
|
|
"testing"
|
|
)
|
|
|
|
func TestIndexByteTwo(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
s string
|
|
b1 byte
|
|
b2 byte
|
|
want int
|
|
}{
|
|
{"empty", "", 'a', 'b', -1},
|
|
{"single_b1", "a", 'a', 'b', 0},
|
|
{"single_b2", "b", 'a', 'b', 0},
|
|
{"single_none", "c", 'a', 'b', -1},
|
|
{"b1_first", "xaxb", 'a', 'b', 1},
|
|
{"b2_first", "xbxa", 'a', 'b', 1},
|
|
{"same_byte", "xxa", 'a', 'a', 2},
|
|
{"at_end", "xxxxa", 'a', 'b', 4},
|
|
{"not_found", "xxxxxxxx", 'a', 'b', -1},
|
|
{"long_b1_at_3000", string(make([]byte, 3000)) + "a" + string(make([]byte, 1000)), 'a', 'b', 3000},
|
|
{"long_b2_at_3000", string(make([]byte, 3000)) + "b" + string(make([]byte, 1000)), 'a', 'b', 3000},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := IndexByteTwo([]byte(tt.s), tt.b1, tt.b2)
|
|
if got != tt.want {
|
|
t.Errorf("IndexByteTwo(%q, %c, %c) = %d, want %d", tt.s[:min(len(tt.s), 40)], tt.b1, tt.b2, got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
|
|
// Exhaustive test: compare against loop reference for various lengths,
|
|
// including sizes around SIMD block boundaries (16, 32, 64).
|
|
for n := 0; n <= 256; n++ {
|
|
data := make([]byte, n)
|
|
for i := range data {
|
|
data[i] = byte('c' + (i % 20))
|
|
}
|
|
// Test with match at every position
|
|
for pos := 0; pos < n; pos++ {
|
|
for _, b := range []byte{'A', 'B'} {
|
|
data[pos] = b
|
|
got := IndexByteTwo(data, 'A', 'B')
|
|
want := loopIndexByteTwo(data, 'A', 'B')
|
|
if got != want {
|
|
t.Fatalf("IndexByteTwo(len=%d, match=%c@%d) = %d, want %d", n, b, pos, got, want)
|
|
}
|
|
data[pos] = byte('c' + (pos % 20))
|
|
}
|
|
}
|
|
// Test with no match
|
|
got := IndexByteTwo(data, 'A', 'B')
|
|
if got != -1 {
|
|
t.Fatalf("IndexByteTwo(len=%d, no match) = %d, want -1", n, got)
|
|
}
|
|
// Test with both bytes present
|
|
if n >= 2 {
|
|
data[n/3] = 'A'
|
|
data[n*2/3] = 'B'
|
|
got := IndexByteTwo(data, 'A', 'B')
|
|
want := loopIndexByteTwo(data, 'A', 'B')
|
|
if got != want {
|
|
t.Fatalf("IndexByteTwo(len=%d, both@%d,%d) = %d, want %d", n, n/3, n*2/3, got, want)
|
|
}
|
|
data[n/3] = byte('c' + ((n / 3) % 20))
|
|
data[n*2/3] = byte('c' + ((n * 2 / 3) % 20))
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestLastIndexByteTwo(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
s string
|
|
b1 byte
|
|
b2 byte
|
|
want int
|
|
}{
|
|
{"empty", "", 'a', 'b', -1},
|
|
{"single_b1", "a", 'a', 'b', 0},
|
|
{"single_b2", "b", 'a', 'b', 0},
|
|
{"single_none", "c", 'a', 'b', -1},
|
|
{"b1_last", "xbxa", 'a', 'b', 3},
|
|
{"b2_last", "xaxb", 'a', 'b', 3},
|
|
{"same_byte", "axx", 'a', 'a', 0},
|
|
{"at_start", "axxxx", 'a', 'b', 0},
|
|
{"both_present", "axbx", 'a', 'b', 2},
|
|
{"not_found", "xxxxxxxx", 'a', 'b', -1},
|
|
{"long_b1_at_3000", string(make([]byte, 3000)) + "a" + string(make([]byte, 1000)), 'a', 'b', 3000},
|
|
{"long_b2_at_end", string(make([]byte, 4000)) + "b", 'a', 'b', 4000},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := lastIndexByteTwo([]byte(tt.s), tt.b1, tt.b2)
|
|
if got != tt.want {
|
|
t.Errorf("lastIndexByteTwo(%q, %c, %c) = %d, want %d", tt.s[:min(len(tt.s), 40)], tt.b1, tt.b2, got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
|
|
// Exhaustive test against loop reference
|
|
for n := 0; n <= 256; n++ {
|
|
data := make([]byte, n)
|
|
for i := range data {
|
|
data[i] = byte('c' + (i % 20))
|
|
}
|
|
for pos := 0; pos < n; pos++ {
|
|
for _, b := range []byte{'A', 'B'} {
|
|
data[pos] = b
|
|
got := lastIndexByteTwo(data, 'A', 'B')
|
|
want := refLastIndexByteTwo(data, 'A', 'B')
|
|
if got != want {
|
|
t.Fatalf("lastIndexByteTwo(len=%d, match=%c@%d) = %d, want %d", n, b, pos, got, want)
|
|
}
|
|
data[pos] = byte('c' + (pos % 20))
|
|
}
|
|
}
|
|
// No match
|
|
got := lastIndexByteTwo(data, 'A', 'B')
|
|
if got != -1 {
|
|
t.Fatalf("lastIndexByteTwo(len=%d, no match) = %d, want -1", n, got)
|
|
}
|
|
// Both bytes present
|
|
if n >= 2 {
|
|
data[n/3] = 'A'
|
|
data[n*2/3] = 'B'
|
|
got := lastIndexByteTwo(data, 'A', 'B')
|
|
want := refLastIndexByteTwo(data, 'A', 'B')
|
|
if got != want {
|
|
t.Fatalf("lastIndexByteTwo(len=%d, both@%d,%d) = %d, want %d", n, n/3, n*2/3, got, want)
|
|
}
|
|
data[n/3] = byte('c' + ((n / 3) % 20))
|
|
data[n*2/3] = byte('c' + ((n * 2 / 3) % 20))
|
|
}
|
|
}
|
|
}
|
|
|
|
func FuzzIndexByteTwo(f *testing.F) {
|
|
f.Add([]byte("hello world"), byte('o'), byte('l'))
|
|
f.Add([]byte(""), byte('a'), byte('b'))
|
|
f.Add([]byte("aaa"), byte('a'), byte('a'))
|
|
f.Fuzz(func(t *testing.T, data []byte, b1, b2 byte) {
|
|
got := IndexByteTwo(data, b1, b2)
|
|
want := loopIndexByteTwo(data, b1, b2)
|
|
if got != want {
|
|
t.Errorf("IndexByteTwo(len=%d, b1=%d, b2=%d) = %d, want %d", len(data), b1, b2, got, want)
|
|
}
|
|
})
|
|
}
|
|
|
|
func FuzzLastIndexByteTwo(f *testing.F) {
|
|
f.Add([]byte("hello world"), byte('o'), byte('l'))
|
|
f.Add([]byte(""), byte('a'), byte('b'))
|
|
f.Add([]byte("aaa"), byte('a'), byte('a'))
|
|
f.Fuzz(func(t *testing.T, data []byte, b1, b2 byte) {
|
|
got := lastIndexByteTwo(data, b1, b2)
|
|
want := refLastIndexByteTwo(data, b1, b2)
|
|
if got != want {
|
|
t.Errorf("lastIndexByteTwo(len=%d, b1=%d, b2=%d) = %d, want %d", len(data), b1, b2, got, want)
|
|
}
|
|
})
|
|
}
|
|
|
|
// Reference implementations for correctness checking
|
|
func refIndexByteTwo(s []byte, b1, b2 byte) int {
|
|
i1 := bytes.IndexByte(s, b1)
|
|
if i1 == 0 {
|
|
return 0
|
|
}
|
|
scope := s
|
|
if i1 > 0 {
|
|
scope = s[:i1]
|
|
}
|
|
if i2 := bytes.IndexByte(scope, b2); i2 >= 0 {
|
|
return i2
|
|
}
|
|
return i1
|
|
}
|
|
|
|
func loopIndexByteTwo(s []byte, b1, b2 byte) int {
|
|
for i, b := range s {
|
|
if b == b1 || b == b2 {
|
|
return i
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
func refLastIndexByteTwo(s []byte, b1, b2 byte) int {
|
|
for i := len(s) - 1; i >= 0; i-- {
|
|
if s[i] == b1 || s[i] == b2 {
|
|
return i
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
func benchIndexByteTwo(b *testing.B, size int, pos int) {
|
|
data := make([]byte, size)
|
|
for i := range data {
|
|
data[i] = byte('a' + (i % 20))
|
|
}
|
|
data[pos] = 'Z'
|
|
|
|
type impl struct {
|
|
name string
|
|
fn func([]byte, byte, byte) int
|
|
}
|
|
impls := []impl{
|
|
{"asm", IndexByteTwo},
|
|
{"2xIndexByte", refIndexByteTwo},
|
|
{"loop", loopIndexByteTwo},
|
|
}
|
|
for _, im := range impls {
|
|
b.Run(im.name, func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
im.fn(data, 'Z', 'z')
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func benchLastIndexByteTwo(b *testing.B, size int, pos int) {
|
|
data := make([]byte, size)
|
|
for i := range data {
|
|
data[i] = byte('a' + (i % 20))
|
|
}
|
|
data[pos] = 'Z'
|
|
|
|
type impl struct {
|
|
name string
|
|
fn func([]byte, byte, byte) int
|
|
}
|
|
impls := []impl{
|
|
{"asm", lastIndexByteTwo},
|
|
{"loop", refLastIndexByteTwo},
|
|
}
|
|
for _, im := range impls {
|
|
b.Run(im.name, func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
im.fn(data, 'Z', 'z')
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func BenchmarkIndexByteTwo_10(b *testing.B) { benchIndexByteTwo(b, 10, 8) }
|
|
func BenchmarkIndexByteTwo_100(b *testing.B) { benchIndexByteTwo(b, 100, 80) }
|
|
func BenchmarkIndexByteTwo_1000(b *testing.B) { benchIndexByteTwo(b, 1000, 800) }
|
|
func BenchmarkLastIndexByteTwo_10(b *testing.B) { benchLastIndexByteTwo(b, 10, 2) }
|
|
func BenchmarkLastIndexByteTwo_100(b *testing.B) { benchLastIndexByteTwo(b, 100, 20) }
|
|
func BenchmarkLastIndexByteTwo_1000(b *testing.B) { benchLastIndexByteTwo(b, 1000, 200) }
|