Add performance measurements, trash test and documentation
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
package platform
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTrashFreedesktop(t *testing.T) {
|
||||
data := t.TempDir()
|
||||
t.Setenv("XDG_DATA_HOME", data)
|
||||
dir := filepath.Join(data, "work")
|
||||
os.Mkdir(dir, 0o755)
|
||||
victim := filepath.Join(dir, "weird name ;rm -rf ~;")
|
||||
if err := os.WriteFile(victim, []byte("x"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := Trash(victim); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := os.Lstat(victim); !os.IsNotExist(err) {
|
||||
t.Fatal("file still present")
|
||||
}
|
||||
trashed := filepath.Join(data, "Trash", "files", "weird name ;rm -rf ~;")
|
||||
if _, err := os.Lstat(trashed); err != nil {
|
||||
t.Fatalf("not in trash: %v", err)
|
||||
}
|
||||
info, err := os.ReadFile(filepath.Join(data, "Trash", "info", "weird name ;rm -rf ~;.trashinfo"))
|
||||
if err != nil || !strings.Contains(string(info), "Path="+strings.ReplaceAll(dir, " ", "%20")) {
|
||||
t.Fatalf("bad trashinfo: %q %v", info, err)
|
||||
}
|
||||
// A second item with the same name gets a distinct trash name.
|
||||
os.WriteFile(victim, []byte("y"), 0o644)
|
||||
if err := Trash(victim); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := os.Lstat(trashed + ".2"); err != nil {
|
||||
t.Fatal("collision not handled")
|
||||
}
|
||||
if err := Trash("relative/path"); err == nil {
|
||||
t.Fatal("relative paths must be refused")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TestMemoryPerNode reports heap use per inventory node for a real tree.
|
||||
// Set MAPSIZE_MEM_ROOT to a directory to run it.
|
||||
func TestMemoryPerNode(t *testing.T) {
|
||||
root := os.Getenv("MAPSIZE_MEM_ROOT")
|
||||
if root == "" {
|
||||
t.Skip("set MAPSIZE_MEM_ROOT")
|
||||
}
|
||||
var before, after runtime.MemStats
|
||||
runtime.GC()
|
||||
runtime.ReadMemStats(&before)
|
||||
start := time.Now()
|
||||
s, err := Start(context.Background(), root, Options{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
<-s.Done()
|
||||
el := time.Since(start)
|
||||
runtime.GC()
|
||||
runtime.ReadMemStats(&after)
|
||||
n := s.Tree.Len()
|
||||
heap := after.HeapAlloc - before.HeapAlloc
|
||||
t.Logf("nodes=%d scan=%s heap=%.1f MiB (%.0f bytes/node)", n, el.Round(time.Millisecond),
|
||||
float64(heap)/(1<<20), float64(heap)/float64(n))
|
||||
runtime.KeepAlive(s)
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/charmbracelet/colorprofile"
|
||||
|
||||
"github.com/andydixon/mapsize/internal/inventory"
|
||||
)
|
||||
|
||||
// bigTree builds ~1M nodes in memory: 200 top-level dirs with nested
|
||||
// subdirectories, plus one flat directory with 200k files.
|
||||
func bigTree() *inventory.Tree {
|
||||
r := rand.New(rand.NewSource(1))
|
||||
t := inventory.New("/big", inventory.KindDir)
|
||||
file := func(p inventory.NodeID, i int) {
|
||||
id := t.Add(p, fmt.Sprintf("f%d.dat", i), inventory.KindFile)
|
||||
n := t.Node(id)
|
||||
n.Size = int64(r.ExpFloat64() * 1e6)
|
||||
n.Alloc = n.Size
|
||||
n.Cat = inventory.Category(r.Intn(int(inventory.NumCategories)))
|
||||
}
|
||||
flat := t.Add(0, "flat", inventory.KindDir)
|
||||
for i := range 200_000 {
|
||||
file(flat, i)
|
||||
}
|
||||
for d := range 200 {
|
||||
top := t.Add(0, fmt.Sprintf("dir%d", d), inventory.KindDir)
|
||||
for s := range 40 {
|
||||
sub := t.Add(top, fmt.Sprintf("sub%d", s), inventory.KindDir)
|
||||
for f := range 100 {
|
||||
file(sub, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
t.Recompute()
|
||||
return t
|
||||
}
|
||||
|
||||
var bigTreeCache *inventory.Tree
|
||||
|
||||
func benchModel(b *testing.B) *Model {
|
||||
if bigTreeCache == nil {
|
||||
bigTreeCache = bigTree()
|
||||
}
|
||||
m := newModel(Options{}, nil, bigTreeCache, func() {})
|
||||
m.profile = colorprofile.TrueColor
|
||||
send(m, size(200, 60))
|
||||
return m
|
||||
}
|
||||
|
||||
// Cold frame: sorting children, LOD selection, layout and paint.
|
||||
func BenchmarkBigColdFrame(b *testing.B) {
|
||||
m := benchModel(b)
|
||||
b.ReportMetric(float64(m.tree.Len()), "nodes")
|
||||
for b.Loop() {
|
||||
m.invalidate()
|
||||
m.View()
|
||||
}
|
||||
}
|
||||
|
||||
// Resize: child lists cached; layout and paint rerun for the new size.
|
||||
func BenchmarkBigResize(b *testing.B) {
|
||||
m := benchModel(b)
|
||||
m.View()
|
||||
i := 0
|
||||
for b.Loop() {
|
||||
send(m, size(150+i%100, 40+i%30))
|
||||
m.View()
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
// Zoomed into a 200k-entry directory (cold child sort each time).
|
||||
func BenchmarkBigFlatDirCold(b *testing.B) {
|
||||
m := benchModel(b)
|
||||
m.tree.Children(0, func(id inventory.NodeID, n *inventory.Node) {
|
||||
if n.Name == "flat" {
|
||||
m.zoom = id
|
||||
}
|
||||
})
|
||||
for b.Loop() {
|
||||
m.invalidate()
|
||||
m.View()
|
||||
}
|
||||
}
|
||||
|
||||
// Arrow-key navigation including the re-render it triggers.
|
||||
func BenchmarkBigNavigate(b *testing.B) {
|
||||
m := benchModel(b)
|
||||
m.View()
|
||||
keys := []string{"right", "down", "left", "up"}
|
||||
i := 0
|
||||
for b.Loop() {
|
||||
send(m, key(keys[i%4]))
|
||||
m.View()
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBigFilter(b *testing.B) {
|
||||
m := benchModel(b)
|
||||
for _, q := range []string{"size > 1MB", "*.dat AND size > 500k", "path contains sub3"} {
|
||||
b.Run(q, func(b *testing.B) {
|
||||
m.search.ed.set(q)
|
||||
m.queryChanged()
|
||||
for b.Loop() {
|
||||
m.applyFilter()()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user