1 /***************************************************************************
2 * Copyright (C) 2005-2018 by the Quassel Project *
3 * devel@quassel-irc.org *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) version 3. *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
21 #include "expressionmatchtests.h"
25 #include <QStringList>
28 #include "expressionmatch.h"
30 void ExpressionMatchTests::runTests()
33 qDebug() << "Running all ExpressionMatch tests";
34 runTestsEmptyPattern();
35 runTestsMatchPhrase();
36 runTestsMatchMultiPhrase();
37 runTestsMatchWildcard();
38 runTestsMatchMultiWildcard();
40 runTestsTrimMultiWildcardWhitespace();
42 qDebug() << "Passed all ExpressionMatch tests";
46 void ExpressionMatchTests::runTestsEmptyPattern()
48 qDebug() << "> Testing ExpressionMatch empty pattern handling";
51 ExpressionMatch emptyMatch =
52 ExpressionMatch("", ExpressionMatch::MatchMode::MatchPhrase, false);
55 Q_ASSERT(emptyMatch.isEmpty());
56 // Assert default match fails (same as setting match empty to false)
57 Q_ASSERT(!emptyMatch.match("something"));
58 // Assert match empty succeeds
59 Q_ASSERT(emptyMatch.match("something", true));
60 // Assert empty is valid
61 Q_ASSERT(emptyMatch.isValid());
63 qDebug() << "* Passed ExpressionMatch empty pattern handling";
67 void ExpressionMatchTests::runTestsMatchPhrase()
69 qDebug() << "> Testing ExpressionMatch phrase matching";
71 // Simple phrase, case-insensitive
72 ExpressionMatch simpleMatch =
73 ExpressionMatch("test", ExpressionMatch::MatchMode::MatchPhrase, false);
74 // Simple phrase, case-sensitive
75 ExpressionMatch simpleMatchCS =
76 ExpressionMatch("test", ExpressionMatch::MatchMode::MatchPhrase, true);
77 // Phrase with space, case-insensitive
78 ExpressionMatch simpleMatchSpace =
79 ExpressionMatch(" space ", ExpressionMatch::MatchMode::MatchPhrase, true);
81 QString complexMatchFull(R"(^(?:norm|norm\-space|\!norm\-escaped|\\\!slash\-invert|\\\\double"
82 "|escape\;sep|slash\-end\-split\\|quad\\\\\!noninvert|newline\-split"
83 "|newline\-split\-slash\\|slash\-at\-end\\)$)");
84 ExpressionMatch complexMatch =
85 ExpressionMatch(complexMatchFull, ExpressionMatch::MatchMode::MatchPhrase, false);
88 // Assert valid and not empty
89 Q_ASSERT(!simpleMatch.isEmpty());
90 Q_ASSERT(simpleMatch.isValid());
91 Q_ASSERT(!simpleMatchCS.isEmpty());
92 Q_ASSERT(simpleMatchCS.isValid());
93 Q_ASSERT(!simpleMatchSpace.isEmpty());
94 Q_ASSERT(simpleMatchSpace.isValid());
95 Q_ASSERT(!complexMatch.isEmpty());
96 Q_ASSERT(complexMatch.isValid());
98 // Assert match succeeds
99 Q_ASSERT(simpleMatch.match("test"));
100 Q_ASSERT(simpleMatch.match("other test;"));
101 Q_ASSERT(simpleMatchSpace.match(" space "));
102 // Assert partial match fails
103 Q_ASSERT(!simpleMatch.match("testing"));
104 Q_ASSERT(!simpleMatchSpace.match("space"));
105 // Assert unrelated fails
106 Q_ASSERT(!simpleMatch.match("not above"));
108 // Assert case sensitivity followed
109 Q_ASSERT(!simpleMatch.sourceCaseSensitive());
110 Q_ASSERT(simpleMatch.match("TeSt"));
111 Q_ASSERT(simpleMatchCS.sourceCaseSensitive());
112 Q_ASSERT(!simpleMatchCS.match("TeSt"));
114 // Assert complex phrases are escaped properly
115 Q_ASSERT(complexMatch.match(complexMatchFull));
116 Q_ASSERT(!complexMatch.match("norm"));
118 qDebug() << "* Passed ExpressionMatch phrase matching";
122 void ExpressionMatchTests::runTestsMatchMultiPhrase()
124 qDebug() << "> Testing ExpressionMatch multiple phrase matching";
126 // Simple phrases, case-insensitive
127 ExpressionMatch simpleMatch =
128 ExpressionMatch("test\nOther ", ExpressionMatch::MatchMode::MatchMultiPhrase, false);
129 // Simple phrases, case-sensitive
130 ExpressionMatch simpleMatchCS =
131 ExpressionMatch("test\nOther ", ExpressionMatch::MatchMode::MatchMultiPhrase, true);
133 QString complexMatchFullA(R"(^(?:norm|norm\-space|\!norm\-escaped|\\\!slash\-invert|\\\\double)"
134 R"(|escape\;sep|slash\-end\-split\\|quad\\\\\!noninvert)"
135 R"(|newline\-split|newline\-split\-slash\\|slash\-at\-end\\)$)");
136 QString complexMatchFullB(R"(^(?:invert|invert\-space)$)$)");
137 ExpressionMatch complexMatch =
138 ExpressionMatch(complexMatchFullA + "\n" + complexMatchFullB,
139 ExpressionMatch::MatchMode::MatchMultiPhrase, false);
142 // Assert valid and not empty
143 Q_ASSERT(!simpleMatch.isEmpty());
144 Q_ASSERT(simpleMatch.isValid());
145 Q_ASSERT(!simpleMatchCS.isEmpty());
146 Q_ASSERT(simpleMatchCS.isValid());
147 Q_ASSERT(!complexMatch.isEmpty());
148 Q_ASSERT(complexMatch.isValid());
150 // Assert match succeeds
151 Q_ASSERT(simpleMatch.match("test"));
152 Q_ASSERT(simpleMatch.match("test[suffix]"));
153 Q_ASSERT(simpleMatch.match("other test;"));
154 Q_ASSERT(simpleMatch.match("Other "));
155 Q_ASSERT(simpleMatch.match(".Other !"));
156 // Assert partial match fails
157 Q_ASSERT(!simpleMatch.match("testing"));
158 Q_ASSERT(!simpleMatch.match("Other!"));
159 // Assert unrelated fails
160 Q_ASSERT(!simpleMatch.match("not above"));
162 // Assert case sensitivity followed
163 Q_ASSERT(!simpleMatch.sourceCaseSensitive());
164 Q_ASSERT(simpleMatch.match("TeSt"));
165 Q_ASSERT(simpleMatchCS.sourceCaseSensitive());
166 Q_ASSERT(!simpleMatchCS.match("TeSt"));
168 // Assert complex phrases are escaped properly
169 Q_ASSERT(complexMatch.match(complexMatchFullA));
170 Q_ASSERT(complexMatch.match(complexMatchFullB));
171 Q_ASSERT(!complexMatch.match("norm"));
172 Q_ASSERT(!complexMatch.match("invert"));
174 qDebug() << "* Passed ExpressionMatch multiple phrase matching";
178 void ExpressionMatchTests::runTestsMatchWildcard()
180 qDebug() << "> Testing ExpressionMatch wildcard matching";
182 // Simple wildcard, case-insensitive
183 ExpressionMatch simpleMatch =
184 ExpressionMatch("?test*", ExpressionMatch::MatchMode::MatchWildcard, false);
185 // Simple wildcard, case-sensitive
186 ExpressionMatch simpleMatchCS =
187 ExpressionMatch("?test*", ExpressionMatch::MatchMode::MatchWildcard, true);
188 // Escaped wildcard, case-insensitive
189 ExpressionMatch simpleMatchEscape =
190 ExpressionMatch(R"(\?test\*)", ExpressionMatch::MatchMode::MatchWildcard, false);
191 // Inverted wildcard, case-insensitive
192 ExpressionMatch simpleMatchInvert =
193 ExpressionMatch("!test*", ExpressionMatch::MatchMode::MatchWildcard, false);
194 // Not inverted wildcard, case-insensitive
195 ExpressionMatch simpleMatchNoInvert =
196 ExpressionMatch(R"(\!test*)", ExpressionMatch::MatchMode::MatchWildcard, false);
197 // Not inverted wildcard literal slash, case-insensitive
198 ExpressionMatch simpleMatchNoInvertSlash =
199 ExpressionMatch(R"(\\!test*)", ExpressionMatch::MatchMode::MatchWildcard, false);
201 ExpressionMatch complexMatch =
202 ExpressionMatch(R"(never?gonna*give\*you\?up\\test|y\yeah\\1\\\\2\\\1inval)",
203 ExpressionMatch::MatchMode::MatchWildcard, false);
206 // Assert valid and not empty
207 Q_ASSERT(!simpleMatch.isEmpty());
208 Q_ASSERT(simpleMatch.isValid());
209 Q_ASSERT(!simpleMatchCS.isEmpty());
210 Q_ASSERT(simpleMatchCS.isValid());
211 Q_ASSERT(!simpleMatchEscape.isEmpty());
212 Q_ASSERT(simpleMatchEscape.isValid());
213 Q_ASSERT(!simpleMatchInvert.isEmpty());
214 Q_ASSERT(simpleMatchInvert.isValid());
215 Q_ASSERT(!simpleMatchNoInvert.isEmpty());
216 Q_ASSERT(simpleMatchNoInvert.isValid());
217 Q_ASSERT(!simpleMatchNoInvertSlash.isEmpty());
218 Q_ASSERT(simpleMatchNoInvertSlash.isValid());
219 Q_ASSERT(!complexMatch.isEmpty());
220 Q_ASSERT(complexMatch.isValid());
222 // Assert match succeeds
223 Q_ASSERT(simpleMatch.match("@test"));
224 Q_ASSERT(simpleMatch.match("@testing"));
225 Q_ASSERT(simpleMatch.match("!test"));
226 Q_ASSERT(simpleMatchEscape.match("?test*"));
227 Q_ASSERT(simpleMatchInvert.match("atest"));
228 Q_ASSERT(simpleMatchNoInvert.match("!test"));
229 Q_ASSERT(simpleMatchNoInvertSlash.match(R"(\!test)"));
230 // Assert partial match fails
231 Q_ASSERT(!simpleMatch.match("test"));
232 // Assert unrelated fails
233 Q_ASSERT(!simpleMatch.match("not above"));
234 // Assert escaped wildcard fails
235 Q_ASSERT(!simpleMatchEscape.match("@testing"));
236 Q_ASSERT(!simpleMatchNoInvert.match("test"));
237 Q_ASSERT(!simpleMatchNoInvert.match("anything"));
238 Q_ASSERT(!simpleMatchNoInvertSlash.match("!test"));
239 Q_ASSERT(!simpleMatchNoInvertSlash.match("test"));
240 Q_ASSERT(!simpleMatchNoInvertSlash.match("anything"));
241 // Assert non-inverted fails
242 Q_ASSERT(!simpleMatchInvert.match("testing"));
244 // Assert case sensitivity followed
245 Q_ASSERT(!simpleMatch.sourceCaseSensitive());
246 Q_ASSERT(simpleMatch.match("@TeSt"));
247 Q_ASSERT(simpleMatchCS.sourceCaseSensitive());
248 Q_ASSERT(!simpleMatchCS.match("@TeSt"));
250 // Assert complex match
251 Q_ASSERT(complexMatch.match(R"(neverAgonnaBBBgive*you?up\test|yyeah\1\\2\1inval)"));
252 // Assert complex not literal match
253 Q_ASSERT(!complexMatch.match(R"(never?gonna*give\*you\?up\\test|y\yeah\\1\\\\2\\\1inval)"));
254 // Assert complex unrelated not match
255 Q_ASSERT(!complexMatch.match("other"));
257 qDebug() << "* Passed ExpressionMatch wildcard matching";
261 void ExpressionMatchTests::runTestsMatchMultiWildcard()
263 qDebug() << "> Testing ExpressionMatch multiple wildcard matching";
265 // Simple wildcards, case-insensitive
266 ExpressionMatch simpleMatch =
267 ExpressionMatch("?test*;another?",
268 ExpressionMatch::MatchMode::MatchMultiWildcard, false);
269 // Simple wildcards, case-sensitive
270 ExpressionMatch simpleMatchCS =
271 ExpressionMatch("?test*;another?",
272 ExpressionMatch::MatchMode::MatchMultiWildcard, true);
273 // Escaped wildcards, case-insensitive
274 ExpressionMatch simpleMatchEscape =
275 ExpressionMatch(R"(\?test\*\;*thing\*)",
276 ExpressionMatch::MatchMode::MatchMultiWildcard, false);
277 // Inverted wildcards, case-insensitive
278 ExpressionMatch simpleMatchInvert =
279 ExpressionMatch(R"(test*;!testing)",
280 ExpressionMatch::MatchMode::MatchMultiWildcard, false);
281 // Implicit wildcards, case-insensitive
282 ExpressionMatch simpleMatchImplicit =
283 ExpressionMatch(R"(!testing*)",
284 ExpressionMatch::MatchMode::MatchMultiWildcard, false);
286 QString complexMatchFull(R"(norm;!invert; norm-space ; !invert-space ;;!;\!norm-escaped;)"
287 R"(\\!slash-invert;\\\\double; escape\;sep;slash-end-split\\;)"
288 R"(quad\\\\!noninvert;newline-split)""\n"
289 R"(newline-split-slash\\)""\n"
290 R"(slash-at-end\\)");
291 // Match normal components
292 QStringList complexMatchNormal = {
299 R"(slash-end-split\)",
300 R"(quad\\!noninvert)",
302 R"(newline-split-slash\)",
305 // Match negating components
306 QStringList complexMatchInvert = {
310 ExpressionMatch complexMatch =
311 ExpressionMatch(complexMatchFull, ExpressionMatch::MatchMode::MatchMultiWildcard,
315 // Assert valid and not empty
316 Q_ASSERT(!simpleMatch.isEmpty());
317 Q_ASSERT(simpleMatch.isValid());
318 Q_ASSERT(!simpleMatchCS.isEmpty());
319 Q_ASSERT(simpleMatchCS.isValid());
320 Q_ASSERT(!simpleMatchEscape.isEmpty());
321 Q_ASSERT(simpleMatchEscape.isValid());
322 Q_ASSERT(!simpleMatchInvert.isEmpty());
323 Q_ASSERT(simpleMatchInvert.isValid());
324 Q_ASSERT(!simpleMatchImplicit.isEmpty());
325 Q_ASSERT(simpleMatchImplicit.isValid());
326 Q_ASSERT(!complexMatch.isEmpty());
327 Q_ASSERT(complexMatch.isValid());
329 // Assert match succeeds
330 Q_ASSERT(simpleMatch.match("@test"));
331 Q_ASSERT(simpleMatch.match("@testing"));
332 Q_ASSERT(simpleMatch.match("!test"));
333 Q_ASSERT(simpleMatch.match("anotherA"));
334 Q_ASSERT(simpleMatchEscape.match("?test*;thing*"));
335 Q_ASSERT(simpleMatchEscape.match("?test*;AAAAAthing*"));
336 Q_ASSERT(simpleMatchInvert.match("test"));
337 Q_ASSERT(simpleMatchInvert.match("testing things"));
338 // Assert implicit wildcard succeeds
339 Q_ASSERT(simpleMatchImplicit.match("AAAAAA"));
340 // Assert partial match fails
341 Q_ASSERT(!simpleMatch.match("test"));
342 Q_ASSERT(!simpleMatch.match("another"));
343 Q_ASSERT(!simpleMatch.match("anotherBB"));
344 // Assert unrelated fails
345 Q_ASSERT(!simpleMatch.match("not above"));
346 // Assert escaped wildcard fails
347 Q_ASSERT(!simpleMatchEscape.match("@testing"));
348 // Assert inverted match fails
349 Q_ASSERT(!simpleMatchInvert.match("testing"));
350 Q_ASSERT(!simpleMatchImplicit.match("testing"));
352 // Assert case sensitivity followed
353 Q_ASSERT(!simpleMatch.sourceCaseSensitive());
354 Q_ASSERT(simpleMatch.match("@TeSt"));
355 Q_ASSERT(simpleMatchCS.sourceCaseSensitive());
356 Q_ASSERT(!simpleMatchCS.match("@TeSt"));
358 // Assert complex match
359 for (auto&& normMatch : complexMatchNormal) {
360 // Each normal component should match
361 Q_ASSERT(complexMatch.match(normMatch));
364 for (auto&& invertMatch : complexMatchInvert) {
365 // Each invert component should not match
366 Q_ASSERT(!complexMatch.match(invertMatch));
369 // Assert complex not literal match
370 Q_ASSERT(!complexMatch.match(complexMatchFull));
371 // Assert complex unrelated not match
372 Q_ASSERT(!complexMatch.match("other"));
374 qDebug() << "* Passed ExpressionMatch multiple wildcard matching";
378 void ExpressionMatchTests::runTestsMatchRegEx()
380 qDebug() << "> Testing ExpressionMatch regex matching";
382 // Simple regex, case-insensitive
383 ExpressionMatch simpleMatch =
384 ExpressionMatch(R"(simple.\*escape-match.*)",
385 ExpressionMatch::MatchMode::MatchRegEx, false);
386 // Simple regex, case-sensitive
387 ExpressionMatch simpleMatchCS =
388 ExpressionMatch(R"(simple.\*escape-match.*)",
389 ExpressionMatch::MatchMode::MatchRegEx, true);
390 // Inverted regex, case-insensitive
391 ExpressionMatch simpleMatchInvert =
392 ExpressionMatch(R"(!invert.\*escape-match.*)",
393 ExpressionMatch::MatchMode::MatchRegEx, false);
394 // Non-inverted regex, case-insensitive
395 ExpressionMatch simpleMatchNoInvert =
396 ExpressionMatch(R"(\!simple.\*escape-match.*)",
397 ExpressionMatch::MatchMode::MatchRegEx, false);
398 // Non-inverted regex literal slash, case-insensitive
399 ExpressionMatch simpleMatchNoInvertSlash =
400 ExpressionMatch(R"(\\!simple.\*escape-match.*)",
401 ExpressionMatch::MatchMode::MatchRegEx, false);
403 // Assert valid and not empty
404 Q_ASSERT(!simpleMatch.isEmpty());
405 Q_ASSERT(simpleMatch.isValid());
406 Q_ASSERT(!simpleMatchCS.isEmpty());
407 Q_ASSERT(simpleMatchCS.isValid());
408 Q_ASSERT(!simpleMatchInvert.isEmpty());
409 Q_ASSERT(simpleMatchInvert.isValid());
410 Q_ASSERT(!simpleMatchNoInvert.isEmpty());
411 Q_ASSERT(simpleMatchNoInvert.isValid());
412 Q_ASSERT(!simpleMatchNoInvertSlash.isEmpty());
413 Q_ASSERT(simpleMatchNoInvertSlash.isValid());
415 // Assert match succeeds
416 Q_ASSERT(simpleMatch.match("simpleA*escape-match"));
417 Q_ASSERT(simpleMatch.match("simpleA*escape-matchBBBB"));
418 Q_ASSERT(simpleMatchInvert.match("not above"));
419 Q_ASSERT(simpleMatchNoInvert.match("!simpleA*escape-matchBBBB"));
420 Q_ASSERT(simpleMatchNoInvertSlash.match(R"(\!simpleA*escape-matchBBBB)"));
421 // Assert partial match fails
422 Q_ASSERT(!simpleMatch.match("simpleA*escape-mat"));
423 Q_ASSERT(!simpleMatch.match("simple*escape-match"));
424 // Assert unrelated fails
425 Q_ASSERT(!simpleMatch.match("not above"));
426 // Assert escaped wildcard fails
427 Q_ASSERT(!simpleMatch.match("simpleABBBBescape-matchBBBB"));
428 // Assert inverted fails
429 Q_ASSERT(!simpleMatchInvert.match("invertA*escape-match"));
430 Q_ASSERT(!simpleMatchInvert.match("invertA*escape-matchBBBB"));
431 Q_ASSERT(!simpleMatchNoInvert.match("simpleA*escape-matchBBBB"));
432 Q_ASSERT(!simpleMatchNoInvert.match("anything"));
433 Q_ASSERT(!simpleMatchNoInvertSlash.match("!simpleA*escape-matchBBBB"));
434 Q_ASSERT(!simpleMatchNoInvertSlash.match("anything"));
436 // Assert case sensitivity followed
437 Q_ASSERT(!simpleMatch.sourceCaseSensitive());
438 Q_ASSERT(simpleMatch.match("SiMpLEA*escape-MATCH"));
439 Q_ASSERT(simpleMatchCS.sourceCaseSensitive());
440 Q_ASSERT(!simpleMatchCS.match("SiMpLEA*escape-MATCH"));
442 qDebug() << "* Passed ExpressionMatch regex matching";
446 void ExpressionMatchTests::runTestsTrimMultiWildcardWhitespace()
448 qDebug() << "> Testing ExpressionMatch multiple wildcard whitespace trimming";
451 static constexpr uint PATTERN_SOURCE = 0;
452 static constexpr uint PATTERN_RESULT = 1;
453 std::vector<std::vector<QString>> patterns = {
457 // Simple semicolon cleanup
458 {"simple1 ;simple2; simple3 ",
459 "simple1; simple2; simple3"},
460 // Simple newline cleanup
461 {"simple1 \nsimple2\n simple3 ",
462 "simple1\nsimple2\nsimple3"},
464 {R"(norm; norm-space ; newline-space )""\n"
465 R"( ;escape \; sep ; slash-end-split\\; quad\\\\norm; newline-split-slash\\)""\n"
467 R"(norm; norm-space; newline-space)""\n"
468 R"(escape \; sep; slash-end-split\\; quad\\\\norm; newline-split-slash\\)""\n"
472 // Check every source string...
474 for (auto&& patternPair : patterns) {
475 // Make sure data is valid
476 Q_ASSERT(patternPair.size() == 2);
477 // Run transformation
478 result = ExpressionMatch::trimMultiWildcardWhitespace(patternPair[PATTERN_SOURCE]);
479 // Assert that source trims into expected pattern
480 Q_ASSERT(result == patternPair[PATTERN_RESULT]);
481 // Assert that re-trimming expected pattern gives the same result
482 Q_ASSERT(result == ExpressionMatch::trimMultiWildcardWhitespace(result));
485 qDebug() << "* Passed ExpressionMatch multiple wildcard whitespace trimming";