GoPLS Viewer

Home|gopls/internal/fuzzy/matcher_test.go
1// Copyright 2019 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// Benchmark results:
6//
7// BenchmarkMatcher-12         1000000          1615 ns/op      30.95 MB/s           0 B/op           0 allocs/op
8package fuzzy_test
9
10import (
11    "bytes"
12    "fmt"
13    "math"
14    "testing"
15
16    "golang.org/x/tools/internal/fuzzy"
17)
18
19type comparator struct {
20    f     func(valref float32bool
21    descr string
22}
23
24var (
25    eq = comparator{
26        f: func(valref float32bool {
27            return val == ref
28        },
29        descr"==",
30    }
31    ge = comparator{
32        f: func(valref float32bool {
33            return val >= ref
34        },
35        descr">=",
36    }
37    gt = comparator{
38        f: func(valref float32bool {
39            return val > ref
40        },
41        descr">",
42    }
43)
44
45func (c comparatoreval(valref float32bool {
46    return c.f(valref)
47}
48
49func (c comparatorString() string {
50    return c.descr
51}
52
53type scoreTest struct {
54    candidate string
55    comparator
56    ref float32
57}
58
59var matcherTests = []struct {
60    pattern string
61    tests   []scoreTest
62}{
63    {
64        pattern"",
65        tests: []scoreTest{
66            {"def"eq1},
67            {"Ab stuff c"eq1},
68        },
69    },
70    {
71        pattern"abc",
72        tests: []scoreTest{
73            {"def"eq0},
74            {"abd"eq0},
75            {"abc"ge0},
76            {"Abc"ge0},
77            {"Ab stuff c"ge0},
78        },
79    },
80    {
81        pattern"Abc",
82        tests: []scoreTest{
83            {"def"eq0},
84            {"abd"eq0},
85            {"abc"ge0},
86            {"Abc"ge0},
87            {"Ab stuff c"ge0},
88        },
89    },
90    {
91        pattern"U",
92        tests: []scoreTest{
93            {"ErrUnexpectedEOF"gt0},
94            {"ErrUnexpectedEOF.Error"eq0},
95        },
96    },
97}
98
99func TestScore(t *testing.T) {
100    for _tc := range matcherTests {
101        m := fuzzy.NewMatcher(tc.pattern)
102        for _sct := range tc.tests {
103            score := m.Score(sct.candidate)
104            if !sct.comparator.eval(scoresct.ref) {
105                t.Errorf("m.Score(%q) = %.2g, want %s %v"sct.candidatescoresct.comparatorsct.ref)
106            }
107        }
108    }
109}
110
111var compareCandidatesTestCases = []struct {
112    pattern           string
113    orderedCandidates []string
114}{
115    {
116        pattern"Foo",
117        orderedCandidates: []string{
118            "Barfoo",
119            "Faoo",
120            "F_o_o",
121            "FaoFooa",
122            "BarFoo",
123            "F__oo",
124            "F_oo",
125            "FooA",
126            "FooBar",
127            "Foo",
128        },
129    },
130    {
131        pattern"U",
132        orderedCandidates: []string{
133            "ErrUnexpectedEOF.Error",
134            "ErrUnexpectedEOF",
135        },
136    },
137}
138
139func TestCompareCandidateScores(t *testing.T) {
140    for _tc := range compareCandidatesTestCases {
141        m := fuzzy.NewMatcher(tc.pattern)
142
143        var prevScore float32
144        prevCand := "MIN_SCORE"
145        for _cand := range tc.orderedCandidates {
146            score := m.Score(cand)
147            if prevScore > score {
148                t.Errorf("%s[=%v] is scored lower than %s[=%v]"candscoreprevCandprevScore)
149            }
150            if score < -1 || score > 1 {
151                t.Errorf("%s score is %v; want value between [-1, 1]"candscore)
152            }
153            prevScore = score
154            prevCand = cand
155        }
156    }
157}
158
159var fuzzyMatcherTestCases = []struct {
160    p    string
161    str  string
162    want string
163}{
164    {p"foo"str"abc::foo"want"abc::[foo]"},
165    {p"foo"str"foo.foo"want"foo.[foo]"},
166    {p"foo"str"fo_oo.o_oo"want"[fo]_oo.[o]_oo"},
167    {p"foo"str"fo_oo.fo_oo"want"fo_oo.[fo]_[o]o"},
168    {p"fo_o"str"fo_oo.o_oo"want"[f]o_oo.[o_o]o"},
169    {p"fOO"str"fo_oo.o_oo"want"[f]o_oo.[o]_[o]o"},
170    {p"tedit"str"foo.TextEdit"want"foo.[T]ext[Edit]"},
171    {p"TEdit"str"foo.TextEdit"want"foo.[T]ext[Edit]"},
172    {p"Tedit"str"foo.TextEdit"want"foo.[T]ext[Edit]"},
173    {p"Tedit"str"foo.Textedit"want"foo.[Te]xte[dit]"},
174    {p"TEdit"str"foo.Textedit"want""},
175    {p"te"str"foo.Textedit"want"foo.[Te]xtedit"},
176    {p"ee"str"foo.Textedit"want""}, // short middle of the word match
177    {p"ex"str"foo.Textedit"want"foo.T[ex]tedit"},
178    {p"exdi"str"foo.Textedit"want""},  // short middle of the word match
179    {p"exdit"str"foo.Textedit"want""}, // short middle of the word match
180    {p"extdit"str"foo.Textedit"want"foo.T[ext]e[dit]"},
181    {p"e"str"foo.Textedit"want"foo.T[e]xtedit"},
182    {p"E"str"foo.Textedit"want"foo.T[e]xtedit"},
183    {p"ed"str"foo.Textedit"want"foo.Text[ed]it"},
184    {p"edt"str"foo.Textedit"want""}, // short middle of the word match
185    {p"edit"str"foo.Textedit"want"foo.Text[edit]"},
186    {p"edin"str"foo.TexteditNum"want"foo.Text[edi]t[N]um"},
187    {p"n"str"node.GoNodeMax"want"[n]ode.GoNodeMax"},
188    {p"N"str"node.GoNodeMax"want"[n]ode.GoNodeMax"},
189    {p"completio"str"completion"want"[completio]n"},
190    {p"completio"str"completion.None"want"[completio]n.None"},
191}
192
193func TestFuzzyMatcherRanges(t *testing.T) {
194    for _tc := range fuzzyMatcherTestCases {
195        matcher := fuzzy.NewMatcher(tc.p)
196        score := matcher.Score(tc.str)
197        if tc.want == "" {
198            if score > 0 {
199                t.Errorf("Score(%s, %s) = %v; want: <= 0"tc.ptc.strscore)
200            }
201            continue
202        }
203        if score < 0 {
204            t.Errorf("Score(%s, %s) = %v, want: > 0"tc.ptc.strscore)
205            continue
206        }
207        got := highlightMatches(tc.strmatcher)
208        if tc.want != got {
209            t.Errorf("highlightMatches(%s, %s) = %v, want: %v"tc.ptc.strgottc.want)
210        }
211    }
212}
213
214var scoreTestCases = []struct {
215    p    string
216    str  string
217    want float64
218}{
219    // Score precision up to five digits. Modify if changing the score, but make sure the new values
220    // are reasonable.
221    {p"abc"str"abc"want1},
222    {p"abc"str"Abc"want1},
223    {p"abc"str"Abcdef"want1},
224    {p"strc"str"StrCat"want1},
225    {p"abc_def"str"abc_def_xyz"want1},
226    {p"abcdef"str"abc_def_xyz"want0.91667},
227    {p"abcxyz"str"abc_def_xyz"want0.91667},
228    {p"sc"str"StrCat"want0.75},
229    {p"abc"str"AbstrBasicCtor"want0.83333},
230    {p"foo"str"abc::foo"want0.91667},
231    {p"afoo"str"abc::foo"want0.9375},
232    {p"abr"str"abc::bar"want0.5},
233    {p"br"str"abc::bar"want0.25},
234    {p"aar"str"abc::bar"want0.41667},
235    {p"edin"str"foo.TexteditNum"want0.125},
236    {p"ediu"str"foo.TexteditNum"want0},
237    // We want the next two items to have roughly similar scores.
238    {p"up"str"unique_ptr"want0.75},
239    {p"up"str"upper_bound"want1},
240}
241
242func TestScores(t *testing.T) {
243    for _tc := range scoreTestCases {
244        matcher := fuzzy.NewMatcher(tc.p)
245        got := math.Round(float64(matcher.Score(tc.str))*1e5) / 1e5
246        if got != tc.want {
247            t.Errorf("Score(%s, %s) = %v, want: %v"tc.ptc.strgottc.want)
248        }
249    }
250}
251
252func highlightMatches(str stringmatcher *fuzzy.Matcherstring {
253    matches := matcher.MatchedRanges()
254
255    var buf bytes.Buffer
256    index := 0
257    for i := 0i < len(matches)-1i += 2 {
258        se := matches[i], matches[i+1]
259        fmt.Fprintf(&buf"%s[%s]"str[index:s], str[s:e])
260        index = e
261    }
262    buf.WriteString(str[index:])
263    return buf.String()
264}
265
266func BenchmarkMatcher(b *testing.B) {
267    pattern := "Foo"
268    candidates := []string{
269        "F_o_o",
270        "Barfoo",
271        "Faoo",
272        "F__oo",
273        "F_oo",
274        "FaoFooa",
275        "BarFoo",
276        "FooA",
277        "FooBar",
278        "Foo",
279    }
280
281    matcher := fuzzy.NewMatcher(pattern)
282
283    b.ResetTimer()
284    for i := 0i < b.Ni++ {
285        for _c := range candidates {
286            matcher.Score(c)
287        }
288    }
289    var numBytes int
290    for _c := range candidates {
291        numBytes += len(c)
292    }
293    b.SetBytes(int64(numBytes))
294}
295
MembersX
TestScore.RangeStmt_1594.tc
TestCompareCandidateScores
TestFuzzyMatcherRanges
highlightMatches.matcher
BenchmarkMatcher
BenchmarkMatcher.numBytes
BenchmarkMatcher.RangeStmt_7104.c
comparator.eval
TestCompareCandidateScores.RangeStmt_2331.BlockStmt.prevScore
BenchmarkMatcher.i
scoreTest
scoreTest.candidate
TestScore.RangeStmt_1594.BlockStmt.RangeStmt_1666.sct
TestScores.RangeStmt_6171.tc
comparator.eval.c
highlightMatches.index
TestCompareCandidateScores.RangeStmt_2331.BlockStmt.RangeStmt_2468.cand
TestFuzzyMatcherRanges.RangeStmt_4602.BlockStmt.score
BenchmarkMatcher.BlockStmt.RangeStmt_7027.c
TestFuzzyMatcherRanges.t
TestCompareCandidateScores.t
TestCompareCandidateScores.RangeStmt_2331.BlockStmt.prevCand
math
TestScore
scoreTest.ref
TestCompareCandidateScores.RangeStmt_2331.BlockStmt.m
TestScores
TestScores.t
highlightMatches.i
BenchmarkMatcher.b
comparator.f
TestScore.RangeStmt_1594.BlockStmt.RangeStmt_1666.BlockStmt.score
TestScore.t
BenchmarkMatcher.matcher
comparator
TestCompareCandidateScores.RangeStmt_2331.BlockStmt.RangeStmt_2468.BlockStmt.score
TestScores.RangeStmt_6171.BlockStmt.matcher
highlightMatches
TestFuzzyMatcherRanges.RangeStmt_4602.BlockStmt.matcher
TestFuzzyMatcherRanges.RangeStmt_4602.BlockStmt.got
highlightMatches.str
comparator.String.c
BenchmarkMatcher.candidates
TestCompareCandidateScores.RangeStmt_2331.tc
comparator.eval.val
TestScore.RangeStmt_1594.BlockStmt.m
TestFuzzyMatcherRanges.RangeStmt_4602.tc
BenchmarkMatcher.pattern
comparator.eval.ref
comparator.String
highlightMatches.buf
comparator.descr
highlightMatches.matches
Members
X