| /* Copyright 2022 The ChromiumOS Authors |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| * |
| * Test standard library functions. |
| */ |
| |
| #include "common.h" |
| #include "compiler.h" |
| #include "console.h" |
| #include "printf.h" |
| #include "shared_mem.h" |
| #include "system.h" |
| #include "test_util.h" |
| #include "timer.h" |
| #include "util.h" |
| |
| /* TODO(b/243192369): EC implementations are broken. */ |
| #if defined(USE_BUILTIN_STDLIB) || \ |
| (defined(BOARD_HOST) && !defined(USE_BUILTIN_STDLIB)) |
| static bool broken_strstr = true; |
| static bool broken_strtoull = true; |
| #else |
| static bool broken_strstr = false; |
| static bool broken_strtoull = false; |
| #endif |
| |
| #include <stdlib.h> |
| |
| #if !defined(USE_BUILTIN_STDLIB) && defined(BOARD_HOST) |
| /* This is ugly, but we want to test the functions in builtin/stdlib.c while |
| * still depending on the system stdlib.c |
| */ |
| #define snprintf TESTED_snprintf |
| #include "../builtin/stdlib.c" |
| #endif |
| |
| __no_optimization static int test_isalpha(void) |
| { |
| TEST_ASSERT(isalpha('a')); |
| TEST_ASSERT(isalpha('z')); |
| TEST_ASSERT(isalpha('A')); |
| TEST_ASSERT(isalpha('Z')); |
| TEST_ASSERT(!isalpha('0')); |
| TEST_ASSERT(!isalpha('~')); |
| TEST_ASSERT(!isalpha(' ')); |
| TEST_ASSERT(!isalpha('\0')); |
| TEST_ASSERT(!isalpha('\n')); |
| return EC_SUCCESS; |
| } |
| |
| __no_optimization static int test_isupper(void) |
| { |
| TEST_ASSERT(!isupper('a')); |
| TEST_ASSERT(!isupper('z')); |
| TEST_ASSERT(isupper('A')); |
| TEST_ASSERT(isupper('Z')); |
| TEST_ASSERT(!isupper('0')); |
| TEST_ASSERT(!isupper('~')); |
| TEST_ASSERT(!isupper(' ')); |
| TEST_ASSERT(!isupper('\0')); |
| TEST_ASSERT(!isupper('\n')); |
| return EC_SUCCESS; |
| } |
| |
| __no_optimization static int test_isprint(void) |
| { |
| TEST_ASSERT(isprint('a')); |
| TEST_ASSERT(isprint('z')); |
| TEST_ASSERT(isprint('A')); |
| TEST_ASSERT(isprint('Z')); |
| TEST_ASSERT(isprint('0')); |
| TEST_ASSERT(isprint('~')); |
| TEST_ASSERT(isprint(' ')); |
| TEST_ASSERT(!isprint('\0')); |
| TEST_ASSERT(!isprint('\n')); |
| return EC_SUCCESS; |
| } |
| |
| __no_optimization static int test_strstr(void) |
| { |
| const char s1[] = "abcde"; |
| |
| TEST_ASSERT(strstr(s1, "ab") == s1); |
| |
| /* |
| * From the man page: If needle is the empty string, the return value is |
| * always haystack itself. |
| * TEST_ASSERT(strstr(s1, "") == s1); |
| */ |
| if (broken_strstr) { |
| TEST_ASSERT(strstr(s1, "") == NULL); |
| } else { |
| TEST_ASSERT(strstr(s1, "") == s1); |
| } |
| TEST_ASSERT(strstr("", "ab") == NULL); |
| TEST_ASSERT(strstr("", "x") == NULL); |
| TEST_ASSERT(strstr(s1, "de") == &s1[3]); |
| TEST_ASSERT(strstr(s1, "def") == NULL); |
| |
| return EC_SUCCESS; |
| } |
| |
| __no_optimization static int test_strtoull(void) |
| { |
| char *e; |
| |
| TEST_ASSERT(strtoull("10", &e, 0) == 10); |
| TEST_ASSERT(e && (*e == '\0')); |
| TEST_ASSERT(strtoull("010", &e, 0) == 8); |
| TEST_ASSERT(e && (*e == '\0')); |
| TEST_ASSERT(strtoull("+010", &e, 0) == 8); |
| TEST_ASSERT(e && (*e == '\0')); |
| |
| /* |
| * From the man page: The strtoull() function returns either the result |
| * of the conversion or, if there was a leading minus sign, the negation |
| * of the result of the conversion represented as an unsigned value, |
| * unless the original (nonnegated) value would overflow |
| */ |
| if (broken_strtoull) { |
| TEST_ASSERT(strtoull("-010", &e, 0) == 0); |
| TEST_ASSERT(e && (*e == '-')); |
| } else { |
| TEST_ASSERT(strtoull("-010", &e, 0) == 0xFFFFFFFFFFFFFFF8); |
| } |
| |
| TEST_ASSERT(strtoull("0x1f z", &e, 0) == 31); |
| TEST_ASSERT(e && (*e == ' ')); |
| TEST_ASSERT(strtoull("0X1f z", &e, 0) == 31); |
| TEST_ASSERT(e && (*e == ' ')); |
| TEST_ASSERT(strtoull("10a", &e, 16) == 266); |
| TEST_ASSERT(e && (*e == '\0')); |
| TEST_ASSERT(strtoull("0x02C", &e, 16) == 44); |
| TEST_ASSERT(e && (*e == '\0')); |
| TEST_ASSERT(strtoull("+0x02C", &e, 16) == 44); |
| TEST_ASSERT(e && (*e == '\0')); |
| |
| if (broken_strtoull) { |
| TEST_ASSERT(strtoull("-0x02C", &e, 16) == 0); |
| TEST_ASSERT(e && (*e == '-')); |
| } else { |
| TEST_ASSERT(strtoull("-0x02C", &e, 16) == 0xFFFFFFFFFFFFFFD4); |
| } |
| TEST_ASSERT(strtoull("0x02C", &e, 0) == 44); |
| TEST_ASSERT(e && (*e == '\0')); |
| TEST_ASSERT(strtoull("+0x02C", &e, 0) == 44); |
| TEST_ASSERT(e && (*e == '\0')); |
| |
| if (broken_strtoull) { |
| TEST_ASSERT(strtoull("-0x02C", &e, 0) == 0); |
| TEST_ASSERT(e && (*e == '-')); |
| } else { |
| TEST_ASSERT(strtoull("-0x02C", &e, 0) == 0xFFFFFFFFFFFFFFD4); |
| } |
| |
| TEST_ASSERT(strtoull("0X02C", &e, 16) == 44); |
| TEST_ASSERT(e && (*e == '\0')); |
| TEST_ASSERT(strtoull("+0X02C", &e, 16) == 44); |
| TEST_ASSERT(e && (*e == '\0')); |
| |
| if (broken_strtoull) { |
| TEST_ASSERT(strtoull("-0X02C", &e, 16) == 0); |
| TEST_ASSERT(e && (*e == '-')); |
| } else { |
| TEST_ASSERT(strtoull("-0X02C", &e, 16) == 0xFFFFFFFFFFFFFFD4); |
| } |
| |
| TEST_ASSERT(strtoull("0X02C", &e, 0) == 44); |
| TEST_ASSERT(e && (*e == '\0')); |
| TEST_ASSERT(strtoull("+0X02C", &e, 0) == 44); |
| TEST_ASSERT(e && (*e == '\0')); |
| |
| if (broken_strtoull) { |
| TEST_ASSERT(strtoull("-0X02C", &e, 0) == 0); |
| TEST_ASSERT(e && (*e == '-')); |
| } else { |
| TEST_ASSERT(strtoull("-0X02C", &e, 0) == 0xFFFFFFFFFFFFFFD4); |
| } |
| |
| if (broken_strtoull) { |
| TEST_ASSERT(strtoull(" -12", &e, 0) == 0); |
| TEST_ASSERT(e && (*e == '-')); |
| } else { |
| TEST_ASSERT(strtoull(" -12", &e, 0) == 0xFFFFFFFFFFFFFFF4); |
| } |
| |
| TEST_ASSERT(strtoull("!", &e, 0) == 0); |
| TEST_ASSERT(e && (*e == '!')); |
| |
| TEST_ASSERT(strtoull("+!", &e, 0) == 0); |
| |
| if (broken_strtoull) { |
| TEST_ASSERT(e && (*e == '!')); |
| } else { |
| TEST_ASSERT(e && (*e == '+')); |
| } |
| |
| TEST_ASSERT(strtoull("+0!", &e, 0) == 0); |
| TEST_ASSERT(e && (*e == '!')); |
| TEST_ASSERT(strtoull("+0x!", &e, 0) == 0); |
| |
| if (broken_strtoull) { |
| TEST_ASSERT(e && (*e == '!')); |
| } else { |
| TEST_ASSERT(e && (*e == '+')); |
| } |
| |
| TEST_ASSERT(strtoull("+0X!", &e, 0) == 0); |
| |
| if (broken_strtoull) { |
| TEST_ASSERT(e && (*e == '!')); |
| } else { |
| TEST_ASSERT(e && (*e == '+')); |
| } |
| return EC_SUCCESS; |
| } |
| |
| __no_optimization static int test_strncpy(void) |
| { |
| char dest[10]; |
| |
| strncpy(dest, "test", 10); |
| TEST_ASSERT_ARRAY_EQ("test", dest, 5); |
| strncpy(dest, "12345", 6); |
| TEST_ASSERT_ARRAY_EQ("12345", dest, 6); |
| /* |
| * gcc complains: |
| * error: ‘__builtin_strncpy’ output truncated copying 10 bytes from a |
| * string of length 12 [-Werror=stringop-truncation] |
| */ |
| DISABLE_GCC_WARNING("-Wstringop-truncation"); |
| strncpy(dest, "testtesttest", 10); |
| ENABLE_GCC_WARNING("-Wstringop-truncation"); |
| TEST_ASSERT_ARRAY_EQ("testtestte", dest, 10); |
| |
| return EC_SUCCESS; |
| } |
| |
| __no_optimization static int test_strncmp(void) |
| { |
| TEST_ASSERT(strncmp("123", "123", 8) == 0); |
| TEST_ASSERT(strncmp("789", "456", 8) > 0); |
| TEST_ASSERT(strncmp("abc", "abd", 4) < 0); |
| TEST_ASSERT(strncmp("abc", "abd", 2) == 0); |
| return EC_SUCCESS; |
| } |
| |
| __no_optimization static int test_memcmp(void) |
| { |
| TEST_ASSERT(memcmp("12345678", "12345678", 8) == 0); |
| TEST_ASSERT(memcmp("78945612", "45612378", 8) > 0); |
| TEST_ASSERT(memcmp("abc", "abd", 4) < 0); |
| TEST_ASSERT(memcmp("abc", "abd", 2) == 0); |
| return EC_SUCCESS; |
| } |
| |
| __no_optimization static int test_strlen(void) |
| { |
| TEST_ASSERT(strlen("this is a string") == 16); |
| return EC_SUCCESS; |
| } |
| |
| __no_optimization static int test_strnlen(void) |
| { |
| TEST_ASSERT(strnlen("this is a string", 17) == 16); |
| TEST_ASSERT(strnlen("this is a string", 16) == 16); |
| TEST_ASSERT(strnlen("this is a string", 5) == 5); |
| |
| return EC_SUCCESS; |
| } |
| |
| __no_optimization static int test_strcasecmp(void) |
| { |
| TEST_ASSERT(strcasecmp("test string", "TEST strIng") == 0); |
| TEST_ASSERT(strcasecmp("test123!@#", "TesT123!@#") == 0); |
| TEST_ASSERT(strcasecmp("lower", "UPPER") != 0); |
| return EC_SUCCESS; |
| } |
| |
| __no_optimization static int test_strncasecmp(void) |
| { |
| TEST_ASSERT(strncasecmp("test string", "TEST str", 4) == 0); |
| TEST_ASSERT(strncasecmp("test string", "TEST str", 8) == 0); |
| TEST_ASSERT(strncasecmp("test123!@#", "TesT321!@#", 5) != 0); |
| TEST_ASSERT(strncasecmp("test123!@#", "TesT321!@#", 4) == 0); |
| TEST_ASSERT(strncasecmp("1test123!@#", "1TesT321!@#", 5) == 0); |
| TEST_ASSERT(strncasecmp("1test123", "teststr", 0) == 0); |
| return EC_SUCCESS; |
| } |
| |
| __no_optimization static int test_atoi(void) |
| { |
| TEST_ASSERT(atoi(" 901") == 901); |
| TEST_ASSERT(atoi("-12c") == -12); |
| TEST_ASSERT(atoi(" 0 ") == 0); |
| TEST_ASSERT(atoi("\t111") == 111); |
| return EC_SUCCESS; |
| } |
| |
| __no_optimization static int test_snprintf(void) |
| { |
| char buffer[32]; |
| |
| TEST_ASSERT(snprintf(buffer, sizeof(buffer), "%u", 1234) == 4); |
| TEST_ASSERT(strncmp(buffer, "1234", sizeof(buffer)) == 0); |
| return EC_SUCCESS; |
| } |
| |
| __no_optimization static int test_strcspn(void) |
| { |
| const char str1[] = "abc"; |
| const char str2[] = "This is a string\nwith newlines!"; |
| |
| TEST_EQ(strcspn(str1, "a"), (size_t)0, "%zu"); |
| TEST_EQ(strcspn(str1, "b"), (size_t)1, "%zu"); |
| TEST_EQ(strcspn(str1, "c"), (size_t)2, "%zu"); |
| TEST_EQ(strcspn(str1, "ccc"), (size_t)2, "%zu"); |
| TEST_EQ(strcspn(str1, "cba"), (size_t)0, "%zu"); |
| TEST_EQ(strcspn(str1, "cb"), (size_t)1, "%zu"); |
| TEST_EQ(strcspn(str1, "bc"), (size_t)1, "%zu"); |
| TEST_EQ(strcspn(str1, "cbc"), (size_t)1, "%zu"); |
| TEST_EQ(strcspn(str1, "z"), strlen(str1), "%zu"); |
| TEST_EQ(strcspn(str1, "xyz"), strlen(str1), "%zu"); |
| TEST_EQ(strcspn(str1, ""), strlen(str1), "%zu"); |
| |
| TEST_EQ(strcspn(str2, " "), (size_t)4, "%zu"); |
| TEST_EQ(strcspn(str2, "\n"), (size_t)16, "%zu"); |
| TEST_EQ(strcspn(str2, "\n "), (size_t)4, "%zu"); |
| TEST_EQ(strcspn(str2, "!"), strlen(str2) - 1, "%zu"); |
| TEST_EQ(strcspn(str2, "z"), strlen(str2), "%zu"); |
| TEST_EQ(strcspn(str2, "z!"), strlen(str2) - 1, "%zu"); |
| |
| return EC_SUCCESS; |
| } |
| |
| static int test_memmove(void) |
| { |
| const int buf_size = 16; |
| char buf[buf_size]; |
| |
| for (int i = 0; i < buf_size; ++i) |
| buf[i] = i & 0x7f; |
| |
| /* Test small moves */ |
| memmove(buf + 1, buf, 1); |
| TEST_ASSERT_ARRAY_EQ(buf + 1, buf, 1); |
| memmove(buf + 5, buf, 4); |
| memmove(buf + 1, buf, 4); |
| TEST_ASSERT_ARRAY_EQ(buf + 1, buf + 5, 4); |
| |
| return EC_SUCCESS; |
| } |
| |
| static int test_memmove_overlap(void) |
| { |
| const int buf_size = 120; |
| const int len = 80; |
| char buf[buf_size]; |
| char *buf_dst; |
| char cmp_buf[len]; |
| |
| for (int i = 0; i < len; ++i) |
| buf[i] = i & 0x7f; |
| for (int i = len; i < buf_size; ++i) |
| buf[i] = 0; |
| /* Store the original buffer. */ |
| memcpy(cmp_buf, buf, len); |
| |
| buf_dst = buf + (buf_size - len) - 1; |
| memmove(buf_dst, buf, len); /* unaligned */ |
| TEST_ASSERT_ARRAY_EQ(buf_dst, cmp_buf, len); |
| |
| for (int i = 0; i < len; ++i) |
| buf[i] = i & 0x7f; |
| for (int i = len; i < buf_size; ++i) |
| buf[i] = 0; |
| buf_dst = buf + (buf_size - len); |
| memmove(buf_dst, buf, len); /* aligned */ |
| TEST_ASSERT_ARRAY_EQ(buf_dst, cmp_buf, len); |
| |
| return EC_SUCCESS; |
| } |
| |
| static int test_memmove_benchmark(void) |
| { |
| int i; |
| timestamp_t t0, t1, t2, t3; |
| char *buf; |
| const int buf_size = 1000; |
| const int len = 400; |
| const int iteration = 1000; |
| |
| TEST_ASSERT(shared_mem_acquire(buf_size, &buf) == EC_SUCCESS); |
| |
| for (int i = 0; i < buf_size; ++i) |
| buf[i] = i & 0x7f; |
| |
| t0 = get_time(); |
| for (i = 0; i < iteration; ++i) |
| memmove(buf + 101, buf, len); /* unaligned */ |
| t1 = get_time(); |
| ccprintf(" (speed gain: %" PRId64 " ->", t1.val - t0.val); |
| |
| t2 = get_time(); |
| for (i = 0; i < iteration; ++i) |
| memmove(buf + 100, buf, len); /* aligned */ |
| t3 = get_time(); |
| ccprintf(" %" PRId64 " us) ", t3.val - t2.val); |
| |
| shared_mem_release(buf); |
| if (!IS_ENABLED(EMU_BUILD) && IS_ENABLED(USE_BUILTIN_STDLIB)) { |
| TEST_ASSERT((t1.val - t0.val) > (t3.val - t2.val)); |
| } |
| |
| return EC_SUCCESS; |
| } |
| |
| static int test_memcpy(void) |
| { |
| int i; |
| timestamp_t t0, t1, t2, t3; |
| char *buf; |
| const int buf_size = 1000; |
| const int len = 400; |
| const int dest_offset = 500; |
| const int iteration = 1000; |
| |
| TEST_ASSERT(shared_mem_acquire(buf_size, &buf) == EC_SUCCESS); |
| |
| for (i = 0; i < len; ++i) |
| buf[i] = i & 0x7f; |
| for (i = len; i < buf_size; ++i) |
| buf[i] = 0; |
| |
| t0 = get_time(); |
| for (i = 0; i < iteration; ++i) |
| memcpy(buf + dest_offset + 1, buf, len); /* unaligned */ |
| t1 = get_time(); |
| TEST_ASSERT_ARRAY_EQ(buf + dest_offset + 1, buf, len); |
| ccprintf(" (speed gain: %" PRId64 " ->", t1.val - t0.val); |
| |
| t2 = get_time(); |
| for (i = 0; i < iteration; ++i) |
| memcpy(buf + dest_offset, buf, len); /* aligned */ |
| t3 = get_time(); |
| ccprintf(" %" PRId64 " us) ", t3.val - t2.val); |
| TEST_ASSERT_ARRAY_EQ(buf + dest_offset, buf, len); |
| |
| if (!IS_ENABLED(EMU_BUILD)) |
| TEST_ASSERT((t1.val - t0.val) > (t3.val - t2.val)); |
| |
| memcpy(buf + dest_offset + 1, buf + 1, len - 1); |
| TEST_ASSERT_ARRAY_EQ(buf + dest_offset + 1, buf + 1, len - 1); |
| |
| /* Test small copies */ |
| memcpy(buf + dest_offset, buf, 1); |
| TEST_ASSERT_ARRAY_EQ(buf + dest_offset, buf, 1); |
| memcpy(buf + dest_offset, buf, 4); |
| TEST_ASSERT_ARRAY_EQ(buf + dest_offset, buf, 4); |
| memcpy(buf + dest_offset + 1, buf, 1); |
| TEST_ASSERT_ARRAY_EQ(buf + dest_offset + 1, buf, 1); |
| memcpy(buf + dest_offset + 1, buf, 4); |
| TEST_ASSERT_ARRAY_EQ(buf + dest_offset + 1, buf, 4); |
| |
| shared_mem_release(buf); |
| return EC_SUCCESS; |
| } |
| |
| /* Plain memset, used as a reference to measure speed gain */ |
| static void *dumb_memset(void *dest, int c, int len) |
| { |
| volatile char *d = (char *)dest; |
| while (len > 0) { |
| *(d++) = c; |
| len--; |
| } |
| return dest; |
| } |
| |
| static int test_memset(void) |
| { |
| int i; |
| timestamp_t t0, t1, t2, t3; |
| char *buf; |
| const int buf_size = 1000; |
| const int len = 400; |
| const int iteration = 1000; |
| |
| TEST_ASSERT(shared_mem_acquire(buf_size, &buf) == EC_SUCCESS); |
| |
| t0 = get_time(); |
| for (i = 0; i < iteration; ++i) |
| dumb_memset(buf, 1, len); |
| t1 = get_time(); |
| TEST_ASSERT_MEMSET(buf, (char)1, len); |
| ccprintf(" (speed gain: %" PRId64 " ->", t1.val - t0.val); |
| |
| t2 = get_time(); |
| for (i = 0; i < iteration; ++i) |
| memset(buf, 1, len); |
| t3 = get_time(); |
| TEST_ASSERT_MEMSET(buf, (char)1, len); |
| ccprintf(" %" PRId64 " us) ", t3.val - t2.val); |
| |
| if (!IS_ENABLED(EMU_BUILD)) |
| TEST_ASSERT((t1.val - t0.val) > (t3.val - t2.val)); |
| |
| memset(buf, 128, len); |
| TEST_ASSERT_MEMSET(buf, (char)128, len); |
| |
| memset(buf, -2, len); |
| TEST_ASSERT_MEMSET(buf, (char)-2, len); |
| |
| memset(buf + 1, 1, len - 2); |
| TEST_ASSERT_MEMSET(buf + 1, (char)1, len - 2); |
| |
| shared_mem_release(buf); |
| return EC_SUCCESS; |
| } |
| |
| __no_optimization static int test_memchr(void) |
| { |
| char *buf = "1234"; |
| |
| TEST_ASSERT(memchr("123567890", '4', 8) == NULL); |
| TEST_ASSERT(memchr("123", '3', 2) == NULL); |
| TEST_ASSERT(memchr(buf, '3', 4) == buf + 2); |
| TEST_ASSERT(memchr(buf, '4', 4) == buf + 3); |
| return EC_SUCCESS; |
| } |
| |
| void run_test(int argc, const char **argv) |
| { |
| test_reset(); |
| |
| RUN_TEST(test_isalpha); |
| RUN_TEST(test_isupper); |
| RUN_TEST(test_isprint); |
| RUN_TEST(test_strstr); |
| RUN_TEST(test_strtoull); |
| RUN_TEST(test_strncpy); |
| RUN_TEST(test_strncmp); |
| RUN_TEST(test_strlen); |
| RUN_TEST(test_strnlen); |
| RUN_TEST(test_strcasecmp); |
| RUN_TEST(test_strncasecmp); |
| RUN_TEST(test_atoi); |
| RUN_TEST(test_snprintf); |
| RUN_TEST(test_strcspn); |
| RUN_TEST(test_memmove); |
| RUN_TEST(test_memmove_overlap); |
| RUN_TEST(test_memmove_benchmark); |
| RUN_TEST(test_memcpy); |
| RUN_TEST(test_memset); |
| RUN_TEST(test_memchr); |
| RUN_TEST(test_memcmp); |
| |
| test_print_result(); |
| } |