uisupport: Provide helpers for dealing with widget changes
[quassel.git] / src / common / expressionmatchtests.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2018 by the Quassel Project                        *
3  *   devel@quassel-irc.org                                                 *
4  *                                                                         *
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.                                           *
9  *                                                                         *
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.                          *
14  *                                                                         *
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  ***************************************************************************/
20
21 #include "expressionmatchtests.h"
22
23 #include <QDebug>
24 #include <QString>
25 #include <QStringList>
26 #include <vector>
27
28 #include "expressionmatch.h"
29
30 void ExpressionMatchTests::runTests()
31 {
32     // Run all tests
33     qDebug() << "Running all ExpressionMatch tests";
34     runTestsEmptyPattern();
35     runTestsMatchPhrase();
36     runTestsMatchMultiPhrase();
37     runTestsMatchWildcard();
38     runTestsMatchMultiWildcard();
39     runTestsMatchRegEx();
40     runTestsTrimMultiWildcardWhitespace();
41
42     qDebug() << "Passed all ExpressionMatch tests";
43 }
44
45
46 void ExpressionMatchTests::runTestsEmptyPattern()
47 {
48     qDebug() << "> Testing ExpressionMatch empty pattern handling";
49
50     // Empty pattern
51     ExpressionMatch emptyMatch =
52             ExpressionMatch("", ExpressionMatch::MatchMode::MatchPhrase, false);
53
54     // Assert empty
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());
62
63     qDebug() << "* Passed ExpressionMatch empty pattern handling";
64 }
65
66
67 void ExpressionMatchTests::runTestsMatchPhrase()
68 {
69     qDebug() << "> Testing ExpressionMatch phrase matching";
70
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);
80     // Complex phrase
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);
86
87
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());
97
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"));
107
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"));
113
114     // Assert complex phrases are escaped properly
115     Q_ASSERT(complexMatch.match(complexMatchFull));
116     Q_ASSERT(!complexMatch.match("norm"));
117
118     qDebug() << "* Passed ExpressionMatch phrase matching";
119 }
120
121
122 void ExpressionMatchTests::runTestsMatchMultiPhrase()
123 {
124     qDebug() << "> Testing ExpressionMatch multiple phrase matching";
125
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);
132     // Complex phrases
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);
140
141
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());
149
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"));
161
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"));
167
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"));
173
174     qDebug() << "* Passed ExpressionMatch multiple phrase matching";
175 }
176
177
178 void ExpressionMatchTests::runTestsMatchWildcard()
179 {
180     qDebug() << "> Testing ExpressionMatch wildcard matching";
181
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);
200     // Complex wildcard
201     ExpressionMatch complexMatch =
202             ExpressionMatch(R"(never?gonna*give\*you\?up\\test|y\yeah\\1\\\\2\\\1inval)",
203                             ExpressionMatch::MatchMode::MatchWildcard, false);
204
205
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());
221
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"));
243
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"));
249
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"));
256
257     qDebug() << "* Passed ExpressionMatch wildcard matching";
258 }
259
260
261 void ExpressionMatchTests::runTestsMatchMultiWildcard()
262 {
263     qDebug() << "> Testing ExpressionMatch multiple wildcard matching";
264
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);
285     // Complex wildcard
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 = {
293         R"(norm)",
294         R"(norm-space)",
295         R"(!norm-escaped)",
296         R"(\!slash-invert)",
297         R"(\\double)",
298         R"(escape;sep)",
299         R"(slash-end-split\)",
300         R"(quad\\!noninvert)",
301         R"(newline-split)",
302         R"(newline-split-slash\)",
303         R"(slash-at-end\)"
304     };
305     // Match negating components
306     QStringList complexMatchInvert = {
307         R"(invert)",
308         R"(invert-space)"
309     };
310     ExpressionMatch complexMatch =
311             ExpressionMatch(complexMatchFull, ExpressionMatch::MatchMode::MatchMultiWildcard,
312                             false);
313
314
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());
328
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"));
351
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"));
357
358     // Assert complex match
359     for (auto&& normMatch : complexMatchNormal) {
360         // Each normal component should match
361         Q_ASSERT(complexMatch.match(normMatch));
362     }
363
364     for (auto&& invertMatch : complexMatchInvert) {
365         // Each invert component should not match
366         Q_ASSERT(!complexMatch.match(invertMatch));
367     }
368
369     // Assert complex not literal match
370     Q_ASSERT(!complexMatch.match(complexMatchFull));
371     // Assert complex unrelated not match
372     Q_ASSERT(!complexMatch.match("other"));
373
374     qDebug() << "* Passed ExpressionMatch multiple wildcard matching";
375 }
376
377
378 void ExpressionMatchTests::runTestsMatchRegEx()
379 {
380     qDebug() << "> Testing ExpressionMatch regex matching";
381
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);
402
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());
414
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"));
435
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"));
441
442     qDebug() << "* Passed ExpressionMatch regex matching";
443 }
444
445
446 void ExpressionMatchTests::runTestsTrimMultiWildcardWhitespace()
447 {
448     qDebug() << "> Testing ExpressionMatch multiple wildcard whitespace trimming";
449
450     // Patterns
451     static constexpr uint PATTERN_SOURCE = 0;
452     static constexpr uint PATTERN_RESULT = 1;
453     std::vector<std::vector<QString>> patterns = {
454         // Literal
455         {"literal",
456          "literal"},
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"},
463         // Complex cleanup
464         {R"(norm; norm-space ; newline-space )""\n"
465          R"( ;escape \; sep ; slash-end-split\\; quad\\\\norm; newline-split-slash\\)""\n"
466          R"(slash-at-end\\)",
467          R"(norm; norm-space; newline-space)""\n"
468          R"(escape \; sep; slash-end-split\\; quad\\\\norm; newline-split-slash\\)""\n"
469          R"(slash-at-end\\)"}
470     };
471
472     // Check every source string...
473     QString result;
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));
483     }
484
485     qDebug() << "* Passed ExpressionMatch multiple wildcard whitespace trimming";
486 }