Blob


1 /* See LICENSE file for copyright and license details. */
2 #include <stdbool.h>
3 #include <stdint.h>
4 #include <stdio.h>
5 #include <string.h>
7 #include "../grapheme.h"
8 #include "util.h"
10 struct unit_test_is_case_utf8 {
11 const char *description;
12 struct {
13 const char *src;
14 size_t srclen;
15 } input;
16 struct {
17 bool ret;
18 size_t caselen;
19 } output;
20 };
22 struct unit_test_to_case_utf8 {
23 const char *description;
24 struct {
25 const char *src;
26 size_t srclen;
27 size_t destlen;
28 } input;
29 struct {
30 const char *dest;
31 size_t ret;
32 } output;
33 };
35 static const struct unit_test_is_case_utf8 is_lowercase_utf8[] = {
36 {
37 .description = "empty input",
38 .input = { "", 0 },
39 .output = { true, 0 },
40 },
41 {
42 .description = "one character, violation",
43 .input = { "A", 1 },
44 .output = { false, 0 },
45 },
46 {
47 .description = "one character, confirmation",
48 .input = { "\xC3\x9F", 2 },
49 .output = { true, 2 },
50 },
51 {
52 .description = "one character, violation, NUL-terminated",
53 .input = { "A", SIZE_MAX },
54 .output = { false, 0 },
55 },
56 {
57 .description = "one character, confirmation, NUL-terminated",
58 .input = { "\xC3\x9F", SIZE_MAX },
59 .output = { true, 2 },
60 },
61 {
62 .description = "one word, violation",
63 .input = { "Hello", 5 },
64 .output = { false, 0 },
65 },
66 {
67 .description = "one word, partial confirmation",
68 .input = { "gru" "\xC3\x9F" "fOrmel", 11 },
69 .output = { false, 6 },
70 },
71 {
72 .description = "one word, full confirmation",
73 .input = { "gru" "\xC3\x9F" "formel", 11 },
74 .output = { true, 11 },
75 },
76 {
77 .description = "one word, violation, NUL-terminated",
78 .input = { "Hello", SIZE_MAX },
79 .output = { false, 0 },
80 },
81 {
82 .description = "one word, partial confirmation, NUL-terminated",
83 .input = { "gru" "\xC3\x9F" "fOrmel", SIZE_MAX },
84 .output = { false, 6 },
85 },
86 {
87 .description = "one word, full confirmation, NUL-terminated",
88 .input = { "gru" "\xC3\x9F" "formel", SIZE_MAX },
89 .output = { true, 11 },
90 },
91 };
93 static const struct unit_test_is_case_utf8 is_uppercase_utf8[] = {
94 {
95 .description = "empty input",
96 .input = { "", 0 },
97 .output = { true, 0 },
98 },
99 {
100 .description = "one character, violation",
101 .input = { "\xC3\x9F", 2 },
102 .output = { false, 0 },
103 },
105 .description = "one character, confirmation",
106 .input = { "A", 1 },
107 .output = { true, 1 },
108 },
110 .description = "one character, violation, NUL-terminated",
111 .input = { "\xC3\x9F", SIZE_MAX },
112 .output = { false, 0 },
113 },
115 .description = "one character, confirmation, NUL-terminated",
116 .input = { "A", SIZE_MAX },
117 .output = { true, 1 },
118 },
120 .description = "one word, violation",
121 .input = { "hello", 5 },
122 .output = { false, 0 },
123 },
125 .description = "one word, partial confirmation",
126 .input = { "GRU" "\xC3\x9F" "formel", 11 },
127 .output = { false, 3 },
128 },
130 .description = "one word, full confirmation",
131 .input = { "HELLO", 5 },
132 .output = { true, 5 },
133 },
135 .description = "one word, violation, NUL-terminated",
136 .input = { "hello", SIZE_MAX },
137 .output = { false, 0 },
138 },
140 .description = "one word, partial confirmation, NUL-terminated",
141 .input = { "GRU" "\xC3\x9F" "formel", SIZE_MAX },
142 .output = { false, 3 },
143 },
145 .description = "one word, full confirmation, NUL-terminated",
146 .input = { "HELLO", SIZE_MAX },
147 .output = { true, 5 },
148 },
149 };
151 static const struct unit_test_is_case_utf8 is_titlecase_utf8[] = {
153 .description = "empty input",
154 .input = { "", 0 },
155 .output = { true, 0 },
156 },
158 .description = "one character, violation",
159 .input = { "\xC3\x9F", 2 },
160 .output = { false, 0 },
161 },
163 .description = "one character, confirmation",
164 .input = { "A", 1 },
165 .output = { true, 1 },
166 },
168 .description = "one character, violation, NUL-terminated",
169 .input = { "\xC3\x9F", SIZE_MAX },
170 .output = { false, 0 },
171 },
173 .description = "one character, confirmation, NUL-terminated",
174 .input = { "A", SIZE_MAX },
175 .output = { true, 1 },
176 },
178 .description = "one word, violation",
179 .input = { "hello", 5 },
180 .output = { false, 0 },
181 },
183 .description = "one word, partial confirmation",
184 .input = { "Gru" "\xC3\x9F" "fOrmel", 11 },
185 .output = { false, 6 },
186 },
188 .description = "one word, full confirmation",
189 .input = { "Gru" "\xC3\x9F" "formel", 11 },
190 .output = { true, 11 },
191 },
193 .description = "one word, violation, NUL-terminated",
194 .input = { "hello", SIZE_MAX },
195 .output = { false, 0 },
196 },
198 .description = "one word, partial confirmation, NUL-terminated",
199 .input = { "Gru" "\xC3\x9F" "fOrmel", SIZE_MAX },
200 .output = { false, 6 },
201 },
203 .description = "one word, full confirmation, NUL-terminated",
204 .input = { "Gru" "\xC3\x9F" "formel", SIZE_MAX },
205 .output = { true, 11 },
206 },
208 .description = "multiple words, partial confirmation",
209 .input = { "Hello Gru" "\xC3\x9F" "fOrmel!", 18 },
210 .output = { false, 12 },
211 },
213 .description = "multiple words, full confirmation",
214 .input = { "Hello Gru" "\xC3\x9F" "formel!", 18 },
215 .output = { true, 18 },
216 },
218 .description = "multiple words, partial confirmation, NUL-terminated",
219 .input = { "Hello Gru" "\xC3\x9F" "fOrmel!", SIZE_MAX },
220 .output = { false, 12 },
221 },
223 .description = "multiple words, full confirmation, NUL-terminated",
224 .input = { "Hello Gru" "\xC3\x9F" "formel!", SIZE_MAX },
225 .output = { true, 18 },
226 },
227 };
229 static const struct unit_test_to_case_utf8 to_lowercase_utf8[] = {
231 .description = "empty input",
232 .input = { "", 0, 10 },
233 .output = { "", 0 },
234 },
236 .description = "empty output",
237 .input = { "hello", 5, 0 },
238 .output = { "", 5 },
239 },
241 .description = "one character, conversion",
242 .input = { "A", 1, 10 },
243 .output = { "a", 1 },
244 },
246 .description = "one character, no conversion",
247 .input = { "\xC3\x9F", 2, 10 },
248 .output = { "\xC3\x9F", 2 },
249 },
251 .description = "one character, conversion, truncation",
252 .input = { "A", 1, 0 },
253 .output = { "", 1 },
254 },
256 .description = "one character, conversion, NUL-terminated",
257 .input = { "A", SIZE_MAX, 10 },
258 .output = { "a", 1 },
259 },
261 .description = "one character, no conversion, NUL-terminated",
262 .input = { "\xC3\x9F", SIZE_MAX, 10 },
263 .output = { "\xC3\x9F", 2 },
264 },
266 .description = "one character, conversion, NUL-terminated, truncation",
267 .input = { "A", SIZE_MAX, 0 },
268 .output = { "", 1 },
269 },
271 .description = "one word, conversion",
272 .input = { "wOrD", 4, 10 },
273 .output = { "word", 4 },
274 },
276 .description = "one word, no conversion",
277 .input = { "word", 4, 10 },
278 .output = { "word", 4 },
279 },
281 .description = "one word, conversion, truncation",
282 .input = { "wOrD", 4, 3 },
283 .output = { "wo", 4 },
284 },
286 .description = "one word, conversion, NUL-terminated",
287 .input = { "wOrD", SIZE_MAX, 10 },
288 .output = { "word", 4 },
289 },
291 .description = "one word, no conversion, NUL-terminated",
292 .input = { "word", SIZE_MAX, 10 },
293 .output = { "word", 4 },
294 },
296 .description = "one word, conversion, NUL-terminated, truncation",
297 .input = { "wOrD", SIZE_MAX, 3 },
298 .output = { "wo", 4 },
299 },
300 };
302 static const struct unit_test_to_case_utf8 to_uppercase_utf8[] = {
304 .description = "empty input",
305 .input = { "", 0, 10 },
306 .output = { "", 0 },
307 },
309 .description = "empty output",
310 .input = { "hello", 5, 0 },
311 .output = { "", 5 },
312 },
314 .description = "one character, conversion",
315 .input = { "\xC3\x9F", 2, 10 },
316 .output = { "SS", 2 },
317 },
319 .description = "one character, no conversion",
320 .input = { "A", 1, 10 },
321 .output = { "A", 1 },
322 },
324 .description = "one character, conversion, truncation",
325 .input = { "\xC3\x9F", 2, 0 },
326 .output = { "", 2 },
327 },
329 .description = "one character, conversion, NUL-terminated",
330 .input = { "\xC3\x9F", SIZE_MAX, 10 },
331 .output = { "SS", 2 },
332 },
334 .description = "one character, no conversion, NUL-terminated",
335 .input = { "A", SIZE_MAX, 10 },
336 .output = { "A", 1 },
337 },
339 .description = "one character, conversion, NUL-terminated, truncation",
340 .input = { "\xC3\x9F", SIZE_MAX, 0 },
341 .output = { "", 2 },
342 },
344 .description = "one word, conversion",
345 .input = { "gRu" "\xC3\x9F" "fOrMel", 11, 15 },
346 .output = { "GRUSSFORMEL", 11 },
347 },
349 .description = "one word, no conversion",
350 .input = { "WORD", 4, 10 },
351 .output = { "WORD", 4 },
352 },
354 .description = "one word, conversion, truncation",
355 .input = { "gRu" "\xC3\x9F" "formel", 11, 5 },
356 .output = { "GRUS", 11 },
357 },
359 .description = "one word, conversion, NUL-terminated",
360 .input = { "gRu" "\xC3\x9F" "formel", SIZE_MAX, 15 },
361 .output = { "GRUSSFORMEL", 11 },
362 },
364 .description = "one word, no conversion, NUL-terminated",
365 .input = { "WORD", SIZE_MAX, 10 },
366 .output = { "WORD", 4 },
367 },
369 .description = "one word, conversion, NUL-terminated, truncation",
370 .input = { "gRu" "\xC3\x9F" "formel", SIZE_MAX, 5 },
371 .output = { "GRUS", 11 },
372 },
373 };
375 static const struct unit_test_to_case_utf8 to_titlecase_utf8[] = {
377 .description = "empty input",
378 .input = { "", 0, 10 },
379 .output = { "", 0 },
380 },
382 .description = "empty output",
383 .input = { "hello", 5, 0 },
384 .output = { "", 5 },
385 },
387 .description = "one character, conversion",
388 .input = { "a", 1, 10 },
389 .output = { "A", 1 },
390 },
392 .description = "one character, no conversion",
393 .input = { "A", 1, 10 },
394 .output = { "A", 1 },
395 },
397 .description = "one character, conversion, truncation",
398 .input = { "a", 1, 0 },
399 .output = { "", 1 },
400 },
402 .description = "one character, conversion, NUL-terminated",
403 .input = { "a", SIZE_MAX, 10 },
404 .output = { "A", 1 },
405 },
407 .description = "one character, no conversion, NUL-terminated",
408 .input = { "A", SIZE_MAX, 10 },
409 .output = { "A", 1 },
410 },
412 .description = "one character, conversion, NUL-terminated, truncation",
413 .input = { "a", SIZE_MAX, 0 },
414 .output = { "", 1 },
415 },
417 .description = "one word, conversion",
418 .input = { "heLlo", 5, 10 },
419 .output = { "Hello", 5 },
420 },
422 .description = "one word, no conversion",
423 .input = { "Hello", 5, 10 },
424 .output = { "Hello", 5 },
425 },
427 .description = "one word, conversion, truncation",
428 .input = { "heLlo", 5, 2 },
429 .output = { "H", 5 },
430 },
432 .description = "one word, conversion, NUL-terminated",
433 .input = { "heLlo", SIZE_MAX, 10 },
434 .output = { "Hello", 5 },
435 },
437 .description = "one word, no conversion, NUL-terminated",
438 .input = { "Hello", SIZE_MAX, 10 },
439 .output = { "Hello", 5 },
440 },
442 .description = "one word, conversion, NUL-terminated, truncation",
443 .input = { "heLlo", SIZE_MAX, 3 },
444 .output = { "He", 5 },
445 },
447 .description = "two words, conversion",
448 .input = { "heLlo wORLd!", 12, 20 },
449 .output = { "Hello World!", 12 },
450 },
452 .description = "two words, no conversion",
453 .input = { "Hello World!", 12, 20 },
454 .output = { "Hello World!", 12 },
455 },
457 .description = "two words, conversion, truncation",
458 .input = { "heLlo wORLd!", 12, 8 },
459 .output = { "Hello W", 12 },
460 },
462 .description = "two words, conversion, NUL-terminated",
463 .input = { "heLlo wORLd!", SIZE_MAX, 20 },
464 .output = { "Hello World!", 12 },
465 },
467 .description = "two words, no conversion, NUL-terminated",
468 .input = { "Hello World!", SIZE_MAX, 20 },
469 .output = { "Hello World!", 12 },
470 },
472 .description = "two words, conversion, NUL-terminated, truncation",
473 .input = { "heLlo wORLd!", SIZE_MAX, 4 },
474 .output = { "Hel", 12 },
475 },
476 };
478 static int
479 unit_test_callback_is_case_utf8(const void *t, size_t off, const char *name,
480 const char *argv0)
482 const struct unit_test_is_case_utf8 *test =
483 (const struct unit_test_is_case_utf8 *)t + off;
484 bool ret = false;
485 size_t caselen = 0x7f;
487 if (t == is_lowercase_utf8) {
488 ret = grapheme_is_lowercase_utf8(test->input.src, test->input.srclen,
489 &caselen);
490 } else if (t == is_uppercase_utf8) {
491 ret = grapheme_is_uppercase_utf8(test->input.src, test->input.srclen,
492 &caselen);
493 } else if (t == is_titlecase_utf8) {
494 ret = grapheme_is_titlecase_utf8(test->input.src, test->input.srclen,
495 &caselen);
497 } else {
498 goto err;
501 /* check results */
502 if (ret != test->output.ret || caselen != test->output.caselen) {
503 goto err;
506 return 0;
507 err:
508 fprintf(stderr, "%s: %s: Failed unit test %zu \"%s\" "
509 "(returned (%s, %zu) instead of (%s, %zu)).\n", argv0,
510 name, off, test->description, ret ? "true" : "false",
511 caselen, test->output.ret ? "true" : "false",
512 test->output.caselen);
513 return 1;
516 static int
517 unit_test_callback_to_case_utf8(const void *t, size_t off, const char *name,
518 const char *argv0)
520 const struct unit_test_to_case_utf8 *test =
521 (const struct unit_test_to_case_utf8 *)t + off;
522 size_t ret = 0, i;
523 char buf[512];
525 /* fill the array with canary values */
526 memset(buf, 0x7f, LEN(buf));
528 if (t == to_lowercase_utf8) {
529 ret = grapheme_to_lowercase_utf8(test->input.src, test->input.srclen,
530 buf, test->input.destlen);
531 } else if (t == to_uppercase_utf8) {
532 ret = grapheme_to_uppercase_utf8(test->input.src, test->input.srclen,
533 buf, test->input.destlen);
534 } else if (t == to_titlecase_utf8) {
535 ret = grapheme_to_titlecase_utf8(test->input.src, test->input.srclen,
536 buf, test->input.destlen);
537 } else {
538 goto err;
541 /* check results */
542 if (ret != test->output.ret ||
543 memcmp(buf, test->output.dest, MIN(test->input.destlen, test->output.ret))) {
544 goto err;
547 /* check that none of the canary values have been overwritten */
548 for (i = test->input.destlen; i < LEN(buf); i++) {
549 if (buf[i] != 0x7f) {
550 goto err;
554 return 0;
555 err:
556 fprintf(stderr, "%s: %s: Failed unit test %zu \"%s\" "
557 "(returned (\"%.*s\", %zu) instead of (\"%.*s\", %zu)).\n", argv0,
558 name, off, test->description, (int)ret, buf, ret,
559 (int)test->output.ret, test->output.dest, test->output.ret);
560 return 1;
563 int
564 main(int argc, char *argv[])
566 (void)argc;
568 return run_unit_tests(unit_test_callback_is_case_utf8, is_lowercase_utf8,
569 LEN(is_lowercase_utf8), "grapheme_is_lowercase_utf8", argv[0]) +
570 run_unit_tests(unit_test_callback_is_case_utf8, is_uppercase_utf8,
571 LEN(is_uppercase_utf8), "grapheme_is_uppercase_utf8", argv[0]) +
572 run_unit_tests(unit_test_callback_is_case_utf8, is_titlecase_utf8,
573 LEN(is_titlecase_utf8), "grapheme_is_titlecase_utf8", argv[0]) +
574 run_unit_tests(unit_test_callback_to_case_utf8, to_lowercase_utf8,
575 LEN(to_lowercase_utf8), "grapheme_to_lowercase_utf8", argv[0]) +
576 run_unit_tests(unit_test_callback_to_case_utf8, to_uppercase_utf8,
577 LEN(to_uppercase_utf8), "grapheme_to_uppercase_utf8", argv[0]) +
578 run_unit_tests(unit_test_callback_to_case_utf8, to_titlecase_utf8,
579 LEN(to_titlecase_utf8), "grapheme_to_titlecase_utf8", argv[0]);