Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/modules/regex/tests/test_regex.h
10278 views
1
/**************************************************************************/
2
/* test_regex.h */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10
/* */
11
/* Permission is hereby granted, free of charge, to any person obtaining */
12
/* a copy of this software and associated documentation files (the */
13
/* "Software"), to deal in the Software without restriction, including */
14
/* without limitation the rights to use, copy, modify, merge, publish, */
15
/* distribute, sublicense, and/or sell copies of the Software, and to */
16
/* permit persons to whom the Software is furnished to do so, subject to */
17
/* the following conditions: */
18
/* */
19
/* The above copyright notice and this permission notice shall be */
20
/* included in all copies or substantial portions of the Software. */
21
/* */
22
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29
/**************************************************************************/
30
31
#pragma once
32
33
#include "../regex.h"
34
35
#include "core/string/ustring.h"
36
37
#include "tests/test_macros.h"
38
39
namespace TestRegEx {
40
41
TEST_CASE("[RegEx] Initialization") {
42
const String pattern = "(?<vowel>[aeiou])";
43
44
RegEx re1(pattern);
45
CHECK(re1.is_valid());
46
CHECK(re1.get_pattern() == pattern);
47
CHECK(re1.get_group_count() == 1);
48
49
PackedStringArray names = re1.get_names();
50
CHECK(names.size() == 1);
51
CHECK(names[0] == "vowel");
52
53
RegEx re2;
54
CHECK(re2.is_valid() == false);
55
CHECK(re2.compile(pattern) == OK);
56
CHECK(re2.is_valid());
57
58
CHECK(re1.get_pattern() == re2.get_pattern());
59
CHECK(re1.get_group_count() == re2.get_group_count());
60
61
names = re2.get_names();
62
CHECK(names.size() == 1);
63
CHECK(names[0] == "vowel");
64
}
65
66
TEST_CASE("[RegEx] Clearing") {
67
RegEx re("Godot");
68
REQUIRE(re.is_valid());
69
re.clear();
70
CHECK(re.is_valid() == false);
71
}
72
73
TEST_CASE("[RegEx] Searching") {
74
const String s = "Searching";
75
const String vowels = "[aeiou]{1,2}";
76
const String numerics = "\\d";
77
78
RegEx re(vowels);
79
REQUIRE(re.is_valid());
80
81
Ref<RegExMatch> match = re.search(s);
82
REQUIRE(match.is_valid());
83
CHECK(match->get_string(0) == "ea");
84
85
match = re.search(s, 1, 2);
86
REQUIRE(match.is_valid());
87
CHECK(match->get_string(0) == "e");
88
match = re.search(s, 2, 4);
89
REQUIRE(match.is_valid());
90
CHECK(match->get_string(0) == "a");
91
match = re.search(s, 3, 5);
92
CHECK(match.is_null());
93
match = re.search(s, 6, 2);
94
CHECK(match.is_null());
95
96
const Array all_results = re.search_all(s);
97
CHECK(all_results.size() == 2);
98
match = all_results[0];
99
REQUIRE(match.is_valid());
100
CHECK(match->get_string(0) == "ea");
101
match = all_results[1];
102
REQUIRE(match.is_valid());
103
CHECK(match->get_string(0) == "i");
104
105
CHECK(re.compile(numerics) == OK);
106
CHECK(re.is_valid());
107
CHECK(re.search(s).is_null());
108
CHECK(re.search_all(s).size() == 0);
109
}
110
111
TEST_CASE("[RegEx] Substitution") {
112
const String s1 = "Double all the vowels.";
113
114
RegEx re1("(?<vowel>[aeiou])");
115
REQUIRE(re1.is_valid());
116
CHECK(re1.sub(s1, "$0$vowel", true) == "Doouublee aall thee vooweels.");
117
118
const String s2 = "Substitution with group.";
119
120
RegEx re2("Substitution (.+)");
121
REQUIRE(re2.is_valid());
122
CHECK(re2.sub(s2, "Test ${1}") == "Test with group.");
123
124
const String s3 = "Useless substitution";
125
126
RegEx re3("Anything");
127
REQUIRE(re3.is_valid());
128
CHECK(re3.sub(s3, "Something") == "Useless substitution");
129
130
const String s4 = "acacac";
131
132
RegEx re4("(a)(b){0}(c)");
133
REQUIRE(re4.is_valid());
134
CHECK(re4.sub(s4, "${1}.${3}.", true) == "a.c.a.c.a.c.");
135
136
const String s5 = "aaaa";
137
138
RegEx re5("a");
139
REQUIRE(re5.is_valid());
140
CHECK(re5.sub(s5, "b", true, 0, 2) == "bbaa");
141
CHECK(re5.sub(s5, "b", true, 1, 3) == "abba");
142
CHECK(re5.sub(s5, "b", true, 0, 0) == "aaaa");
143
CHECK(re5.sub(s5, "b", true, 1, 1) == "aaaa");
144
CHECK(re5.sub(s5, "cc", true, 0, 2) == "ccccaa");
145
CHECK(re5.sub(s5, "cc", true, 1, 3) == "acccca");
146
CHECK(re5.sub(s5, "", true, 0, 2) == "aa");
147
148
const String s6 = "property get_property set_property";
149
150
RegEx re6("(get_|set_)?property");
151
REQUIRE(re6.is_valid());
152
CHECK(re6.sub(s6, "$1new_property", true) == "new_property get_new_property set_new_property");
153
ERR_PRINT_OFF;
154
CHECK(re6.sub(s6, "$5new_property", true) == "new_property new_property new_property");
155
ERR_PRINT_ON;
156
}
157
158
TEST_CASE("[RegEx] Substitution with empty input and/or replacement") {
159
const String s1 = "";
160
const String s2 = "gogogo";
161
162
RegEx re1("");
163
REQUIRE(re1.is_valid());
164
CHECK(re1.sub(s1, "") == "");
165
CHECK(re1.sub(s1, "a") == "a");
166
CHECK(re1.sub(s2, "") == "gogogo");
167
168
RegEx re2("go");
169
REQUIRE(re2.is_valid());
170
CHECK(re2.sub(s2, "") == "gogo");
171
CHECK(re2.sub(s2, "", true) == "");
172
}
173
174
TEST_CASE("[RegEx] Uninitialized use") {
175
const String s = "Godot";
176
177
RegEx re;
178
ERR_PRINT_OFF;
179
CHECK(re.search(s).is_null());
180
CHECK(re.search_all(s).size() == 0);
181
CHECK(re.sub(s, "") == "");
182
CHECK(re.get_group_count() == 0);
183
CHECK(re.get_names().size() == 0);
184
ERR_PRINT_ON
185
}
186
187
TEST_CASE("[RegEx] Empty pattern") {
188
const String s = "Godot";
189
190
RegEx re;
191
CHECK(re.compile("") == OK);
192
CHECK(re.is_valid());
193
}
194
195
TEST_CASE("[RegEx] Complex Grouping") {
196
const String test = "https://docs.godotengine.org/en/latest/contributing/";
197
198
// Ignored protocol in grouping.
199
RegEx re("^(?:https?://)([a-zA-Z]{2,4})\\.([a-zA-Z][a-zA-Z0-9_\\-]{2,64})\\.([a-zA-Z]{2,4})");
200
REQUIRE(re.is_valid());
201
Ref<RegExMatch> expr = re.search(test);
202
203
CHECK(expr->get_group_count() == 3);
204
205
CHECK(expr->get_string(0) == "https://docs.godotengine.org");
206
207
CHECK(expr->get_string(1) == "docs");
208
CHECK(expr->get_string(2) == "godotengine");
209
CHECK(expr->get_string(3) == "org");
210
}
211
212
TEST_CASE("[RegEx] Number Expression") {
213
const String test = "(2.5e-3 + 35 + 46) / 2.8e0 = 28.9294642857";
214
215
// Not an exact regex for number but a good test.
216
RegEx re("([+-]?\\d+)(\\.\\d+([eE][+-]?\\d+)?)?");
217
REQUIRE(re.is_valid());
218
Array number_match = re.search_all(test);
219
220
CHECK(number_match.size() == 5);
221
222
Ref<RegExMatch> number = number_match[0];
223
CHECK(number->get_string(0) == "2.5e-3");
224
CHECK(number->get_string(1) == "2");
225
number = number_match[1];
226
CHECK(number->get_string(0) == "35");
227
number = number_match[2];
228
CHECK(number->get_string(0) == "46");
229
number = number_match[3];
230
CHECK(number->get_string(0) == "2.8e0");
231
number = number_match[4];
232
CHECK(number->get_string(0) == "28.9294642857");
233
CHECK(number->get_string(1) == "28");
234
CHECK(number->get_string(2) == ".9294642857");
235
}
236
237
TEST_CASE("[RegEx] Invalid end position") {
238
const String s = "Godot";
239
240
RegEx re("o");
241
REQUIRE(re.is_valid());
242
Ref<RegExMatch> match = re.search(s, 0, 10);
243
CHECK(match->get_string(0) == "o");
244
245
const Array all_results = re.search_all(s, 0, 10);
246
CHECK(all_results.size() == 2);
247
match = all_results[0];
248
REQUIRE(match.is_valid());
249
CHECK(match->get_string(0) == String("o"));
250
match = all_results[1];
251
REQUIRE(match.is_valid());
252
CHECK(match->get_string(0) == String("o"));
253
254
CHECK(re.sub(s, "", true, 0, 10) == "Gdt");
255
}
256
257
TEST_CASE("[RegEx] Get match string list") {
258
const String s = "Godot Engine";
259
260
RegEx re("(Go)(dot)");
261
Ref<RegExMatch> match = re.search(s);
262
REQUIRE(match.is_valid());
263
PackedStringArray result;
264
result.append("Godot");
265
result.append("Go");
266
result.append("dot");
267
CHECK(match->get_strings() == result);
268
}
269
270
TEST_CASE("[RegEx] Match start and end positions") {
271
const String s = "Whole pattern";
272
273
RegEx re1("pattern");
274
REQUIRE(re1.is_valid());
275
Ref<RegExMatch> match = re1.search(s);
276
REQUIRE(match.is_valid());
277
CHECK(match->get_start(0) == 6);
278
CHECK(match->get_end(0) == 13);
279
280
RegEx re2("(?<vowel>[aeiou])");
281
REQUIRE(re2.is_valid());
282
match = re2.search(s);
283
REQUIRE(match.is_valid());
284
CHECK(match->get_start("vowel") == 2);
285
CHECK(match->get_end("vowel") == 3);
286
}
287
288
TEST_CASE("[RegEx] Asterisk search all") {
289
const String s = "Godot Engine";
290
291
RegEx re("o*");
292
REQUIRE(re.is_valid());
293
Ref<RegExMatch> match;
294
const Array all_results = re.search_all(s);
295
CHECK(all_results.size() == 13);
296
297
match = all_results[0];
298
CHECK(match->get_string(0) == "");
299
match = all_results[1];
300
CHECK(match->get_string(0) == "o");
301
match = all_results[2];
302
CHECK(match->get_string(0) == "");
303
match = all_results[3];
304
CHECK(match->get_string(0) == "o");
305
306
for (int i = 4; i < 13; i++) {
307
match = all_results[i];
308
CHECK(match->get_string(0) == "");
309
}
310
}
311
312
TEST_CASE("[RegEx] Simple lookahead") {
313
const String s = "Godot Engine";
314
315
RegEx re("o(?=t)");
316
REQUIRE(re.is_valid());
317
Ref<RegExMatch> match = re.search(s);
318
REQUIRE(match.is_valid());
319
CHECK(match->get_start(0) == 3);
320
CHECK(match->get_end(0) == 4);
321
}
322
323
TEST_CASE("[RegEx] Lookahead groups empty matches") {
324
const String s = "12";
325
326
RegEx re("(?=(\\d+))");
327
REQUIRE(re.is_valid());
328
Ref<RegExMatch> match = re.search(s);
329
CHECK(match->get_string(0) == "");
330
CHECK(match->get_string(1) == "12");
331
332
const Array all_results = re.search_all(s);
333
CHECK(all_results.size() == 2);
334
335
match = all_results[0];
336
REQUIRE(match.is_valid());
337
CHECK(match->get_string(0) == String(""));
338
CHECK(match->get_string(1) == String("12"));
339
340
match = all_results[1];
341
REQUIRE(match.is_valid());
342
CHECK(match->get_string(0) == String(""));
343
CHECK(match->get_string(1) == String("2"));
344
}
345
346
TEST_CASE("[RegEx] Simple lookbehind") {
347
const String s = "Godot Engine";
348
349
RegEx re("(?<=d)o");
350
REQUIRE(re.is_valid());
351
Ref<RegExMatch> match = re.search(s);
352
REQUIRE(match.is_valid());
353
CHECK(match->get_start(0) == 3);
354
CHECK(match->get_end(0) == 4);
355
}
356
357
TEST_CASE("[RegEx] Simple lookbehind search all") {
358
const String s = "ababbaabab";
359
360
RegEx re("(?<=a)b");
361
REQUIRE(re.is_valid());
362
const Array all_results = re.search_all(s);
363
CHECK(all_results.size() == 4);
364
365
Ref<RegExMatch> match = all_results[0];
366
REQUIRE(match.is_valid());
367
CHECK(match->get_start(0) == 1);
368
CHECK(match->get_end(0) == 2);
369
370
match = all_results[1];
371
REQUIRE(match.is_valid());
372
CHECK(match->get_start(0) == 3);
373
CHECK(match->get_end(0) == 4);
374
375
match = all_results[2];
376
REQUIRE(match.is_valid());
377
CHECK(match->get_start(0) == 7);
378
CHECK(match->get_end(0) == 8);
379
380
match = all_results[3];
381
REQUIRE(match.is_valid());
382
CHECK(match->get_start(0) == 9);
383
CHECK(match->get_end(0) == 10);
384
}
385
386
TEST_CASE("[RegEx] Lookbehind groups empty matches") {
387
const String s = "abaaabab";
388
389
RegEx re("(?<=(b))");
390
REQUIRE(re.is_valid());
391
Ref<RegExMatch> match;
392
393
const Array all_results = re.search_all(s);
394
CHECK(all_results.size() == 3);
395
396
match = all_results[0];
397
REQUIRE(match.is_valid());
398
CHECK(match->get_start(0) == 2);
399
CHECK(match->get_end(0) == 2);
400
CHECK(match->get_start(1) == 1);
401
CHECK(match->get_end(1) == 2);
402
CHECK(match->get_string(0) == String(""));
403
CHECK(match->get_string(1) == String("b"));
404
405
match = all_results[1];
406
REQUIRE(match.is_valid());
407
CHECK(match->get_start(0) == 6);
408
CHECK(match->get_end(0) == 6);
409
CHECK(match->get_start(1) == 5);
410
CHECK(match->get_end(1) == 6);
411
CHECK(match->get_string(0) == String(""));
412
CHECK(match->get_string(1) == String("b"));
413
414
match = all_results[2];
415
REQUIRE(match.is_valid());
416
CHECK(match->get_start(0) == 8);
417
CHECK(match->get_end(0) == 8);
418
CHECK(match->get_start(1) == 7);
419
CHECK(match->get_end(1) == 8);
420
CHECK(match->get_string(0) == String(""));
421
CHECK(match->get_string(1) == String("b"));
422
}
423
424
} // namespace TestRegEx
425
426