all repos

clerk @ 2f348cb

missing tooling for ledger/hledger

clerk/internal/lsp/workspace_symbol_test.go (view raw)

Oleksandr Smirnov Oleksandr Smirnov
olexsmir@gmail.com
lsp: list transactions in workspace/symbol, 1 month ago
1
package lsp
2
3
import (
4
	"context"
5
	"fmt"
6
	"path/filepath"
7
	"strings"
8
	"testing"
9
	"time"
10
11
	"go.lsp.dev/protocol"
12
	"go.lsp.dev/uri"
13
14
	"olexsmir.xyz/clerk/internal/testutil/golden"
15
)
16
17
func TestSortScoredSymbols(t *testing.T) {
18
	scored := []scoredSymbol{
19
		{symbolAccount, "zz:zz", 0.5, nil},
20
		{symbolTag, "aa", 1.0, nil},
21
		{symbolPayee, "bb", 0.5, nil},
22
		{symbolTag, "ccc", 0.5, nil},
23
		{symbolPayee, "bb", 0.5, nil},
24
	}
25
	sortScoredSymbols(scored)
26
27
	want := []string{"aa", "bb", "bb", "ccc", "zz:zz"}
28
	for i, s := range scored {
29
		if s.name != want[i] {
30
			t.Errorf("pos %d = %q, want %q", i, s.name, want[i])
31
		}
32
	}
33
}
34
35
func TestServer_Symbols_EmptyQuery(t *testing.T) {
36
	srv := NewServer("test")
37
	u := uri.URI("file:///test.journal")
38
	srv.server.openDoc(u, "account expenses:food\n", 1, "journal")
39
40
	res, err := srv.server.Symbols(context.Background(), &protocol.WorkspaceSymbolParams{Query: ""})
41
	if err != nil {
42
		t.Fatal(err)
43
	}
44
	if res != nil {
45
		t.Errorf("got %v, want nil for empty query", res)
46
	}
47
}
48
49
func TestGolden_Symbols(t *testing.T) {
50
	ar := golden.Read(t, "workspace-symbol")
51
	t.Run("workspace-symbol", func(t *testing.T) {
52
		h := newTxtarHarness(t, ar)
53
54
		// One query per line; a trailing blank line is the empty-query case.
55
		queries := strings.Split(strings.TrimSuffix(string(ar.Get("queries")), "\n"), "\n")
56
		var b strings.Builder
57
		for _, q := range queries {
58
			res, err := h.srv.Symbols(t.Context(), &protocol.WorkspaceSymbolParams{Query: q})
59
			if err != nil {
60
				t.Fatal(err)
61
			}
62
			if res == nil {
63
				fmt.Fprintf(&b, "%q <none>\n", q)
64
				continue
65
			}
66
			list, ok := res.(protocol.WorkspaceSymbolSlice)
67
			if !ok {
68
				t.Fatalf("Symbols returned %T, want WorkspaceSymbolSlice", res)
69
			}
70
			for _, sym := range list {
71
				loc, ok := sym.Location.(*protocol.Location)
72
				if !ok {
73
					t.Fatalf("Symbol %q location is %T, want *Location", sym.Name, sym.Location)
74
				}
75
				r := loc.Range
76
				fmt.Fprintf(&b, "%q %s %s %s %d:%d-%d:%d\n", q, symbolKindName(sym.Kind), sym.Name,
77
					filepath.Base(loc.URI.Path()),
78
					r.Start.Line, r.Start.Character, r.End.Line, r.End.Character)
79
			}
80
		}
81
		golden.Assert(t, ar, b.String())
82
	})
83
}
84
85
func BenchmarkSymbols(b *testing.B) {
86
	content := openJournal(b, "../../journal/testdata/journals/actual-1ktxns-100accts.journal")
87
88
	srv := NewServer("test")
89
	u := uri.URI("file:///test.journal")
90
	srv.server.openDoc(u, content, 1, "journal")
91
	srv.server.analysisFor(u) // warm the per-doc cache
92
93
	for tname, tt := range map[string]struct {
94
		query string
95
		want  bool // expect non-empty results
96
	}{
97
		"account prefix": {"1:2", true},
98
		"all payees":     {"transaction", true},
99
		"txn dates":      {"2000", true},
100
		"mixed kinds":    {"B", true},
101
		"no match":       {"xyz", false},
102
	} {
103
		b.Run(tname, func(b *testing.B) {
104
			params := &protocol.WorkspaceSymbolParams{Query: tt.query}
105
106
			// warm up: assert the query matches as expected
107
			res, err := srv.server.Symbols(b.Context(), params)
108
			if err != nil {
109
				b.Fatal(err)
110
			}
111
			if (res != nil) != tt.want {
112
				b.Fatalf("%s: query %q: got res==nil=%v, want %v", tname, tt.query, res == nil, !tt.want)
113
			}
114
			b.ReportAllocs()
115
			b.ResetTimer()
116
			for b.Loop() {
117
				if _, err := srv.server.Symbols(b.Context(), params); err != nil {
118
					b.Fatal(err)
119
				}
120
			}
121
122
			// guard: a whole-file re-parse per request (~13ms) would blow past
123
			// this and must be caught
124
			if avg := b.Elapsed() / time.Duration(b.N); avg > 5*time.Millisecond {
125
				b.Fatalf("symbols %v/op: reparse regression", avg)
126
			}
127
		})
128
	}
129
}
130
131
func symbolKindName(k protocol.SymbolKind) string {
132
	switch k {
133
	case protocol.SymbolKindClass:
134
		return "class"
135
	case protocol.SymbolKindVariable:
136
		return "variable"
137
	case protocol.SymbolKindObject:
138
		return "object"
139
	case protocol.SymbolKindProperty:
140
		return "property"
141
	case protocol.SymbolKindEvent:
142
		return "event"
143
	}
144
	return "other"
145
}