/* * Copyright © 2019 Red Hat, Inc. * SPDX-License-Identifier: MIT */ #include "config.h" #include "test-config.h" #include #include #include #include #include #include #include "test.h" #include "utils.h" #include "utils-numbers.h" #include "utils-paths.h" #include "utils-random.h" #include "test/utils-text.h" static void test_string_functions(void) { char buffer[10]; assert(!snprintf_safe(buffer, 0, "foo")); assert(!snprintf_safe(buffer, 1, "foo")); assert(!snprintf_safe(buffer, 3, "foo")); assert(snprintf_safe(buffer, 10, "foo")); assert(streq(buffer, "foo")); assert(!snprintf_safe(buffer, 10, "%s", "1234567890")); assert(snprintf_safe(buffer, 10, "%s", "123456789")); assert(streq_null("foo", "foo")); assert(!streq_null("foobar", "foo")); assert(!streq_null("foobar", NULL)); assert(!streq_null(NULL, "foobar")); assert(streq_null(NULL, NULL)); const char text[] = "123; // abc\n" " // def\n" "456 // ghi // jkl\n" "// mno\n" "//\n" "ok; // pqr\n" "foo\n"; char *out; out = strip_lines(text, ARRAY_SIZE(text), "//"); assert_streq_not_null( "strip_lines", "123; \n" "456 \n" "ok; \n" "foo\n", out ); free(out); out = uncomment(text, ARRAY_SIZE(text), "//"); assert_streq_not_null( "uncomment", "123; abc\n" " def\n" "456 ghi // jkl\n" " mno\n" "\n" "ok; pqr\n" "foo\n", out ); free(out); } static void test_path_functions(void) { /* Absolute paths */ assert(!is_absolute_path("")); #ifdef _WIN32 assert(!is_absolute_path("path\\test")); assert(is_absolute_path("c:\\test")); assert(!is_absolute_path("c:test")); assert(is_absolute_path("c:\\")); assert(is_absolute_path("c:/")); assert(!is_absolute_path("c:")); assert(is_absolute_path("\\\\foo")); assert(is_absolute_path("\\\\?\\foo")); assert(is_absolute_path("\\\\?\\UNC\\foo")); assert(is_absolute_path("/foo")); assert(is_absolute_path("\\foo")); #else assert(!is_absolute_path("test/path")); assert(is_absolute_path("/test" )); assert(is_absolute_path("/" )); #endif } static uint32_t rand_uint32(void) { /* First decide how many bits we’ll actually use (1-32) */ int bits = 1 + (int)(random() % 32); /* Generate a number with that many bits */ uint32_t result = 0; while (bits > 0) { int bits_this_round = bits > 16 ? 16 : bits; result = (result << bits_this_round) | (random() & ((1U << bits_this_round) - 1)); bits -= bits_this_round; } return result; } static uint64_t rand_uint64(void) { /* First decide how many bits we’ll actually use (1-64) */ int bits = 1 + (int)(random() % 64); /* Generate a number with that many bits */ uint64_t result = 0; while (bits > 0) { int bits_this_round = bits > 16 ? 16 : bits; result = (result << bits_this_round) | (random() & ((1U << bits_this_round) - 1)); bits -= bits_this_round; } return result; } /* NOLINTBEGIN(google-readability-function-size) */ static void test_number_parsers(void) { /* Check the claim that it always works on normal strings using SIZE_MAX and * that it always stops on the first NULL byte */ { static const struct { const char* input; struct { int count; uint64_t val; } dec; struct { int count; uint64_t val; } hex; } tests[] = { { .input = "", .dec = { 0, UINT64_C(0) }, .hex = { 0, UINT64_C(0) } }, { .input = "\0""123", .dec = { 0, UINT64_C(0) }, .hex = { 0, UINT64_C(0) } }, { .input = "/", .dec = { 0, UINT64_C(0) }, .hex = { 0, UINT64_C(0) } }, { .input = ";", .dec = { 0, UINT64_C(0) }, .hex = { 0, UINT64_C(0) } }, { .input = "x", .dec = { 0, UINT64_C(0) }, .hex = { 0, UINT64_C(0) } }, { .input = "/1", .dec = { 0, UINT64_C(0) }, .hex = { 0, UINT64_C(0) } }, { .input = ";1", .dec = { 0, UINT64_C(0) }, .hex = { 0, UINT64_C(0) } }, { .input = "x1", .dec = { 0, UINT64_C(0) }, .hex = { 0, UINT64_C(0) } }, { .input = "0", .dec = { 1, UINT64_C(0) }, .hex = { 1, UINT64_C(0) } }, { .input = "1", .dec = { 1, UINT64_C(1) }, .hex = { 1, UINT64_C(1) } }, { .input = "123", .dec = { 3, UINT64_C(123) }, .hex = { 3, UINT64_C(0x123)} }, { .input = "123x", .dec = { 3, UINT64_C(123) }, .hex = { 3, UINT64_C(0x123)} }, { .input = "123""\0""456", .dec = { 3, UINT64_C(123) }, .hex = { 3, UINT64_C(0x123)} }, { .input = "18446744073709551615", .dec = { 20, UINT64_MAX }, .hex = { -1, UINT64_C(0x1844674407370955)} }, { .input = "18446744073709551616", .dec = { -1, UINT64_C(1844674407370955161)}, .hex = { -1, UINT64_C(0x1844674407370955) } }, { .input = "99999999999999999999", .dec = { -1, UINT64_C(9999999999999999999)}, .hex = { -1, UINT64_C(0x9999999999999999) } }, { .input = "184467440737095516150", .dec = { -1, UINT64_MAX }, .hex = { -1, UINT64_C(0x1844674407370955)} }, { .input = "00000000000000000", .dec = { 17, UINT64_C(0) }, .hex = { 17, UINT64_C(0) } }, { .input = "00000000000000001", .dec = { 17, UINT64_C(1) }, .hex = { 17, UINT64_C(1) } }, { .input = "ffffffffffffffff", .dec = { 0 , 0 }, .hex = { 16, UINT64_MAX} }, { .input = "ffffffffffffffff0", .dec = { 0 , 0 }, .hex = { -1, UINT64_MAX} }, { .input = "10000000000000000", .dec = { 17, UINT64_C(10000000000000000) }, .hex = { -1, UINT64_C(0x1000000000000000)} }, { .input = "fffffffffffffffff", .dec = { 0 , 0 }, .hex = { -1, UINT64_MAX} }, }; for (size_t k = 0; k < ARRAY_SIZE(tests); k++) { const size_t len = strlen(tests[k].input); /* Try different lengths */ const struct {const char* label; size_t len;} sizes[] = { { .label = "buffer" , .len = len }, { .label = "string" , .len = len + 1 }, { .label = "SIZE_MAX", .len = SIZE_MAX} }; for (unsigned int s = 0; s < ARRAY_SIZE(sizes); s++) { int count; /* Decimal */ uint64_t dec = 0; count = parse_dec_to_uint64_t(tests[k].input, sizes[s].len, &dec); assert_printf(count == tests[k].dec.count, "Dec %s #%zu \"%s\" (%zu), " "expected: %d, got: %d\n", sizes[s].label, k, tests[k].input, sizes[s].len, tests[k].dec.count, count); assert_printf(dec == tests[k].dec.val, "Dec %s #%zu \"%s\", " "expected: %"PRIu64", got: %"PRIu64"\n", sizes[s].label, k, tests[k].input, tests[k].dec.val, dec); /* Hexadecimal */ uint64_t hex = 0; count = parse_hex_to_uint64_t(tests[k].input, sizes[s].len, &hex); assert_printf(count == tests[k].hex.count, "Hex %s #%zu \"%s\" (%zu), " "expected: %d, got: %d\n", sizes[s].label, k, tests[k].input, sizes[s].len, tests[k].hex.count, count); assert_printf(hex == tests[k].hex.val, "Hex %s #%zu \"%s\", " "expected: %#"PRIx64", got: %#"PRIx64"\n", sizes[s].label, k, tests[k].input, tests[k].hex.val, hex); } } } #define PRIuint64_t PRIx64 #define PRIuint32_t PRIx32 #define test_parse_to(type, format, input, count, expected) do { \ type n = 0; \ int r = parse_##format##_to_##type(input, ARRAY_SIZE(input), &n);\ assert_printf(r == (count), \ "Buffer: expected count: %d, " \ "got: %d (value: %#"PRI##type", string: %.*s)\n", \ count, r, n, (int) ARRAY_SIZE(input), (input)); \ assert_printf(n == (expected), \ "Buffer: expected value: %#"PRI##type", got: " \ "%#"PRI##type"\n", expected, n); \ } while (0) /* Test syntax variants */ const uint64_t values[] = { UINT64_C(0), UINT64_C(1), UINT64_C(10), UINT64_C(0xA), UINT64_C(0xF), UINT64_C(123), UINT32_MAX / 10, UINT32_MAX / 10 + 9, UINT32_MAX >> 4, UINT32_MAX >> 4 | 0xf, UINT32_MAX - 1, UINT32_MAX, UINT32_MAX + UINT64_C(1), UINT64_C(9999999999999999999), UINT64_MAX / 10, UINT64_MAX / 10 + 9, UINT64_MAX >> 4, UINT64_MAX >> 4 | 0xf, UINT64_MAX - 1, UINT64_MAX, }; char buffer[30] = {0}; for (size_t k = 0; k < ARRAY_SIZE(values); k++) { int count; /* Basic: decimal */ count = snprintf(buffer, sizeof(buffer), "%"PRIu32, (uint32_t) values[k]); assert(count > 0); test_parse_to(uint32_t, dec, buffer, count, (uint32_t) values[k]); count = snprintf(buffer, sizeof(buffer), "%"PRIu64, values[k]); assert(count > 0); test_parse_to(uint64_t, dec, buffer, count, values[k]); /* Basic: hex lower case */ count = snprintf(buffer, sizeof(buffer), "%"PRIx32, (uint32_t) values[k]); assert(count > 0); test_parse_to(uint32_t, hex, buffer, count, (uint32_t) values[k]); count = snprintf(buffer, sizeof(buffer), "%"PRIx64, values[k]); assert(count > 0); test_parse_to(uint64_t, hex, buffer, count, values[k]); /* Basic: hex upper case */ count = snprintf(buffer, sizeof(buffer), "%"PRIX32, (uint32_t) values[k]); assert(count > 0); test_parse_to(uint32_t, hex, buffer, count, (uint32_t) values[k]); count = snprintf(buffer, sizeof(buffer), "%"PRIX64, values[k]); assert(count > 0); test_parse_to(uint64_t, hex, buffer, count, values[k]); /* Prefix with some zeroes */ for (int z = 0; z < 10 ; z++) { /* Decimal */ count = snprintf(buffer, sizeof(buffer), "%0*"PRIu32, z, (uint32_t) values[k]); assert(count > 0); test_parse_to(uint32_t, dec, buffer, count, (uint32_t) values[k]); count = snprintf(buffer, sizeof(buffer), "%0*"PRIu64, z, values[k]); assert(count > 0); test_parse_to(uint64_t, dec, buffer, count, values[k]); /* Hexadecimal */ count = snprintf(buffer, sizeof(buffer), "%0*u%"PRIx32, z, 0, (uint32_t) values[k]); assert(count > 0); test_parse_to(uint32_t, hex, buffer, count, (uint32_t) values[k]); count = snprintf(buffer, sizeof(buffer), "%0*u%"PRIx64, z, 0, values[k]); assert(count > 0); test_parse_to(uint64_t, hex, buffer, count, values[k]); /* Append some garbage */ for (int c = 0; c < 0x100 ; c++) { if (c < '0' || c > '9') { /* Decimal */ count = snprintf(buffer, sizeof(buffer), "%0*u%"PRIu32"%c", z, 0, (uint32_t) values[k], (char) c); assert(count > 0); test_parse_to(uint32_t, dec, buffer, count - 1, (uint32_t) values[k]); count = snprintf(buffer, sizeof(buffer), "%0*u%"PRIu64"%c", z, 0, values[k], (char) c); assert(count > 0); test_parse_to(uint64_t, dec, buffer, count - 1, values[k]); } if (!is_xdigit((char) c)) { /* Hexadecimal */ count = snprintf(buffer, sizeof(buffer), "%0*u%"PRIx32"%c", z, 0, (uint32_t) values[k], (char) c); assert(count > 0); test_parse_to(uint32_t, hex, buffer, count - 1, (uint32_t) values[k]); count = snprintf(buffer, sizeof(buffer), "%0*u%"PRIx64"%c", z, 0, values[k], (char) c); assert(count > 0); test_parse_to(uint64_t, hex, buffer, count - 1, values[k]); } } } } /* Random */ for (unsigned int k = 0; k < 10000; k++) { const uint32_t x32 = rand_uint32(); const uint64_t x64 = rand_uint64(); int count; /* Hex: Lower case */ count = snprintf(buffer, sizeof(buffer), "%"PRIx32, x32); assert(count > 0); test_parse_to(uint32_t, hex, buffer, count, x32); count = snprintf(buffer, sizeof(buffer), "%"PRIx64, x64); assert(count > 0); test_parse_to(uint64_t, hex, buffer, count, x64); /* Hex: Upper case (32 bits) */ count = snprintf(buffer, sizeof(buffer), "%"PRIX32, x32); assert(count > 0); test_parse_to(uint32_t, hex, buffer, count, x32); /* Hex: some garbage after */ buffer[count] = (char) (((unsigned int) random()) % '0'); test_parse_to(uint32_t, hex, buffer, count, x32); /* Hex: Upper case (64 bits) */ count = snprintf(buffer, sizeof(buffer), "%"PRIX64, x64); assert(count > 0); test_parse_to(uint64_t, hex, buffer, count, x64); /* Hex: some garbage after */ buffer[count] = (char) (((unsigned int) random()) % '0'); test_parse_to(uint64_t, hex, buffer, count, x64); /* Decimal (32 bits) */ count = snprintf(buffer, sizeof(buffer), "%"PRIu32, x32); assert(count > 0); test_parse_to(uint32_t, dec, buffer, count, x32); /* Decimal: some garbage after */ buffer[count] = (char) (((unsigned int) random()) % '0'); test_parse_to(uint32_t, dec, buffer, count, x32); /* Decimal (64 bits) */ count = snprintf(buffer, sizeof(buffer), "%"PRIu64, x64); assert(count > 0); test_parse_to(uint64_t, dec, buffer, count, x64); buffer[count] = (char) (((unsigned int) random()) % '0'); test_parse_to(uint64_t, dec, buffer, count, x64); } } /* NOLINTEND(google-readability-function-size) */ /* CLI positional arguments: * 1. Seed for the pseudo-random generator: * - Leave it unset or set it to “-” to use current time. * - Use an integer to set it explicitly. */ int main(int argc, char *argv[]) { test_init(); /* Initialize pseudo-random generator with program arg or current time */ unsigned int seed; if (argc >= 2 && !streq(argv[1], "-")) { char *endp = argv[1]; errno = 0; const unsigned long raw = strtoul(argv[1], &endp, 10); if (errno || endp == argv[1] || *endp != '\0' || raw > UINT_MAX) { fprintf(stderr, "ERROR: Invalid seed: \"%s\"\n", argv[1]); exit(TEST_SETUP_FAILURE); } seed = (unsigned int) raw; } else { seed = (unsigned int) time(NULL); } fprintf(stderr, "Seed for the pseudo-random generator: %u\n", seed); srand(seed); test_string_functions(); test_path_functions(); test_number_parsers(); return EXIT_SUCCESS; }