| /* See LICENSE file for copyright and license details. */ |
| #include <stdbool.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <string.h> |
| |
| #include "../grapheme.h" |
| #include "util.h" |
| |
| struct unit_test_is_case_utf8 { |
| const char *description; |
| |
| struct { |
| const char *src; |
| size_t srclen; |
| } input; |
| |
| struct { |
| bool ret; |
| size_t caselen; |
| } output; |
| }; |
| |
| struct unit_test_to_case_utf8 { |
| const char *description; |
| |
| struct { |
| const char *src; |
| size_t srclen; |
| size_t destlen; |
| } input; |
| |
| struct { |
| const char *dest; |
| size_t ret; |
| } output; |
| }; |
| |
| static const struct unit_test_is_case_utf8 is_lowercase_utf8[] = { |
| { |
| .description = "empty input", |
| .input = { "", 0 }, |
| .output = { true, 0 }, |
| }, |
| { |
| .description = "one character, violation", |
| .input = { "A", 1 }, |
| .output = { false, 0 }, |
| }, |
| { |
| .description = "one character, confirmation", |
| .input = { "\xC3\x9F", 2 }, |
| .output = { true, 2 }, |
| }, |
| { |
| .description = "one character, violation, NUL-terminated", |
| .input = { "A", SIZE_MAX }, |
| .output = { false, 0 }, |
| }, |
| { |
| .description = "one character, confirmation, NUL-terminated", |
| .input = { "\xC3\x9F", SIZE_MAX }, |
| .output = { true, 2 }, |
| }, |
| { |
| .description = "one word, violation", |
| .input = { "Hello", 5 }, |
| .output = { false, 0 }, |
| }, |
| { |
| .description = "one word, partial confirmation", |
| .input = { "gru" |
| "\xC3\x9F" |
| "fOrmel", |
| 11 }, |
| .output = { false, 6 }, |
| }, |
| { |
| .description = "one word, full confirmation", |
| .input = { "gru" |
| "\xC3\x9F" |
| "formel", |
| 11 }, |
| .output = { true, 11 }, |
| }, |
| { |
| .description = "one word, violation, NUL-terminated", |
| .input = { "Hello", SIZE_MAX }, |
| .output = { false, 0 }, |
| }, |
| { |
| .description = "one word, partial confirmation, NUL-terminated", |
| .input = { "gru" |
| "\xC3\x9F" |
| "fOrmel", |
| SIZE_MAX }, |
| .output = { false, 6 }, |
| }, |
| { |
| .description = "one word, full confirmation, NUL-terminated", |
| .input = { "gru" |
| "\xC3\x9F" |
| "formel", |
| SIZE_MAX }, |
| .output = { true, 11 }, |
| }, |
| }; |
| |
| static const struct unit_test_is_case_utf8 is_uppercase_utf8[] = { |
| { |
| .description = "empty input", |
| .input = { "", 0 }, |
| .output = { true, 0 }, |
| }, |
| { |
| .description = "one character, violation", |
| .input = { "\xC3\x9F", 2 }, |
| .output = { false, 0 }, |
| }, |
| { |
| .description = "one character, confirmation", |
| .input = { "A", 1 }, |
| .output = { true, 1 }, |
| }, |
| { |
| .description = "one character, violation, NUL-terminated", |
| .input = { "\xC3\x9F", SIZE_MAX }, |
| .output = { false, 0 }, |
| }, |
| { |
| .description = "one character, confirmation, NUL-terminated", |
| .input = { "A", SIZE_MAX }, |
| .output = { true, 1 }, |
| }, |
| { |
| .description = "one word, violation", |
| .input = { "hello", 5 }, |
| .output = { false, 0 }, |
| }, |
| { |
| .description = "one word, partial confirmation", |
| .input = { "GRU" |
| "\xC3\x9F" |
| "formel", |
| 11 }, |
| .output = { false, 3 }, |
| }, |
| { |
| .description = "one word, full confirmation", |
| .input = { "HELLO", 5 }, |
| .output = { true, 5 }, |
| }, |
| { |
| .description = "one word, violation, NUL-terminated", |
| .input = { "hello", SIZE_MAX }, |
| .output = { false, 0 }, |
| }, |
| { |
| .description = "one word, partial confirmation, NUL-terminated", |
| .input = { "GRU" |
| "\xC3\x9F" |
| "formel", |
| SIZE_MAX }, |
| .output = { false, 3 }, |
| }, |
| { |
| .description = "one word, full confirmation, NUL-terminated", |
| .input = { "HELLO", SIZE_MAX }, |
| .output = { true, 5 }, |
| }, |
| }; |
| |
| static const struct unit_test_is_case_utf8 is_titlecase_utf8[] = { |
| { |
| .description = "empty input", |
| .input = { "", 0 }, |
| .output = { true, 0 }, |
| }, |
| { |
| .description = "one character, violation", |
| .input = { "\xC3\x9F", 2 }, |
| .output = { false, 0 }, |
| }, |
| { |
| .description = "one character, confirmation", |
| .input = { "A", 1 }, |
| .output = { true, 1 }, |
| }, |
| { |
| .description = "one character, violation, NUL-terminated", |
| .input = { "\xC3\x9F", SIZE_MAX }, |
| .output = { false, 0 }, |
| }, |
| { |
| .description = "one character, confirmation, NUL-terminated", |
| .input = { "A", SIZE_MAX }, |
| .output = { true, 1 }, |
| }, |
| { |
| .description = "one word, violation", |
| .input = { "hello", 5 }, |
| .output = { false, 0 }, |
| }, |
| { |
| .description = "one word, partial confirmation", |
| .input = { "Gru" |
| "\xC3\x9F" |
| "fOrmel", |
| 11 }, |
| .output = { false, 6 }, |
| }, |
| { |
| .description = "one word, full confirmation", |
| .input = { "Gru" |
| "\xC3\x9F" |
| "formel", |
| 11 }, |
| .output = { true, 11 }, |
| }, |
| { |
| .description = "one word, violation, NUL-terminated", |
| .input = { "hello", SIZE_MAX }, |
| .output = { false, 0 }, |
| }, |
| { |
| .description = "one word, partial confirmation, NUL-terminated", |
| .input = { "Gru" |
| "\xC3\x9F" |
| "fOrmel", |
| SIZE_MAX }, |
| .output = { false, 6 }, |
| }, |
| { |
| .description = "one word, full confirmation, NUL-terminated", |
| .input = { "Gru" |
| "\xC3\x9F" |
| "formel", |
| SIZE_MAX }, |
| .output = { true, 11 }, |
| }, |
| { |
| .description = "multiple words, partial confirmation", |
| .input = { "Hello Gru" |
| "\xC3\x9F" |
| "fOrmel!", |
| 18 }, |
| .output = { false, 12 }, |
| }, |
| { |
| .description = "multiple words, full confirmation", |
| .input = { "Hello Gru" |
| "\xC3\x9F" |
| "formel!", |
| 18 }, |
| .output = { true, 18 }, |
| }, |
| { |
| .description = |
| "multiple words, partial confirmation, NUL-terminated", |
| .input = { "Hello Gru" |
| "\xC3\x9F" |
| "fOrmel!", |
| SIZE_MAX }, |
| .output = { false, 12 }, |
| }, |
| { |
| .description = |
| "multiple words, full confirmation, NUL-terminated", |
| .input = { "Hello Gru" |
| "\xC3\x9F" |
| "formel!", |
| SIZE_MAX }, |
| .output = { true, 18 }, |
| }, |
| }; |
| |
| static const struct unit_test_to_case_utf8 to_lowercase_utf8[] = { |
| { |
| .description = "empty input", |
| .input = { "", 0, 10 }, |
| .output = { "", 0 }, |
| }, |
| { |
| .description = "empty output", |
| .input = { "hello", 5, 0 }, |
| .output = { "", 5 }, |
| }, |
| { |
| .description = "one character, conversion", |
| .input = { "A", 1, 10 }, |
| .output = { "a", 1 }, |
| }, |
| { |
| .description = "one character, no conversion", |
| .input = { "\xC3\x9F", 2, 10 }, |
| .output = { "\xC3\x9F", 2 }, |
| }, |
| { |
| .description = "one character, conversion, truncation", |
| .input = { "A", 1, 0 }, |
| .output = { "", 1 }, |
| }, |
| { |
| .description = "one character, conversion, NUL-terminated", |
| .input = { "A", SIZE_MAX, 10 }, |
| .output = { "a", 1 }, |
| }, |
| { |
| .description = "one character, no conversion, NUL-terminated", |
| .input = { "\xC3\x9F", SIZE_MAX, 10 }, |
| .output = { "\xC3\x9F", 2 }, |
| }, |
| { |
| .description = |
| "one character, conversion, NUL-terminated, truncation", |
| .input = { "A", SIZE_MAX, 0 }, |
| .output = { "", 1 }, |
| }, |
| { |
| .description = "one word, conversion", |
| .input = { "wOrD", 4, 10 }, |
| .output = { "word", 4 }, |
| }, |
| { |
| .description = "one word, no conversion", |
| .input = { "word", 4, 10 }, |
| .output = { "word", 4 }, |
| }, |
| { |
| .description = "one word, conversion, truncation", |
| .input = { "wOrD", 4, 3 }, |
| .output = { "wo", 4 }, |
| }, |
| { |
| .description = "one word, conversion, NUL-terminated", |
| .input = { "wOrD", SIZE_MAX, 10 }, |
| .output = { "word", 4 }, |
| }, |
| { |
| .description = "one word, no conversion, NUL-terminated", |
| .input = { "word", SIZE_MAX, 10 }, |
| .output = { "word", 4 }, |
| }, |
| { |
| .description = |
| "one word, conversion, NUL-terminated, truncation", |
| .input = { "wOrD", SIZE_MAX, 3 }, |
| .output = { "wo", 4 }, |
| }, |
| }; |
| |
| static const struct unit_test_to_case_utf8 to_uppercase_utf8[] = { |
| { |
| .description = "empty input", |
| .input = { "", 0, 10 }, |
| .output = { "", 0 }, |
| }, |
| { |
| .description = "empty output", |
| .input = { "hello", 5, 0 }, |
| .output = { "", 5 }, |
| }, |
| { |
| .description = "one character, conversion", |
| .input = { "\xC3\x9F", 2, 10 }, |
| .output = { "SS", 2 }, |
| }, |
| { |
| .description = "one character, no conversion", |
| .input = { "A", 1, 10 }, |
| .output = { "A", 1 }, |
| }, |
| { |
| .description = "one character, conversion, truncation", |
| .input = { "\xC3\x9F", 2, 0 }, |
| .output = { "", 2 }, |
| }, |
| { |
| .description = "one character, conversion, NUL-terminated", |
| .input = { "\xC3\x9F", SIZE_MAX, 10 }, |
| .output = { "SS", 2 }, |
| }, |
| { |
| .description = "one character, no conversion, NUL-terminated", |
| .input = { "A", SIZE_MAX, 10 }, |
| .output = { "A", 1 }, |
| }, |
| { |
| .description = |
| "one character, conversion, NUL-terminated, truncation", |
| .input = { "\xC3\x9F", SIZE_MAX, 0 }, |
| .output = { "", 2 }, |
| }, |
| { |
| .description = "one word, conversion", |
| .input = { "gRu" |
| "\xC3\x9F" |
| "fOrMel", |
| 11, 15 }, |
| .output = { "GRUSSFORMEL", 11 }, |
| }, |
| { |
| .description = "one word, no conversion", |
| .input = { "WORD", 4, 10 }, |
| .output = { "WORD", 4 }, |
| }, |
| { |
| .description = "one word, conversion, truncation", |
| .input = { "gRu" |
| "\xC3\x9F" |
| "formel", |
| 11, 5 }, |
| .output = { "GRUS", 11 }, |
| }, |
| { |
| .description = "one word, conversion, NUL-terminated", |
| .input = { "gRu" |
| "\xC3\x9F" |
| "formel", |
| SIZE_MAX, 15 }, |
| .output = { "GRUSSFORMEL", 11 }, |
| }, |
| { |
| .description = "one word, no conversion, NUL-terminated", |
| .input = { "WORD", SIZE_MAX, 10 }, |
| .output = { "WORD", 4 }, |
| }, |
| { |
| .description = |
| "one word, conversion, NUL-terminated, truncation", |
| .input = { "gRu" |
| "\xC3\x9F" |
| "formel", |
| SIZE_MAX, 5 }, |
| .output = { "GRUS", 11 }, |
| }, |
| }; |
| |
| static const struct unit_test_to_case_utf8 to_titlecase_utf8[] = { |
| { |
| .description = "empty input", |
| .input = { "", 0, 10 }, |
| .output = { "", 0 }, |
| }, |
| { |
| .description = "empty output", |
| .input = { "hello", 5, 0 }, |
| .output = { "", 5 }, |
| }, |
| { |
| .description = "one character, conversion", |
| .input = { "a", 1, 10 }, |
| .output = { "A", 1 }, |
| }, |
| { |
| .description = "one character, no conversion", |
| .input = { "A", 1, 10 }, |
| .output = { "A", 1 }, |
| }, |
| { |
| .description = "one character, conversion, truncation", |
| .input = { "a", 1, 0 }, |
| .output = { "", 1 }, |
| }, |
| { |
| .description = "one character, conversion, NUL-terminated", |
| .input = { "a", SIZE_MAX, 10 }, |
| .output = { "A", 1 }, |
| }, |
| { |
| .description = "one character, no conversion, NUL-terminated", |
| .input = { "A", SIZE_MAX, 10 }, |
| .output = { "A", 1 }, |
| }, |
| { |
| .description = |
| "one character, conversion, NUL-terminated, truncation", |
| .input = { "a", SIZE_MAX, 0 }, |
| .output = { "", 1 }, |
| }, |
| { |
| .description = "one word, conversion", |
| .input = { "heLlo", 5, 10 }, |
| .output = { "Hello", 5 }, |
| }, |
| { |
| .description = "one word, no conversion", |
| .input = { "Hello", 5, 10 }, |
| .output = { "Hello", 5 }, |
| }, |
| { |
| .description = "one word, conversion, truncation", |
| .input = { "heLlo", 5, 2 }, |
| .output = { "H", 5 }, |
| }, |
| { |
| .description = "one word, conversion, NUL-terminated", |
| .input = { "heLlo", SIZE_MAX, 10 }, |
| .output = { "Hello", 5 }, |
| }, |
| { |
| .description = "one word, no conversion, NUL-terminated", |
| .input = { "Hello", SIZE_MAX, 10 }, |
| .output = { "Hello", 5 }, |
| }, |
| { |
| .description = |
| "one word, conversion, NUL-terminated, truncation", |
| .input = { "heLlo", SIZE_MAX, 3 }, |
| .output = { "He", 5 }, |
| }, |
| { |
| .description = "two words, conversion", |
| .input = { "heLlo wORLd!", 12, 20 }, |
| .output = { "Hello World!", 12 }, |
| }, |
| { |
| .description = "two words, no conversion", |
| .input = { "Hello World!", 12, 20 }, |
| .output = { "Hello World!", 12 }, |
| }, |
| { |
| .description = "two words, conversion, truncation", |
| .input = { "heLlo wORLd!", 12, 8 }, |
| .output = { "Hello W", 12 }, |
| }, |
| { |
| .description = "two words, conversion, NUL-terminated", |
| .input = { "heLlo wORLd!", SIZE_MAX, 20 }, |
| .output = { "Hello World!", 12 }, |
| }, |
| { |
| .description = "two words, no conversion, NUL-terminated", |
| .input = { "Hello World!", SIZE_MAX, 20 }, |
| .output = { "Hello World!", 12 }, |
| }, |
| { |
| .description = |
| "two words, conversion, NUL-terminated, truncation", |
| .input = { "heLlo wORLd!", SIZE_MAX, 4 }, |
| .output = { "Hel", 12 }, |
| }, |
| }; |
| |
| static int |
| unit_test_callback_is_case_utf8(const void *t, size_t off, const char *name, |
| const char *argv0) |
| { |
| const struct unit_test_is_case_utf8 *test = |
| (const struct unit_test_is_case_utf8 *)t + off; |
| bool ret = false; |
| size_t caselen = 0x7f; |
| |
| if (t == is_lowercase_utf8) { |
| ret = grapheme_is_lowercase_utf8(test->input.src, |
| test->input.srclen, &caselen); |
| } else if (t == is_uppercase_utf8) { |
| ret = grapheme_is_uppercase_utf8(test->input.src, |
| test->input.srclen, &caselen); |
| } else if (t == is_titlecase_utf8) { |
| ret = grapheme_is_titlecase_utf8(test->input.src, |
| test->input.srclen, &caselen); |
| |
| } else { |
| goto err; |
| } |
| |
| /* check results */ |
| if (ret != test->output.ret || caselen != test->output.caselen) { |
| goto err; |
| } |
| |
| return 0; |
| err: |
| fprintf(stderr, |
| "%s: %s: Failed unit test %zu \"%s\" " |
| "(returned (%s, %zu) instead of (%s, %zu)).\n", |
| argv0, name, off, test->description, ret ? "true" : "false", |
| caselen, test->output.ret ? "true" : "false", |
| test->output.caselen); |
| return 1; |
| } |
| |
| static int |
| unit_test_callback_to_case_utf8(const void *t, size_t off, const char *name, |
| const char *argv0) |
| { |
| const struct unit_test_to_case_utf8 *test = |
| (const struct unit_test_to_case_utf8 *)t + off; |
| size_t ret = 0, i; |
| char buf[512]; |
| |
| /* fill the array with canary values */ |
| memset(buf, 0x7f, LEN(buf)); |
| |
| if (t == to_lowercase_utf8) { |
| ret = grapheme_to_lowercase_utf8(test->input.src, |
| test->input.srclen, buf, |
| test->input.destlen); |
| } else if (t == to_uppercase_utf8) { |
| ret = grapheme_to_uppercase_utf8(test->input.src, |
| test->input.srclen, buf, |
| test->input.destlen); |
| } else if (t == to_titlecase_utf8) { |
| ret = grapheme_to_titlecase_utf8(test->input.src, |
| test->input.srclen, buf, |
| test->input.destlen); |
| } else { |
| goto err; |
| } |
| |
| /* check results */ |
| if (ret != test->output.ret || |
| memcmp(buf, test->output.dest, |
| MIN(test->input.destlen, test->output.ret))) { |
| goto err; |
| } |
| |
| /* check that none of the canary values have been overwritten */ |
| for (i = test->input.destlen; i < LEN(buf); i++) { |
| if (buf[i] != 0x7f) { |
| goto err; |
| } |
| } |
| |
| return 0; |
| err: |
| fprintf(stderr, |
| "%s: %s: Failed unit test %zu \"%s\" " |
| "(returned (\"%.*s\", %zu) instead of (\"%.*s\", %zu)).\n", |
| argv0, name, off, test->description, (int)ret, buf, ret, |
| (int)test->output.ret, test->output.dest, test->output.ret); |
| return 1; |
| } |
| |
| int |
| main(int argc, char *argv[]) |
| { |
| (void)argc; |
| |
| return run_unit_tests(unit_test_callback_is_case_utf8, |
| is_lowercase_utf8, LEN(is_lowercase_utf8), |
| "grapheme_is_lowercase_utf8", argv[0]) + |
| run_unit_tests(unit_test_callback_is_case_utf8, |
| is_uppercase_utf8, LEN(is_uppercase_utf8), |
| "grapheme_is_uppercase_utf8", argv[0]) + |
| run_unit_tests(unit_test_callback_is_case_utf8, |
| is_titlecase_utf8, LEN(is_titlecase_utf8), |
| "grapheme_is_titlecase_utf8", argv[0]) + |
| run_unit_tests(unit_test_callback_to_case_utf8, |
| to_lowercase_utf8, LEN(to_lowercase_utf8), |
| "grapheme_to_lowercase_utf8", argv[0]) + |
| run_unit_tests(unit_test_callback_to_case_utf8, |
| to_uppercase_utf8, LEN(to_uppercase_utf8), |
| "grapheme_to_uppercase_utf8", argv[0]) + |
| run_unit_tests(unit_test_callback_to_case_utf8, |
| to_titlecase_utf8, LEN(to_titlecase_utf8), |
| "grapheme_to_titlecase_utf8", argv[0]); |
| } |