1package kvconf
2
3import (
4 "context"
5 "fmt"
6 "os"
7 "strings"
8 "testing"
9 "time"
10)
11
12type benchConfig struct {
13 Theme string `kv:"theme" oneof:"auto|dark|light"`
14 Enabled bool `kv:"enabled"`
15 Count int `kv:"count" min:"0" max:"1000"`
16 After time.Duration `kv:"lock.after,off"`
17 Ratio float64 `kv:"ratio"`
18 Tags []string `kv:"tags"`
19 Color string `kv:"border.color"`
20 Binds map[string]string `kv:"bind.*"`
21 Outputs map[string]output `kv:"output.*"`
22}
23
24// benchFile is a realistic config: ~100 lines with comments, binds and
25// per-output settings.
26var benchFile = func() []byte {
27 var b strings.Builder
28 b.WriteString("# a comment\ntheme = dark\nenabled = on\ncount = 42\nlock.after = 5m\nratio = 1.5\n")
29 b.WriteString("tags = a, b, c\nborder.color = #ff8800 # orange\n\n")
30 for i := range 60 {
31 fmt.Fprintf(&b, "bind.cmd+%d = spawn \"foot -e app%d\"\n", i, i)
32 }
33 for i := range 8 {
34 fmt.Fprintf(&b, "output.DP-%d.scale = 1.25\noutput.DP-%d.hdr = off\noutput.DP-%d.sdr-brightness = 203\n", i, i, i)
35 }
36 return []byte(b.String())
37}()
38
39func BenchmarkParse(b *testing.B) {
40 b.ReportAllocs()
41 b.SetBytes(int64(len(benchFile)))
42 for b.Loop() {
43 if _, _, err := ParseBytes(benchFile, Options{}); err != nil {
44 b.Fatal(err)
45 }
46 }
47}
48
49func BenchmarkUnmarshal(b *testing.B) {
50 b.ReportAllocs()
51 b.SetBytes(int64(len(benchFile)))
52 for b.Loop() {
53 var c benchConfig
54 if w, err := Unmarshal(benchFile, &c, Options{}); err != nil || len(w) > 0 {
55 b.Fatal(w, err)
56 }
57 }
58}
59
60func BenchmarkDecodeScalars(b *testing.B) {
61 entries, _, _ := ParseBytes([]byte("theme = dark\nenabled = on\ncount = 42\nlock.after = 5m\nratio = 1.5\n"), Options{})
62 b.ReportAllocs()
63 for b.Loop() {
64 var c benchConfig
65 if _, err := Decode(entries, &c, Options{}); err != nil {
66 b.Fatal(err)
67 }
68 }
69}
70
71func BenchmarkMarshal(b *testing.B) {
72 var c benchConfig
73 if _, err := Unmarshal(benchFile, &c, Options{}); err != nil {
74 b.Fatal(err)
75 }
76 b.ReportAllocs()
77 for b.Loop() {
78 if _, err := Marshal(&c); err != nil {
79 b.Fatal(err)
80 }
81 }
82}
83
84// BenchmarkWatchEvent measures one change seen by the watcher: the event,
85// its classification and the debounced report.
86func BenchmarkWatchEvent(b *testing.B) {
87 dir := b.TempDir()
88 path := dir + "/config"
89 if err := os.WriteFile(path, benchFile, 0o600); err != nil {
90 b.Fatal(err)
91 }
92 out := make(chan struct{}, 1)
93 ctx, cancel := context.WithCancel(context.Background())
94 defer cancel()
95 ready := make(chan struct{})
96 go func() { _ = Watch(ctx, path, WatchOptions{Debounce: -1, Ready: ready}, out) }()
97 <-ready
98 f, err := os.OpenFile(path, os.O_WRONLY, 0)
99 if err != nil {
100 b.Fatal(err)
101 }
102 defer f.Close()
103 b.ReportAllocs()
104 for b.Loop() {
105 if _, err := f.WriteAt([]byte{'#'}, 0); err != nil {
106 b.Fatal(err)
107 }
108 <-out
109 }
110}
111
112func BenchmarkSet(b *testing.B) {
113 values := map[string]*string{"output.DP-3.scale": ptr("2")}
114 b.ReportAllocs()
115 for b.Loop() {
116 if _, err := Set(benchFile, values); err != nil {
117 b.Fatal(err)
118 }
119 }
120}