aboutsummaryrefslogtreecommitdiff
path: root/internal/engine/plan_bench_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/engine/plan_bench_test.go')
-rw-r--r--internal/engine/plan_bench_test.go108
1 files changed, 108 insertions, 0 deletions
diff --git a/internal/engine/plan_bench_test.go b/internal/engine/plan_bench_test.go
new file mode 100644
index 0000000..a3d08bb
--- /dev/null
+++ b/internal/engine/plan_bench_test.go
@@ -0,0 +1,108 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package engine
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "path/filepath"
+ "testing"
+ "time"
+
+ "krino/internal/plan"
+)
+
+// BenchmarkPlan measures krino's own cost of planning: walking a tree,
+// matching rules and building action chains (Engine.Plan, which wraps
+// Engine.Match and plan.Build). It does NOT measure krino's real-world
+// throughput - spec ยง13 is explicit that there is no performance target
+// for 0.0.1, because a full run's wall time is dominated by the external
+// extractors (pdftotext and friends), not by krino itself. This benchmark
+// therefore uses a config with no (content ...) test, so no extractor
+// ever runs and the result is identical on any machine, with or without
+// poppler installed.
+//
+// The tree is built once in b.TempDir(), before the timer starts; each
+// iteration re-plans the same on-disk tree with a fresh plan.Claims, so
+// iterations are independent and repeatable.
+func BenchmarkPlan(b *testing.B) {
+ root := b.TempDir()
+ scanRoot := filepath.Join(root, "Filed")
+ buildBenchTree(b, scanRoot)
+
+ mainFile := filepath.Join(root, "krino.conf")
+ if err := os.WriteFile(mainFile, []byte(`(include "dl")`), 0o644); err != nil {
+ b.Fatal(err)
+ }
+ dirsDir := filepath.Join(root, "dirs")
+ if err := os.MkdirAll(dirsDir, 0o755); err != nil {
+ b.Fatal(err)
+ }
+ // Type-only rules (no content test), one per group present in the
+ // generated tree, mirroring examples/by-type.conf; "dat" files match
+ // none of them and take the unmatched path through Match.
+ dirConf := fmt.Sprintf(`
+(path %q)
+(recursive yes)
+(min-age 0s)
+(rule "images" (when (type image)) (move "Sorted/Images") (stop))
+(rule "documents" (when (type document)) (move "Sorted/Documents") (stop))
+(rule "spreadsheets" (when (type spreadsheet)) (move "Sorted/Spreadsheets") (stop))
+(rule "archives" (when (type archive)) (move "Sorted/Archives") (stop))
+(rule "media" (when (or (type audio) (type video))) (move "Sorted/Media") (stop))
+`, scanRoot)
+ if err := os.WriteFile(filepath.Join(dirsDir, "dl.conf"), []byte(dirConf), 0o644); err != nil {
+ b.Fatal(err)
+ }
+
+ e, errs := Load(mainFile, "dl")
+ if len(errs) > 0 {
+ b.Fatalf("config errors: %v", errs)
+ }
+
+ ctx := context.Background()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ if _, err := e.Plan(ctx, e.Dirs[0], plan.NewClaims()); err != nil {
+ b.Fatal(err)
+ }
+ }
+}
+
+// benchTreeDirs * benchFilesPerDir files are generated, spread over
+// nested directories so the walk itself is exercised, not just a single
+// flat directory read.
+const (
+ benchTreeDirs = 12
+ benchFilesPerDir = 150
+)
+
+// buildBenchTree creates a synthetic tree under root for BenchmarkPlan:
+// nested "Sub" directories holding files that cycle through extensions
+// spanning several of Appendix A's type groups, plus one extension
+// ("dat") that matches no rule. Names are neutral (Sub, a<N>.<ext>) -
+// never anything from a real folder, per the leak-check patterns.
+func buildBenchTree(b *testing.B, root string) {
+ b.Helper()
+ exts := []string{"pdf", "jpg", "xlsx", "zip", "mp3", "dat"}
+ old := time.Now().Add(-time.Hour)
+ n := 0
+ for d := 0; d < benchTreeDirs; d++ {
+ dir := filepath.Join(root, fmt.Sprintf("Sub%d", d), "Nested")
+ if err := os.MkdirAll(dir, 0o755); err != nil {
+ b.Fatal(err)
+ }
+ for f := 0; f < benchFilesPerDir; f++ {
+ ext := exts[n%len(exts)]
+ p := filepath.Join(dir, fmt.Sprintf("a%04d.%s", n, ext))
+ if err := os.WriteFile(p, []byte("x"), 0o644); err != nil {
+ b.Fatal(err)
+ }
+ if err := os.Chtimes(p, old, old); err != nil {
+ b.Fatal(err)
+ }
+ n++
+ }
+ }
+}