all repos

clerk @ master

missing tooling for ledger/hledger

clerk/internal/settings/settings_test.go (view raw)

Oleksandr Smirnov Oleksandr Smirnov
olexsmir@gmail.com
feat: config (#6), 1 month ago
1
package settings
2
3
import (
4
	"fmt"
5
	"os"
6
	"path/filepath"
7
	"slices"
8
	"strings"
9
	"testing"
10
11
	"olexsmir.xyz/clerk/internal/linter"
12
	"olexsmir.xyz/clerk/internal/testutil/golden"
13
	"olexsmir.xyz/clerk/journal/printer"
14
)
15
16
func TestNormKey(t *testing.T) {
17
	cases := map[string]string{
18
		"semantic_highlighting":        "semantic_highlighting",
19
		"semantic-highlighting":        "semantic_highlighting",
20
		"semanticHighlighting":         "semantic_highlighting",
21
		"latin_to_cyrillic_completion": "latin_to_cyrillic_completion",
22
		"latinToCyrillicCompletion":    "latin_to_cyrillic_completion",
23
		"format":                       "format",
24
		"ALIGN_STYLE":                  "align_style",
25
		"indent-width":                 "indent_width",
26
	}
27
	for in, want := range cases {
28
		if got := normalizeKey(in); got != want {
29
			t.Errorf("normKey(%q) = %q, want %q", in, got, want)
30
		}
31
	}
32
}
33
34
func TestApplyLSP(t *testing.T) {
35
	s := DefaultConfig
36
	_, err := s.ApplyLSP(map[string]any{
37
		"semanticHighlighting":      false,
38
		"latinToCyrillicCompletion": true,
39
		"format":                    map[string]any{"indent-width": int64(4)},
40
	})
41
	if err != nil {
42
		t.Fatalf("ApplyLSP: %v", err)
43
	}
44
	if s.SemanticHighlighting {
45
		t.Error("SemanticHighlighting = true, want false")
46
	}
47
	if !s.LatinToCyrillicCompletion {
48
		t.Error("LatinToCyrillicCompletion = false, want true")
49
	}
50
	if s.Format.IndentWidth != 4 {
51
		t.Errorf("IndentWidth = %d, want 4", s.Format.IndentWidth)
52
	}
53
}
54
55
func TestApply_duplicateKey(t *testing.T) {
56
	s := DefaultConfig
57
	_, err := s.Apply(map[string]any{
58
		"format": map[string]any{
59
			"indent-width": 4,
60
			"indent_width": 8,
61
		},
62
	})
63
	if err == nil {
64
		t.Fatal("expected duplicate-key error, got nil")
65
	}
66
	// Neither conflicting value is applied; default prevails.
67
	if s.Format.IndentWidth != DefaultConfig.Format.IndentWidth {
68
		t.Errorf("IndentWidth = %d, want default %d", s.Format.IndentWidth, DefaultConfig.Format.IndentWidth)
69
	}
70
}
71
72
// Benchmarks
73
74
func BenchmarkParseMap(b *testing.B) {
75
	benchRawMap := map[string]any{
76
		"semantic_highlighting": true,
77
		"format": map[string]any{
78
			"tab-indent":           true,
79
			"indent-width":         int64(4),
80
			"align-style":          "right",
81
			"commodity-pos":        "before",
82
			"align-column":         int64(80),
83
			"preserve-blank-lines": true,
84
		},
85
		"lint": map[string]any{
86
			"unbalanced-transaction": "error",
87
			"missing-payee":          false,
88
			"empty-postings":         "warn",
89
			"account-depth":          map[string]any{"severity": "error", "max-depth": int64(8)},
90
		},
91
	}
92
93
	b.ReportAllocs()
94
	for b.Loop() {
95
		if _, _, err := parse(benchRawMap); err != nil {
96
			b.Fatal(err)
97
		}
98
	}
99
}
100
101
func BenchmarkParseLargeLint(b *testing.B) {
102
	raw := map[string]any{"lint": map[string]any{}}
103
	for id := range linter.Rules {
104
		raw["lint"].(map[string]any)[string(id)] = "error"
105
	}
106
	b.ReportAllocs()
107
	for b.Loop() {
108
		if _, _, err := parse(raw); err != nil {
109
			b.Fatal(err)
110
		}
111
	}
112
}
113
114
func BenchmarkLoad(b *testing.B) {
115
	path := filepath.Join("testdata", "bench.toml")
116
	b.ReportAllocs()
117
	for b.Loop() {
118
		if _, _, err := Load(path); err != nil {
119
			b.Fatal(err)
120
		}
121
	}
122
}
123
124
func BenchmarkNormKey(b *testing.B) {
125
	tests := []string{"latinToCyrillicCompletion", "semantic_highlighting", "unbalanced-transaction"}
126
	for _, test := range tests {
127
		b.ReportAllocs()
128
		b.Run(test, func(b *testing.B) {
129
			for b.Loop() {
130
				_ = normalizeKey(test)
131
			}
132
		})
133
	}
134
}
135
136
// Golden tests
137
138
func TestGolden_Load(t *testing.T) {
139
	for _, tt := range []string{"different-cases", "full", "invalid-values", "missing", "non-table", "unknown-keys"} {
140
		t.Run(tt, func(t *testing.T) {
141
			ar := golden.Read(t, tt)
142
			path := filepath.Join(t.TempDir(), "clerk.toml")
143
			if cfg := ar.Get("config.toml"); cfg != nil {
144
				if err := os.WriteFile(path, cfg, 0o644); err != nil {
145
					t.Fatal(err)
146
				}
147
			}
148
			s, warns, err := Load(path)
149
			golden.Assert(t, ar, renderTOML(s, warns, err))
150
		})
151
	}
152
}
153
154
func renderTOML(s Settings, warns []string, err error) string {
155
	var lines []string
156
	for _, w := range warns {
157
		lines = append(lines, "warning: "+w)
158
	}
159
	if err != nil {
160
		for line := range strings.SplitSeq(err.Error(), "\n") {
161
			if line != "" {
162
				lines = append(lines, "error: "+line)
163
			}
164
		}
165
	}
166
	slices.Sort(lines)
167
	if err == nil {
168
		lines = append(lines, changedLines(configLines(s), configLines(DefaultConfig))...)
169
	}
170
	if len(lines) == 0 {
171
		return ""
172
	}
173
	return strings.Join(lines, "\n") + "\n"
174
}
175
176
func configLines(s Settings) []string {
177
	var lines []string
178
	for _, line := range renderFormat(s.Format) {
179
		lines = append(lines, "format."+line)
180
	}
181
	for _, id := range sortedRuleIDs() {
182
		rc := s.Linter.Rules[id]
183
		line := "lint." + string(id) + ": "
184
		if rc.Disabled {
185
			line += "disabled"
186
		} else {
187
			line += rc.Severity.String()
188
			if len(rc.Options) > 0 {
189
				line += " options=" + string(rc.Options)
190
			}
191
		}
192
		lines = append(lines, line)
193
	}
194
	return lines
195
}
196
197
func changedLines(got, want []string) []string {
198
	var out []string
199
	for i, line := range got {
200
		if line != want[i] {
201
			out = append(out, line)
202
		}
203
	}
204
	return out
205
}
206
207
func renderFormat(c printer.Config) []string {
208
	return []string{
209
		"tab-indent: " + fmt.Sprint(c.TabIndent),
210
		"indent-width: " + fmt.Sprint(c.IndentWidth),
211
		"preserve-blank-lines: " + fmt.Sprint(c.PreserveBlankLines),
212
		"align-style: " + c.AlignStyle.String(),
213
		"align-column: " + fmt.Sprint(c.AlignColumn),
214
		"commodity-pos: " + c.CommodityPos.String(),
215
	}
216
}
217
218
func sortedRuleIDs() []linter.RuleID {
219
	ids := make([]linter.RuleID, 0, len(linter.Rules))
220
	for id := range linter.Rules {
221
		ids = append(ids, id)
222
	}
223
	slices.Sort(ids)
224
	return ids
225
}