blob: 6efcdbe8077d7a38d7ed6713cfe7fbec7799cd62 [file] [log] [blame]
Sam McCall87496412017-12-01 17:08:02 +00001//===-- FuzzyMatchTests.cpp - String fuzzy matcher tests --------*- C++ -*-===//
2//
3// The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9
10#include "FuzzyMatch.h"
11
12#include "llvm/ADT/StringExtras.h"
13#include "gmock/gmock.h"
14#include "gtest/gtest.h"
15
16namespace clang {
17namespace clangd {
18namespace {
19using namespace llvm;
20using testing::Not;
21
22struct ExpectedMatch {
23 ExpectedMatch(StringRef Annotated) : Word(Annotated), Annotated(Annotated) {
24 for (char C : "[]")
25 Word.erase(std::remove(Word.begin(), Word.end(), C), Word.end());
26 }
27 std::string Word;
28 StringRef Annotated;
29};
30raw_ostream &operator<<(raw_ostream &OS, const ExpectedMatch &M) {
31 return OS << "'" << M.Word << "' as " << M.Annotated;
32}
33
34struct MatchesMatcher : public testing::MatcherInterface<StringRef> {
35 ExpectedMatch Candidate;
36 MatchesMatcher(ExpectedMatch Candidate) : Candidate(std::move(Candidate)) {}
37
38 void DescribeTo(::std::ostream *OS) const override {
39 raw_os_ostream(*OS) << "Matches " << Candidate;
40 }
41
42 bool MatchAndExplain(StringRef Pattern,
43 testing::MatchResultListener *L) const override {
44 std::unique_ptr<raw_ostream> OS(
45 L->stream() ? (raw_ostream *)(new raw_os_ostream(*L->stream()))
46 : new raw_null_ostream());
47 FuzzyMatcher Matcher(Pattern);
48 auto Result = Matcher.match(Candidate.Word);
49 auto AnnotatedMatch = Matcher.dumpLast(*OS << "\n");
50 return Result && AnnotatedMatch == Candidate.Annotated;
51 }
52};
53
54// Accepts patterns that match a given word.
55// Dumps the debug tables on match failure.
56testing::Matcher<StringRef> matches(StringRef M) {
57 return testing::MakeMatcher<StringRef>(new MatchesMatcher(M));
58}
59
60TEST(FuzzyMatch, Matches) {
Sam McCall09b4be52018-01-13 16:46:26 +000061 EXPECT_THAT("", matches("unique_ptr"));
Sam McCall87496412017-12-01 17:08:02 +000062 EXPECT_THAT("u_p", matches("[u]nique[_p]tr"));
63 EXPECT_THAT("up", matches("[u]nique_[p]tr"));
64 EXPECT_THAT("uq", matches("[u]ni[q]ue_ptr"));
65 EXPECT_THAT("qp", Not(matches("unique_ptr")));
66 EXPECT_THAT("log", Not(matches("SVGFEMorphologyElement")));
67
68 EXPECT_THAT("tit", matches("win.[tit]"));
69 EXPECT_THAT("title", matches("win.[title]"));
70 EXPECT_THAT("WordCla", matches("[Word]Character[Cla]ssifier"));
71 EXPECT_THAT("WordCCla", matches("[WordC]haracter[Cla]ssifier"));
72
73 EXPECT_THAT("dete", Not(matches("editor.quickSuggestionsDelay")));
74
75 EXPECT_THAT("highlight", matches("editorHover[Highlight]"));
76 EXPECT_THAT("hhighlight", matches("editor[H]over[Highlight]"));
77 EXPECT_THAT("dhhighlight", Not(matches("editorHoverHighlight")));
78
79 EXPECT_THAT("-moz", matches("[-moz]-foo"));
80 EXPECT_THAT("moz", matches("-[moz]-foo"));
81 EXPECT_THAT("moza", matches("-[moz]-[a]nimation"));
82
83 EXPECT_THAT("ab", matches("[ab]A"));
84 EXPECT_THAT("ccm", matches("[c]a[cm]elCase"));
85 EXPECT_THAT("bti", Not(matches("the_black_knight")));
86 EXPECT_THAT("ccm", Not(matches("camelCase")));
87 EXPECT_THAT("cmcm", Not(matches("camelCase")));
88 EXPECT_THAT("BK", matches("the_[b]lack_[k]night"));
89 EXPECT_THAT("KeyboardLayout=", Not(matches("KeyboardLayout")));
90 EXPECT_THAT("LLL", matches("SVisual[L]ogger[L]ogs[L]ist"));
91 EXPECT_THAT("LLLL", Not(matches("SVilLoLosLi")));
92 EXPECT_THAT("LLLL", Not(matches("SVisualLoggerLogsList")));
93 EXPECT_THAT("TEdit", matches("[T]ext[Edit]"));
94 EXPECT_THAT("TEdit", matches("[T]ext[Edit]or"));
95 EXPECT_THAT("TEdit", matches("[Te]xte[dit]"));
96 EXPECT_THAT("TEdit", matches("[t]ext_[edit]"));
97 EXPECT_THAT("TEditDit", matches("[T]ext[Edit]or[D]ecorat[i]on[T]ype"));
98 EXPECT_THAT("TEdit", matches("[T]ext[Edit]orDecorationType"));
99 EXPECT_THAT("Tedit", matches("[T]ext[Edit]"));
100 EXPECT_THAT("ba", Not(matches("?AB?")));
101 EXPECT_THAT("bkn", matches("the_[b]lack_[kn]ight"));
102 EXPECT_THAT("bt", matches("the_[b]lack_knigh[t]"));
103 EXPECT_THAT("ccm", matches("[c]amelCase[cm]"));
104 EXPECT_THAT("fdm", matches("[f]in[dM]odel"));
105 EXPECT_THAT("fob", matches("[fo]o[b]ar"));
106 EXPECT_THAT("fobz", Not(matches("foobar")));
107 EXPECT_THAT("foobar", matches("[foobar]"));
108 EXPECT_THAT("form", matches("editor.[form]atOnSave"));
109 EXPECT_THAT("g p", matches("[G]it:[ P]ull"));
110 EXPECT_THAT("g p", matches("[G]it:[ P]ull"));
111 EXPECT_THAT("gip", matches("[Gi]t: [P]ull"));
112 EXPECT_THAT("gip", matches("[Gi]t: [P]ull"));
113 EXPECT_THAT("gp", matches("[G]it: [P]ull"));
114 EXPECT_THAT("gp", matches("[G]it_Git_[P]ull"));
115 EXPECT_THAT("is", matches("[I]mport[S]tatement"));
116 EXPECT_THAT("is", matches("[is]Valid"));
117 EXPECT_THAT("lowrd", matches("[low]Wo[rd]"));
118 EXPECT_THAT("myvable", matches("[myva]ria[ble]"));
119 EXPECT_THAT("no", Not(matches("")));
120 EXPECT_THAT("no", Not(matches("match")));
121 EXPECT_THAT("ob", Not(matches("foobar")));
122 EXPECT_THAT("sl", matches("[S]Visual[L]oggerLogsList"));
123 EXPECT_THAT("sllll", matches("[S]Visua[lL]ogger[L]ogs[L]ist"));
124 EXPECT_THAT("Three", matches("H[T]ML[HRE]l[e]ment"));
125 EXPECT_THAT("Three", matches("[Three]"));
126 EXPECT_THAT("fo", Not(matches("barfoo")));
127 EXPECT_THAT("fo", matches("bar_[fo]o"));
128 EXPECT_THAT("fo", matches("bar_[Fo]o"));
129 EXPECT_THAT("fo", matches("bar [fo]o"));
130 EXPECT_THAT("fo", matches("bar.[fo]o"));
131 EXPECT_THAT("fo", matches("bar/[fo]o"));
132 EXPECT_THAT("fo", matches("bar\\[fo]o"));
133
134 EXPECT_THAT(
135 "aaaaaa",
136 matches("[aaaaaa]aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
137 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"));
138 EXPECT_THAT("baba", Not(matches("ababababab")));
139 EXPECT_THAT("fsfsfs", Not(matches("dsafdsafdsafdsafdsafdsafdsafasdfdsa")));
140 EXPECT_THAT("fsfsfsfsfsfsfsf",
141 Not(matches("dsafdsafdsafdsafdsafdsafdsafasdfdsafdsafdsafdsafdsfd"
142 "safdsfdfdfasdnfdsajfndsjnafjndsajlknfdsa")));
143
144 EXPECT_THAT(" g", matches("[ g]roup"));
145 EXPECT_THAT("g", matches(" [g]roup"));
146 EXPECT_THAT("g g", Not(matches(" groupGroup")));
147 EXPECT_THAT("g g", matches(" [g]roup[ G]roup"));
148 EXPECT_THAT(" g g", matches("[ ] [g]roup[ G]roup"));
149 EXPECT_THAT("zz", matches("[zz]Group"));
150 EXPECT_THAT("zzg", matches("[zzG]roup"));
151 EXPECT_THAT("g", matches("zz[G]roup"));
152
153 EXPECT_THAT("aaaa", matches("_a_[aaaa]")); // Prefer consecutive.
154 EXPECT_THAT("printf", matches("s[printf]"));
155 EXPECT_THAT("str", matches("o[str]eam"));
156}
157
158struct RankMatcher : public testing::MatcherInterface<StringRef> {
159 std::vector<ExpectedMatch> RankedStrings;
160 RankMatcher(std::initializer_list<ExpectedMatch> RankedStrings)
161 : RankedStrings(RankedStrings) {}
162
163 void DescribeTo(::std::ostream *OS) const override {
164 raw_os_ostream O(*OS);
165 O << "Ranks strings in order: [";
166 for (const auto &Str : RankedStrings)
167 O << "\n\t" << Str;
168 O << "\n]";
169 }
170
171 bool MatchAndExplain(StringRef Pattern,
172 testing::MatchResultListener *L) const override {
173 std::unique_ptr<raw_ostream> OS(
174 L->stream() ? (raw_ostream *)(new raw_os_ostream(*L->stream()))
175 : new raw_null_ostream());
176 FuzzyMatcher Matcher(Pattern);
177 const ExpectedMatch *LastMatch;
178 Optional<float> LastScore;
179 bool Ok = true;
180 for (const auto &Str : RankedStrings) {
181 auto Score = Matcher.match(Str.Word);
182 if (!Score) {
183 *OS << "\nDoesn't match '" << Str.Word << "'";
184 Matcher.dumpLast(*OS << "\n");
185 Ok = false;
186 } else {
187 std::string Buf;
188 llvm::raw_string_ostream Info(Buf);
189 auto AnnotatedMatch = Matcher.dumpLast(Info);
190
191 if (AnnotatedMatch != Str.Annotated) {
192 *OS << "\nMatched " << Str.Word << " as " << AnnotatedMatch
193 << " instead of " << Str.Annotated << "\n"
194 << Info.str();
195 Ok = false;
196 } else if (LastScore && *LastScore < *Score) {
197 *OS << "\nRanks '" << Str.Word << "'=" << *Score << " above '"
198 << LastMatch->Word << "'=" << *LastScore << "\n"
199 << Info.str();
200 Matcher.match(LastMatch->Word);
201 Matcher.dumpLast(*OS << "\n");
202 Ok = false;
203 }
204 }
205 LastMatch = &Str;
206 LastScore = Score;
207 }
208 return Ok;
209 }
210};
211
212// Accepts patterns that match all the strings and rank them in the given order.
213// Dumps the debug tables on match failure.
214template <typename... T> testing::Matcher<StringRef> ranks(T... RankedStrings) {
215 return testing::MakeMatcher<StringRef>(
216 new RankMatcher{ExpectedMatch(RankedStrings)...});
217}
218
219TEST(FuzzyMatch, Ranking) {
220 EXPECT_THAT("eb", ranks("[e]mplace_[b]ack", "[e]m[b]ed"));
221 EXPECT_THAT("cons",
222 ranks("[cons]ole", "[Cons]ole", "ArrayBuffer[Cons]tructor"));
223 EXPECT_THAT("foo", ranks("[foo]", "[Foo]"));
224 EXPECT_THAT("onMess",
225 ranks("[onMess]age", "[onmess]age", "[on]This[M]ega[Es]cape[s]"));
226 EXPECT_THAT("CC", ranks("[C]amel[C]ase", "[c]amel[C]ase"));
227 EXPECT_THAT("cC", ranks("[c]amel[C]ase", "[C]amel[C]ase"));
228 EXPECT_THAT("p", ranks("[p]arse", "[p]osix", "[p]afdsa", "[p]ath", "[p]"));
229 EXPECT_THAT("pa", ranks("[pa]rse", "[pa]th", "[pa]fdsa"));
230 EXPECT_THAT("log", ranks("[log]", "Scroll[Log]icalPosition"));
231 EXPECT_THAT("e", ranks("[e]lse", "Abstract[E]lement"));
232 EXPECT_THAT("workbench.sideb",
233 ranks("[workbench.sideB]ar.location",
234 "[workbench.]editor.default[SideB]ySideLayout"));
235 EXPECT_THAT("editor.r", ranks("[editor.r]enderControlCharacter",
236 "[editor.]overview[R]ulerlanes",
237 "diff[Editor.r]enderSideBySide"));
238 EXPECT_THAT("-mo", ranks("[-mo]z-columns", "[-]ms-ime-[mo]de"));
239 EXPECT_THAT("convertModelPosition",
240 ranks("[convertModelPosition]ToViewPosition",
241 "[convert]ViewTo[ModelPosition]"));
242 EXPECT_THAT("is", ranks("[is]ValidViewletId", "[i]mport [s]tatement"));
243 EXPECT_THAT("title", ranks("window.[title]",
244 "files.[t]r[i]m[T]rai[l]ingWhit[e]space"));
245 EXPECT_THAT("strcpy", ranks("[strcpy]", "[strcpy]_s", "[str]n[cpy]"));
246 EXPECT_THAT("close", ranks("workbench.quickOpen.[close]OnFocusOut",
247 "[c]ss.[l]int.imp[o]rt[S]tat[e]ment",
248 "[c]ss.co[lo]rDecorator[s].[e]nable"));
249}
250
251} // namespace
252} // namespace clangd
253} // namespace clang