all repos

clerk @ ecdc664

missing tooling for ledger/hledger

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

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