mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-14 04:24:52 +02:00
196 lines
4.7 KiB
Go
196 lines
4.7 KiB
Go
package choose_files
|
|
|
|
import (
|
|
"fmt"
|
|
"io/fs"
|
|
"math/rand"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/kovidgoyal/kitty/tools/utils"
|
|
)
|
|
|
|
var _ = fmt.Print
|
|
|
|
func TestAsLower(t *testing.T) {
|
|
buf := [512]byte{}
|
|
for _, q := range []string{
|
|
"abc", "aBc", "aBCCf83Dx", "mOoseÇa", "89ÇĞxxA", "", "23", "aIİBc",
|
|
} {
|
|
n := as_lower(q, buf[:])
|
|
actual := utils.UnsafeBytesToString(buf[:n])
|
|
if diff := cmp.Diff(strings.ToLower(q), actual); diff != "" {
|
|
t.Fatalf("Failed to lowercase: %#v\n%s", q, diff)
|
|
}
|
|
}
|
|
}
|
|
|
|
type node struct {
|
|
name string
|
|
children map[string]*node
|
|
}
|
|
|
|
func (n node) Name() string {
|
|
return n.name
|
|
}
|
|
|
|
func (n node) IsDir() bool {
|
|
return n.children != nil
|
|
}
|
|
|
|
func (n node) String() string {
|
|
return fmt.Sprintf("{name: %s num_children: %d}", n.name, len(n.children))
|
|
}
|
|
|
|
func (n node) Type() fs.FileMode {
|
|
if n.children == nil {
|
|
return 0
|
|
}
|
|
return fs.ModeDir
|
|
}
|
|
|
|
func (n node) Info() (fs.FileInfo, error) {
|
|
return nil, fmt.Errorf("Info() not implemented")
|
|
}
|
|
|
|
func random_name(r *rand.Rand) string {
|
|
length := 3 + r.Intn(23)
|
|
bytes := make([]byte, length)
|
|
for i := range length {
|
|
bytes[i] = byte(r.Intn(26) + 'a')
|
|
}
|
|
return string(bytes)
|
|
}
|
|
|
|
func (n *node) generate_random_tree(depth, breadth int) {
|
|
r := rand.New(rand.NewSource(111))
|
|
n.children = make(map[string]*node)
|
|
for range breadth {
|
|
c := &node{name: random_name(r)}
|
|
n.children[c.name] = c
|
|
if depth > 0 && r.Intn(10) < 3 {
|
|
c.generate_random_tree(depth-1, breadth)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (n node) dir_entries() []fs.DirEntry {
|
|
entries := make([]fs.DirEntry, 0, len(n.children))
|
|
for _, v := range n.children {
|
|
entries = append(entries, v)
|
|
}
|
|
return entries
|
|
}
|
|
|
|
func (n node) ReadDir(name string) ([]fs.DirEntry, error) {
|
|
if name == string(os.PathSeparator) {
|
|
return n.dir_entries(), nil
|
|
}
|
|
p := &n
|
|
for _, x := range strings.Split(strings.Trim(name, string(os.PathSeparator)), string(os.PathSeparator)) {
|
|
c, found := p.children[x]
|
|
if !found {
|
|
return nil, fs.ErrNotExist
|
|
}
|
|
if !c.IsDir() {
|
|
return nil, fs.ErrExist
|
|
}
|
|
p = c
|
|
}
|
|
return p.dir_entries(), nil
|
|
}
|
|
|
|
func TestChooseFilesScoring(t *testing.T) {
|
|
root := node{name: string(os.PathSeparator), children: map[string]*node{
|
|
"b": {name: "b"},
|
|
"a": {name: "a"},
|
|
"c": {name: "c"},
|
|
"x": {name: "x", children: map[string]*node{
|
|
"1": {"1", nil}, "2": {"2", nil}, "3": {"3", nil},
|
|
"s": {"s", map[string]*node{
|
|
"m": {"m", nil}, "n": {"n", nil},
|
|
}},
|
|
}},
|
|
"y": {name: "y", children: map[string]*node{
|
|
"3": {"3", nil}, "4": {"4", nil}, "5": {"5", nil},
|
|
}},
|
|
}}
|
|
wg := sync.WaitGroup{}
|
|
wg.Add(1)
|
|
s := NewFileSystemScorer(string(os.PathSeparator), "", false, func(err error, is_complete bool) {
|
|
if is_complete {
|
|
wg.Done()
|
|
}
|
|
})
|
|
sc := NewFileSystemScanner(s.root_dir, make(chan bool))
|
|
s.scanner = sc
|
|
sc.dir_reader = root.ReadDir
|
|
s.scanner.Start()
|
|
s.Start()
|
|
wg.Wait()
|
|
results := func() (ans []string) {
|
|
rr, _ := s.Results()
|
|
for _, r := range rr {
|
|
ans = append(ans, r.text)
|
|
}
|
|
return
|
|
}
|
|
ae := func(query string, expected ...string) {
|
|
if query != "" {
|
|
wg.Add(1)
|
|
s.Change_query(query)
|
|
wg.Wait()
|
|
}
|
|
if diff := cmp.Diff(expected, results()); diff != "" {
|
|
t.Fatalf("Query less scoring failed\n%s", diff)
|
|
}
|
|
}
|
|
ae("", "x", "y", "a", "b", "c", "x/s", "x/1", "x/2", "x/3", "y/3", "y/4", "y/5", "x/s/m", "x/s/n")
|
|
ae("a", "a")
|
|
ae("3", "x/3", "y/3")
|
|
ae("s", "x/s", "x/s/m", "x/s/n")
|
|
ae("sn", "x/s/n")
|
|
}
|
|
|
|
func run_scoring(b *testing.B, depth, breadth int, query string) {
|
|
b.StopTimer()
|
|
root := node{name: string(os.PathSeparator)}
|
|
root.generate_random_tree(depth, breadth)
|
|
b.StartTimer()
|
|
for range b.N {
|
|
b.StopTimer()
|
|
wg := sync.WaitGroup{}
|
|
wg.Add(1)
|
|
s := NewFileSystemScorer(string(os.PathSeparator), query, false, func(err error, is_complete bool) {
|
|
if is_complete {
|
|
wg.Done()
|
|
}
|
|
})
|
|
sc := NewFileSystemScanner(s.root_dir, make(chan bool))
|
|
s.scanner = sc
|
|
sc.dir_reader = root.ReadDir
|
|
b.StartTimer()
|
|
s.scanner.Start()
|
|
s.Start()
|
|
wg.Wait()
|
|
}
|
|
fmt.Println("\nnumber of iterations: ", b.N)
|
|
fmt.Println("time per iteration:", b.Elapsed()/time.Duration(b.N))
|
|
}
|
|
|
|
// To run this benchmark with profiling use:
|
|
// go test -bench=FileNameScoringWithoutQuery -benchmem -cpuprofile=/tmp/cpu.prof -memprofile=/tmp/mem.prof github.com/kovidgoyal/kitty/kittens/choose_files -o /tmp/cfexe
|
|
func BenchmarkFileNameScoringWithoutQuery(b *testing.B) {
|
|
run_scoring(b, 5, 20, "")
|
|
}
|
|
|
|
// To run this benchmark with profiling use:
|
|
// go test -bench=FileNameScoringWithQuery -benchmem -cpuprofile=/tmp/cpu.prof -memprofile=/tmp/mem.prof github.com/kovidgoyal/kitty/kittens/choose_files -o /tmp/cfexe
|
|
func BenchmarkFileNameScoringWithQuery(b *testing.B) {
|
|
run_scoring(b, 5, 20, "abc")
|
|
}
|