diff options
author | Ian Moffett <ian@osmora.org> | 2024-03-07 17:28:00 -0500 |
---|---|---|
committer | Ian Moffett <ian@osmora.org> | 2024-03-07 17:28:32 -0500 |
commit | bd5969fc876a10b18613302db7087ef3c40f18e1 (patch) | |
tree | 7c2b8619afe902abf99570df2873fbdf40a4d1a1 /lib/mlibc/tests | |
parent | a95b38b1b92b172e6cc4e8e56a88a30cc65907b0 (diff) |
lib: Add mlibc
Signed-off-by: Ian Moffett <ian@osmora.org>
Diffstat (limited to 'lib/mlibc/tests')
165 files changed, 6763 insertions, 0 deletions
diff --git a/lib/mlibc/tests/ansi/abs.c b/lib/mlibc/tests/ansi/abs.c new file mode 100644 index 0000000..492040c --- /dev/null +++ b/lib/mlibc/tests/ansi/abs.c @@ -0,0 +1,16 @@ +#include <stdlib.h> +#include <assert.h> +#include <limits.h> + +int main(){ + assert(abs(-10) == 10); + assert(abs(2021) == 2021); + + assert(labs(-256) == 256); + assert(labs(10034890) == 10034890); + + assert(llabs(-0x2deadbeef) == 0x2deadbeef); + assert(llabs(49238706947) == 49238706947); + + return 0; +}
\ No newline at end of file diff --git a/lib/mlibc/tests/ansi/alloc.c b/lib/mlibc/tests/ansi/alloc.c new file mode 100644 index 0000000..c6ce870 --- /dev/null +++ b/lib/mlibc/tests/ansi/alloc.c @@ -0,0 +1,37 @@ +#include <stdlib.h> +#include <assert.h> +#include <stdint.h> +#include <errno.h> + +int main() { + void *p; + + p = aligned_alloc(sizeof(void *), sizeof(void *)); + assert(p != NULL && (uintptr_t)p % sizeof(void *) == 0); + free(p); + + p = aligned_alloc(256, 256); + assert(p != NULL && (uintptr_t)p % 256 == 0); + free(p); + + // small alignments are okay + p = aligned_alloc(1, 8); + assert(p != NULL); + free(p); + p = aligned_alloc(1, 1); + assert(p != NULL); + free(p); + + // It seems that glibc doesn't report error in these cases. +#if !(defined(USE_HOST_LIBC) && defined(__GLIBC__)) + // size % align must be 0 + p = aligned_alloc(256, 1); + assert(errno == EINVAL); + assert(p == NULL); + + // align must be a 'valid alignment supported by the implementation' + p = aligned_alloc(3, 1); + assert(errno == EINVAL); + assert(p == NULL); +#endif +} diff --git a/lib/mlibc/tests/ansi/calloc.c b/lib/mlibc/tests/ansi/calloc.c new file mode 100644 index 0000000..6cfc87d --- /dev/null +++ b/lib/mlibc/tests/ansi/calloc.c @@ -0,0 +1,20 @@ +#include <assert.h> +#include <errno.h> +#include <limits.h> +#include <stdint.h> +#include <stdlib.h> + +int main() { + errno = 0; + void *ptr = calloc((SIZE_MAX / 2) + 2, 2); + assert(!ptr); + assert(errno); + + errno = 0; + ptr = calloc(sizeof(size_t), 10); + assert(ptr); + for(size_t i = 0; i < 10; i++) { + size_t *p = ptr; + assert(!p[i]); + } +} diff --git a/lib/mlibc/tests/ansi/creal-cimag.c b/lib/mlibc/tests/ansi/creal-cimag.c new file mode 100644 index 0000000..fc70aa3 --- /dev/null +++ b/lib/mlibc/tests/ansi/creal-cimag.c @@ -0,0 +1,37 @@ +#include <assert.h>
+#include <math.h>
+#include <complex.h>
+
+// FIXME: We should create a proper floating point facility
+// in order for other functions to be tested properly
+
+#define APPROXIMATELY_EQUAL(calculated, expected) fabs((calculated) - (expected)) <= 0.0000005
+#define APPROXIMATELY_EQUALF(calculated, expected) fabsf((calculated) - (expected)) <= 0.0000005f
+#define APPROXIMATELY_EQUALL(calculated, expected) fabsl((calculated) - (expected)) <= 0.0000005L
+
+#define IS_COMPLEX_NUMBER(Z) \
+ _Generic((Z), \
+ double complex: 1, \
+ float complex: 1, \
+ long double complex: 1, \
+ default: 0 \
+ )
+
+int main() {
+ assert(IS_COMPLEX_NUMBER(CMPLX(5.2, 4.3)));
+ double complex cz = CMPLX(5.2, 4.3);
+ assert(APPROXIMATELY_EQUAL(creal(cz), 5.2));
+ assert(APPROXIMATELY_EQUAL(cimag(cz), 4.3));
+
+ assert(IS_COMPLEX_NUMBER(CMPLXF(1.2f, 2.5f)));
+ float complex czf = CMPLXF(1.2f, 2.5f);
+ assert(APPROXIMATELY_EQUALF(crealf(czf), 1.2f));
+ assert(APPROXIMATELY_EQUALF(cimagf(czf), 2.5f));
+
+ assert(IS_COMPLEX_NUMBER(CMPLXL(0.1L, 123.54L)));
+ long double complex czl = CMPLXL(0.1L, 123.54L);
+ assert(APPROXIMATELY_EQUALL(creall(czl), 0.1L));
+ assert(APPROXIMATELY_EQUALL(cimagl(czl), 123.54L));
+
+ return 0;
+}
\ No newline at end of file diff --git a/lib/mlibc/tests/ansi/fenv.c b/lib/mlibc/tests/ansi/fenv.c new file mode 100644 index 0000000..629a5cb --- /dev/null +++ b/lib/mlibc/tests/ansi/fenv.c @@ -0,0 +1,91 @@ +#include <assert.h> +#include <stdbool.h> +#include <fenv.h> +#include <float.h> +#include <math.h> + +#define NO_OPTIMIZE(x) asm volatile("" :: "r,m" (x) : "memory") + +static void div_by_zero() { + volatile float zero = 0.0f; + NO_OPTIMIZE(69.0f / zero); +} + +static bool float_cmp(float a, float b) { + return a == b || fabs(a - b) < (fabs(a) + fabs(b)) * FLT_EPSILON; +} + +static void test_rounding(float expectation1, float expectation2) { + float x; + volatile float f = 1.968750f; + volatile float m = 0x1.0p23f; + + NO_OPTIMIZE(x = f + m); + assert(float_cmp(expectation1, x)); + NO_OPTIMIZE(x = x - m); + assert(x == expectation2); +} + +void test0() { + // test whether the divide-by-zero exception is raised + feclearexcept(FE_ALL_EXCEPT); + assert(fetestexcept(FE_ALL_EXCEPT) == 0); + + div_by_zero(); + int raised = fetestexcept(FE_DIVBYZERO); + assert((raised & FE_DIVBYZERO)); +} + +void test1() { + // test various rounding modes + feclearexcept(FE_DIVBYZERO); + assert(fetestexcept(FE_ALL_EXCEPT) == 0); + + fesetround(FE_UPWARD); + assert(fegetround() == FE_UPWARD); + test_rounding(8388610.0f, 2.0f); + + fesetround(FE_DOWNWARD); + assert(fegetround() == FE_DOWNWARD); + test_rounding(8388609.0f, 1.0f); + + fesetround(FE_TONEAREST); + assert(fegetround() == FE_TONEAREST); + test_rounding(8388610.0f, 2.0f); + + fesetround(FE_TOWARDZERO); + assert(fegetround() == FE_TOWARDZERO); + test_rounding(8388609.0f, 1.0f); +} + +void test2() { + // test feraiseexcept + feclearexcept(FE_ALL_EXCEPT); + assert(fetestexcept(FE_ALL_EXCEPT) == 0); + assert(feraiseexcept(FE_DIVBYZERO | FE_OVERFLOW) == 0); + assert(fetestexcept(FE_ALL_EXCEPT) == (FE_DIVBYZERO | FE_OVERFLOW)); +} + +void test3() { + // test fe{get,set}env + feclearexcept(FE_ALL_EXCEPT); + assert(fetestexcept(FE_ALL_EXCEPT) == 0); + assert(feraiseexcept(FE_OVERFLOW) == 0); + + fenv_t state; + assert(fegetenv(&state) == 0); + assert(fetestexcept(FE_ALL_EXCEPT) == FE_OVERFLOW); + + div_by_zero(); + assert(fetestexcept(FE_ALL_EXCEPT) == (FE_DIVBYZERO | FE_OVERFLOW)); + + assert(fesetenv(&state) == 0); + assert(fetestexcept(FE_ALL_EXCEPT) == FE_OVERFLOW); +} + +int main() { + test0(); + test1(); + test2(); + test3(); +} diff --git a/lib/mlibc/tests/ansi/fopen.c b/lib/mlibc/tests/ansi/fopen.c new file mode 100644 index 0000000..f4818b2 --- /dev/null +++ b/lib/mlibc/tests/ansi/fopen.c @@ -0,0 +1,59 @@ +#include <stdio.h> +#include <assert.h> +#include <string.h> + +#ifdef USE_HOST_LIBC +#define TEST_FILE "fopen-host-libc.tmp" +#else +#define TEST_FILE "fopen.tmp" +#endif + +int main() { + FILE *file; + char str[] = "mlibc fopen test"; + char str2[] = " mlibc appending"; + char completestr[] = "mlibc fopen test mlibc appending"; + char buffer[100]; + char buffer2[100]; + + // Clear all the buffers to zero. + memset(buffer, 0, sizeof(buffer)); + memset(buffer2, 0, sizeof(buffer2)); + + // Open the file for writing. + file = fopen(TEST_FILE, "w"); + assert(file); + + // Write string minus null terminator, flush and close. + fwrite(str, 1, sizeof(str) - 1, file); + fflush(file); + fclose(file); + + // Open the file for reading. + file = fopen(TEST_FILE, "r"); + assert(file); + + // Verify that we read back the written string and close the file. + assert(fread(buffer, 1, sizeof(str) - 1, file)); + assert(!strcmp(buffer, str)); + fclose(file); + + // Open the file in appending mode, append string 2 (minus the null terminator) to the file, flush and close. + file = fopen(TEST_FILE, "a"); + fwrite(str2, 1, sizeof(str2) - 1, file); + fflush(file); + fclose(file); + + // Open the file for reading again, verify the contents, close the file and return. + file = fopen(TEST_FILE, "r"); + assert(fread(buffer2, 1, sizeof(completestr) - 1, file)); + assert(!strcmp(buffer2, completestr)); + fclose(file); + + // Check that stdout, stdin and stderr can be closed by the application (issue #12). + fclose(stdout); + fclose(stdin); + fclose(stderr); + + return 0; +} diff --git a/lib/mlibc/tests/ansi/freopen.c b/lib/mlibc/tests/ansi/freopen.c new file mode 100644 index 0000000..c91577f --- /dev/null +++ b/lib/mlibc/tests/ansi/freopen.c @@ -0,0 +1,49 @@ +#include <assert.h> +#include <stdio.h> +#include <string.h> + +#ifdef USE_HOST_LIBC +#define TEST_FILE "freopen-host-libc.tmp" +#else +#define TEST_FILE "freopen.tmp" +#endif + +int main() { + FILE *file = fopen(TEST_FILE, "w"); + assert(file); + + assert(freopen("/dev/null", "w", file)); + + char str[] = "mlibc freopen test"; + fwrite(str, 1, sizeof(str) - 1, file); + fflush(file); + fclose(file); + + file = fopen(TEST_FILE, "r"); + assert(file); + + char buf[sizeof(str)]; + memset(buf, 0, sizeof(buf)); + int ret = fread(buf, 1, sizeof(buf) - 1, file); + fprintf(stderr, "ret %d\n", ret); + assert(ret == 0); + fclose(file); + + file = fopen("/dev/null", "w"); + assert(file); + + assert(freopen(TEST_FILE, "w", file)); + fwrite(str, 1, sizeof(str) - 1, file); + fflush(file); + fclose(file); + + memset(buf, 0, sizeof(buf)); + file = fopen(TEST_FILE, "r"); + assert(fread(buf, 1, sizeof(buf) - 1, file)); + + fprintf(stderr, "buffer content '%s'\n", buf); + assert(!strcmp(buf, "mlibc freopen test")); + fclose(file); + + return 0; +} diff --git a/lib/mlibc/tests/ansi/locale.c b/lib/mlibc/tests/ansi/locale.c new file mode 100644 index 0000000..056298b --- /dev/null +++ b/lib/mlibc/tests/ansi/locale.c @@ -0,0 +1,17 @@ +#include <stdio.h> +#include <wchar.h> +#include <locale.h> +#include <assert.h> + +int main() { + wchar_t c = 0xC9; + unsigned char buf[sizeof(wchar_t)] = { 0 }; + setlocale(LC_ALL, ""); + if (sprintf(buf, "%lc", c) < 0) + return -1; + + assert(buf[0] == 0xc3 && buf[1] == 0x89 + && buf[2] == '\0' && buf[3] == '\0'); + + return 0; +} diff --git a/lib/mlibc/tests/ansi/longjmp.c b/lib/mlibc/tests/ansi/longjmp.c new file mode 100644 index 0000000..9f9c1ed --- /dev/null +++ b/lib/mlibc/tests/ansi/longjmp.c @@ -0,0 +1,29 @@ +#include <stdio.h> +#include <setjmp.h> +#include <stdnoreturn.h> +#include <assert.h> + +jmp_buf buf; + +noreturn void do_jump(int arg) { + longjmp(buf, 2 * arg); +} + +int main(void) { + volatile int times_called = 0; + + if (setjmp(buf) != 8) { + do_jump(++times_called); + } + + assert(times_called == 4); + + times_called = 0; + int ret = setjmp(buf); + assert(ret == times_called); + + if (!ret) { + times_called = 1; + longjmp(buf, 0); + } +} diff --git a/lib/mlibc/tests/ansi/mbrtoc32.c b/lib/mlibc/tests/ansi/mbrtoc32.c new file mode 100644 index 0000000..781bc71 --- /dev/null +++ b/lib/mlibc/tests/ansi/mbrtoc32.c @@ -0,0 +1,53 @@ +#include <assert.h> +#include <locale.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> +#include <uchar.h> + +struct { + size_t ret; + char32_t c32; + uint8_t bytes[4]; +} expected_results[] = { + {1, 0x7A, {0x7A, 0, 0, 0}}, + {2, 0xDF, {0xC3, 0x9F, 0, 0}}, + {3, 0x6C34, {0xE6, 0xB0, 0xB4, 0}}, + {4, 0x1F34C, {0xF0, 0x9F, 0x8D, 0x8C}}, +}; + +int main() { + setlocale(LC_ALL, "en_US.utf8"); + + size_t ret = 0; + + char *str = "z\u00df\u6c34\U0001F34C"; + + fprintf(stderr, "string: '%s'\n", str); + + mbstate_t state = {}; + char32_t c32; + size_t loop = 0; + while((ret = mbrtoc32(&c32, str, strlen(str), &state))) { + assert(ret != (size_t)-3); + if(ret == (size_t)-1) + break; + if(ret == (size_t)-2) + break; + + fprintf(stderr, "Next UTF-32 char: 0x%x obtained from %zu bytes [", c32, ret); + for(size_t n = 0; n < ret; ++n) { + fprintf(stderr, " 0x%02x ", (uint8_t) str[n]); + } + fprintf(stderr, "]\n"); + + assert(ret == expected_results[loop].ret); + assert(c32 == expected_results[loop].c32); + for(size_t n = 0; n < ret; ++n) { + assert((uint8_t) str[n] == expected_results[loop].bytes[n]); + } + + str += ret; + loop++; + } +} diff --git a/lib/mlibc/tests/ansi/memmem.c b/lib/mlibc/tests/ansi/memmem.c new file mode 100644 index 0000000..8de558f --- /dev/null +++ b/lib/mlibc/tests/ansi/memmem.c @@ -0,0 +1,22 @@ +#include <string.h> +#include <assert.h> + +int main() { + char *haystack = "abc123\0x45"; + + char *needle1 = "abc"; + void *rv = memmem(haystack, strlen(haystack), needle1, strlen(needle1)); + assert(rv == haystack); + + char *needle2 = "123"; + rv = memmem(haystack, strlen(haystack), needle2, strlen(needle2)); + assert(rv == haystack + 3); + + char *needle3 = "1234"; + rv = memmem(haystack, strlen(haystack), needle3, strlen(needle3)); + assert(rv == NULL); + + char *needle4 = "23\0x45"; + rv = memmem(haystack, 10, needle4, 6); + assert(rv == haystack + 4); +} diff --git a/lib/mlibc/tests/ansi/qsort.c b/lib/mlibc/tests/ansi/qsort.c new file mode 100644 index 0000000..bb48a23 --- /dev/null +++ b/lib/mlibc/tests/ansi/qsort.c @@ -0,0 +1,31 @@ +#include <assert.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +static const char *arr[] = { "xyz", "abc", "ghi", "def" }; + +static const size_t magic = 0xDEADBEEF; + +static int cmpstringp(const void *p1, const void *p2, void *ctx) { + /* The actual arguments to this function are "pointers to + * pointers to char", but strcmp(3) arguments are "pointers + * to char", hence the following cast plus dereference. */ + assert(*(size_t *) ctx == magic); + return strcmp(*(const char **) p1, *(const char **) p2); +} + +int main() { + qsort_r(&arr[0], sizeof(arr) / sizeof(*arr), sizeof(char *), cmpstringp, (void *) &magic); + + assert(!strcmp(arr[0], "abc")); + assert(!strcmp(arr[1], "def")); + assert(!strcmp(arr[2], "ghi")); + assert(!strcmp(arr[3], "xyz")); + + for(size_t i = 0; i < sizeof(arr) / sizeof(*arr); i++) { + fprintf(stderr, "%s\n", arr[i]); + } + + return 0; +} diff --git a/lib/mlibc/tests/ansi/snprintf.c b/lib/mlibc/tests/ansi/snprintf.c new file mode 100644 index 0000000..82e4640 --- /dev/null +++ b/lib/mlibc/tests/ansi/snprintf.c @@ -0,0 +1,23 @@ +#include <stdio.h> +#include <assert.h> +#include <string.h> + +int main() { + char buffer[10]; + int ret = snprintf(buffer, 10, "%d", 123456789); + assert(strncmp("123456789", buffer, 10) == 0); + assert(ret == 9); + + // We deliberately induce a warning here. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-truncation" + ret = snprintf(buffer, 10, "%d", 1234567890); + assert(strncmp("123456789", buffer, 10) == 0); + assert(ret == 10); +#pragma GCC diagnostic pop + + // mlibc issue #118. + ret = snprintf(NULL, 0, "%d", 123456789); + assert(ret == 9); + return 0; +} diff --git a/lib/mlibc/tests/ansi/sprintf.c b/lib/mlibc/tests/ansi/sprintf.c new file mode 100644 index 0000000..fd9398c --- /dev/null +++ b/lib/mlibc/tests/ansi/sprintf.c @@ -0,0 +1,191 @@ +#include <stdio.h> +#include <assert.h> +#include <math.h> +#include <string.h> + +int main() { + char buf[11] = { 0 }; + sprintf(buf, "%d", 12); + assert(!strcmp(buf, "12")); + sprintf(buf, "%f", 3.14); + assert(!strcmp(buf, "3.140000")); + + // Test %c right padding. + sprintf(buf, "%-2c", 'a'); + assert(!strcmp(buf, "a ")); + + // Test %c left padding. + sprintf(buf, "%2c", 'a'); + assert(!strcmp(buf, " a")); + + // Test %d right padding - mlibc issue #58. + sprintf(buf, "%-2d", 1); + assert(!strcmp(buf, "1 ")); + sprintf(buf, "%-2.2d", 1); + assert(!strcmp(buf, "01")); + sprintf(buf, "%-3.2d", 1); + assert(!strcmp(buf, "01 ")); + sprintf(buf, "%-3.2d", 12); + assert(!strcmp(buf, "12 ")); + sprintf(buf, "%-3.2d", 123); + assert(!strcmp(buf, "123")); + sprintf(buf, "%-3.2u", 12); + assert(!strcmp(buf, "12 ")); + + // Test %d left padding. + sprintf(buf, "%2d", 1); + assert(!strcmp(buf, " 1")); + sprintf(buf, "%3.2d", 1); + assert(!strcmp(buf, " 01")); + sprintf(buf, "%3.2d", 12); + assert(!strcmp(buf, " 12")); + sprintf(buf, "%3.2d", 123); + assert(!strcmp(buf, "123")); + sprintf(buf, "%3.2u", 12); + assert(!strcmp(buf, " 12")); + + // Test %f padding. + // TODO: Test printing of huge numbers (larger than 2^64) + sprintf(buf, "%2.f", 1.2); + assert(!strcmp(buf, " 1")); + sprintf(buf, "%2.f", 12.3); + assert(!strcmp(buf, "12")); + sprintf(buf, "%5.2f", 1.0); + assert(!strcmp(buf, " 1.00")); + sprintf(buf, "%.1f", -4.0); + assert(!strcmp(buf, "-4.0")); + sprintf(buf, "%-3.f", 8.0); + assert(!strcmp(buf, "8 ")); + sprintf(buf, "%4f", INFINITY); + assert(!strcmp(buf, " inf") || !strcmp(buf, "infinity")); + sprintf(buf, "%4f", NAN); + assert(!strcmp(buf, " nan")); + sprintf(buf, "%4F", INFINITY); + assert(!strcmp(buf, " INF") || !strcmp(buf, "INFINITY")); + sprintf(buf, "%4F", NAN); + assert(!strcmp(buf, " NAN")); + sprintf(buf, "%05.2f", 1.0); + assert(!strcmp(buf, "01.00")); + sprintf(buf, "%09f", INFINITY); // 0 ignored when padding infs + assert(!strcmp(buf, " inf") || !strcmp(buf, " infinity")); + // TODO: We don't yet round properly + // sprintf(buf, "%5.2f", 1.2); + // assert(!strcmp(buf, " 1.20")); + // sprintf(buf, "%5.2f", 1.23); + // assert(!strcmp(buf, " 1.23")); + // sprintf(buf, "%5.2f", 1.234); + // assert(!strcmp(buf, " 1.23")); + // sprintf(buf, "%5.2f", 12.345); + // assert(!strcmp(buf, "12.35")); + // sprintf(buf, "%-5.2f", 1.2); + // assert(!strcmp(buf, "1.20 ")); + + // Test '+' and ' ' flags - mlibc issue #229. + // Disable -Wformat here since we deliberately induce some warnings. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat" + sprintf(buf, "%+d", 12); + assert(!strcmp(buf, "+12")); + sprintf(buf, "% d", 12); + assert(!strcmp(buf, " 12")); + sprintf(buf, "% +d", 12); + assert(!strcmp(buf, "+12")); + sprintf(buf, "%+ d", 12); + assert(!strcmp(buf, "+12")); + sprintf(buf, "%+d", -12); + assert(!strcmp(buf, "-12")); + sprintf(buf, "% d", -12); + assert(!strcmp(buf, "-12")); + sprintf(buf, "% +d", -12); + assert(!strcmp(buf, "-12")); + sprintf(buf, "%+ d", -12); + assert(!strcmp(buf, "-12")); +#pragma GCC diagnostic pop + + // Test '#' flag. + // TODO: Test with a, A, e, E, f, F, g, G conversions. + sprintf(buf, "%#x", 12); + assert(!strcmp(buf, "0xc")); + sprintf(buf, "%#X", 12); + assert(!strcmp(buf, "0XC")); + sprintf(buf, "%#o", 12); + assert(!strcmp(buf, "014")); + sprintf(buf, "%#x", 0); + assert(!strcmp(buf, "0")); + sprintf(buf, "%#X", 0); + assert(!strcmp(buf, "0")); + sprintf(buf, "%#o", 0); + assert(!strcmp(buf, "0")); + + // Test 'd' with different size mods to see + // if they work + sprintf(buf, "%d", 12); + assert(!strcmp(buf, "12")); + sprintf(buf, "%ld", 12L); + assert(!strcmp(buf, "12")); + sprintf(buf, "%lld", 12LL); + assert(!strcmp(buf, "12")); + sprintf(buf, "%zd", (size_t)12); + assert(!strcmp(buf, "12")); + sprintf(buf, "%hd", 12); + assert(!strcmp(buf, "12")); + sprintf(buf, "%hhd", 12); + assert(!strcmp(buf, "12")); + + // Test 'x' with different size mods to see + // if they work + sprintf(buf, "%x", 12); + assert(!strcmp(buf, "c")); + sprintf(buf, "%lx", 12L); + assert(!strcmp(buf, "c")); + sprintf(buf, "%llx", 12LL); + assert(!strcmp(buf, "c")); + sprintf(buf, "%zx", (size_t)12); + assert(!strcmp(buf, "c")); + sprintf(buf, "%hx", 12); + assert(!strcmp(buf, "c")); + sprintf(buf, "%hhx", 12); + assert(!strcmp(buf, "c")); + + // Test 'X' with different size mods to see + // if they work + sprintf(buf, "%X", 12); + assert(!strcmp(buf, "C")); + sprintf(buf, "%lX", 12L); + assert(!strcmp(buf, "C")); + sprintf(buf, "%llX", 12LL); + assert(!strcmp(buf, "C")); + sprintf(buf, "%zX", (size_t)12); + assert(!strcmp(buf, "C")); + sprintf(buf, "%hX", 12); + assert(!strcmp(buf, "C")); + sprintf(buf, "%hhX", 12); + assert(!strcmp(buf, "C")); + + // Test 'o' with different size mods to see + // if they work + sprintf(buf, "%o", 12); + assert(!strcmp(buf, "14")); + sprintf(buf, "%lo", 12L); + assert(!strcmp(buf, "14")); + sprintf(buf, "%llo", 12LL); + assert(!strcmp(buf, "14")); + sprintf(buf, "%zo", (size_t)12); + assert(!strcmp(buf, "14")); + sprintf(buf, "%ho", 12); + assert(!strcmp(buf, "14")); + sprintf(buf, "%hho", 12); + assert(!strcmp(buf, "14")); + + // Test %n$ syntax. + sprintf(buf, "%1$d", 12); + assert(!strcmp(buf, "12")); + sprintf(buf, "%1$d %1$d", 12); + assert(!strcmp(buf, "12 12")); + sprintf(buf, "%1$d %2$d %1$d", 12, 14); + assert(!strcmp(buf, "12 14 12")); + sprintf(buf, "%1$d %2$s %2$s", 12, "foo"); + assert(!strcmp(buf, "12 foo foo")); + + return 0; +} diff --git a/lib/mlibc/tests/ansi/sscanf.c b/lib/mlibc/tests/ansi/sscanf.c new file mode 100644 index 0000000..f3a881f --- /dev/null +++ b/lib/mlibc/tests/ansi/sscanf.c @@ -0,0 +1,143 @@ +#include <stdio.h> +#include <string.h> +#include <assert.h> + +struct format_test_cases { + const char *format; + const char *data; + int expected_int; + enum { + T_INT, + T_UINT, + T_CHAR, + T_NONE, + } type; + int ret; +} formats[] = { + {"%i", "0x420", 0x420, T_INT, 1}, + {"%i", "0420", 0420, T_INT, 1}, + {"%i", "420", 420, T_INT, 1}, + {"%i", "-420", -420, T_INT, 1}, + {"%d", "-12345", -12345, T_INT, 1}, + {"%u", "69", 69, T_UINT, 1}, + {"%u", "0420", 420, T_UINT, 1}, + {"%o", "0420", 0420, T_UINT, 1}, + {"%x", "0xCB7", 0xCB7, T_UINT, 1}, + {"%%", "%", 0, T_NONE, 0}, + {"%c", " I am not a fan of this solution.", ' ', T_CHAR, 1}, + {" %c", " CBT (capybara therapy)", 'C', T_CHAR, 1}, +}; + +#pragma GCC diagnostic ignored "-Wformat-security" + +static void test_matrix() { + for(size_t i = 0; i < (sizeof(formats) / sizeof(*formats)); i++) { + struct format_test_cases *f = &formats[i]; + int ret = -1; + int data_int; + unsigned int data_uint; + char data_char; + + switch(f->type) { + case T_INT: { + ret = sscanf(f->data, f->format, &data_int); + assert(data_int == f->expected_int); + break; + } + case T_UINT: { + ret = sscanf(f->data, f->format, &data_uint); + assert(data_uint == (unsigned int) f->expected_int); + break; + } + case T_CHAR: { + ret = sscanf(f->data, f->format, &data_char); + assert(data_char == (unsigned char) f->expected_int); + break; + } + case T_NONE: { + ret = sscanf(f->data, f->format); + break; + } + + } + + assert(ret == f->ret); + } +} + +int main() { + { + int x = 0; + char buf[] = "12345"; + sscanf(buf, "%d", &x); + assert(x == 12345); + } + + { + char c; + int n1; + int n2; + char buf[] = "z$ 7 5 440";; + int count = sscanf(buf, "%*c%c %d %*d %d", &c, &n1, &n2); + assert(count == 3); + assert(c == '$'); + assert(n1 == 7); + assert(n2 == 440); + } + + { + // From dsda-doom + char buf[] = "process_priority 0\n"; + char def[80], strparm[128]; + memset(def, '!', 80); + memset(strparm, '!', 128); + sscanf(buf, "%s %[^\n]\n", def, strparm); + assert(!strcmp(def, "process_priority")); + assert(!strcmp(strparm, "0")); + } + + { + char buf[] = "fffff100"; + unsigned long y = 0; + sscanf(buf, "%lx", &y); + assert(y == 0xfffff100); + } + +#if !defined(__i386__) + { + char buf[] = "fffffffff100"; + unsigned long y = 0; + sscanf(buf, "%lx", &y); + assert(y == 0xfffffffff100); + } +#endif + + { + char buf[] = "410dc000"; + unsigned long y = 0; + sscanf(buf, "%lx", &y); + assert(y == 0x410dc000); + } + + { + // From webkitgtk + char buf[] = "MemTotal: 16299664 kB\n"; + char token[51] = {0}; + size_t amount = 0; + int ret = sscanf(buf, "%50s%zukB", token, &amount); + assert(ret == 2); + assert(!strcmp(token, "MemTotal:")); + assert(amount == 16299664); + } + + { + char buf[] = "SIGINT"; + int sig; + int ret = sscanf(buf, "%d", &sig); + assert(!ret); + } + + test_matrix(); + + return 0; +} diff --git a/lib/mlibc/tests/ansi/strchr.c b/lib/mlibc/tests/ansi/strchr.c new file mode 100644 index 0000000..a80a1f9 --- /dev/null +++ b/lib/mlibc/tests/ansi/strchr.c @@ -0,0 +1,20 @@ +#include <assert.h> +#include <string.h> +#include <stddef.h> + +int main() { + char str[] = "This is a sample string"; + char *pch; + // The character 's' is at position 4, 7, 11 and 18 + pch = strchr(str, 's'); + assert(pch - str + 1 == 4); + pch = strchr(pch + 1, 's'); + assert(pch - str + 1 == 7); + pch = strchr(pch + 1, 's'); + assert(pch - str + 1 == 11); + pch = strchr(pch + 1, 's'); + assert(pch - str + 1 == 18); + pch = strchr(pch + 1, 's'); + assert(pch == NULL); + return 0; +} diff --git a/lib/mlibc/tests/ansi/strftime.c b/lib/mlibc/tests/ansi/strftime.c new file mode 100644 index 0000000..69ec642 --- /dev/null +++ b/lib/mlibc/tests/ansi/strftime.c @@ -0,0 +1,48 @@ +#include <string.h> +#include <time.h> +#include <assert.h> +#include <locale.h> +#include <stdio.h> + +int main() { + // Date representation depends on locale, here only C is tested. + // Maybe consider testing more locales? + if (setlocale (LC_ALL, "C") == NULL) { + fputs("strftime testcase could not set locale, errors may be expected!", stderr); + } + + char timebuf[16]; + char result[16] = " 8"; + struct tm tm; + tm.tm_sec = 0; + tm.tm_min = 17; + tm.tm_hour = 17; + tm.tm_mday = 8; + tm.tm_mon = 2; + tm.tm_year = 121; + tm.tm_wday = 2; + tm.tm_yday = 39; + strftime(timebuf, sizeof(timebuf), "%e", &tm); + assert(!strcmp(timebuf, result)); + + memset(timebuf, 0, sizeof(timebuf)); + strftime(timebuf, sizeof(timebuf), "%x", &tm); + assert(!strcmp(timebuf, "03/08/21")); + + memset(timebuf, 0, sizeof(timebuf)); + strftime(timebuf, sizeof(timebuf), "%X", &tm); + assert(!strcmp(timebuf, "17:17:00")); + + memset(timebuf, 0, sizeof(timebuf)); + strftime(timebuf, sizeof(timebuf), "%a %A", &tm); + assert(!strcmp(timebuf, "Tue Tuesday")); + + memset(timebuf, 0, sizeof(timebuf)); + strftime(timebuf, sizeof(timebuf), "%b %B %h", &tm); + assert(!strcmp(timebuf, "Mar March Mar")); + + memset(timebuf, 0, sizeof(timebuf)); + assert(!strftime(timebuf, sizeof(timebuf), "%a %A %a %A %b %B %h", &tm)); + + return 0; +} diff --git a/lib/mlibc/tests/ansi/strrchr.c b/lib/mlibc/tests/ansi/strrchr.c new file mode 100644 index 0000000..2de9821 --- /dev/null +++ b/lib/mlibc/tests/ansi/strrchr.c @@ -0,0 +1,11 @@ +#include <string.h> +#include <assert.h> + +int main() { + char str[] = "This is a sample string"; + char *pch; + pch = strrchr(str, 's'); + // The last occurence of 's' is at position 18 + assert(pch - str + 1 == 18); + return 0; +} diff --git a/lib/mlibc/tests/ansi/strtof.c b/lib/mlibc/tests/ansi/strtof.c new file mode 100644 index 0000000..a13af90 --- /dev/null +++ b/lib/mlibc/tests/ansi/strtof.c @@ -0,0 +1,83 @@ +#include <stdlib.h> +#include <assert.h> +#include <string.h> +#include <math.h> + +#define FLT_RANGE 0.000001f +#define DBL_RANGE 0.000001 +#define LDBL_RANGE 0.000001 + +#define DO_TEST(str, value, off, func, range) ({ \ + char s[] = (str); \ + char *pEnd = NULL; \ + __typeof(func(s, &pEnd)) result = func(s, &pEnd); \ + assert(result >= (value) - (range)); \ + assert(result <= (value) + (range)); \ + assert(pEnd == (off == -1 ? s + strlen(s) : s + off)); }) + +#define DO_TEST_SUCCESS_FUNC(str, success_func, off, func) ({ \ + char s[] = (str); \ + char *pEnd = NULL; \ + assert(success_func(func(s, &pEnd))); \ + assert(pEnd == (off == -1 ? s + strlen(s) : s + off)); }) + +int main () { + DO_TEST("0", 0.0f, -1, strtof, FLT_RANGE); + DO_TEST("0.12", 0.12f, -1, strtof, FLT_RANGE); + DO_TEST("12", 12.0f, -1, strtof, FLT_RANGE); + DO_TEST("12.13", 12.13f, -1, strtof, FLT_RANGE); + DO_TEST("10.0e1", 100.0f, -1, strtof, FLT_RANGE); + DO_TEST("10.0e10", 100000000000.0f, -1, strtof, FLT_RANGE); + DO_TEST("100.0e-1", 10.0f, -1, strtof, FLT_RANGE); + DO_TEST("0x0", 0.0f, -1, strtof, FLT_RANGE); + DO_TEST("0x0.12", 0.0703125f, -1, strtof, FLT_RANGE); + DO_TEST("0x12", 18.0f, -1, strtof, FLT_RANGE); + DO_TEST("0x12.13", 18.07421875f, -1, strtof, FLT_RANGE); + DO_TEST("0x10.0p1", 32.0f, -1, strtof, FLT_RANGE); + DO_TEST("0x10.0p10", 16384.0f, -1, strtof, FLT_RANGE); + DO_TEST("0x100.0p-1", 128.0f, -1, strtof, FLT_RANGE); + DO_TEST_SUCCESS_FUNC("NAN", isnan, -1, strtof); + DO_TEST_SUCCESS_FUNC("nan", isnan, -1, strtof); + DO_TEST_SUCCESS_FUNC("INF", isinf, -1, strtof); + DO_TEST_SUCCESS_FUNC("INFINITY", isinf, -1, strtof); + + DO_TEST("0", 0.0, -1, strtod, DBL_RANGE); + DO_TEST("0.12", 0.12, -1, strtod, DBL_RANGE); + DO_TEST("12", 12.0, -1, strtod, DBL_RANGE); + DO_TEST("12.13", 12.13, -1, strtod, DBL_RANGE); + DO_TEST("10.0e1", 100.0, -1, strtod, DBL_RANGE); + DO_TEST("10.0e10", 100000000000.0, -1, strtod, DBL_RANGE); + DO_TEST("100.0e-1", 10.0, -1, strtod, DBL_RANGE); + DO_TEST("0x0", 0.0, -1, strtod, DBL_RANGE); + DO_TEST("0x0.12", 0.0703125, -1, strtod, DBL_RANGE); + DO_TEST("0x12", 18.0, -1, strtod, DBL_RANGE); + DO_TEST("0x12.13", 18.07421875, -1, strtod, DBL_RANGE); + DO_TEST("0x10.0p1", 32.0, -1, strtod, DBL_RANGE); + DO_TEST("0x10.0p10", 16384.0, -1, strtod, DBL_RANGE); + DO_TEST("0x100.0p-1", 128.0, -1, strtod, DBL_RANGE); + DO_TEST_SUCCESS_FUNC("NAN", isnan, -1, strtod); + DO_TEST_SUCCESS_FUNC("nan", isnan, -1, strtod); + DO_TEST_SUCCESS_FUNC("INF", isinf, -1, strtod); + DO_TEST_SUCCESS_FUNC("INFINITY", isinf, -1, strtod); + + DO_TEST("0", 0.0, -1, strtold, LDBL_RANGE); + DO_TEST("0.12", 0.12, -1, strtold, LDBL_RANGE); + DO_TEST("12", 12.0, -1, strtold, LDBL_RANGE); + DO_TEST("12.13", 12.13, -1, strtold, LDBL_RANGE); + DO_TEST("10.0e1", 100.0, -1, strtold, LDBL_RANGE); + DO_TEST("10.0e10", 100000000000.0, -1, strtold, LDBL_RANGE); + DO_TEST("100.0e-1", 10.0, -1, strtold, LDBL_RANGE); + DO_TEST("0x0", 0.0, -1, strtold, LDBL_RANGE); + DO_TEST("0x0.12", 0.0703125, -1, strtold, LDBL_RANGE); + DO_TEST("0x12", 18.0, -1, strtold, LDBL_RANGE); + DO_TEST("0x12.13", 18.07421875, -1, strtold, LDBL_RANGE); + DO_TEST("0x10.0p1", 32.0, -1, strtold, LDBL_RANGE); + DO_TEST("0x10.0p10", 16384.0, -1, strtold, LDBL_RANGE); + DO_TEST("0x100.0p-1", 128.0, -1, strtold, LDBL_RANGE); + DO_TEST_SUCCESS_FUNC("NAN", isnan, -1, strtold); + DO_TEST_SUCCESS_FUNC("nan", isnan, -1, strtold); + DO_TEST_SUCCESS_FUNC("INF", isinf, -1, strtold); + DO_TEST_SUCCESS_FUNC("INFINITY", isinf, -1, strtold); + + return 0; +} diff --git a/lib/mlibc/tests/ansi/strtol.c b/lib/mlibc/tests/ansi/strtol.c new file mode 100644 index 0000000..823cb4a --- /dev/null +++ b/lib/mlibc/tests/ansi/strtol.c @@ -0,0 +1,140 @@ +#include <stdlib.h> +#include <assert.h> +#include <limits.h> +#include <string.h> +#include <errno.h> +#include <stdio.h> +#include <wchar.h> + +#define DO_TEST(str, value, off, func, base) ({ \ + char s[] = (str); \ + char *pEnd = NULL; \ + errno = 0; \ + assert(func(s, &pEnd, base) == (value)); \ + assert(errno == 0); \ + assert(pEnd == (off == -1 ? s + strlen(s) : s + off)); }) + +#define DO_ERR_TEST(str, value, err, func, base) ({ \ + char s[] = (str); \ + char *pEnd = NULL; \ + errno = 0; \ + assert(func(s, &pEnd, base) == (value)); \ + assert(errno == err); \ + assert(pEnd == (err == ERANGE ? s + strlen(s) : s)); }) + +#define DO_TESTL(str, value, off, func, base) ({ \ + wchar_t s[] = (str); \ + wchar_t *pEnd = NULL; \ + errno = 0; \ + assert(func(s, &pEnd, base) == (value)); \ + assert(errno == 0); \ + assert(pEnd == (off == -1 ? s + wcslen(s) : s + off)); }) + +#define DO_ERR_TESTL(str, value, err, func, base) ({ \ + wchar_t s[] = (str); \ + wchar_t *pEnd = NULL; \ + errno = 0; \ + assert(func(s, &pEnd, base) == (value)); \ + assert(errno == err); \ + assert(pEnd == (err == ERANGE ? s + wcslen(s) : s)); }) + +int main () { + // A few generic checks. + DO_TEST("0", 0, -1, strtol, 0); + DO_TEST("0", 0, -1, strtol, 10); + DO_TEST("2001", 2001, -1, strtol, 10); + DO_TEST("+2001", 2001, -1, strtol, 10); + DO_TEST("60c0c0", 0x60c0c0, -1, strtol, 16); + DO_TEST("-1101110100110100100000", -3624224, -1, strtol, 2); + DO_TEST("0x6fffff", 0x6fffff, -1, strtol, 0); + DO_TEST("0666", 0666, -1, strtol, 0); + DO_TEST("0xzzz", 0, 1, strtol, 0); + DO_TEST("0yzzz", 0, 1, strtol, 0); + DO_TEST("00xzz", 0, 2, strtol, 0); + DO_ERR_TEST("", 0, 0, strtol, 10); + DO_ERR_TEST("asd", 0, 0, strtol, 10); + DO_ERR_TEST("999999999999999999999999999", LONG_MAX, ERANGE, strtol, 10); + DO_ERR_TEST("-999999999999999999999999999", LONG_MIN, ERANGE, strtol, 10); + + // strtol +#if defined(__i386__) + DO_TEST("-2147483648", LONG_MIN, -1, strtol, 10); + DO_TEST("2147483647", LONG_MAX, -1, strtol, 10); + DO_ERR_TEST("-2147483649", LONG_MIN, ERANGE, strtol, 10); + DO_ERR_TEST("2147483648", LONG_MAX, ERANGE, strtol, 10); +#else + DO_TEST("-9223372036854775808", LONG_MIN, -1, strtol, 10); + DO_TEST("9223372036854775807", LONG_MAX, -1, strtol, 10); + DO_ERR_TEST("9223372036854775808", LONG_MAX, ERANGE, strtol, 10); + DO_ERR_TEST("-9223372036854775809", LONG_MIN, ERANGE, strtol, 10); +#endif + + // wcstol +#if defined(__i386__) + DO_TESTL(L"-2147483648", LONG_MIN, -1, wcstol, 10); + DO_TESTL(L"2147483647", LONG_MAX, -1, wcstol, 10); + DO_ERR_TESTL(L"2147483648", LONG_MAX, ERANGE, wcstol, 10); + DO_ERR_TESTL(L"-2147483649", LONG_MIN, ERANGE, wcstol, 10); +#else + DO_TESTL(L"-9223372036854775808", LONG_MIN, -1, wcstol, 10); + DO_TESTL(L"9223372036854775807", LONG_MAX, -1, wcstol, 10); + DO_ERR_TESTL(L"9223372036854775808", LONG_MAX, ERANGE, wcstol, 10); + DO_ERR_TESTL(L"-9223372036854775809", LONG_MIN, ERANGE, wcstol, 10); +#endif + + // strtoll + DO_TEST("-9223372036854775808", LLONG_MIN, -1, strtoll, 10); + DO_TEST("9223372036854775807", LLONG_MAX, -1, strtoll, 10); + DO_ERR_TEST("9223372036854775808", LLONG_MAX, ERANGE, strtoll, 10); + DO_ERR_TEST("-9223372036854775809", LLONG_MIN, ERANGE, strtoll, 10); + // wcstoll + DO_TESTL(L"-9223372036854775808", LLONG_MIN, -1, wcstoll, 10); + DO_TESTL(L"9223372036854775807", LLONG_MAX, -1, wcstoll, 10); + DO_ERR_TESTL(L"9223372036854775808", LLONG_MAX, ERANGE, wcstoll, 10); + DO_ERR_TESTL(L"-9223372036854775809", LLONG_MIN, ERANGE, wcstoll, 10); + + // strtoul +#if defined(__i386__) + DO_TEST("-1", -(1UL), -1, strtoul, 10); + DO_TEST("2147483647", LONG_MAX, -1, strtoul, 10); + DO_TEST("4294967295", ULONG_MAX, -1, strtoul, 10); + DO_TEST("-4294967295", 1UL, -1, strtoul, 10); + DO_ERR_TEST("4294967296", ULONG_MAX, ERANGE, strtoul, 10); +#else + DO_TEST("-1", -(1UL), -1, strtoul, 10); + DO_TEST("9223372036854775807", LONG_MAX, -1, strtoul, 10); + DO_TEST("18446744073709551615", ULONG_MAX, -1, strtoul, 10); + DO_TEST("-18446744073709551615", 1UL, -1, strtoul, 10); + DO_ERR_TEST("18446744073709551616", ULONG_MAX, ERANGE, strtoul, 10); +#endif + + // wcstoul +#if defined(__i386__) + DO_TESTL(L"-1", -(1UL), -1, wcstoul, 10); + DO_TESTL(L"2147483647", LONG_MAX, -1, wcstoul, 10); + DO_TESTL(L"4294967295", ULONG_MAX, -1, wcstoul, 10); + DO_TESTL(L"-4294967295", 1UL, -1, wcstoul, 10); + DO_ERR_TESTL(L"4294967296", ULONG_MAX, ERANGE, wcstoul, 10); +#else + DO_TESTL(L"-1", -(1UL), -1, wcstoul, 10); + DO_TESTL(L"9223372036854775807", LONG_MAX, -1, wcstoul, 10); + DO_TESTL(L"18446744073709551615", ULONG_MAX, -1, wcstoul, 10); + DO_TESTL(L"-18446744073709551615", 1UL, -1, wcstoul, 10); + DO_ERR_TESTL(L"18446744073709551616", ULONG_MAX, ERANGE, wcstoul, 10); +#endif + + // strtoull + DO_TEST("-1", -(1ULL), -1, strtoull, 10); + DO_TEST("9223372036854775807", LLONG_MAX, -1, strtoull, 10); + DO_TEST("18446744073709551615", ULLONG_MAX, -1, strtoull, 10); + DO_TEST("-18446744073709551615", 1ULL, -1, strtoull, 10); + DO_ERR_TEST("18446744073709551616", ULLONG_MAX, ERANGE, strtoull, 10); + // wcstoull + DO_TESTL(L"-1", -(1ULL), -1, wcstoull, 10); + DO_TESTL(L"9223372036854775807", LLONG_MAX, -1, wcstoull, 10); + DO_TESTL(L"18446744073709551615", ULLONG_MAX, -1, wcstoull, 10); + DO_TESTL(L"-18446744073709551615", 1ULL, -1, wcstoull, 10); + DO_ERR_TESTL(L"18446744073709551616", ULLONG_MAX, ERANGE, wcstoull, 10); + + return 0; +} diff --git a/lib/mlibc/tests/ansi/strverscmp.c b/lib/mlibc/tests/ansi/strverscmp.c new file mode 100644 index 0000000..d6501a1 --- /dev/null +++ b/lib/mlibc/tests/ansi/strverscmp.c @@ -0,0 +1,16 @@ +#include <string.h> +#include <stdlib.h> +#include <assert.h> + +int main() { + int res; + + res = strverscmp("jan1", "jan10"); + assert(res < 0); + + res = strverscmp("jan11", "jan10"); + assert(res > 0); + + res = strverscmp("jan1", "jan1"); + assert(res == 0); +} diff --git a/lib/mlibc/tests/ansi/strxfrm.c b/lib/mlibc/tests/ansi/strxfrm.c new file mode 100644 index 0000000..07a4082 --- /dev/null +++ b/lib/mlibc/tests/ansi/strxfrm.c @@ -0,0 +1,14 @@ +#include <assert.h> +#include <locale.h> +#include <string.h> + +int main() { + setlocale(LC_ALL, "C"); + + const char *buf = "cbtteststring"; + char dest[14]; + size_t ret = strxfrm(dest, buf, strlen(buf) + 1); + assert(ret == 13); + int cmp = strncmp(dest, buf, 13); + assert(!cmp); +} diff --git a/lib/mlibc/tests/ansi/timegm.c b/lib/mlibc/tests/ansi/timegm.c new file mode 100644 index 0000000..f5af03e --- /dev/null +++ b/lib/mlibc/tests/ansi/timegm.c @@ -0,0 +1,45 @@ +#include <time.h> +#include <stdio.h> +#include <assert.h> + +int main() { + struct tm soon = {}; + soon.tm_sec = 0; + soon.tm_min = 0; + soon.tm_hour = 0; + soon.tm_mday = 1; + soon.tm_mon = 0; + soon.tm_year = 70; + time_t result; + time_t expected_result = 0; + // This should be epoch. + result = timegm(&soon); + printf("epoch: %ld\n", result); + assert(result == expected_result); + + soon.tm_sec = 12; + soon.tm_min = 8; + soon.tm_hour = 16; + soon.tm_mday = 17; + soon.tm_mon = 4; + soon.tm_year = 122; + expected_result = 1652803692; + result = timegm(&soon); + // On my host, this returned 1652803692, verify this. + printf("epoch: %ld\n", result); + assert(result == expected_result); + + soon.tm_sec = 45; + soon.tm_min = 42; + soon.tm_hour = 17; + soon.tm_mday = 16; + soon.tm_mon = 8; + soon.tm_year = 69; + expected_result = -9181035; + result = timegm(&soon); + // On my host, this returned -9181035, verify this. + printf("epoch: %ld\n", result); + assert(result == expected_result); + + return 0; +} diff --git a/lib/mlibc/tests/ansi/ungetc.c b/lib/mlibc/tests/ansi/ungetc.c new file mode 100644 index 0000000..8773c6d --- /dev/null +++ b/lib/mlibc/tests/ansi/ungetc.c @@ -0,0 +1,74 @@ +#include <stdio.h> +#include <assert.h> + +#ifdef USE_HOST_LIBC +#define TEST_FILE "ungetc-host-libc.tmp" +#else +#define TEST_FILE "ungetc.tmp" +#endif + +void test(int buffering) { + FILE *f = fopen(TEST_FILE, "w"); + for(int c = '0'; c <= '9'; c++) { + fputc(c, f); + } + fclose(f); + + f = fopen(TEST_FILE, "r"); + if (!buffering) { + setbuf(f, NULL); + } + + assert(ungetc('x', f) == 'x'); + assert(fgetc(f) == 'x'); + + // Test pushing back the same character + for (int c = '0'; c <= '9'; c++) { + assert(fgetc(f) == c); + assert(ungetc(c, f) == c); + assert(fgetc(f) == c); + } + assert(fgetc(f) == EOF); + assert(ungetc(EOF, f) == EOF); + + // Even though the spec does not guarantee it, we should be able to + // ungetc more than one character. + assert(ungetc('x', f) == 'x'); + assert(ungetc('y', f) == 'y'); + assert(fgetc(f) == 'y'); + assert(fgetc(f) == 'x'); + + // Seeking should discard the effects of ungetc. + assert(ungetc('x', f) == 'x'); + rewind(f); + + // Test pushing back a different character + for (int c = '0'; c <= '9'; c++) { + assert(fgetc(f) == c); + assert(ungetc(c - '0' + 'a', f) == c - '0' + 'a'); + assert(fgetc(f) == c - '0' + 'a'); + } + +#ifndef USE_HOST_LIBC + // Too many ungetcs should fail. + int eof = 0; + for (int i = 0; i < 100; i++) { + if (ungetc('x', f) == EOF) { + eof = 1; + break; + } + } + assert(eof); +#endif + + fclose(f); + + // TODO: Test with other operations, like fread. +} + +int main() { + fprintf(stderr, "with buffering...\n"); + test(1); + fprintf(stderr, "without buffering...\n"); + test(0); +} diff --git a/lib/mlibc/tests/ansi/utf8.c b/lib/mlibc/tests/ansi/utf8.c new file mode 100644 index 0000000..18b0d5a --- /dev/null +++ b/lib/mlibc/tests/ansi/utf8.c @@ -0,0 +1,63 @@ +#include <assert.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <wchar.h> +#include <locale.h> + +#if UINTPTR_MAX == UINT64_MAX +#define WCHAR_SPEC "" +#else +#define WCHAR_SPEC "l" +#endif + +#define EXPECT(func) ({ \ + if(res != expected_ret) { \ + printf(#func " decoded %d bytes (expected %d bytes), at %s:%d\n", \ + res, expected_ret, __FILE__, line); \ + fflush(stdout); \ + abort(); \ + } \ + if(wc != expected) { \ + printf(#func " output cp %" WCHAR_SPEC "x (expected cp %" WCHAR_SPEC "x), at %s:%d\n", wc, \ + expected, __FILE__, line); \ + fflush(stdout); \ + abort(); \ + } \ + }) + +void verify_decode(const char *input, wchar_t expected, int line) { + wchar_t wc; + int bytes = *input ? strlen(input) : 1; + int expected_ret = *input ? strlen(input) : 0; + + // Check mbrtowc(). + mbstate_t state; + memset(&state, 0, sizeof(state)); + assert(mbrtowc(NULL, NULL, -1, &state) == 0); + int res = mbrtowc(&wc, input, bytes, &state); + EXPECT("mbrtowc"); + res = mbrtowc(NULL, input, bytes, &state); + EXPECT("mbrtowc (pwc == NULL)"); + + // Check mbtowc(). + assert(mbtowc(NULL, NULL, -1) == 0); + res = mbtowc(&wc, input, bytes); + EXPECT("mbtowc"); + res = mbtowc(NULL, input, bytes); + EXPECT("mbtowc (pwc == NULL)"); +} + +int main() { + setlocale(LC_ALL, "C.UTF-8"); + +#define verify_decode(in, out) verify_decode(in, out, __LINE__) + verify_decode("\x24", 0x24); + verify_decode("\xC2\xA2", 0xA2); + verify_decode("\xE0\xA4\xB9", 0x939); + verify_decode("\xE2\x82\xAC", 0x20AC); + verify_decode("\xED\x95\x9C", 0xD55C); + verify_decode("\xF0\x90\x8D\x88", 0x10348); + verify_decode("", L'\0'); +} diff --git a/lib/mlibc/tests/ansi/wcsdup.c b/lib/mlibc/tests/ansi/wcsdup.c new file mode 100644 index 0000000..564b77e --- /dev/null +++ b/lib/mlibc/tests/ansi/wcsdup.c @@ -0,0 +1,11 @@ +#include <assert.h> +#include <string.h> +#include <stdlib.h> +#include <wchar.h> + +int main() { + wchar_t *wide_string = L"test string\n"; + wchar_t *dup_string = wcsdup(wide_string); + assert(!memcmp(wide_string, dup_string, (wcslen(wide_string) + 1) * sizeof(wchar_t))); + free(dup_string); +} diff --git a/lib/mlibc/tests/ansi/wcsncasecmp.c b/lib/mlibc/tests/ansi/wcsncasecmp.c new file mode 100644 index 0000000..fcbe98f --- /dev/null +++ b/lib/mlibc/tests/ansi/wcsncasecmp.c @@ -0,0 +1,11 @@ +#include <assert.h> +#include <wchar.h> + +int main() { + assert(!wcsncasecmp(L"foo", L"foo", 3)); + assert(!wcsncasecmp(L"foo", L"FOO", 3)); + assert(!wcsncasecmp(L"fooa", L"FOOB", 3)); + assert(wcsncasecmp(L"foo", L"bar", 3)); + assert(!wcsncasecmp(L"fooa", L"FOOA", 4)); + assert(!wcsncasecmp(L"123abc", L"123AbC", 6)); +} diff --git a/lib/mlibc/tests/ansi/wcsrtombs.c b/lib/mlibc/tests/ansi/wcsrtombs.c new file mode 100644 index 0000000..80df257 --- /dev/null +++ b/lib/mlibc/tests/ansi/wcsrtombs.c @@ -0,0 +1,12 @@ +#include <stdlib.h> +#include <wchar.h> +#include <assert.h> +#include <string.h> + +int main() { + const wchar_t str[] = L"wcsrtombs"; + const wchar_t *p = str; + int ret = wcsrtombs(NULL, &p, 343245234, NULL); + assert(ret == 9); + return 0; +} diff --git a/lib/mlibc/tests/ansi/wmemcmp.c b/lib/mlibc/tests/ansi/wmemcmp.c new file mode 100644 index 0000000..74de9bd --- /dev/null +++ b/lib/mlibc/tests/ansi/wmemcmp.c @@ -0,0 +1,19 @@ +#include <assert.h> +#include <wchar.h> +#include <stdlib.h> + +int main(void){ + wchar_t *ws1 = calloc(10, sizeof(wchar_t)); + wchar_t *ws2 = calloc(10, sizeof(wchar_t)); + + mbstowcs(ws1, "Test 1", 10); + mbstowcs(ws2, "Tester 2", 10); + + assert(wmemcmp(ws1, ws2, 10) < 0); + assert(wmemcmp(ws2, ws1, 10) > 0); + assert(wmemcmp(ws2, ws2, 10) == 0); + + free(ws1); + free(ws2); + return 0; +} diff --git a/lib/mlibc/tests/bsd/ns_get_put.c b/lib/mlibc/tests/bsd/ns_get_put.c new file mode 100644 index 0000000..d64bb2a --- /dev/null +++ b/lib/mlibc/tests/bsd/ns_get_put.c @@ -0,0 +1,21 @@ +#include <assert.h> +#include <arpa/nameser.h> +#include <string.h> +#include <stdlib.h> +#include <stdint.h> + +int main() { + uint8_t *buf = malloc(256); + uint16_t original16 = 4891; + uint32_t original32 = 4861984; + memset(buf, 0x0, sizeof(*buf)); + ns_put16(original16, buf); + uint16_t result16 = ns_get16(buf); + assert(result16 == original16); + memset(buf, 0x0, sizeof(*buf)); + ns_put32(original32, buf); + uint32_t result32 = ns_get32(buf); + assert(result32 == original32); + free(buf); + return 0; +} diff --git a/lib/mlibc/tests/bsd/reallocarray.c b/lib/mlibc/tests/bsd/reallocarray.c new file mode 100644 index 0000000..905d68d --- /dev/null +++ b/lib/mlibc/tests/bsd/reallocarray.c @@ -0,0 +1,7 @@ +#include <stdlib.h> + +int main() { + void *ret = reallocarray(NULL, 69, 0xCB7); + free(ret); + return !ret; +} diff --git a/lib/mlibc/tests/bsd/sbrk.c b/lib/mlibc/tests/bsd/sbrk.c new file mode 100644 index 0000000..88b0407 --- /dev/null +++ b/lib/mlibc/tests/bsd/sbrk.c @@ -0,0 +1,9 @@ +#include <assert.h> +#include <stdio.h> +#include <unistd.h> + +int main() { + void *ret = sbrk(0); + assert(ret != (void *) -1); + assert(ret); +} diff --git a/lib/mlibc/tests/bsd/strl.c b/lib/mlibc/tests/bsd/strl.c new file mode 100644 index 0000000..a502850 --- /dev/null +++ b/lib/mlibc/tests/bsd/strl.c @@ -0,0 +1,56 @@ +#include <assert.h> +#include <string.h> + +int main() { + char buf[256]; + + /* Test copy to 0-sized buffer . */ + memset(buf, 0, sizeof(buf)); + assert(strlcpy(buf, "xyz", 0) == 3); + assert(strcmp(buf, "") == 0); + + /* Test normal copy. */ + memset(buf, 0, sizeof(buf)); + assert(strlcpy(buf, "xyz", sizeof(buf)) == 3); + assert(strcmp(buf, "xyz") == 0); + + /* Test truncated copy. */ + memset(buf, 0, sizeof(buf)); + assert(strlcpy(buf, "abcdefabcdef", 10) == 12); + assert(strcmp(buf, "abcdefabc") == 0); + + /* Test concat to 0-sized buffer. */ + memset(buf, 0, sizeof(buf)); + assert(strlcat(buf, "abc", 0) == 3); + assert(strcmp(buf, "") == 0); + + /* Test concat to full buffer. */ + memset(buf, 0, sizeof(buf)); + assert(strlcat(buf, "abcde", 6) == 5); + assert(strcmp(buf, "abcde") == 0); + assert(strlcat(buf, "xyz", 5) == 8); + assert(strcmp(buf, "abcde") == 0); + + /* Test normal concat. */ + memset(buf, 0, sizeof(buf)); + assert(strlcat(buf, "abc", sizeof(buf)) == 3); + assert(strcmp(buf, "abc") == 0); + assert(strlcat(buf, "xyz", sizeof(buf)) == 6); + assert(strcmp(buf, "abcxyz") == 0); + + /* Test truncated concat. */ + memset(buf, 0, sizeof(buf)); + assert(strlcat(buf, "abcabc", 10) == 6); + assert(strcmp(buf, "abcabc") == 0); + assert(strlcat(buf, "xyzxyz", 10) == 12); + assert(strcmp(buf, "abcabcxyz") == 0); + + /* Test truncated concat w/ truncated dst. */ + memset(buf, 0, sizeof(buf)); + assert(strlcat(buf, "abcabc", 10) == 6); + assert(strcmp(buf, "abcabc") == 0); + assert(strlcat(buf, "xyz", 4) == 7); + assert(strcmp(buf, "abcabc") == 0); + + return 0; +} diff --git a/lib/mlibc/tests/glibc/error.c b/lib/mlibc/tests/glibc/error.c new file mode 100644 index 0000000..2ae752a --- /dev/null +++ b/lib/mlibc/tests/glibc/error.c @@ -0,0 +1,6 @@ +#include <errno.h> +#include <error.h> + +int main() { + error(0, EINVAL, "test: %s", "error"); +} diff --git a/lib/mlibc/tests/glibc/error.py b/lib/mlibc/tests/glibc/error.py new file mode 100644 index 0000000..3833974 --- /dev/null +++ b/lib/mlibc/tests/glibc/error.py @@ -0,0 +1,11 @@ +import subprocess +import sys +import os +from pyexpect import expect + +wrapper = os.getenv("MESON_EXE_WRAPPER") +wrapper = [x for x in (wrapper,) if x] + +output = subprocess.check_output(wrapper + [sys.argv[1]], stderr=subprocess.STDOUT) + +expect(bytes(sys.argv[1], 'utf-8') + b': test: error: Invalid argument (EINVAL)\n').to_equal(output) diff --git a/lib/mlibc/tests/glibc/error_at_line.c b/lib/mlibc/tests/glibc/error_at_line.c new file mode 100644 index 0000000..257ddd2 --- /dev/null +++ b/lib/mlibc/tests/glibc/error_at_line.c @@ -0,0 +1,6 @@ +#include <errno.h> +#include <error.h> + +int main() { + error_at_line(0, EINVAL, "error_at_line", 5, "test: %s", "error"); +} diff --git a/lib/mlibc/tests/glibc/error_at_line.py b/lib/mlibc/tests/glibc/error_at_line.py new file mode 100644 index 0000000..7499e05 --- /dev/null +++ b/lib/mlibc/tests/glibc/error_at_line.py @@ -0,0 +1,11 @@ +import subprocess +import sys +import os +from pyexpect import expect + +wrapper = os.getenv("MESON_EXE_WRAPPER") +wrapper = [x for x in (wrapper,) if x] + +output = subprocess.check_output(wrapper + [sys.argv[1]], stderr=subprocess.STDOUT) + +expect(bytes(sys.argv[1], 'utf-8') + b':error_at_line:5: test: error: Invalid argument (EINVAL)\n').to_equal(output) diff --git a/lib/mlibc/tests/glibc/error_expect_fail.c b/lib/mlibc/tests/glibc/error_expect_fail.c new file mode 100644 index 0000000..17f0907 --- /dev/null +++ b/lib/mlibc/tests/glibc/error_expect_fail.c @@ -0,0 +1,6 @@ +#include <errno.h> +#include <error.h> + +int main() { + error(1, EINVAL, "test error #1"); +} diff --git a/lib/mlibc/tests/glibc/error_message_count.c b/lib/mlibc/tests/glibc/error_message_count.c new file mode 100644 index 0000000..c2c694a --- /dev/null +++ b/lib/mlibc/tests/glibc/error_message_count.c @@ -0,0 +1,11 @@ +#include <assert.h> +#include <errno.h> +#include <error.h> + +int main() { + assert(error_message_count == 0); + error(0, EINVAL, "test error #1"); + assert(error_message_count == 1); + error_at_line(0, EINVAL, __FILE__, __LINE__, "test error #2"); + assert(error_message_count == 2); +} diff --git a/lib/mlibc/tests/glibc/error_one_per_line.c b/lib/mlibc/tests/glibc/error_one_per_line.c new file mode 100644 index 0000000..98721ec --- /dev/null +++ b/lib/mlibc/tests/glibc/error_one_per_line.c @@ -0,0 +1,13 @@ +#include <assert.h> +#include <errno.h> +#include <error.h> + +int main() { + assert(error_one_per_line == 0); + assert(error_message_count == 0); + error_one_per_line = 1; + error_at_line(0, EINVAL, __FILE__, 0, "test error #1"); + assert(error_message_count == 1); + error_at_line(0, EINVAL, __FILE__, 0, "test error #2"); + assert(error_message_count == 1); +} diff --git a/lib/mlibc/tests/glibc/error_print_progname.c b/lib/mlibc/tests/glibc/error_print_progname.c new file mode 100644 index 0000000..8840cb2 --- /dev/null +++ b/lib/mlibc/tests/glibc/error_print_progname.c @@ -0,0 +1,19 @@ +#include <assert.h> +#include <stdbool.h> +#include <stdio.h> +#include <errno.h> +#include <error.h> + +bool ran = false; + +void test_progname(void) { + ran = true; + fprintf(stderr, "progname test"); +} + +int main() { + assert(error_print_progname == NULL); + error_print_progname = &test_progname; + error(0, EINVAL, "test error #1"); + assert(ran == true); +} diff --git a/lib/mlibc/tests/glibc/ffsl-ffsll.c b/lib/mlibc/tests/glibc/ffsl-ffsll.c new file mode 100644 index 0000000..ed70ad6 --- /dev/null +++ b/lib/mlibc/tests/glibc/ffsl-ffsll.c @@ -0,0 +1,36 @@ +#include <assert.h> +#include <stdint.h> +#include <string.h> +#include <limits.h> + +int main(void){ + // ffsl + assert(ffsl(0x8000) == 16); + assert(ffsl(0) == 0); +#if UINTPTR_MAX == UINT64_MAX + assert(ffsl(LLONG_MAX - 1) == 2); + assert(ffsl(LLONG_MAX) == 1); +#endif + assert(ffsl(LONG_MIN) == (long)(sizeof(long) * CHAR_BIT)); + assert(ffsl(LONG_MIN + 1) == 1); + + for (int i = 1; i < 0x1000; i++) { + assert(ffsl(i) - 1 == __builtin_ctz(i)); + } + + // ffsll + assert(ffsll(0x8000) == 16); + assert(ffsll(0) == 0); +#if UINTPTR_MAX == UINT64_MAX + assert(ffsll(LLONG_MAX - 1) == 2); + assert(ffsll(LLONG_MAX) == 1); +#endif + assert(ffsll(LLONG_MIN) == (long long)(sizeof(long long) * CHAR_BIT)); + assert(ffsll(LLONG_MIN + 1) == 1); + + for (int i = 1; i < 0x1000; i++) { + assert(ffsll(i) - 1 == __builtin_ctz(i)); + } + + return 0; +} diff --git a/lib/mlibc/tests/glibc/getopt.c b/lib/mlibc/tests/glibc/getopt.c new file mode 100644 index 0000000..a0e27bb --- /dev/null +++ b/lib/mlibc/tests/glibc/getopt.c @@ -0,0 +1,288 @@ +#include <assert.h> +#include <getopt.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x]))))) +#define dump(x) fprintf(stderr, "getopt c '%c' (%d), optind %d ('%s'), optarg '%s', optopt '%c' (%d)\n", \ + (x), (x), optind, (size_t)optind >= COUNT_OF(test_argv) ? "out of range" : test_argv[optind], optarg, optopt, optopt); + +void test1() { + const char *shortopts = "b:cdef"; + + const struct option longopts[] = { + {"foo", required_argument, NULL, 'f'}, + {NULL, no_argument, NULL, 0} + }; + + int test_argc = 7; + + char *test_argv[] = { + "dummy", + "--foo", + "abc", + "-b", + "abc", + "abc", + "abc" + }; + + optind = 0; + int c = getopt_long(test_argc, test_argv, shortopts, longopts, NULL); + dump(c) + assert(c == 'f'); + + c = getopt_long(test_argc, test_argv, shortopts, longopts, NULL); + dump(c) + assert(optarg && !strcmp(optarg, "abc")); + assert(c == 'b'); + + c = getopt_long(test_argc, test_argv, shortopts, longopts, NULL); + dump(c); + assert(c == -1); + assert(optarg == NULL); + + // we have 2 non-option arguments + assert((optind + 2) == test_argc); +} + +void test2() { + const struct option longopts[] = { + {"foo", required_argument, NULL, 'f'}, + {NULL, no_argument, NULL, 0} + }; + + char *test_argv[] = { + "dummy", + "-c", + }; + + fputs("Situation: we pass a non-existant short option in argv\n", stderr); + + optind = 0; + int c = getopt_long(COUNT_OF(test_argv), test_argv, "ab:", longopts, NULL); + // Exprected result: return '?', optopt is set to the offender + assert(c == '?'); + assert(optopt == 'c'); + + fputs("Situation: we pass a non-existant short option in argv, while passing a leading colon in optstring\n", stderr); + optind = 0; + + c = getopt_long(COUNT_OF(test_argv), test_argv, ":ab:", longopts, NULL); + // Exprected result: return '?', optopt is set to the offender + assert(c == '?'); + assert(optopt == 'c'); + + fputs("Situation: we omit the required arg to a short option\n", stderr); + optind = 0; + + c = getopt_long(COUNT_OF(test_argv), test_argv, "ab:c:", longopts, NULL); + // Exprected result: return '?', optopt is set to the offender + assert(c == '?'); + assert(optopt == 'c'); + + fputs("Situation: we omit the required arg to a short option, while passing a leading colon in optstring\n", stderr); + optind = 0; + + c = getopt_long(COUNT_OF(test_argv), test_argv, ":ab:c:", longopts, NULL); + // Exprected result: return ':', optopt is set to the offender + assert(c == ':'); + assert(optopt == 'c'); +} + +void test3() { + const struct option longopts[] = { + {"foo", required_argument, NULL, 'f'}, + {NULL, no_argument, NULL, 0} + }; + + char *test_argv[] = { + "dummy", + "-cmanagarm", + }; + + fputs("Situation: we pass a concatenated argument to a short option\n", stderr); + + optind = 0; + int c = getopt_long(COUNT_OF(test_argv), test_argv, "ab:c:", longopts, NULL); + dump(c); + // Exprected result: + assert(c == 'c'); + assert(!strcmp(optarg, "managarm")); +} + +void test4() { + const struct option longopts[] = { + {"foo", required_argument, NULL, 'f'}, + {NULL, no_argument, NULL, 0} + }; + + char *test_argv[] = { + "dummy", + "-acmanagarm", + }; + + fputs("Situation: we pass concatenated short options to getopt\n", stderr); + + optind = 0; + int c = getopt_long(COUNT_OF(test_argv), test_argv, "ab:c:", longopts, NULL); + assert(c == 'a'); + + c = getopt_long(COUNT_OF(test_argv), test_argv, "ab:c:", longopts, NULL); + // Exprected result: + assert(c == 'c'); + assert(!strcmp(optarg, "managarm")); +} + +void test5() { + const struct option longopts[] = { + {"foo", required_argument, NULL, 'f'}, + {NULL, no_argument, NULL, 0} + }; + + char *test_argv[] = { + "su", + "-", + "managarm", + 0 + }; + + int test_argc = 3; + + fputs("Situation: testing `su - managarm`\n", stderr); + + optind = 0; + int c = getopt_long(test_argc, test_argv, "ab:", longopts, NULL); + dump(c); + assert(c == -1); + + if (optind < test_argc && !strcmp(test_argv[optind], "-")) { + ++optind; + } + + assert(optind < test_argc); + assert(!strcmp(test_argv[optind++], "managarm")); + assert(optind == test_argc); +} + +void test6() { + char *test_argv[] = { + "telescope", + "gemini://electrode.codes", + "-S", + 0 + }; + + int test_argc = 3; + optind = 0; + + const struct option longopts[] = { + {"colors", no_argument, NULL, 'C'}, + {"colours", no_argument, NULL, 'C'}, + {"help", no_argument, NULL, 'h'}, + {"safe", no_argument, NULL, 'S'}, + {"version", no_argument, NULL, 'v'}, + {NULL, 0, NULL, 0}, + }; + + int c = getopt_long(test_argc, test_argv, "Cc:hnST:v", longopts, NULL); + dump(c); + assert(c == 'S'); + assert(optind == 3); + assert(!optarg); +} + +void test7() { + int c; + + static const struct option options[] = { + { "debug", no_argument, NULL, 'd' }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + {} + }; + const char *command; + + char *test_argv[] = { + "udevadm", + "hwdb", + "--update", + 0 + }; + + int test_argc = 3; + setenv("POSIXLY_CORRECT", "1", 1); + optind = 0; + + while ((c = getopt_long(test_argc, test_argv, "+dhV", options, NULL)) >= 0) { + switch (c) { + case 'd': + break; + case 'h': + break; + case 'V': + break; + default: + break; + } + } + + dump(c); + command = test_argv[optind]; + assert(command); + assert(!strcmp(command, "hwdb")); +} + +void test8() { + const char *shortopts = "b:cde"; + + int foo = false; + int bar = false; + + const struct option longopts[] = { + {"foo", required_argument, &foo, 'x'}, + {"bar", required_argument, &bar, 'y'}, + {NULL, no_argument, NULL, 0} + }; + + int test_argc = 6; + + char *test_argv[] = { + "dummy", + "-foo", + "abc", + "-bar=def", + "ghi", + "jkl" + }; + + #ifdef __GLIBC__ + optind = 0; + #else + optreset = 1; + #endif + optopt = 0; + int c = getopt_long_only(test_argc, test_argv, shortopts, longopts, NULL); + dump(c); + assert(foo && !c); + assert(optind == 3); + + c = getopt_long_only(test_argc, test_argv, shortopts, longopts, NULL); + dump(c); + assert(bar && !c); + assert(optind == 4); +} + +int main() { + test1(); + test2(); + test3(); + test4(); + test5(); + test6(); + test7(); + test8(); +} diff --git a/lib/mlibc/tests/glibc/gnu-basename.c b/lib/mlibc/tests/glibc/gnu-basename.c new file mode 100644 index 0000000..9566532 --- /dev/null +++ b/lib/mlibc/tests/glibc/gnu-basename.c @@ -0,0 +1,13 @@ +#define _GNU_SOURCE +#include <assert.h> +#include <string.h> + +#define test_string(x, expectval) assert(strcmp(basename(x), expectval) == 0) + +int main() { + test_string("/usr/lib", "lib"); + test_string("/usr/", ""); + test_string("/", ""); + test_string(".", "."); + test_string("..", ".."); +} diff --git a/lib/mlibc/tests/glibc/linux-syscall.c b/lib/mlibc/tests/glibc/linux-syscall.c new file mode 100644 index 0000000..2760df4 --- /dev/null +++ b/lib/mlibc/tests/glibc/linux-syscall.c @@ -0,0 +1,14 @@ +#include <sys/syscall.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +int main() { + char *data = "hello mlibc\n"; + long ret = syscall(SYS_write, 1, data, strlen(data)); + assert(ret == (long)strlen(data)); + + syscall(SYS_exit, 0); + abort(); +} diff --git a/lib/mlibc/tests/linux/cpuset.c b/lib/mlibc/tests/linux/cpuset.c new file mode 100644 index 0000000..3f55732 --- /dev/null +++ b/lib/mlibc/tests/linux/cpuset.c @@ -0,0 +1,27 @@ +#include <assert.h> +#include <stdlib.h> +#include <sched.h> + +#define SET_SIZE 15 + +int main() { + cpu_set_t *set = CPU_ALLOC(SET_SIZE); + size_t setsize = CPU_ALLOC_SIZE(SET_SIZE); + + CPU_ZERO_S(setsize, set); + + assert(!CPU_ISSET_S(11, setsize, set)); + + CPU_SET_S(11, setsize, set); + assert(CPU_ISSET_S(11, setsize, set)); + assert(CPU_COUNT_S(setsize, set) == 1); + + assert(!CPU_ISSET_S(CPU_SETSIZE - 1, setsize, set)); + + CPU_CLR_S(11, setsize, set); + assert(!CPU_ISSET_S(11, setsize, set)); + + CPU_FREE(set); + + return 0; +} diff --git a/lib/mlibc/tests/linux/malloc-usable-size.c b/lib/mlibc/tests/linux/malloc-usable-size.c new file mode 100644 index 0000000..3bb0394 --- /dev/null +++ b/lib/mlibc/tests/linux/malloc-usable-size.c @@ -0,0 +1,13 @@ +#include <assert.h> +#include <malloc.h> +#include <stdio.h> +#include <stdlib.h> + +int main() { + void *p1 = malloc(1023); + fprintf(stderr, "size: %zu\n", malloc_usable_size(p1)); + assert(malloc_usable_size(p1) >= 1023); + free(p1); + + return 0; +} diff --git a/lib/mlibc/tests/linux/pthread_attr.c b/lib/mlibc/tests/linux/pthread_attr.c new file mode 100644 index 0000000..4c1907c --- /dev/null +++ b/lib/mlibc/tests/linux/pthread_attr.c @@ -0,0 +1,90 @@ +#include <assert.h> +#include <pthread.h> +#include <stdio.h> +#include <string.h> +#include <signal.h> + +static void test_affinity() { + pthread_attr_t attr; + cpu_set_t set = {0}; + assert(!pthread_attr_init(&attr)); + assert(!pthread_attr_setaffinity_np(&attr, 1, &set)); + + cpu_set_t other_set = {0}; + assert(!pthread_attr_getaffinity_np(&attr, 1, &set)); + + assert(!memcmp(&set, &other_set, sizeof(cpu_set_t))); + + pthread_attr_destroy(&attr); +} + +#if !defined(USE_HOST_LIBC) || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 32) +static void test_sigmask() { + pthread_attr_t attr; + sigset_t set; + sigemptyset(&set); + sigaddset(&set, SIGUSR1); +#ifndef USE_HOST_LIBC + sigaddset(&set, SIGCANCEL); +#endif + + assert(!pthread_attr_init(&attr)); + assert(!pthread_attr_setsigmask_np(&attr, &set)); + + sigset_t other_set; + sigemptyset(&other_set); + + assert(!pthread_attr_getsigmask_np(&attr, &other_set)); + + assert(sigismember(&other_set, SIGUSR1)); +#ifndef USE_HOST_LIBC + // Test whether internal signals get filtered properly. + assert(!sigismember(&other_set, SIGCANCEL)); +#endif + + pthread_attr_destroy(&attr); +} + +static void *getattr_worker(void *arg) { + (void)arg; + return NULL; +} + +static void test_getattrnp() { + pthread_attr_t attr; + size_t stacksize = PTHREAD_STACK_MIN; + assert(!pthread_attr_init(&attr)); + assert(!pthread_attr_setstacksize(&attr, stacksize)); + + pthread_t thread; + assert(!pthread_create(&thread, &attr, getattr_worker, NULL)); + assert(!pthread_getattr_np(thread, &attr)); + size_t other_stacksize; + assert(!pthread_attr_getstacksize(&attr, &other_stacksize)); + assert(other_stacksize == stacksize); + assert(!pthread_join(thread, NULL)); + + pthread_t own_thread = pthread_self(); + void *stack; + assert(!pthread_getattr_np(own_thread, &attr)); + assert(!pthread_attr_getstack(&attr, &stack, &other_stacksize)); + assert(stack); + assert(other_stacksize); + // Check that we can read from the highest byte returned. + // pthread_getattr_np() should return the lowest byte + // of the stack. + printf("highest byte: %hhu\n", *(char *)(stack + other_stacksize - 1)); + + pthread_attr_destroy(&attr); +} +#endif // !defined(USE_HOST_LIBC) || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 32) + +int main() { + test_affinity(); +#if !defined(USE_HOST_LIBC) || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 32) + test_sigmask(); + test_getattrnp(); +#endif + + return 0; +} diff --git a/lib/mlibc/tests/linux/pthread_setname_np.c b/lib/mlibc/tests/linux/pthread_setname_np.c new file mode 100644 index 0000000..95c95c9 --- /dev/null +++ b/lib/mlibc/tests/linux/pthread_setname_np.c @@ -0,0 +1,33 @@ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include <assert.h> +#include <errno.h> +#include <string.h> +#include <pthread.h> + +int main() { + int ret = pthread_setname_np(pthread_self(), "mlibc-test-123"); + assert(!ret); + + char buf[16]; + ret = pthread_getname_np(pthread_self(), buf, 16); + assert(!ret); + assert(!strcmp("mlibc-test-123", buf)); + + ret = pthread_setname_np(pthread_self(), "mlibc-test-123-too-long"); + assert(ret == ERANGE); + + ret = pthread_getname_np(pthread_self(), buf, 1); + assert(ret == ERANGE); + + ret = pthread_getname_np(pthread_self(), buf, 15); + assert(ret == ERANGE); + + ret = pthread_getname_np(pthread_self(), buf, 16); + assert(!ret); + assert(!strcmp("mlibc-test-123", buf)); + + return 0; +} diff --git a/lib/mlibc/tests/linux/xattr.c b/lib/mlibc/tests/linux/xattr.c new file mode 100644 index 0000000..c6dd152 --- /dev/null +++ b/lib/mlibc/tests/linux/xattr.c @@ -0,0 +1,80 @@ +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <string.h> +#include <unistd.h> +#include <sys/xattr.h> + +#define assert_perror(x) \ + ({ \ + int res = (x); \ + if (res < 0) { \ + perror(#x); \ + return 1; \ + } \ + res; \ + }) + +#define soft_assert(x, m) \ + ({ \ + if (!(x)) { \ + fprintf(stderr, "cond \"%s\" failed: %s\n", #x, m); \ + return 1; \ + } \ + }) + +void remove_tmp(const char (*fname)[]) { + unlink(&(*fname)[0]); +} +#define _cleanup_file_ __attribute__((cleanup(remove_tmp))) + +int main(void) { + int ret; + char buf[32] = { 0 }; + _cleanup_file_ char filename[] = "xattr_test.XXXXXX"; + _cleanup_file_ char filename2[] = "xattr_test.XXXXXX.2"; + + int tmpfile = assert_perror(mkstemp(filename)); + memcpy(filename2, filename, sizeof(filename) - 1); + assert_perror(symlink(filename, filename2)); + + assert_perror(setxattr(filename, "user.T1", "ABC", 3, XATTR_CREATE)); + assert_perror(fsetxattr(tmpfile, "user.T2", "DEF", 3, XATTR_CREATE)); + + // for testing remove + assert_perror(fsetxattr(tmpfile, "user.T3", "DEF", 3, XATTR_CREATE)); + + if ((ret = getxattr(filename, "user.T1", buf, 3)) != 3) { + if (ret < 0) { + perror("getxattr"); + return 1; + } + + soft_assert(memcmp(buf, "ABC", 3) == 0, "xattr read wrong"); + } + + ret = lgetxattr(filename2, "user.T1", buf, 3); + soft_assert(ret < 0 && errno == ENODATA, "lgetxattr deref'd"); + + if ((ret = fgetxattr(tmpfile, "user.T3", buf, 3)) != 3) { + if (ret < 0) { + perror("fgetxattr"); + return 1; + } + + soft_assert(memcmp(buf, "DEF", 3) == 0, "xattr read wrong"); + } + + assert_perror(removexattr(filename, "user.T2")); + assert_perror(fremovexattr(tmpfile, "user.T3")); + + ret = assert_perror(listxattr(filename, buf, sizeof(buf) - 1)); + soft_assert(memmem(buf, ret, "user.T1", 7), "user.T1 not found"); + soft_assert(!memmem(buf, ret, "user.T2", 7), "user.T2 found"); + soft_assert(!memmem(buf, ret, "user.T3", 7), "user.T3 found"); + + ret = assert_perror(flistxattr(tmpfile, buf, sizeof(buf) - 1)); + soft_assert(memmem(buf, ret, "user.T1", 7), "user.T1 not found"); + soft_assert(!memmem(buf, ret, "user.T2", 7), "user.T2 found"); + soft_assert(!memmem(buf, ret, "user.T3", 7), "user.T3 found"); +} diff --git a/lib/mlibc/tests/meson.build b/lib/mlibc/tests/meson.build new file mode 100644 index 0000000..b7d65ca --- /dev/null +++ b/lib/mlibc/tests/meson.build @@ -0,0 +1,261 @@ +timeout_sec = 10 + +all_test_cases = [ + 'ansi/alloc', + 'ansi/sscanf', + 'ansi/sprintf', + 'ansi/snprintf', + 'ansi/utf8', + 'ansi/strtol', + 'ansi/strtof', + 'ansi/abs', + 'ansi/longjmp', + 'ansi/mbrtoc32', + 'ansi/strverscmp', + 'ansi/strftime', + 'ansi/strchr', + 'ansi/strrchr', + 'ansi/wcsrtombs', + 'ansi/wmemcmp', + 'ansi/timegm', + 'ansi/ungetc', + 'ansi/wcsdup', + 'ansi/wcsncasecmp', + 'ansi/fopen', + 'ansi/memmem', + 'ansi/creal-cimag', + 'ansi/fenv', + 'ansi/qsort', + 'ansi/freopen', + 'ansi/strxfrm', + 'ansi/calloc', + 'bsd/ns_get_put', + 'bsd/reallocarray', + 'bsd/strl', + 'bsd/sbrk', + 'posix/inet_ntop', + 'posix/inet_pton', + 'posix/access', + 'posix/pthread_barrier', + 'posix/pthread_rwlock', + 'posix/pthread_cond', + 'posix/pthread_create', + 'posix/pthread_cancel', + 'posix/pthread_atfork', + 'posix/pthread_cleanup', + 'posix/pthread_kill', + 'posix/pthread_mutex', + 'posix/pthread_key', + 'posix/pthread_thread_local', + 'posix/pthread_attr', + 'posix/pthread_schedparam', + 'posix/pwd', + 'posix/fdopen', + 'posix/fopencookie', + 'posix/fmemopen', + 'posix/getaddrinfo', + 'posix/getdelim', + 'posix/getnameinfo', + 'posix/getservbyname', + 'posix/getservbyport', + 'posix/grp', + 'posix/dprintf', + 'posix/posix_memalign', + 'posix/posix_spawn', + 'posix/index', + 'posix/rindex', + 'posix/search', + 'posix/open_memstream', + 'posix/popen', + 'posix/system', # This test should be in the ANSI tests, but it depends on sys/wait.h + 'posix/sigsuspend', + 'posix/sigaltstack', + 'posix/time', + 'posix/realpath', + 'posix/ffs', + 'posix/getcwd', + 'posix/memrchr', + 'posix/wordexp', + 'posix/rlimits', + 'posix/accept4', + 'posix/setpriority', + 'posix/alarm', + 'posix/abort', # This test should be in the ANSI tests, but it depends on sigaction + 'posix/timer', + 'posix/vfork', + 'posix/wcwidth', + 'posix/pause', + 'posix/flockfile', + 'posix/basename', + 'posix/regex', + 'posix/sigtimedwait', + 'posix/if_indextoname', + 'posix/readv-writev', + 'posix/posix-timer', + 'posix/strdupa', + 'posix/mkstemp', + 'posix/waitid', + 'glibc/getopt', + 'glibc/ffsl-ffsll', + 'glibc/error_message_count', + 'glibc/error_one_per_line', + 'glibc/error_print_progname', + 'glibc/error_expect_fail', + 'glibc/error', + 'glibc/error_at_line', + 'linux/xattr', + 'linux/pthread_setname_np', + 'linux/pthread_attr', + 'linux/cpuset', + 'linux/malloc-usable-size', +] + +if host_machine.system() == 'linux' + all_test_cases += 'glibc/linux-syscall' +endif + +fail_test_cases = [ + 'posix/abort', + 'glibc/error_expect_fail', +] + +wrapped_test_cases = [ + 'glibc/error', + 'glibc/error_at_line', +] + +host_libc_excluded_test_cases = [ + 'bsd/strl', # These functions do not exist on Linux. +] +host_libc_noasan_test_cases = [ + 'posix/pthread_cancel', + 'posix/pthread_attr', # does some stack overflowing to check stack size + 'posix/posix_memalign', + 'posix/search', # requires tdelete (#351) + 'ansi/calloc', # does some overflowing + 'linux/pthread_attr', # encounters memory leaks +] + +extra_cflags_test_cases = { + 'ansi/calloc': ['-Wno-alloc-size-larger-than'], +} + +test_sources = [] +test_link_args = [] +test_c_args = [] +use_pie = false + +test_c_args = [] +test_link_args = [] + +# Our ubsan implementation can't be used by the tests themselves, +# since it is internal to libc.so and ld.so. +test_override_options = ['b_sanitize=none'] + +if library_type == 'static' + libc_dep = declare_dependency( + include_directories: libc_include_dirs, + link_with: libc_static, + dependencies: [libc_deps, rtlib_deps] + ) + use_pie = false + test_c_args += '-no-pie' + test_link_args += ['-no-pie', '-static'] + test_sources += [ + '../options/internal' / host_machine.cpu_family() / 'mlibc_crtbegin.S', + '../options/internal' / host_machine.cpu_family() / 'mlibc_crtend.S', + crt, + ] +else + libc_dep = declare_dependency( + include_directories: libc_include_dirs, + link_with: libc_shared, + dependencies: [libc_deps, rtlib_deps] + ) + test_link_args += ['-Wl,--dynamic-linker=' + meson.build_root() + '/ld.so'] + + if host_machine.system() in ['linux'] + use_pie = true + test_sources += crt_pie + else + use_pie = false + test_sources += crt + + # Meson doesn't set these for us (issue #4651). + test_c_args += '-no-pie' + test_link_args += '-no-pie' + endif + + # Add the rtdl tests. + if not disable_posix_option + subdir('rtdl') + endif +endif + +py = import('python').find_installation('python3') + +foreach test_name : all_test_cases + test_subdir = test_name.split('/')[0] + test_short_name = test_name.split('/')[1] + test_exec_name = test_name.replace('/', '-') + + if test_subdir == 'ansi' and disable_ansi_option + continue + elif test_subdir == 'bsd' and disable_bsd_option + continue + elif test_subdir == 'glibc' and disable_glibc_option + continue + elif test_subdir == 'posix' and disable_posix_option + continue + elif test_subdir == 'linux' and disable_linux_option + continue + endif + + should_fail = fail_test_cases.contains(test_name) + exec = executable(test_exec_name, [test_name + '.c', test_sources], + dependencies: libc_dep, + build_rpath: meson.build_root(), + override_options: test_override_options, + c_args: test_c_args, + link_args: test_link_args, + pie: use_pie, + ) + + if wrapped_test_cases.contains(test_name) + test(test_short_name, + py, + suite: test_subdir, + should_fail: should_fail, + args: [ + meson.source_root() + '/tests/' + test_name + '.py', + exec + ], + timeout: timeout_sec, + ) + else + test(test_short_name, exec, suite: test_subdir, should_fail: should_fail, timeout: timeout_sec) + endif + + if build_tests_host_libc and not host_libc_excluded_test_cases.contains(test_name) + if test_name in host_libc_noasan_test_cases + host_libc_sanitize_options = 'b_sanitize=undefined' + else + host_libc_sanitize_options = 'b_sanitize=address,undefined' + endif + + if test_name in extra_cflags_test_cases + extra_cflags = extra_cflags_test_cases[test_name] + else + extra_cflags = [] + endif + + exec = executable('host-libc-' + test_exec_name, test_name + '.c', + build_rpath: meson.build_root(), + override_options: host_libc_sanitize_options, + c_args: ['-D_GNU_SOURCE', '-DUSE_HOST_LIBC', '-pthread', extra_cflags], + link_args: ['-lresolv', '-ldl', '-pthread', '-lm', '-lrt'], + native: true, + ) + test(test_short_name, exec, suite: ['host-libc', test_subdir], should_fail: should_fail, timeout: timeout_sec) + endif +endforeach diff --git a/lib/mlibc/tests/posix/abort.c b/lib/mlibc/tests/posix/abort.c new file mode 100644 index 0000000..5f7bb7d --- /dev/null +++ b/lib/mlibc/tests/posix/abort.c @@ -0,0 +1,21 @@ +#include <signal.h> +#include <assert.h> +#include <stdlib.h> + +void handler(int sig, siginfo_t *info, void *ctx) { + (void)sig; + (void)info; + (void)ctx; +} + +int main() { + struct sigaction sa; + sa.sa_sigaction = handler; + sa.sa_flags = SA_SIGINFO; + sigemptyset(&sa.sa_mask); + + int ret = sigaction(SIGABRT, &sa, NULL); + assert(!ret); + + abort(); +} diff --git a/lib/mlibc/tests/posix/accept4.c b/lib/mlibc/tests/posix/accept4.c new file mode 100644 index 0000000..ba593ee --- /dev/null +++ b/lib/mlibc/tests/posix/accept4.c @@ -0,0 +1,123 @@ +#include <unistd.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <stdbool.h> +#include <errno.h> + +#ifdef USE_HOST_LIBC +#define TEST_PORT 31337 +#else +#define TEST_PORT 42069 +#endif + +static struct sockaddr_in connect_addr, accept_addr; +static int listen_fd; + +int permutations[] = { + 0, + SOCK_CLOEXEC, + SOCK_NONBLOCK, + SOCK_CLOEXEC | SOCK_NONBLOCK, +}; + +static bool run_test(int flags) +{ + int connect_fd; + int fd_flags, access; + socklen_t addrlen; + + connect_fd = socket(AF_INET, SOCK_STREAM, 0); + connect(connect_fd, (struct sockaddr *)&connect_addr, sizeof(connect_addr)); + addrlen = sizeof(accept_addr); + + int accept_fd = accept4(listen_fd, (struct sockaddr *)&accept_addr, &addrlen, flags); + + if(accept_fd == -1) { + fprintf(stderr, "accept4 failed: %s\n", strerror(errno)); + goto cleanup; + } + + fd_flags = fcntl(accept_fd, F_GETFD); + if(!(!!(fd_flags & FD_CLOEXEC) == !!(flags & SOCK_CLOEXEC))) { + fprintf(stderr, "CLOEXEC mismatch, got %d instead of %d\n", !!(fd_flags & FD_CLOEXEC), !!(flags & SOCK_CLOEXEC)); + goto cleanup; + } + + access = fcntl(accept_fd, F_GETFL); + if(!(!!(access & O_NONBLOCK) == !!(flags & SOCK_NONBLOCK))) { + fprintf(stderr, "NONBLOCK flag mismatch, %d vs %d\n", !!(access & O_NONBLOCK), !!(flags & SOCK_NONBLOCK)); + goto cleanup; + } + + close(accept_fd); + close(connect_fd); + + fprintf(stderr, "tested CLOEXEC %d, NONBLOCK %d\n", !!(flags & SOCK_CLOEXEC), !!(flags & SOCK_NONBLOCK)); + return true; + +cleanup: + close(accept_fd); + close(connect_fd); + return false; +} + +static int socket_setup(void) +{ + struct sockaddr_in addr; + int reuseaddr; + + memset(&addr, 0, sizeof(struct sockaddr_in)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_ANY); + addr.sin_port = htons(TEST_PORT); + + int socket_fd = socket(AF_INET, SOCK_STREAM, 0); + + if(socket_fd == -1) { + fprintf(stderr, "socket failed: %s\n", strerror(errno)); + exit(1); + } + + reuseaddr = 1; + int ret = setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr)); + + if(ret == -1) { + fprintf(stderr, "setsockopt failed: %s\n", strerror(errno)); + exit(1); + } + + ret = bind(socket_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)); + + if(ret == -1) { + fprintf(stderr, "bind failed: %s\n", strerror(errno)); + exit(1); + } + + ret = listen(socket_fd, 5); + + if(ret == -1) { + fprintf(stderr, "listen failed: %s\n", strerror(errno)); + exit(1); + } + + return socket_fd; +} + +int main() { + memset(&connect_addr, 0, sizeof(connect_addr)); + connect_addr.sin_family = AF_INET; + connect_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + connect_addr.sin_port = htons(TEST_PORT); + + for(size_t i = 0; i < 4; i++) { + listen_fd = socket_setup(); + if(!run_test(permutations[i])) { + exit(1); + } + close(listen_fd); + } +} diff --git a/lib/mlibc/tests/posix/access.c b/lib/mlibc/tests/posix/access.c new file mode 100644 index 0000000..e0c9f45 --- /dev/null +++ b/lib/mlibc/tests/posix/access.c @@ -0,0 +1,30 @@ +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include <fcntl.h> + +#ifdef USE_HOST_LIBC +#define TEST_FILE "access-host-libc.tmp" +#else +#define TEST_FILE "access.tmp" +#endif + +int main() { + // Make sure that it wasn't created by a previous test run + unlink(TEST_FILE); + + assert(access(TEST_FILE, F_OK) == -1); + assert(access(TEST_FILE, W_OK) == -1); + assert(access(TEST_FILE, R_OK) == -1); + assert(access(TEST_FILE, X_OK) == -1); + + close(open(TEST_FILE, O_CREAT | O_RDWR, 0666)); + + assert(access(TEST_FILE, F_OK) == 0); + assert(access(TEST_FILE, W_OK) == 0); + assert(access(TEST_FILE, R_OK) == 0); + assert(access(TEST_FILE, X_OK) == -1); + + unlink(TEST_FILE); +} diff --git a/lib/mlibc/tests/posix/alarm.c b/lib/mlibc/tests/posix/alarm.c new file mode 100644 index 0000000..b8a43e3 --- /dev/null +++ b/lib/mlibc/tests/posix/alarm.c @@ -0,0 +1,34 @@ +#include <signal.h> +#include <assert.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +static volatile int alarms_fired = 0; + +static void sigalrm_handler(int signal) { + if(signal == SIGALRM) + alarms_fired++; +} + +int main() { + signal(SIGALRM, sigalrm_handler); + + alarms_fired = 0; + + unsigned int ret = alarm(10); + assert(!ret); + + sleep(1); + + ret = alarm(1); + assert(ret == 9); + + sleep(2); + + if(alarms_fired != 1) { + fprintf(stderr, "alarm handler fired %u times instead of 1\n", alarms_fired); + exit(1); + } +} diff --git a/lib/mlibc/tests/posix/basename.c b/lib/mlibc/tests/posix/basename.c new file mode 100644 index 0000000..260bf90 --- /dev/null +++ b/lib/mlibc/tests/posix/basename.c @@ -0,0 +1,17 @@ +#include <assert.h> +#include <libgen.h> +#include <string.h> + +int main() { + /* intentionally a macro: basename modifies its argument */ +#define test_string(x, expectval) do { \ + char str[] = x; \ + assert(strcmp(basename(str), expectval) == 0); \ + } while (0) + + test_string("/usr/lib", "lib"); + test_string("/usr/", "usr"); + test_string("/", "/"); + test_string(".", "."); + test_string("..", ".."); +} diff --git a/lib/mlibc/tests/posix/dprintf.c b/lib/mlibc/tests/posix/dprintf.c new file mode 100644 index 0000000..4746edb --- /dev/null +++ b/lib/mlibc/tests/posix/dprintf.c @@ -0,0 +1,39 @@ +#include <stdio.h> +#include <fcntl.h> +#include <unistd.h> +#include <assert.h> +#include <errno.h> + +#ifdef USE_HOST_LIBC +#define TEST_FILE "dprintf-host-libc.tmp" +#else +#define TEST_FILE "dprintf.tmp" +#endif + +int main() { + int fd = open(TEST_FILE, O_RDWR | O_CREAT, 0666); + assert(fd > -1); + + int ret = dprintf(fd, "aaa"); + assert(ret == 3); + + // Check if we can read back the same things. + // Also checks to see if the fd is still open, + // which is issue #199. + + off_t seek = lseek(fd, 0, SEEK_SET); + assert(seek == 0); + + char buf[3]; + ssize_t num_read = read(fd, buf, 3); + + assert(num_read == 3); + assert(buf[0] == 'a' + && buf[1] == 'a' + && buf[2] == 'a'); + + // All the tests pass, now we can unlink the file. + ret = unlink(TEST_FILE); + assert(ret == 0); + return 0; +} diff --git a/lib/mlibc/tests/posix/fdopen.c b/lib/mlibc/tests/posix/fdopen.c new file mode 100644 index 0000000..1066bfb --- /dev/null +++ b/lib/mlibc/tests/posix/fdopen.c @@ -0,0 +1,37 @@ +#include <stdio.h> +#include <assert.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> + +#define TEST_FILE "fdopen.tmp" + +int main() { + int fd = open(TEST_FILE, O_CREAT | O_RDWR, 0666); + assert(fd >= 0); + + char *str = "mlibc fdopen test"; + assert(write(fd, str, strlen(str))); + + // Seek to the beginning, then reopen with fdopen in append mode. + lseek(fd, 0, SEEK_SET); + FILE *file = fdopen(fd, "a"); + assert(file); + + // Append and close. + str = " appended"; + fwrite(str, strlen(str), 1, file); + fflush(file); + fclose(file); + + // Open it again and check that the append succeeded. + fd = open(TEST_FILE, O_RDONLY); + assert(fd >= 0); + file = fdopen(fd, "r"); + assert(file); + str = "mlibc fdopen test appended"; + char buf[100] = {0}; + assert(fread(buf, 1, strlen(str), file)); + assert(!strcmp(buf, str)); + fclose(file); +} diff --git a/lib/mlibc/tests/posix/ffs.c b/lib/mlibc/tests/posix/ffs.c new file mode 100644 index 0000000..3f7ba84 --- /dev/null +++ b/lib/mlibc/tests/posix/ffs.c @@ -0,0 +1,18 @@ +#include <assert.h> +#include <strings.h> +#include <limits.h> + +int main(void){ + assert(ffs(0x8000) == 16); + assert(ffs(0) == 0); + assert(ffs(INT_MAX - 1) == 2); + assert(ffs(INT_MAX) == 1); + assert(ffs(INT_MIN) == (int)(sizeof(int) * CHAR_BIT)); + assert(ffs(INT_MIN + 1) == 1); + + for (int i = 1; i < 0x1000; i++) { + assert(ffs(i) - 1 == __builtin_ctz(i)); + } + + return 0; +} diff --git a/lib/mlibc/tests/posix/flockfile.c b/lib/mlibc/tests/posix/flockfile.c new file mode 100644 index 0000000..982311e --- /dev/null +++ b/lib/mlibc/tests/posix/flockfile.c @@ -0,0 +1,35 @@ +#include <assert.h> +#include <time.h> +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +static void *worker(void *arg) { + (void)arg; + flockfile(stdout); + fputs_unlocked("hello from worker", stdout); + funlockfile(stdout); + return NULL; +} + +int main() { + // Check that recursive locking works. + assert(!ftrylockfile(stdout)); + flockfile(stdout); + flockfile(stdout); + funlockfile(stdout); + funlockfile(stdout); + funlockfile(stdout); + + assert(!ftrylockfile(stdout)); + + pthread_t thread; + int ret = pthread_create(&thread, NULL, &worker, NULL); + assert(!ret); + + sleep(1); + funlockfile(stdout); + + assert(!pthread_join(thread, NULL)); + return 0; +} diff --git a/lib/mlibc/tests/posix/fmemopen.c b/lib/mlibc/tests/posix/fmemopen.c new file mode 100644 index 0000000..4d5046e --- /dev/null +++ b/lib/mlibc/tests/posix/fmemopen.c @@ -0,0 +1,150 @@ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include <assert.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define BUFFER0_SIZE 0x1000 +#define BUFFER1 "Hello world" +#define BUFFER1_SIZE (sizeof(BUFFER1)) + +int main() { + // test seek with mode "r" + FILE *f = fmemopen(NULL, BUFFER0_SIZE, "r"); + assert(f); + + int ret = fseek(f, 0, SEEK_END); + assert(!ret); + long pos = ftell(f); + fprintf(stderr, "pos = %ld\n", pos); + // Despite the fact that this is correct behavior (see below), + // this sometimes fails on glibc; newlib seems to get this wrong, too. + // to quote what posix says about it: + // > The stream shall also maintain the size of the current buffer contents; + // > use of fseek() or fseeko() on the stream with SEEK_END shall seek relative to this size. + // > For modes r and r+ the size shall be set to the value given by the size argument. +#if !defined(__GLIBC__) + assert(pos == BUFFER0_SIZE); +#endif + + fclose(f); + + // test seek with mode "w" + f = fmemopen(NULL, BUFFER0_SIZE, "w"); + assert(f); + + ret = fseek(f, 0, SEEK_END); + assert(!ret); + pos = ftell(f); + assert(pos == 0); + + fclose(f); + + // test seek with mode "a" and a NULL buffer + f = fmemopen(NULL, BUFFER0_SIZE, "a"); + assert(f); + + ret = fseek(f, 0, SEEK_END); + assert(!ret); + pos = ftell(f); + assert(pos == 0); + + fclose(f); + + // test seek with mode "a" and a buffer containing a '\0' + f = fmemopen(BUFFER1, BUFFER1_SIZE + 2, "a"); + assert(f); + + pos = ftell(f); + assert(pos == (long) (BUFFER1_SIZE - 1)); + + ret = fseek(f, 0, SEEK_SET); + assert(!ret); + pos = ftell(f); + assert(!pos); + + ret = fseek(f, 0, SEEK_END); + assert(!ret); + pos = ftell(f); + assert(pos == (long) (BUFFER1_SIZE - 1)); + + fclose(f); + + // test seek with mode "a" and a buffer not containing a '\0' + f = fmemopen(BUFFER1, BUFFER1_SIZE - 2, "a"); + assert(f); + + ret = fseek(f, 0, SEEK_END); + assert(!ret); + pos = ftell(f); + assert(pos == (long) (BUFFER1_SIZE - 2)); + + fclose(f); + + f = fmemopen(BUFFER1, BUFFER1_SIZE, "r"); + assert(f); + + ret = fseek(f, 0, SEEK_SET); + assert(!ret); + + char buf[BUFFER1_SIZE]; + int read = fread(buf, 1, BUFFER1_SIZE - 2, f); + assert(read == BUFFER1_SIZE - 2); + assert(!strncmp(BUFFER1, buf, BUFFER1_SIZE - 2)); + + fseek(f, 0, SEEK_END); + + read = fread(buf, 1, 2, f); + assert(read == 0); + assert(feof(f)); + + fclose(f); + + // Open a buffer for read+write + char *buf1 = strdup(BUFFER1); + f = fmemopen(buf1, BUFFER1_SIZE, "w+"); + assert(f); + assert(strlen(BUFFER1) == BUFFER1_SIZE - 1); + + // seek to somewhere in the middle of the buffer + fseek(f, BUFFER1_SIZE - 5, SEEK_SET); + // write as much data to it as possible + read = fwrite(BUFFER1, 1, 9, f); + rewind(f); + + // seek the the same position in the middle of the buffer + ret = fseek(f, BUFFER1_SIZE - 5, SEEK_SET); + assert(!ret); + memset(buf, 0, BUFFER1_SIZE); + // read what we just wrote + read = fread(buf, 1, 5, f); + // check that the write got correctly truncated + fprintf(stderr, "buf '%s' (%zu)\n", buf, strlen(buf)); + assert(!strncmp(BUFFER1, buf, 4) && strlen(buf) == 4); + + fclose(f); + free(buf1); + + char *buf2 = strdup(BUFFER1); + f = fmemopen(buf2, 0, "r"); + assert(f || errno == EINVAL); + + if(f) { + memset(buf, 0, BUFFER1_SIZE); + read = fread(buf, 10, 1, f); + assert(!read); + rewind(f); + + read = fwrite(BUFFER1, 1, 12, f); + assert(read == 0); + + fclose(f); + } + free(buf2); + + return 0; +} diff --git a/lib/mlibc/tests/posix/fopencookie.c b/lib/mlibc/tests/posix/fopencookie.c new file mode 100644 index 0000000..4325038 --- /dev/null +++ b/lib/mlibc/tests/posix/fopencookie.c @@ -0,0 +1,69 @@ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include <assert.h> +#include <stdbool.h> +#include <stdio.h> + +struct testcookie { + bool read; + bool write; + bool seek; + bool close; +}; + +ssize_t cookie_read(void *c, char *buf, size_t size) { + (void) buf; + struct testcookie *cookie = c; + cookie->read = true; + return size; +} + +ssize_t cookie_write(void *c, const char *buf, size_t size) { + (void) buf; + struct testcookie *cookie = c; + cookie->write = true; + return size; +} + +int cookie_seek(void *c, off64_t *offset, int whence) { + (void) offset; + (void) whence; + struct testcookie *cookie = c; + cookie->seek = true; + return 0; +} + +int cookie_close(void *c) { + struct testcookie *cookie = c; + cookie->close = true; + return 0; +} + +int main() { + struct testcookie cookie = { false, false, false, false }; + + cookie_io_functions_t funcs = { + .read = cookie_read, + .write = cookie_write, + .seek = cookie_seek, + .close = cookie_close, + }; + + FILE *stream = fopencookie(&cookie, "w+", funcs); + assert(stream); + + unsigned char buf[1]; + int ret = fread(buf, 1, 1, stream); + assert(ret == 1); + ret = fwrite(buf, 1, 1, stream); + assert(ret == 1); + ret = fseek(stream, 0, SEEK_SET); + assert(!ret); + + ret = fclose(stream); + assert(!ret); + + assert(cookie.read && cookie.write && cookie.seek && cookie.close); +} diff --git a/lib/mlibc/tests/posix/getaddrinfo.c b/lib/mlibc/tests/posix/getaddrinfo.c new file mode 100644 index 0000000..d52c93f --- /dev/null +++ b/lib/mlibc/tests/posix/getaddrinfo.c @@ -0,0 +1,52 @@ +#include <netdb.h> +#include <assert.h> +#include <stddef.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <stdio.h> + +int main() { + struct addrinfo *res = NULL; + struct addrinfo hints = {0}; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + int ret = getaddrinfo(NULL, "443", &hints, &res); + assert(ret == 0); + + struct sockaddr_in *addr = (struct sockaddr_in*)(res[0].ai_addr); + assert(addr->sin_port == htons(443)); + assert(res[0].ai_socktype == SOCK_STREAM); + assert(res[0].ai_protocol == IPPROTO_TCP); + + freeaddrinfo(res); + res = NULL; + + /* check we can resolve any domain */ + ret = getaddrinfo("example.net", NULL, &hints, &res); + assert(ret == 0); + + freeaddrinfo(res); + res = NULL; + + hints.ai_flags = AI_NUMERICHOST; + ret = getaddrinfo("10.10.10.10", NULL, &hints, &res); + assert(ret == 0); + + addr = (struct sockaddr_in*)res[0].ai_addr; + assert((addr->sin_addr.s_addr & 0xFF) == 10); + assert(((addr->sin_addr.s_addr >> 8) & 0xFF) == 10); + assert(((addr->sin_addr.s_addr >> 16) & 0xFF) == 10); + assert(((addr->sin_addr.s_addr >> 24) & 0xFF) == 10); + + freeaddrinfo(res); + res = NULL; + + ret = getaddrinfo("example.net", NULL, &hints, &res); + assert(ret == EAI_NONAME); + + freeaddrinfo(res); + + return 0; +} diff --git a/lib/mlibc/tests/posix/getcwd.c b/lib/mlibc/tests/posix/getcwd.c new file mode 100644 index 0000000..f3ba8da --- /dev/null +++ b/lib/mlibc/tests/posix/getcwd.c @@ -0,0 +1,22 @@ +#include <assert.h> +#include <limits.h> +#include <string.h> +#include <unistd.h> +#include <stdlib.h> + +int main() { + char buf[PATH_MAX]; + char *ret = getcwd(buf, PATH_MAX); + + assert(ret); + assert((strlen(ret) == strlen(buf)) && strlen(ret)); + assert(!strcmp(ret, buf)); + + char *ret2 = getcwd(NULL, 0); + assert(ret2); + assert(strlen(ret2)); + assert(!strcmp(ret, ret2)); + free(ret2); + + return 0; +} diff --git a/lib/mlibc/tests/posix/getdelim.c b/lib/mlibc/tests/posix/getdelim.c new file mode 100644 index 0000000..bc43ef6 --- /dev/null +++ b/lib/mlibc/tests/posix/getdelim.c @@ -0,0 +1,90 @@ +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include <unistd.h> +#include <string.h> + +#ifdef USE_HOST_LIBC +#define TEST_FILE "getdelim-host-libc.tmp" +#else +#define TEST_FILE "getdelim.tmp" +#endif + +int main(void) { + FILE *fp; + char *line = NULL; + size_t len = 0; + ssize_t read; + + // We have to open the file for writing and then reading separately, + // as mlibc doesn't allow us to do both at the same time (yet). + fp = fopen(TEST_FILE, "w"); + assert(fp); + fputs("foo\nbar\nbaz\nquux\n", fp); + fclose(fp); + + fp = fopen(TEST_FILE, "r"); + assert(fp); + while ((read = getline(&line, &len, fp)) != -1) { + printf("read line of length %zd, capacity %zu\n", read, len); + } + assert(!ferror(fp)); + free(line); + fclose(fp); + + size_t nchars = 10000; + fp = fopen(TEST_FILE, "w"); + assert(fp); + for (size_t i = 0; i < nchars; i++) + fputc('a', fp); + fputc('b', fp); + fclose(fp); + + line = NULL; + len = 0; + fp = fopen(TEST_FILE, "r"); + assert(fp); + while ((read = getdelim(&line, &len, 'b', fp)) != -1) { + printf("read line of length %zd, capacity %zu\n", read, len); + assert((size_t)read == nchars + 1); + } + + assert(len > nchars + 1); + assert(!ferror(fp)); + assert(feof(fp)); + + assert(getdelim(&line, &len, 'b', fp) == -1); + assert(feof(fp)); + + free(line); + fclose(fp); + + fp = fopen(TEST_FILE, "w"); + assert(fp); + assert(fwrite("1234ef\0f", 1, 8, fp) == 8); + fclose(fp); + + fp = fopen(TEST_FILE, "r"); + assert(fp); + + /* test with line = NULL and large len */ + line = NULL; + len = (size_t) ~0; + assert(getdelim(&line, &len, 'e', fp) == 5); + assert(!memcmp(line, "1234e", 6)); + assert(5 < len); + + /* test handling of internal nulls */ + assert(getdelim(&line, &len, 'e', fp) == 3); + assert(!memcmp(line, "f\0f", 4)); + assert(3 < len); + + /* test handling of EOF */ + assert(getdelim(&line, &len, 'e', fp) == -1); + + free(line); + fclose(fp); + + // Delete the file + unlink(TEST_FILE); +} diff --git a/lib/mlibc/tests/posix/getnameinfo.c b/lib/mlibc/tests/posix/getnameinfo.c new file mode 100644 index 0000000..d22a0eb --- /dev/null +++ b/lib/mlibc/tests/posix/getnameinfo.c @@ -0,0 +1,22 @@ +#include <netdb.h> +#include <assert.h> +#include <arpa/inet.h> +#include <stddef.h> +#include <string.h> + +int main() { + struct sockaddr_in addr; + addr.sin_family = AF_INET; + assert(inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr)); + + char host[64]; + assert(!getnameinfo((struct sockaddr*)&addr, sizeof(addr), host, + sizeof(host), NULL, 0, 0)); + + assert(inet_pton(AF_INET, "8.8.8.8", &addr.sin_addr)); + + assert(!getnameinfo((struct sockaddr*)&addr, sizeof(addr), host, + sizeof(host), NULL, 0, 0)); + assert(!strcmp(host, "dns.google")); + return 0; +} diff --git a/lib/mlibc/tests/posix/getservbyname.c b/lib/mlibc/tests/posix/getservbyname.c new file mode 100644 index 0000000..8c51710 --- /dev/null +++ b/lib/mlibc/tests/posix/getservbyname.c @@ -0,0 +1,27 @@ +#include <assert.h> +#include <netdb.h> +#include <string.h> +#include <arpa/inet.h> + +int main() { + struct servent *ret = getservbyname("http", "tcp"); + assert(ret); + assert(!strcmp("http", ret->s_name)); + assert(ret->s_port == htons(80)); + assert(!strcmp("tcp", ret->s_proto)); + + ret = getservbyname("http", NULL); + assert(ret); + assert(!strcmp("http", ret->s_name)); + assert(ret->s_port == htons(80)); + assert(!strcmp("tcp", ret->s_proto)); + + ret = getservbyname("babel", "udp"); + assert(ret); + ret = getservbyname("babel", "tcp"); + assert(!ret); + + ret = getservbyname("", NULL); + assert(!ret); + return 0; +} diff --git a/lib/mlibc/tests/posix/getservbyport.c b/lib/mlibc/tests/posix/getservbyport.c new file mode 100644 index 0000000..f522d71 --- /dev/null +++ b/lib/mlibc/tests/posix/getservbyport.c @@ -0,0 +1,28 @@ +#include <netdb.h> +#include <arpa/inet.h> +#include <string.h> +#include <assert.h> +#include <stdio.h> + +int main() { + struct servent *ret = getservbyport(htons(80), "tcp"); + assert(ret); + assert(!strcmp("http", ret->s_name)); + assert(ret->s_port == htons(80)); + assert(!strcmp("tcp", ret->s_proto)); + + ret = getservbyport(htons(80), NULL); + assert(ret); + assert(!strcmp("http", ret->s_name)); + assert(ret->s_port == htons(80)); + assert(!strcmp("tcp", ret->s_proto)); + + ret = getservbyport(htons(6696), "udp"); + assert(ret); + ret = getservbyport(htons(6696), "tcp"); + assert(!ret); + + ret = getservbyport(htons(0), NULL); + assert(!ret); + return 0; +} diff --git a/lib/mlibc/tests/posix/grp.c b/lib/mlibc/tests/posix/grp.c new file mode 100644 index 0000000..3d334fe --- /dev/null +++ b/lib/mlibc/tests/posix/grp.c @@ -0,0 +1,110 @@ +#include <stdlib.h> +#include <stdio.h> +#include <grp.h> +#include <unistd.h> +#include <assert.h> +#include <string.h> +#include <errno.h> +#include <stdbool.h> + +void check_root_group(struct group *grp) { + assert(grp); + + printf("group name: %s\n", grp->gr_name); + fflush(stdout); + + assert(grp->gr_gid == 0); + assert(!strcmp(grp->gr_name, "root")); + + // Depending on your system, the root group may or may not contain the root user. + if (grp->gr_mem[0] != NULL) { + bool found = false; + for (size_t i = 0; grp->gr_mem[i] != NULL; i++) { + printf("group member: %s\n", grp->gr_mem[i]); + fflush(stdout); + + if (!strcmp(grp->gr_mem[i], "root")) { + found = true; + break; + } + } + assert(found); + } +} + +int main() +{ + struct group grp, *result = NULL; + char *buf; + size_t bufsize; + int s; + bool password = false; + + bufsize = sysconf(_SC_GETGR_R_SIZE_MAX); + assert(bufsize > 0 && bufsize < 0x100000); + + buf = malloc(bufsize); + assert(buf); + + s = getgrnam_r("root", &grp, buf, bufsize, &result); + assert(!s); + check_root_group(result); + + s = getgrgid_r(0, &grp, buf, bufsize, &result); + assert(!s); + check_root_group(result); + + result = getgrnam("root"); + check_root_group(result); + + result = getgrgid(0); + check_root_group(result); + + result = getgrnam("this_group_doesnt_exist"); + assert(!result); +#ifndef USE_HOST_LIBC + // This is not guaranteed. + assert(errno == ESRCH); +#endif + + s = getgrnam_r("this_group_doesnt_exist", &grp, buf, bufsize, &result); + assert(!result); +#ifndef USE_HOST_LIBC + // This is not guaranteed. + assert(s == ESRCH); +#endif + + errno = 0; + setgrent(); + assert(errno == 0); + + result = getgrent(); + assert(result); + grp.gr_name = strdup(result->gr_name); + if(result->gr_passwd) { + password = true; + } + grp.gr_passwd = strdup(result->gr_passwd); + grp.gr_gid = result->gr_gid; + assert(grp.gr_name); + if(password) + assert(grp.gr_passwd); + + assert(grp.gr_name); + if(password) + assert(grp.gr_passwd); + + endgrent(); + + result = getgrent(); + assert(result); + assert(strcmp(result->gr_name, grp.gr_name) == 0); + if(password) + assert(strcmp(result->gr_passwd, grp.gr_passwd) == 0); + assert(result->gr_gid == grp.gr_gid); + + free(grp.gr_name); + if(password) + free(grp.gr_passwd); + free(buf); +} diff --git a/lib/mlibc/tests/posix/if_indextoname.c b/lib/mlibc/tests/posix/if_indextoname.c new file mode 100644 index 0000000..15dc12a --- /dev/null +++ b/lib/mlibc/tests/posix/if_indextoname.c @@ -0,0 +1,11 @@ +#include <assert.h> +#include <net/if.h> +#include <stdio.h> + +int main() { + char name[IF_NAMESIZE]; + + assert(name == if_indextoname(1, name)); + printf("test: name '%s'\n", name); + assert(1 == if_nametoindex(name)); +} diff --git a/lib/mlibc/tests/posix/index.c b/lib/mlibc/tests/posix/index.c new file mode 100644 index 0000000..63be75d --- /dev/null +++ b/lib/mlibc/tests/posix/index.c @@ -0,0 +1,20 @@ +#include <assert.h> +#include <strings.h> +#include <stddef.h> + +int main() { + char str[] = "This is a sample string"; + char *pch; + // The character 's' is at position 4, 7, 11 and 18 + pch = index(str, 's'); + assert(pch - str + 1 == 4); + pch = index(pch + 1, 's'); + assert(pch - str + 1 == 7); + pch = index(pch + 1, 's'); + assert(pch - str + 1 == 11); + pch = index(pch + 1, 's'); + assert(pch - str + 1 == 18); + pch = index(pch + 1, 's'); + assert(pch == NULL); + return 0; +} diff --git a/lib/mlibc/tests/posix/inet_ntop.c b/lib/mlibc/tests/posix/inet_ntop.c new file mode 100644 index 0000000..feca26c --- /dev/null +++ b/lib/mlibc/tests/posix/inet_ntop.c @@ -0,0 +1,30 @@ +#include <arpa/inet.h> +#include <stdio.h> +#include <assert.h> +#include <string.h> + +int main() { + struct in_addr addr; + addr.s_addr = (1 << 24) | + (1 << 16) | (1 << 8) | 1; + char buf[INET_ADDRSTRLEN]; + assert(inet_ntop(AF_INET, &addr, buf, INET_ADDRSTRLEN) != NULL); + assert(strncmp("1.1.1.1", buf, INET_ADDRSTRLEN) == 0); + + struct in6_addr addr2 = { .s6_addr = {0x20, 0x1, 0xd, 0xb8, 0, 0, 0, 0, 0, 0x1, 0, 0, 0, 0, 0, 0x1} }; + char buf2[INET6_ADDRSTRLEN]; + assert(inet_ntop(AF_INET6, &addr2, buf2, INET6_ADDRSTRLEN) != NULL); + assert(strncmp("2001:db8::1:0:0:1", buf2, INET6_ADDRSTRLEN) == 0); + + struct in6_addr addr3 = { .s6_addr = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1} }; + char buf3[INET6_ADDRSTRLEN]; + assert(inet_ntop(AF_INET6, &addr3, buf3, INET6_ADDRSTRLEN) != NULL); + assert(strncmp("::1", buf3, INET6_ADDRSTRLEN) == 0); + + struct in6_addr addr4 = { .s6_addr = {0x20, 0x1, 0xd, 0xb8, 00, 00, 00, 0x1, 00, 0x1, 00, 0x1, 00, 0x1, 00, 0x1} }; + char buf4[INET6_ADDRSTRLEN]; + assert(inet_ntop(AF_INET6, &addr4, buf4, INET6_ADDRSTRLEN) != NULL); + assert(strncmp("2001:db8:0:1:1:1:1:1", buf4, INET6_ADDRSTRLEN) == 0); + + return 0; +} diff --git a/lib/mlibc/tests/posix/inet_pton.c b/lib/mlibc/tests/posix/inet_pton.c new file mode 100644 index 0000000..6c0f342 --- /dev/null +++ b/lib/mlibc/tests/posix/inet_pton.c @@ -0,0 +1,16 @@ +#include <arpa/inet.h> +#include <assert.h> + +int main() { + struct in_addr addr; + assert(inet_pton(AF_INET, "1.1.1.1", &addr)); + assert((addr.s_addr & 0xFF) == 1); + assert(((addr.s_addr >> 8) & 0xFF) == 1); + assert(((addr.s_addr >> 16) & 0xFF) == 1); + assert(((addr.s_addr >> 24) & 0xFF) == 1); + + assert(!inet_pton(AF_INET, "256.999.1234.555", &addr)); + assert(!inet_pton(AF_INET, "a.b.c.d", &addr)); + return 0; +} + diff --git a/lib/mlibc/tests/posix/memrchr.c b/lib/mlibc/tests/posix/memrchr.c new file mode 100644 index 0000000..99e3e25 --- /dev/null +++ b/lib/mlibc/tests/posix/memrchr.c @@ -0,0 +1,25 @@ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#include <stdio.h> +#include <string.h> +#include <assert.h> + +int main () { + char str[] = "The Last Supper by Leonardo da Vinci"; + char *str_temp; + + // Test strstr + str_temp = strstr(str, "Supper"); /* Find a substring in the string */ + assert(!strcmp(str_temp, "Supper by Leonardo da Vinci")); + + /* Following calls use memory APIs for the above tasks */ + // Test memchr + str_temp = (char *)memchr((void *)str, 'L', strlen(str)); + assert(!strcmp(str_temp, "Last Supper by Leonardo da Vinci")); + + // Test memrchr + str_temp = (char *)memrchr((void *)str, 'L', strlen(str)); + assert(!strcmp(str_temp, "Leonardo da Vinci")); + return 0; +} diff --git a/lib/mlibc/tests/posix/mkstemp.c b/lib/mlibc/tests/posix/mkstemp.c new file mode 100644 index 0000000..d06783e --- /dev/null +++ b/lib/mlibc/tests/posix/mkstemp.c @@ -0,0 +1,70 @@ +#include <assert.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> + +void validate_pattern(char *p) { + assert(memcmp(p, "XXXXXX", 6)); + assert(memchr(p, 0, 6) == NULL); + + for (int i = 0; i < 6; i++) { + assert(isalnum(p[i])); + } +} + +int main() { + int ret; + + // Make sure the patterns themselves cannot be chosen. This + // *could* happen on glibc, or if we widen the character set + // used for generating random names. Odds are 1 in 60 billion, + // but I'd rather not worry about this. + ret = open("prefixXXXXXX", O_RDWR | O_CREAT | O_EXCL, 0600); + assert(ret >= 0 || (ret == -1 && errno == EEXIST)); + ret = open("longprefixXXXXXXlongsuffix", O_RDWR | O_CREAT | O_EXCL, 0600); + assert(ret >= 0 || (ret == -1 && errno == EEXIST)); + + ret = mkstemp("short"); + assert(ret == -1); + assert(errno == EINVAL); + + ret = mkstemp("lessthan6XXX"); + assert(ret == -1); + assert(errno == EINVAL); + + ret = mkstemps("lessthan6XXXswithsuffix", 11); + assert(ret == -1); + assert(errno == EINVAL); + + char *p = strdup("prefixXXXXXX"); + ret = mkstemp(p); + // We can't really protect against EEXIST... + assert(ret >= 0 || (ret == -1 && errno == EEXIST)); + assert(!memcmp(p, "prefix", 6)); + assert(p[12] == 0); + validate_pattern(p + 6); + + if (ret >= 0) { + ret = close(ret); + assert(!ret); + } + free(p); + + p = strdup("longprefixXXXXXXlongsuffix"); + ret = mkstemps(p, 10); + // We can't really protect against EEXIST... + assert(ret >= 0 || (ret == -1 && errno == EEXIST)); + assert(!memcmp(p, "longprefix", 10)); + assert(!memcmp(p + 16, "longsuffix", 10)); + assert(p[26] == 0); + validate_pattern(p + 10); + + if (ret >= 0) { + ret = close(ret); + assert(!ret); + } + free(p); +} diff --git a/lib/mlibc/tests/posix/open_memstream.c b/lib/mlibc/tests/posix/open_memstream.c new file mode 100644 index 0000000..0347003 --- /dev/null +++ b/lib/mlibc/tests/posix/open_memstream.c @@ -0,0 +1,65 @@ +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> + +#define WRITE_NO 1024 + +int main() { + size_t size; + char *buf; + + FILE *fp = open_memstream(&buf, &size); + assert(fp); + + char c = 'A'; + for (size_t i = 0; i < WRITE_NO; i++) + assert(fwrite(&c, sizeof(char), 1, fp) == 1); + + // Flush the file to update the pointers. + assert(!fflush(fp)); + + assert(size == WRITE_NO); + for (size_t i = 0; i < size; i++) + assert(buf[i] == c); + + // Check if the stream maintains a null-bye at the end. + assert(buf[size] == '\0'); + + // Stream should be expanded, size should be == 2*WRITE_NO. + assert(!fseek(fp, WRITE_NO, SEEK_END)); + assert(!fflush(fp)); + assert(size == 2*WRITE_NO); + assert(buf[size] == '\0'); + + // Check if it's filled with zero's. + for (size_t i = WRITE_NO; i < size; i++) + assert(buf[i] == '\0'); + + // Go back and overwrite the 0's with 'B'. + assert(!fseek(fp, -WRITE_NO, SEEK_CUR)); + c = 'B'; + for (size_t i = 0; i < WRITE_NO; i++) + assert(fwrite(&c, sizeof(char), 1, fp) == 1); + + // Check if that happened. + assert(size == 2*WRITE_NO); + for (size_t i = WRITE_NO; i < size; i++) + assert(buf[i] == c); + assert(buf[size] == '\0'); + + // Go to the front and write 'B'. + assert(!fseek(fp, 0, SEEK_SET)); + for (size_t i = 0; i < WRITE_NO; i++) + assert(fwrite(&c, sizeof(char), 1, fp) == 1); + + // Check if that happened. + assert(size == 2*WRITE_NO); + for (size_t i = 0; i < size; i++) + assert(buf[i] == c); + assert(buf[size] == '\0'); + + // Close the file, we have tested everything. + assert(!fclose(fp)); + free(buf); + return 0; +} diff --git a/lib/mlibc/tests/posix/pause.c b/lib/mlibc/tests/posix/pause.c new file mode 100644 index 0000000..054c5a4 --- /dev/null +++ b/lib/mlibc/tests/posix/pause.c @@ -0,0 +1,49 @@ +#include <assert.h> +#include <signal.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <sys/wait.h> + +static void noop(int x) { + (void)x; +} + +int main() { + signal(SIGUSR1, &noop); + + int pid; + switch(pid = fork()) { + case -1: + perror("fork"); + abort(); + case 0: + pause(); + assert(errno == EINTR); + return 0; + default: + while (1) { + usleep(100); + kill(pid, SIGUSR1); + usleep(100); + int status; + + errno = 0; + if (waitpid(-1, &status, WNOHANG) <= 0) { + if (errno && errno != EAGAIN) { + perror("wait"); + kill(pid, SIGKILL); + } + continue; + } + + if (!WIFEXITED(status)) { + printf("wait returned %x\n", status); + abort(); + } + + return WEXITSTATUS(status); + } + } +} diff --git a/lib/mlibc/tests/posix/popen.c b/lib/mlibc/tests/posix/popen.c new file mode 100644 index 0000000..87bbde2 --- /dev/null +++ b/lib/mlibc/tests/posix/popen.c @@ -0,0 +1,42 @@ +#include <stdio.h> +#include <string.h> +#include <assert.h> +#include <unistd.h> + +#ifdef USE_HOST_LIBC +#define TEST_FILE "popen-host-libc.tmp" +#else +#define TEST_FILE "popen.tmp" +#endif + +#define TEST_STRING1 "the quick brown fox jumps over the lazy dog" +#define TEST_STRING1_LEN 43 +#define TEST_STRING2 "Lorem ipsum dolor sit amet, consectetur adipiscing elit" +#define TEST_STRING2_LEN 55 + +int main() { + // Test reading + FILE *f1 = popen("echo -n '" TEST_STRING1 "'", "r"); + assert(f1); + char buf1[TEST_STRING1_LEN]; + assert(fread(buf1, 1, TEST_STRING1_LEN, f1) == TEST_STRING1_LEN); + assert(memcmp(buf1, TEST_STRING1, TEST_STRING1_LEN) == 0); + pclose(f1); + + // Test writing + FILE *f2 = popen("cat - > " TEST_FILE, "w"); + assert(f2); + assert(fwrite(TEST_STRING2, 1, TEST_STRING2_LEN, f2) == TEST_STRING2_LEN); + pclose(f2); + + // Read test file back + FILE *test_file = fopen(TEST_FILE, "r"); + assert(test_file); + char buf2[TEST_STRING2_LEN]; + assert(fread(buf2, 1, TEST_STRING2_LEN, test_file) == TEST_STRING2_LEN); + assert(memcmp(buf2, TEST_STRING2, TEST_STRING2_LEN) == 0); + fclose(test_file); + assert(unlink(TEST_FILE) == 0); + + return 0; +} diff --git a/lib/mlibc/tests/posix/posix-timer.c b/lib/mlibc/tests/posix/posix-timer.c new file mode 100644 index 0000000..d097818 --- /dev/null +++ b/lib/mlibc/tests/posix/posix-timer.c @@ -0,0 +1,47 @@ +#include <assert.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/time.h> +#include <time.h> + +int main() { + struct sigevent evp; + memset(&evp, 0, sizeof(evp)); + + sigset_t set; + sigemptyset(&set); + sigaddset(&set, SIGUSR1); + sigprocmask(SIG_BLOCK, &set, 0); + evp.sigev_notify = SIGEV_SIGNAL; + evp.sigev_signo = SIGUSR1; + + struct timeval start; + gettimeofday(&start, NULL); + + timer_t timer; + if (timer_create(CLOCK_MONOTONIC, &evp, &timer)) { + perror("timer_create"); + exit(1); + } + + struct itimerspec spec; + memset(&spec, 0, sizeof(spec)); + spec.it_value.tv_sec = 1; + spec.it_value.tv_nsec = 0; + + int sig; + timer_settime(timer, 0, &spec, NULL); + sigwait(&set, &sig); + + struct timeval end; + gettimeofday(&end, NULL); + timer_delete(timer); + + double diff = end.tv_sec - start.tv_sec; + diff += (end.tv_usec - start.tv_usec) / 1000000.0; + assert(diff >= 1.0); + + return 0; +} diff --git a/lib/mlibc/tests/posix/posix_memalign.c b/lib/mlibc/tests/posix/posix_memalign.c new file mode 100644 index 0000000..34652b9 --- /dev/null +++ b/lib/mlibc/tests/posix/posix_memalign.c @@ -0,0 +1,21 @@ +#include <stdlib.h> +#include <assert.h> +#include <stdint.h> +#include <errno.h> + +int main() { + void *p = NULL; + + // align must be a power of two + assert(posix_memalign(&p, 3, 1) == EINVAL && p == NULL); + + // align must be a multiple of sizeof(void *) + assert(posix_memalign(&p, sizeof(void *) / 2, 8) == EINVAL && p == NULL); + + assert(posix_memalign(&p, sizeof(void *), sizeof(void *)) == 0 && p != NULL && (uintptr_t)p % sizeof(void *) == 0); + free(p); + assert(posix_memalign(&p, 256, 1) == 0 && p != NULL && (uintptr_t)p % 256 == 0); + free(p); + assert(posix_memalign(&p, 256, 256) == 0 && p != NULL && (uintptr_t)p % 256 == 0); + free(p); +} diff --git a/lib/mlibc/tests/posix/posix_spawn.c b/lib/mlibc/tests/posix/posix_spawn.c new file mode 100644 index 0000000..cc5ccff --- /dev/null +++ b/lib/mlibc/tests/posix/posix_spawn.c @@ -0,0 +1,38 @@ +#include <stdlib.h> +#include <assert.h> +#include <stdio.h> +#include <string.h> + +#include <unistd.h> +#include <spawn.h> +#include <sys/wait.h> + +extern char **environ; + +void run_cmd(char *cmd) +{ + pid_t pid; + char *argv[] = {"sh", "-c", cmd, NULL}; + int status; + printf("Run command: %s\n", cmd); + status = posix_spawn(&pid, "/bin/sh", NULL, NULL, argv, environ); + if(status == 0) { + printf("Child pid: %i\n", pid); + if(waitpid(pid, &status, 0) != -1) { + printf("Child exited with status %i\n", status); + printf("Child exit status: %i\n", WEXITSTATUS(status)); + assert(WEXITSTATUS(status) == 0); + } else { + perror("waitpid"); + assert(0 == 1); + } + } else { + printf("posix_spawn: %s\n", strerror(status)); + assert(0 == 1); + } +} + +int main() { + run_cmd(":"); + return 0; +} diff --git a/lib/mlibc/tests/posix/pthread_atfork.c b/lib/mlibc/tests/posix/pthread_atfork.c new file mode 100644 index 0000000..45be136 --- /dev/null +++ b/lib/mlibc/tests/posix/pthread_atfork.c @@ -0,0 +1,54 @@ +#include <assert.h> +#include <pthread.h> +#include <signal.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <sys/wait.h> + +_Atomic int prepare_order = 0; +_Atomic int parent_order = 0; +_Atomic int child_order = 0; + +static void prepare1() { prepare_order = 1; } +static void prepare2() { prepare_order = 2; } + +static void parent1() { parent_order = 1; } +static void parent2() { parent_order = 2; } + +static void child1() { child_order = 1; } +static void child2() { child_order = 2; } + +int main() { + assert(!pthread_atfork(prepare1, parent1, child1)); + assert(!pthread_atfork(prepare2, parent2, child2)); + + pid_t pid = fork(); + assert(pid >= 0); + + if (!pid) { + assert(child_order == 2); + exit(0); + } else { + assert(prepare_order == 1); + assert(parent_order == 2); + + while (1) { + int status = 0; + + int ret = waitpid(pid, &status, 0); + + if (ret == -1 && errno == EINTR) + continue; + + assert(ret > 0); + + if (WIFSIGNALED(status) && WTERMSIG(status) == SIGABRT) + return 1; + + return WEXITSTATUS(status); + } + } + + return 0; +} diff --git a/lib/mlibc/tests/posix/pthread_attr.c b/lib/mlibc/tests/posix/pthread_attr.c new file mode 100644 index 0000000..c901a90 --- /dev/null +++ b/lib/mlibc/tests/posix/pthread_attr.c @@ -0,0 +1,157 @@ +#include <pthread.h> +#include <assert.h> +#include <errno.h> +#include <alloca.h> +#include <string.h> +#include <sys/mman.h> +#include <signal.h> + +static void test_detachstate() { + pthread_attr_t attr; + assert(!pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED)); + int detachstate; + assert(!pthread_attr_getdetachstate(&attr, &detachstate)); + assert(detachstate == PTHREAD_CREATE_DETACHED); + assert(pthread_attr_setdetachstate(&attr, 2* (PTHREAD_CREATE_DETACHED + + PTHREAD_CREATE_JOINABLE)) == EINVAL); +} + +static void *stacksize_worker(void *arg) { + size_t default_stacksize = (*(size_t*)arg); + size_t alloc_size = default_stacksize + default_stacksize/2; + void *area = alloca(alloc_size); + // If the allocated stack was not enough this will crash. + *(volatile int*)(area + alloc_size) = 1; + return NULL; +} + +static void test_stacksize() { + pthread_attr_t attr; + assert(!pthread_attr_init(&attr)); + size_t stacksize; + assert(!pthread_attr_getstacksize(&attr, &stacksize)); + assert(!pthread_attr_setstacksize(&attr, stacksize * 2)); + pthread_t thread; + assert(!pthread_create(&thread, &attr, stacksize_worker, &stacksize)); + assert(!pthread_join(thread, NULL)); +} + +static void test_guardsize() { + pthread_attr_t attr; + assert(!pthread_attr_init(&attr)); + assert(!pthread_attr_setguardsize(&attr, 0)); + size_t guardsize; + assert(!pthread_attr_getguardsize(&attr, &guardsize)); + assert(!guardsize); +} + +static void test_scope() { + pthread_attr_t attr; + assert(!pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM)); + int scope; + assert(!pthread_attr_getscope(&attr, &scope)); + assert(scope == PTHREAD_SCOPE_SYSTEM); + assert(pthread_attr_setscope(&attr, 2* (PTHREAD_SCOPE_SYSTEM + + PTHREAD_SCOPE_PROCESS)) == EINVAL); +} + +static void test_inheritsched() { + pthread_attr_t attr; + assert(!pthread_attr_setinheritsched(&attr, PTHREAD_INHERIT_SCHED)); + int inheritsched; + assert(!pthread_attr_getinheritsched(&attr, &inheritsched)); + assert(inheritsched == PTHREAD_INHERIT_SCHED); + assert(pthread_attr_setinheritsched(&attr, 2* (PTHREAD_INHERIT_SCHED + + PTHREAD_EXPLICIT_SCHED)) == EINVAL); +} + +static void test_schedparam() { + pthread_attr_t attr; + struct sched_param init_param = {0}; + assert(!pthread_attr_setschedparam(&attr, &init_param)); + struct sched_param param = {1}; + assert(!pthread_attr_getschedparam(&attr, ¶m)); + assert(param.sched_priority == init_param.sched_priority); +} + +static void test_schedpolicy() { + pthread_attr_t attr; + assert(!pthread_attr_setschedpolicy(&attr, SCHED_FIFO)); + int policy; + assert(!pthread_attr_getschedpolicy(&attr, &policy)); + assert(policy == SCHED_FIFO); + assert(pthread_attr_setinheritsched(&attr, 2* (SCHED_FIFO + SCHED_RR + + SCHED_OTHER)) == EINVAL); +} + +static void *stackaddr_worker(void *arg) { + void *addr = *(void**)arg; + + void *sp; +#if defined(__x86_64__) + asm volatile ("mov %%rsp, %0" : "=r"(sp)); +#elif defined(__i386__) + asm volatile ("mov %%esp, %0" : "=r"(sp)); +#elif defined(__aarch64__) + asm volatile ("mov %0, sp" : "=r"(sp)); +#elif defined (__riscv) + asm volatile ("mv %0, sp" : "=r"(sp)); +#else +# error Unknown architecture +#endif + + // Check if our stack pointer is in a sane range. + assert(sp > addr); + return NULL; +} +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +static void test_stackaddr() { + pthread_attr_t attr; + assert(!pthread_attr_init(&attr)); + size_t size; + assert(!pthread_attr_getstacksize(&attr, &size)); + void *addr = mmap(NULL, size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + assert(!pthread_attr_setstack(&attr, addr, size)); + assert(!pthread_attr_setguardsize(&attr, 0)); + void *new_addr; + size_t new_size; + assert(!pthread_attr_getstack(&attr, &new_addr, &new_size)); + assert(new_addr == addr); + assert(new_size == size); + + pthread_t thread; + assert(!pthread_create(&thread, &attr, stackaddr_worker, &addr)); + assert(!pthread_join(thread, NULL)); +} +#pragma GCC diagnostic pop + +#if !defined(USE_HOST_LIBC) || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 32) +static void test_stack() { + pthread_attr_t attr; + void *stackaddr = (void*)1; + size_t stacksize = PTHREAD_STACK_MIN; + + assert(!pthread_attr_setstack(&attr, stackaddr, stacksize)); + void *new_addr; + size_t new_size; + assert(!pthread_attr_getstack(&attr, &new_addr, &new_size)); + assert(new_addr == stackaddr); + assert(new_size == stacksize); +} +#endif + +int main() { + test_detachstate(); + test_stacksize(); + test_guardsize(); + test_scope(); + test_inheritsched(); + test_schedparam(); + test_schedpolicy(); + test_stackaddr(); +#if !defined(USE_HOST_LIBC) || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 32) + test_stack(); +#endif +} diff --git a/lib/mlibc/tests/posix/pthread_barrier.c b/lib/mlibc/tests/posix/pthread_barrier.c new file mode 100644 index 0000000..213ba8f --- /dev/null +++ b/lib/mlibc/tests/posix/pthread_barrier.c @@ -0,0 +1,56 @@ +#include <pthread.h> +#include <unistd.h> +#include <assert.h> + +pthread_barrier_t barrier; +_Atomic int hitBarrierCount, pastBarrierCount; + +static void *worker(void *arg) { + (void)arg; + hitBarrierCount++; + pthread_barrier_wait(&barrier); + pastBarrierCount++; + return NULL; +} + +int main() { + // pthread_barrierattr_t + pthread_barrierattr_t attr; + pthread_barrierattr_init(&attr); + + int pshared; + pthread_barrierattr_getpshared(&attr, &pshared); + assert(pshared == PTHREAD_PROCESS_PRIVATE); + + pthread_barrierattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); + pthread_barrierattr_getpshared(&attr, &pshared); + assert(pshared == PTHREAD_PROCESS_SHARED); + + pthread_barrierattr_setpshared(&attr, PTHREAD_PROCESS_PRIVATE); + pthread_barrierattr_getpshared(&attr, &pshared); + assert(pshared == PTHREAD_PROCESS_PRIVATE); + + // pthread_barrier_t + pthread_barrier_init(&barrier, &attr, 3); + pthread_barrierattr_destroy(&attr); + + pthread_t thread1; + int ret = pthread_create(&thread1, NULL, &worker, NULL); + assert(!ret); + + pthread_t thread2; + ret = pthread_create(&thread2, NULL, &worker, NULL); + assert(!ret); + + sleep(1); + + // Make sure the barrier actually stops threads from proceeding. + assert(pastBarrierCount == 0); + assert(hitBarrierCount <= 2); + + hitBarrierCount++; + pthread_barrier_wait(&barrier); + assert(hitBarrierCount == 3); + + pthread_barrier_destroy(&barrier); +} diff --git a/lib/mlibc/tests/posix/pthread_cancel.c b/lib/mlibc/tests/posix/pthread_cancel.c new file mode 100644 index 0000000..ca4ca34 --- /dev/null +++ b/lib/mlibc/tests/posix/pthread_cancel.c @@ -0,0 +1,150 @@ +#include <assert.h> +#include <pthread.h> +#include <unistd.h> + +_Atomic int worker_ready = 0; +_Atomic int main_ready = 0; + +static void *worker1(void *arg) { + (void)arg; + assert(!pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL)); + assert(!pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL)); + + worker_ready = 1; + + while (1) sleep(1); + + return NULL; +} + +static void *worker2(void *arg) { + (void) arg; + assert(!pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL)); + + worker_ready = 1; + + while(!main_ready); + + // Cancellation point - we should cancel right now + sleep(1); + + assert(!"Expected to be cancelled!"); + __builtin_unreachable(); +} + +static void *worker3(void *arg) { + (void) arg; + assert(!pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL)); + + worker_ready = 1; + + while(!main_ready); + + // Cancellation point - we should cancel right now + pthread_testcancel(); + + assert(!"Expected to be cancelled!"); + __builtin_unreachable(); +} + +static void *worker4(void *arg) { + (void) arg; + assert(!pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL)); + + worker_ready = 1; + sleep(1); + + // We expect to be canceled during the sleep + + assert(!"Expected to be cancelled!"); + __builtin_unreachable(); +} + +static void *worker5(void *arg) { + assert(!pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL)); + + worker_ready = 1; + + while(!main_ready); + + // Cancellation point - we should NOT cancel right now + pthread_testcancel(); + + int *arg_int = (int*)arg; + *arg_int = 1; + + return NULL; +} + +static void check_result(pthread_t thread) { + + void *ret_val = NULL; + int ret = pthread_join(thread, &ret_val); + assert(!ret); + assert(ret_val == PTHREAD_CANCELED); +} + +int main() { + pthread_t thread; + int ret = pthread_create(&thread, NULL, &worker1, NULL); + assert(!ret); + + while (!worker_ready); + ret = pthread_cancel(thread); + assert(!ret); + check_result(thread); + + main_ready = 0; + worker_ready = 0; + main_ready = 0; + ret = pthread_create(&thread, NULL, &worker2, NULL); + assert(!ret); + + while(!worker_ready); + ret = pthread_cancel(thread); + assert(!ret); + main_ready = 1; + check_result(thread); + + main_ready = 0; + worker_ready = 0; + main_ready = 0; + ret = pthread_create(&thread, NULL, &worker3, NULL); + assert(!ret); + + while(!worker_ready); + ret = pthread_cancel(thread); + assert(!ret); + main_ready = 1; + check_result(thread); + + worker_ready = 0; + main_ready = 0; + ret = pthread_create(&thread, NULL, &worker4, NULL); + assert(!ret); + + while(!worker_ready); + ret = pthread_cancel(thread); + assert(!ret); + main_ready = 1; + check_result(thread); + + // Test for bug where pthread_testcancel() was not checking if + // cancellation was triggered properly. + worker_ready = 0; + int pthread_arg = 0; + main_ready = 0; + ret = pthread_create(&thread, NULL, &worker5, &pthread_arg); + assert(!ret); + + while(!worker_ready); + main_ready = 1; + + void *ret_val = NULL; + ret = pthread_join(thread, &ret_val); + assert(!ret); + assert(!ret_val); + assert(pthread_arg == 1); + + return 0; +} diff --git a/lib/mlibc/tests/posix/pthread_cleanup.c b/lib/mlibc/tests/posix/pthread_cleanup.c new file mode 100644 index 0000000..b6136aa --- /dev/null +++ b/lib/mlibc/tests/posix/pthread_cleanup.c @@ -0,0 +1,42 @@ +#include <stdint.h> +#include <assert.h> +#include <pthread.h> + +_Atomic uintptr_t cleanup_val = 0; + +static void cleanup(void *arg) { + cleanup_val = (uintptr_t)arg; +} + +static void *worker(void *arg) { + (void)arg; + + pthread_cleanup_push(cleanup, (void *)1); + pthread_cleanup_push(cleanup, (void *)2); + pthread_cleanup_push(cleanup, (void *)3); + pthread_cleanup_push(cleanup, (void *)4); + + pthread_cleanup_pop(1); + assert(cleanup_val == 4); + + pthread_cleanup_pop(0); + assert(cleanup_val == 4); + + pthread_exit(NULL); + + pthread_cleanup_pop(0); + pthread_cleanup_pop(0); + + return NULL; +} + +int main() { + pthread_t thread; + assert(!pthread_create(&thread, NULL, &worker, NULL)); + assert(!pthread_join(thread, NULL)); + + assert(cleanup_val == 1); + + return 0; +} + diff --git a/lib/mlibc/tests/posix/pthread_cond.c b/lib/mlibc/tests/posix/pthread_cond.c new file mode 100644 index 0000000..6203928 --- /dev/null +++ b/lib/mlibc/tests/posix/pthread_cond.c @@ -0,0 +1,106 @@ +#include <assert.h> +#include <errno.h> +#include <pthread.h> +#include <unistd.h> + +_Atomic int waiting, should_exit; +pthread_cond_t cond = PTHREAD_COND_INITIALIZER; +pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; + +static void *worker(void *arg) { + (void)arg; + pthread_mutex_lock(&mtx); + ++waiting; + while (!should_exit) + pthread_cond_wait(&cond, &mtx); + pthread_mutex_unlock(&mtx); + return NULL; +} + +static void test_broadcast_wakes_all() { + pthread_t t1, t2; + pthread_create(&t1, NULL, &worker, NULL); + pthread_create(&t2, NULL, &worker, NULL); + + // Wait until the workers have actually entered the cond_wait + // before doing a broadcast. + while (waiting != 2 || pthread_mutex_trylock(&mtx) == EBUSY) + usleep(150000); // 150ms + + should_exit = 1; + assert(!pthread_cond_broadcast(&cond)); + pthread_mutex_unlock(&mtx); + + pthread_join(t1, NULL); + pthread_join(t2, NULL); +} + +static void test_timedwait_timedout() { + // Use CLOCK_MONOTONIC. + pthread_condattr_t attr; + pthread_condattr_init(&attr); + assert(!pthread_condattr_setclock(&attr, CLOCK_MONOTONIC)); + + struct timespec before_now; + assert(!clock_gettime(CLOCK_MONOTONIC, &before_now)); + before_now.tv_nsec -= 10000; + + pthread_mutex_lock(&mtx); + int e = pthread_cond_timedwait(&cond, &mtx, &before_now); + assert(e == ETIMEDOUT); + pthread_mutex_unlock(&mtx); + + long nanos_per_second = 1000000000; + struct timespec after_now; + assert(!clock_gettime(CLOCK_MONOTONIC, &after_now)); + after_now.tv_nsec += nanos_per_second / 10; // 100ms + if (after_now.tv_nsec >= nanos_per_second) { + after_now.tv_nsec -= nanos_per_second; + after_now.tv_sec++; + } + + pthread_mutex_lock(&mtx); + e = pthread_cond_timedwait(&cond, &mtx, &after_now); + assert(e == ETIMEDOUT); + pthread_mutex_unlock(&mtx); + + after_now.tv_nsec += nanos_per_second; + pthread_mutex_lock(&mtx); + e = pthread_cond_timedwait(&cond, &mtx, &after_now); + assert(e == EINVAL); + pthread_mutex_unlock(&mtx); +} + +static void test_attr() { + pthread_condattr_t attr; + pthread_condattr_init(&attr); + + clockid_t clock; + assert(!pthread_condattr_getclock(&attr, &clock)); + assert(clock == CLOCK_REALTIME); + assert(!pthread_condattr_setclock(&attr, CLOCK_MONOTONIC)); + assert(!pthread_condattr_getclock(&attr, &clock)); + assert(clock == CLOCK_MONOTONIC); + + int pshared; + assert(!pthread_condattr_getpshared(&attr, &pshared)); + assert(pshared == PTHREAD_PROCESS_PRIVATE); + assert(!pthread_condattr_setpshared(&attr, PTHREAD_PROCESS_SHARED)); + assert(!pthread_condattr_getpshared(&attr, &pshared)); + assert(pshared == PTHREAD_PROCESS_SHARED); + + pthread_condattr_destroy(&attr); + pthread_condattr_init(&attr); + + // Make sure that we can create a pthread_cond_t with an attr. + pthread_cond_t cond; + pthread_cond_init(&cond, &attr); + pthread_cond_destroy(&cond); + pthread_condattr_destroy(&attr); +} + +int main() { + test_attr(); + test_broadcast_wakes_all(); + test_timedwait_timedout(); +} diff --git a/lib/mlibc/tests/posix/pthread_create.c b/lib/mlibc/tests/posix/pthread_create.c new file mode 100644 index 0000000..b78674f --- /dev/null +++ b/lib/mlibc/tests/posix/pthread_create.c @@ -0,0 +1,22 @@ +#include <assert.h> +#include <time.h> +#include <pthread.h> + +int variable = 0; + +static void *worker(void *arg) { + (void) arg; + variable = 1; + return NULL; +} + +int main() { + pthread_t thread; + int ret = pthread_create(&thread, NULL, &worker, NULL); + assert(!ret); + + ret = pthread_join(thread, NULL); + assert(!ret); + assert(variable == 1); + return 0; +} diff --git a/lib/mlibc/tests/posix/pthread_key.c b/lib/mlibc/tests/posix/pthread_key.c new file mode 100644 index 0000000..40bd882 --- /dev/null +++ b/lib/mlibc/tests/posix/pthread_key.c @@ -0,0 +1,95 @@ +#include <assert.h> +#include <pthread.h> +#include <errno.h> +#include <limits.h> + +_Atomic int dtors_entered = 0; +pthread_key_t key3; + +static void dtor1(void *arg) { + (void)arg; + dtors_entered++; + + // Set the key during destruction to trigger dtor2 (as it only runs if + // the key value is non-NULL). + assert(!pthread_setspecific(key3, &key3)); + assert(pthread_getspecific(key3) == &key3); +} + +static void dtor2(void *arg) { + (void)arg; + dtors_entered++; +} + +static void *worker1(void *arg) { + (void)arg; + + pthread_key_t key1, key2; + assert(!pthread_key_create(&key1, NULL)); + assert(!pthread_key_create(&key2, dtor1)); + assert(!pthread_key_create(&key3, dtor2)); + + assert(!pthread_setspecific(key1, &key1)); + assert(pthread_getspecific(key1) == &key1); + + assert(!pthread_setspecific(key2, &key2)); + assert(pthread_getspecific(key2) == &key2); + + pthread_exit(0); + return NULL; +} + +static void dtor3(void *arg) { + (void)arg; + + // Make sure that we can create and destroy keys inside the dtor. + pthread_key_t dtorKey; + assert(!pthread_key_create(&dtorKey, NULL)); + + assert(!pthread_setspecific(dtorKey, &dtorKey)); + assert(pthread_getspecific(dtorKey) == &dtorKey); + + assert(!pthread_key_delete(dtorKey)); +} + +static void *worker2(void *arg) { + (void)arg; + + pthread_key_t key; + assert(!pthread_key_create(&key, dtor3)); + + assert(!pthread_setspecific(key, &key)); + assert(pthread_getspecific(key) == &key); + + pthread_exit(0); + return NULL; +} + +int main() { + // NOTE that the EINVAL return from pthread_setspecific is mlibc-specific, + // POSIX specifies that accessing an invalid key is undefined behavior. + + assert(pthread_getspecific(PTHREAD_KEYS_MAX) == NULL); + assert(pthread_setspecific(PTHREAD_KEYS_MAX, NULL) == EINVAL); + + pthread_key_t key; + assert(!pthread_key_create(&key, NULL)); + assert(!pthread_setspecific(key, &key)); + assert(pthread_getspecific(key) == &key); + assert(!pthread_key_delete(key)); + + pthread_t thread; + assert(!pthread_create(&thread, NULL, &worker1, NULL)); + assert(!pthread_join(thread, NULL)); + + assert(pthread_getspecific(key) == NULL); + assert(!pthread_setspecific(key, &key)); + assert(pthread_getspecific(key) == &key); + + assert(dtors_entered == 2); + + assert(!pthread_create(&thread, NULL, &worker2, NULL)); + assert(!pthread_join(thread, NULL)); + + return 0; +} diff --git a/lib/mlibc/tests/posix/pthread_kill.c b/lib/mlibc/tests/posix/pthread_kill.c new file mode 100644 index 0000000..b740b70 --- /dev/null +++ b/lib/mlibc/tests/posix/pthread_kill.c @@ -0,0 +1,46 @@ +#include <assert.h> +#include <pthread.h> +#include <signal.h> + +_Atomic int handler_ready = 0; +_Atomic int thread_signal_ran = 0; + +static void sig_handler(int sig, siginfo_t *info, void *ctx) { + (void)sig; + (void)info; + (void)ctx; + + thread_signal_ran = 1; +} + +static void *worker(void *arg) { + (void)arg; + + struct sigaction sa; + sigemptyset(&sa.sa_mask); + sa.sa_sigaction = sig_handler; + sa.sa_flags = SA_SIGINFO; + assert(!sigaction(SIGUSR1, &sa, NULL)); + + handler_ready = 1; + + while (!thread_signal_ran) + ; + + return NULL; +} + +int main() { + pthread_t thread; + assert(!pthread_create(&thread, NULL, &worker, NULL)); + + while (!handler_ready) + ; + + assert(!pthread_kill(thread, SIGUSR1)); + assert(!pthread_join(thread, NULL)); + + assert(thread_signal_ran); + + return 0; +} diff --git a/lib/mlibc/tests/posix/pthread_mutex.c b/lib/mlibc/tests/posix/pthread_mutex.c new file mode 100644 index 0000000..0a9b82f --- /dev/null +++ b/lib/mlibc/tests/posix/pthread_mutex.c @@ -0,0 +1,101 @@ +#include <pthread.h> +#include <assert.h> +#include <errno.h> + +#define TEST_ATTR(attr, field, value) ({ \ + int x; \ + assert(!pthread_mutexattr_set ## field (&(attr), (value))); \ + assert(!pthread_mutexattr_get ## field (&(attr), &x)); \ + assert(x == (value)); \ + }) + +int variable; +pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; + +static void *worker(void *arg) { + (void)arg; + pthread_mutex_lock(&mutex); + variable = 1; + pthread_mutex_unlock(&mutex); + return NULL; +} + +static void testAttr() { + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + + TEST_ATTR(attr, type, PTHREAD_MUTEX_DEFAULT); + TEST_ATTR(attr, type, PTHREAD_MUTEX_NORMAL); + TEST_ATTR(attr, type, PTHREAD_MUTEX_ERRORCHECK); + TEST_ATTR(attr, type, PTHREAD_MUTEX_RECURSIVE); + + TEST_ATTR(attr, robust, PTHREAD_MUTEX_STALLED); + TEST_ATTR(attr, robust, PTHREAD_MUTEX_ROBUST); + + TEST_ATTR(attr, protocol, PTHREAD_PRIO_NONE); + TEST_ATTR(attr, protocol, PTHREAD_PRIO_INHERIT); + TEST_ATTR(attr, protocol, PTHREAD_PRIO_PROTECT); + + TEST_ATTR(attr, pshared, PTHREAD_PROCESS_PRIVATE); + TEST_ATTR(attr, pshared, PTHREAD_PROCESS_SHARED); + + // TODO: sched_get_priority* is unimplemented. + // int prio = sched_get_priority_max(SCHED_FIFO); + // TEST_ATTR(attr, prioceiling, prio); + + pthread_mutexattr_destroy(&attr); +} + +static void testNormal() { + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutex_init(&mutex, &attr); + pthread_mutexattr_destroy(&attr); + + pthread_mutex_lock(&mutex); + variable = 0; + + pthread_t thread; + int ret = pthread_create(&thread, NULL, &worker, NULL); + assert(!ret); + + assert(pthread_mutex_trylock(&mutex) == EBUSY); + pthread_mutex_unlock(&mutex); + + ret = pthread_join(thread, NULL); + assert(!ret); + assert(variable == 1); + + pthread_mutex_destroy(&mutex); +} + +static void testRecursive() { + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&mutex, &attr); + pthread_mutexattr_destroy(&attr); + + pthread_mutex_lock(&mutex); + variable = 0; + + pthread_t thread; + int ret = pthread_create(&thread, NULL, &worker, NULL); + assert(!ret); + + assert(pthread_mutex_trylock(&mutex) == 0); + pthread_mutex_unlock(&mutex); + pthread_mutex_unlock(&mutex); + + ret = pthread_join(thread, NULL); + assert(!ret); + assert(variable == 1); + + pthread_mutex_destroy(&mutex); +} + +int main() { + testAttr(); + testNormal(); + testRecursive(); +} diff --git a/lib/mlibc/tests/posix/pthread_rwlock.c b/lib/mlibc/tests/posix/pthread_rwlock.c new file mode 100644 index 0000000..5316372 --- /dev/null +++ b/lib/mlibc/tests/posix/pthread_rwlock.c @@ -0,0 +1,114 @@ +#include <assert.h> +#include <errno.h> +#include <pthread.h> + +static void test_write_lock_unlock() { + int res; + pthread_rwlock_t rw = PTHREAD_RWLOCK_INITIALIZER; + res = pthread_rwlock_wrlock(&rw); + assert(!res); + res = pthread_rwlock_unlock(&rw); + assert(!res); +} + +static void test_read_lock_unlock() { + int res; + pthread_rwlock_t rw = PTHREAD_RWLOCK_INITIALIZER; + res = pthread_rwlock_rdlock(&rw); + assert(!res); + res = pthread_rwlock_unlock(&rw); + assert(!res); +} + +static void test_write_trylock_unlock() { + int res; + pthread_rwlock_t rw = PTHREAD_RWLOCK_INITIALIZER; + res = pthread_rwlock_trywrlock(&rw); + assert(!res); + res = pthread_rwlock_unlock(&rw); + assert(!res); +} + +static void test_read_trylock_unlock() { + int res; + pthread_rwlock_t rw = PTHREAD_RWLOCK_INITIALIZER; + res = pthread_rwlock_tryrdlock(&rw); + assert(!res); + res = pthread_rwlock_unlock(&rw); + assert(!res); +} + +static void test_write_prevents_read() { + int res; + pthread_rwlock_t rw = PTHREAD_RWLOCK_INITIALIZER; + res = pthread_rwlock_wrlock(&rw); + assert(!res); + res = pthread_rwlock_tryrdlock(&rw); + assert(res == EBUSY); + res = pthread_rwlock_unlock(&rw); + assert(!res); +} + +static void test_write_prevents_write() { + int res; + pthread_rwlock_t rw = PTHREAD_RWLOCK_INITIALIZER; + res = pthread_rwlock_wrlock(&rw); + assert(!res); + res = pthread_rwlock_trywrlock(&rw); + assert(res == EBUSY); + res = pthread_rwlock_unlock(&rw); + assert(!res); +} + +static void test_read_prevents_write() { + int res; + pthread_rwlock_t rw = PTHREAD_RWLOCK_INITIALIZER; + res = pthread_rwlock_rdlock(&rw); + assert(!res); + res = pthread_rwlock_trywrlock(&rw); + assert(res == EBUSY); + res = pthread_rwlock_unlock(&rw); + assert(!res); +} + +static void test_read_allows_read() { + int res; + pthread_rwlock_t rw = PTHREAD_RWLOCK_INITIALIZER; + res = pthread_rwlock_rdlock(&rw); + assert(!res); + res = pthread_rwlock_tryrdlock(&rw); + assert(!res); + res = pthread_rwlock_unlock(&rw); + assert(!res); +} + +static void test_attr() { + pthread_rwlockattr_t attr; + pthread_rwlockattr_init(&attr); + + int pshared; + pthread_rwlockattr_getpshared(&attr, &pshared); + assert(pshared == PTHREAD_PROCESS_PRIVATE); + + pthread_rwlockattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); + pthread_rwlockattr_getpshared(&attr, &pshared); + assert(pshared == PTHREAD_PROCESS_SHARED); + + pthread_rwlockattr_setpshared(&attr, PTHREAD_PROCESS_PRIVATE); + pthread_rwlockattr_getpshared(&attr, &pshared); + assert(pshared == PTHREAD_PROCESS_PRIVATE); + + pthread_rwlockattr_destroy(&attr); +} + +int main() { + test_write_lock_unlock(); + test_read_lock_unlock(); + test_write_trylock_unlock(); + test_read_trylock_unlock(); + test_write_prevents_read(); + test_write_prevents_write(); + test_read_prevents_write(); + test_read_allows_read(); + test_attr(); +} diff --git a/lib/mlibc/tests/posix/pthread_schedparam.c b/lib/mlibc/tests/posix/pthread_schedparam.c new file mode 100644 index 0000000..dde70fb --- /dev/null +++ b/lib/mlibc/tests/posix/pthread_schedparam.c @@ -0,0 +1,32 @@ +#include <assert.h> +#include <errno.h> +#include <pthread.h> +#include <stdlib.h> + +int main() { + struct sched_param param = { + .sched_priority = 100, + }; + + int policy = 0xDEADBEEF; + + int ret = pthread_getschedparam(pthread_self(), &policy, ¶m); + assert(policy == SCHED_OTHER); + assert(!ret); + + param.sched_priority = 10; + + ret = pthread_setschedparam(pthread_self(), SCHED_FIFO, ¶m); + assert(!ret || ret == EPERM); + + if(ret == EPERM) { + exit(0); + } + + param.sched_priority = 0xDEADBEEF; + + ret = pthread_getschedparam(pthread_self(), &policy, ¶m); + assert(policy == SCHED_FIFO); + assert(param.sched_priority == 10); + assert(!ret); +} diff --git a/lib/mlibc/tests/posix/pthread_thread_local.c b/lib/mlibc/tests/posix/pthread_thread_local.c new file mode 100644 index 0000000..90c6f72 --- /dev/null +++ b/lib/mlibc/tests/posix/pthread_thread_local.c @@ -0,0 +1,53 @@ +#include <stdio.h> +#include <pthread.h> +#include <assert.h> + +_Thread_local unsigned int counter = 9999; +_Thread_local unsigned int uninitialized; + +void *check_counter(void *arg) +{ + (void)arg; + fprintf(stderr, "counter for worker thread: %d, at %p\n", counter, &counter); + fflush(stderr); + assert(counter == 9999); + + fprintf(stderr, "uninitialized data for worker thread: %d, at %p\n", uninitialized, &uninitialized); + fflush(stderr); + assert(uninitialized == 0); + + ++counter; + ++uninitialized; + fprintf(stderr, "counter for worker thread: %d\n", counter); + fflush(stderr); + assert(counter == 10000); + assert(uninitialized == 1); + return NULL; +} + +int main() +{ + fprintf(stderr, "counter for main thread: %d, at %p\n", counter, &counter); + fflush(stderr); + assert(counter == 9999); + + fprintf(stderr, "uninitialized data for main thread: %d, at %p\n", uninitialized, &uninitialized); + fflush(stderr); + assert(uninitialized == 0); + + ++counter; + ++uninitialized; + fprintf(stderr, "counter for main: %d\n", counter); + fflush(stderr); + assert(counter == 10000); + assert(uninitialized == 1); + + pthread_t thd; + pthread_create(&thd, NULL, check_counter, NULL); + pthread_join(thd, NULL); + + fprintf(stderr, "counter for main: %d\n", counter); + fflush(stderr); + assert(counter == 10000); + assert(uninitialized == 1); +} diff --git a/lib/mlibc/tests/posix/pwd.c b/lib/mlibc/tests/posix/pwd.c new file mode 100644 index 0000000..a9ebd8e --- /dev/null +++ b/lib/mlibc/tests/posix/pwd.c @@ -0,0 +1,88 @@ +#include <stdlib.h> +#include <stdio.h> +#include <pwd.h> +#include <unistd.h> +#include <assert.h> +#include <string.h> +#include <errno.h> + +int main() +{ + struct passwd pwd, *result = NULL; + char *buf; + size_t bufsize; + int s; + + bufsize = sysconf(_SC_GETPW_R_SIZE_MAX); + assert(bufsize > 0 && bufsize < 0x100000); + + buf = malloc(bufsize); + assert(buf); + + s = getpwnam_r("root", &pwd, buf, bufsize, &result); + assert(!s); + assert(pwd.pw_uid == 0); + assert(!strcmp(pwd.pw_name, "root")); + assert(strlen(pwd.pw_passwd) <= 1000); + + s = getpwuid_r(0, &pwd, buf, bufsize, &result); + assert(!s); + assert(pwd.pw_uid == 0); + assert(!strcmp(pwd.pw_name, "root")); + assert(strlen(pwd.pw_passwd) <= 1000); + + result = getpwnam("root"); + assert(result); + assert(result->pw_uid == 0); + assert(!strcmp(result->pw_name, "root")); + assert(strlen(result->pw_passwd) <= 1000); + + result = getpwuid(0); + assert(result); + assert(result->pw_uid == 0); + assert(!strcmp(result->pw_name, "root")); + assert(strlen(result->pw_passwd) <= 1000); + + errno = 0; + setpwent(); + assert(errno == 0); + + errno = 0; + result = getpwent(); + assert(result); + + pwd = *result; + pwd.pw_name = strdup(result->pw_name); + pwd.pw_passwd = strdup(result->pw_passwd); + pwd.pw_gecos = strdup(result->pw_gecos); + pwd.pw_dir = strdup(result->pw_dir); + pwd.pw_shell = strdup(result->pw_shell); + assert(pwd.pw_name); + assert(pwd.pw_passwd); + assert(pwd.pw_gecos); + assert(pwd.pw_dir); + assert(pwd.pw_shell); + + errno = 0; + setpwent(); + assert(errno == 0); + + errno = 0; + result = getpwent(); + assert(result); + + assert(!strcmp(pwd.pw_name, result->pw_name)); + assert(!strcmp(pwd.pw_passwd, result->pw_passwd)); + assert(!strcmp(pwd.pw_gecos, result->pw_gecos)); + assert(!strcmp(pwd.pw_dir, result->pw_dir)); + assert(!strcmp(pwd.pw_shell, result->pw_shell)); + assert(pwd.pw_uid == result->pw_uid); + assert(pwd.pw_gid == result->pw_gid); + + free(buf); + free(pwd.pw_name); + free(pwd.pw_passwd); + free(pwd.pw_gecos); + free(pwd.pw_dir); + free(pwd.pw_shell); +} diff --git a/lib/mlibc/tests/posix/readv-writev.c b/lib/mlibc/tests/posix/readv-writev.c new file mode 100644 index 0000000..5025ce2 --- /dev/null +++ b/lib/mlibc/tests/posix/readv-writev.c @@ -0,0 +1,51 @@ +#include <assert.h> +#include <fcntl.h> +#include <string.h> +#include <unistd.h> +#include <sys/uio.h> + +#ifdef USE_HOST_LIBC +#define TEST_FILE "readv-writev-host-libc.tmp" +#else +#define TEST_FILE "readv-writev.tmp" +#endif + +int main() { + // Make sure that it wasn't created by a previous test run + unlink(TEST_FILE); + + int fd = open(TEST_FILE, O_RDWR | O_CREAT, 0644); + assert(fd != -1); + + char str0[] = "hello "; + char str1[] = "world!"; + + struct iovec bufs[2] = { + { + .iov_base = &str0, + .iov_len = strlen(str0), + }, + { + .iov_base = &str1, + .iov_len = strlen(str1), + }, + }; + + ssize_t written = writev(fd, bufs, 2); + assert(written == 12); + + memset(&str0, 0, strlen(str0)); + memset(&str1, 0, strlen(str1)); + + assert(!lseek(fd, 0, SEEK_SET)); + + ssize_t read = readv(fd, bufs, 2); + assert(read == 12); + + assert(!strncmp(str0, "hello ", 7)); + assert(!strncmp(str1, "world!", 7)); + + assert(!close(fd)); + + unlink(TEST_FILE); +} diff --git a/lib/mlibc/tests/posix/realpath.c b/lib/mlibc/tests/posix/realpath.c new file mode 100644 index 0000000..e44de81 --- /dev/null +++ b/lib/mlibc/tests/posix/realpath.c @@ -0,0 +1,133 @@ +#include <unistd.h> +#include <sys/stat.h> +#include <assert.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <signal.h> + +#ifdef USE_HOST_LIBC +#define TEST_BASE "/tmp/mlibc-realpath-host-libc" +#else +#define TEST_BASE "/tmp/mlibc-realpath" +#endif + +void prepare() { + assert(!mkdir(TEST_BASE "/", S_IRWXU)); + assert(!mkdir(TEST_BASE "/dir1", S_IRWXU)); + assert(!mkdir(TEST_BASE "/dir2", S_IRWXU)); + assert(!symlink(TEST_BASE "/dir2/", TEST_BASE "/dir1/abs-link")); + assert(!chdir(TEST_BASE "/dir1")); + assert(!symlink("../dir2/", TEST_BASE "/dir1/rel-link")); +} + +void cleanup(int do_assert) { + if (do_assert) { + assert(!unlink(TEST_BASE "/dir1/rel-link")); + assert(!unlink(TEST_BASE "/dir1/abs-link")); + assert(!rmdir(TEST_BASE "/dir2")); + assert(!rmdir(TEST_BASE "/dir1")); + assert(!rmdir(TEST_BASE "/")); + } else { + unlink(TEST_BASE "/dir1/rel-link"); + unlink(TEST_BASE "/dir1/abs-link"); + rmdir(TEST_BASE "/dir2"); + rmdir(TEST_BASE "/dir1"); + rmdir(TEST_BASE "/"); + } +} + +void signal_handler(int sig, siginfo_t *info, void *ctx) { + (void)sig; + (void)info; + (void)ctx; + + cleanup(0); + + struct sigaction sa; + sigemptyset(&sa.sa_mask); + sa.sa_handler = SIG_DFL; + sa.sa_flags = 0; + + sigaction(SIGABRT, &sa, NULL); + abort(); +} + +int main() { + char *path; + + struct sigaction sa; + sigemptyset(&sa.sa_mask); + sa.sa_sigaction = signal_handler; + sa.sa_flags = SA_SIGINFO; + sigaction(SIGABRT, &sa, NULL); + + prepare(); + + path = realpath(TEST_BASE "/dir1/", NULL); + assert(path); + assert(!strcmp(path, TEST_BASE "/dir1")); + free(path); + + path = realpath(TEST_BASE "/dir1/../dir2", NULL); + assert(path); + assert(!strcmp(path, TEST_BASE "/dir2")); + free(path); + + path = realpath(TEST_BASE "/dir1/abs-link/", NULL); + assert(path); + assert(!strcmp(path, TEST_BASE "/dir2")); + free(path); + + path = realpath(TEST_BASE "/dir1/rel-link/", NULL); + assert(path); + assert(!strcmp(path, TEST_BASE "/dir2")); + free(path); + + path = realpath(TEST_BASE "/dir1/abs-link/../", NULL); + assert(path); + assert(!strcmp(path, TEST_BASE "")); + free(path); + + path = realpath(TEST_BASE "/dir1/rel-link/../", NULL); + assert(path); + assert(!strcmp(path, TEST_BASE "")); + free(path); + + path = realpath(TEST_BASE "/dir1/abs-link/../dir1/abs-link/", NULL); + assert(path); + assert(!strcmp(path, TEST_BASE "/dir2")); + free(path); + + path = realpath(TEST_BASE "/dir1/rel-link/../dir1/rel-link/", NULL); + assert(path); + assert(!strcmp(path, TEST_BASE "/dir2")); + free(path); + + path = realpath(TEST_BASE "/dir1/abs-link/../dir1/rel-link/", NULL); + assert(path); + assert(!strcmp(path, TEST_BASE "/dir2")); + free(path); + + path = realpath(TEST_BASE "/dir1/rel-link/../dir1/abs-link/", NULL); + assert(path); + assert(!strcmp(path, TEST_BASE "/dir2")); + free(path); + + path = realpath("/tmp", NULL); + assert(path); + assert(!strcmp(path, "/tmp")); + free(path); + + path = realpath("/", NULL); + assert(path); + assert(!strcmp(path, "/")); + free(path); + + path = realpath("//", NULL); + assert(path); + assert(!strcmp(path, "/")); + free(path); + + cleanup(1); +} diff --git a/lib/mlibc/tests/posix/regex.c b/lib/mlibc/tests/posix/regex.c new file mode 100644 index 0000000..6c2ca3f --- /dev/null +++ b/lib/mlibc/tests/posix/regex.c @@ -0,0 +1,30 @@ +#include <regex.h> +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> + +int main(void) { + char *testString = "mlibc is the best best best libc"; + char *pattern = "\\(be[a-z]t\\) \\1"; + + regex_t reg; + int rc = regcomp(®, pattern, 0); + assert(!rc); + + regmatch_t matches[2]; + rc = regexec(®, testString, 2, matches, 0); + assert(!rc); + + printf("Whole pattern: \"%.*s\" at %zd-%zd.\n", + (int)(matches[0].rm_eo - matches[0].rm_so), &testString[matches[0].rm_so], + (ssize_t)matches[0].rm_so, (ssize_t)(matches[0].rm_eo - 1)); + assert(matches[0].rm_so == 13 && matches[0].rm_eo == 22); + + printf("Substring: \"%.*s\" at %zd-%zd.\n", + (int)(matches[1].rm_eo - matches[1].rm_so), &testString[matches[1].rm_so], + (ssize_t)matches[1].rm_so, (ssize_t)matches[1].rm_eo - 1); + assert(matches[1].rm_so == 13 && matches[1].rm_eo == 17); + + regfree(®); + return 0; +} diff --git a/lib/mlibc/tests/posix/rindex.c b/lib/mlibc/tests/posix/rindex.c new file mode 100644 index 0000000..0eb2f54 --- /dev/null +++ b/lib/mlibc/tests/posix/rindex.c @@ -0,0 +1,11 @@ +#include <strings.h> +#include <assert.h> + +int main() { + char str[] = "This is a sample string"; + char *pch; + pch = rindex(str, 's'); + // The last occurence of 's' is at position 18 + assert(pch - str + 1 == 18); + return 0; +} diff --git a/lib/mlibc/tests/posix/rlimits.c b/lib/mlibc/tests/posix/rlimits.c new file mode 100644 index 0000000..3565521 --- /dev/null +++ b/lib/mlibc/tests/posix/rlimits.c @@ -0,0 +1,26 @@ +#include <sys/resource.h> +#include <stdio.h> +#include <errno.h> +#include <assert.h> +#include <string.h> + +int main() { + struct rlimit getlim, setlim; + + setlim.rlim_cur = 16; + setlim.rlim_max = 4096; + + int ret = setrlimit(RLIMIT_NOFILE, &setlim); + + if(ret == -1) { + fprintf(stderr, "%s\n", strerror(errno)); + assert(!ret); + } + + assert(!getrlimit(RLIMIT_NOFILE, &getlim)); + + assert(setlim.rlim_cur == getlim.rlim_cur); + assert(setlim.rlim_max == getlim.rlim_max); + + return 0; +} diff --git a/lib/mlibc/tests/posix/search.c b/lib/mlibc/tests/posix/search.c new file mode 100644 index 0000000..6b68ef9 --- /dev/null +++ b/lib/mlibc/tests/posix/search.c @@ -0,0 +1,51 @@ +#include <search.h> +#include <assert.h> +#include <stddef.h> +#include <stdlib.h> + +static int compare(const void *pa, const void *pb) { + if (*(int*)pa < *(int*) pb) + return -1; + if (*(int*)pa > *(int*) pb) + return 1; + return 0; +} + +static void check_key(int key, void *root) { + int keyp = key; + void *ret = tfind((void*) &keyp, &root, compare); + assert(ret); + assert(**((int **) ret) == key); +} + +static void free_key(void *key) { + free(key); +} + +int main() { + void *root = NULL; + for (int i = 0; i < 12; i++) { + int *ptr = malloc(sizeof(int)); + assert(ptr); + *ptr = i; + + void *ret = tsearch((void*) ptr, &root, compare); + assert(ret); + assert(**((int **) ret) == i); + } + + // Test a couple of keys + check_key(1, root); + check_key(5, root); + check_key(10, root); + + // Verify NULL on non-existent key + int key = -1; + void *ret = tfind((void*) &key, &root, compare); + assert(ret == NULL); + + // tdelete is not implemented yet (#351) + (void)free_key; + // tdestroy(root, free_key); + return 0; +} diff --git a/lib/mlibc/tests/posix/setpriority.c b/lib/mlibc/tests/posix/setpriority.c new file mode 100644 index 0000000..11cb7ed --- /dev/null +++ b/lib/mlibc/tests/posix/setpriority.c @@ -0,0 +1,27 @@ +#include <assert.h> +#include <errno.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <sys/resource.h> +#include <unistd.h> + +int main() { + errno = 0; + int original_priority = getpriority(PRIO_PROCESS, getpid()); + + assert(original_priority != -1 || !errno); + + int ret = setpriority(PRIO_PROCESS, getpid(), original_priority + 1); + + if(ret) { + fprintf(stderr, "%s", strerror(errno)); + exit(1); + } + + errno = 0; + int new_priority = getpriority(PRIO_PROCESS, getpid()); + + assert(new_priority != -1 || !errno); + assert(new_priority == original_priority + 1); +} diff --git a/lib/mlibc/tests/posix/sigaltstack.c b/lib/mlibc/tests/posix/sigaltstack.c new file mode 100644 index 0000000..bebcc30 --- /dev/null +++ b/lib/mlibc/tests/posix/sigaltstack.c @@ -0,0 +1,55 @@ +#include <assert.h> +#include <setjmp.h> +#include <signal.h> +#include <stdlib.h> + +static jmp_buf env; +static char *sigStack; + +static void sig_handler(int sig, siginfo_t *info, void *ctx) { + (void)sig; + (void)info; + (void)ctx; + + longjmp(env, 1); +} + +int main() { + if (setjmp(env)) { + free(sigStack); + return 0; + } + + sigStack = malloc(SIGSTKSZ); + + stack_t ss; + ss.ss_sp = sigStack; + ss.ss_size = SIGSTKSZ; + ss.ss_flags = 0; + + assert(!sigaltstack(&ss, NULL)); + + struct sigaction sa; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_SIGINFO | SA_ONSTACK; + sa.sa_sigaction = sig_handler; + + assert(!sigaction(SIGSEGV, &sa, NULL)); + + // This is used to trash the stack to ensure sigaltstack actually switched stacks. +#if defined(__x86_64__) + asm volatile ("mov $0, %rsp\n" + "\t" "push $0"); +#elif defined(__i386__) + asm volatile ("mov $0, %esp\n" + "\t" "push $0"); +#elif defined(__aarch64__) + asm volatile ("mov sp, %0\n" + "\t" "stp x0, x1, [sp, #-16]!" :: "r"((uint64_t)0)); +#elif defined(__riscv) && __riscv_xlen == 64 + asm volatile ("li sp, 0\n" + "\t" "sd zero, 0(sp)"); +#else +# error Unknown architecture +#endif +} diff --git a/lib/mlibc/tests/posix/sigsuspend.c b/lib/mlibc/tests/posix/sigsuspend.c new file mode 100644 index 0000000..09b9a1e --- /dev/null +++ b/lib/mlibc/tests/posix/sigsuspend.c @@ -0,0 +1,53 @@ +#include <assert.h> +#include <unistd.h> +#include <pthread.h> +#include <signal.h> +#include <errno.h> + +_Atomic int handler_ready = 0; +_Atomic int thread_signal_ran = 0; + +static void sig_handler(int sig, siginfo_t *info, void *ctx) { + (void)sig; + (void)info; + (void)ctx; + + thread_signal_ran = 1; +} + +static void *worker(void *arg) { + (void)arg; + + struct sigaction sa; + sigemptyset(&sa.sa_mask); + sa.sa_sigaction = sig_handler; + sa.sa_flags = SA_SIGINFO; + assert(!sigaction(SIGUSR1, &sa, NULL)); + + handler_ready = 1; + + sigset_t set; + sigfillset(&set); + sigdelset(&set, SIGUSR1); + + assert(sigsuspend(&set)); + assert(thread_signal_ran); + assert(errno == EINTR); + + return NULL; +} + +int main() { + pthread_t thread; + assert(!pthread_create(&thread, NULL, &worker, NULL)); + + while (!handler_ready) + ; + + sleep(1); + + assert(!pthread_kill(thread, SIGUSR1)); + assert(!pthread_join(thread, NULL)); + + return 0; +} diff --git a/lib/mlibc/tests/posix/sigtimedwait.c b/lib/mlibc/tests/posix/sigtimedwait.c new file mode 100644 index 0000000..4793fc1 --- /dev/null +++ b/lib/mlibc/tests/posix/sigtimedwait.c @@ -0,0 +1,91 @@ +#include <stdio.h> + +#include <assert.h> +#include <signal.h> +#include <unistd.h> +#include <sys/wait.h> +#include <errno.h> + +pid_t parent; +pid_t child; + +int fds[2]; + +void parent_fn() { + int res; + + sigset_t set; + sigemptyset(&set); + sigaddset(&set, SIGUSR2); + sigprocmask(SIG_BLOCK, &set, NULL); + + res = write(fds[1], "!", 1); + assert(res == 1); + + siginfo_t info; + res = sigwaitinfo(&set, &info); + assert(res == SIGUSR2); + assert(info.si_signo == SIGUSR2); + + res = write(fds[1], "!", 1); + assert(res == 1); + + // XXX: This may not be long enough to get scheduled + struct timespec tout = {1, 0}; + res = sigtimedwait(&set, &info, &tout); + assert(res == SIGUSR2); + assert(info.si_signo == SIGUSR2); + + res = write(fds[1], "!", 1); + assert(res == 1); + + int sig; + res = sigwait(&set, &sig); + assert(res == 0); + assert(sig == SIGUSR2); + + res = write(fds[1], "!", 1); + assert(res == 1); + + res = sigtimedwait(&set, &info, &tout); + assert(res < 0); + assert(errno == EAGAIN); + + int wsts; + res = waitpid(child, &wsts, 0); + assert(res >= 0); +} + +void child_fn() { + int res; + char c; + + res = read(fds[0], &c, 1); + assert(res == 1); + kill(parent, SIGUSR2); + res = read(fds[0], &c, 1); + assert(res == 1); + kill(parent, SIGUSR2); + res = read(fds[0], &c, 1); + assert(res == 1); + kill(parent, SIGUSR2); + res = read(fds[0], &c, 1); + assert(res == 1); +} + +int main() { + int res; + + parent = getpid(); + assert(parent > 0); + + res = pipe(fds); + assert(res == 0); + + child = fork(); + assert(child >= 0); + if (child) + parent_fn(); + else + child_fn(); +} diff --git a/lib/mlibc/tests/posix/strdupa.c b/lib/mlibc/tests/posix/strdupa.c new file mode 100644 index 0000000..c79de8e --- /dev/null +++ b/lib/mlibc/tests/posix/strdupa.c @@ -0,0 +1,14 @@ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#include <assert.h> +#include <string.h> + +int main() { + char test[19] = "Hello mlibc World!"; + char *alloca_ed = strdupa(test); + assert(!strcmp(test, alloca_ed)); + + char *trimmed = strndupa(test, 5); + assert(!strcmp("Hello", trimmed)); +} diff --git a/lib/mlibc/tests/posix/string.c b/lib/mlibc/tests/posix/string.c new file mode 100644 index 0000000..9c6e036 --- /dev/null +++ b/lib/mlibc/tests/posix/string.c @@ -0,0 +1,29 @@ +#include <string.h> +#include <assert.h> + +int main() { + char buf[4]; + + // stpncpy + assert(stpncpy(buf, "", 4) == buf); + assert(!strcmp(buf, "")); + + assert(stpncpy(buf, "123", 4) == buf + 3); + assert(!strcmp(buf, "123")); + + assert(stpncpy(buf, "12", 4) == buf + 2); + assert(buf[0] == '1' && buf[1] == '2' && buf[2] == '\0' && buf[3] == '\0'); + + assert(stpncpy(buf, "123456", 4) == buf + 4); + assert(buf[0] == '1' && buf[1] == '2' && buf[2] == '3' && buf[3] == '4'); + + // stpcpy + assert(stpcpy(buf, "") == buf); + assert(!strcmp(buf, "")); + + assert(stpcpy(buf, "12") == buf + 2); + assert(!strcmp(buf, "12")); + + assert(stpcpy(buf, "123") == buf + 3); + assert(!strcmp(buf, "123")); +} diff --git a/lib/mlibc/tests/posix/system.c b/lib/mlibc/tests/posix/system.c new file mode 100644 index 0000000..d5d6317 --- /dev/null +++ b/lib/mlibc/tests/posix/system.c @@ -0,0 +1,18 @@ +#include <assert.h> +#include <stdlib.h> +#include <sys/wait.h> + +int main() { + int res; + + res = system("true"); + assert(WIFEXITED(res)); + assert(WEXITSTATUS(res) == 0); + + res = system("false"); + assert(WIFEXITED(res)); + assert(WEXITSTATUS(res) == 1); + + res = system(NULL); + assert(res == 1); +} diff --git a/lib/mlibc/tests/posix/time.c b/lib/mlibc/tests/posix/time.c new file mode 100644 index 0000000..add7b92 --- /dev/null +++ b/lib/mlibc/tests/posix/time.c @@ -0,0 +1,191 @@ +#include <assert.h> +#include <locale.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#define BUF_SIZE 1024 + +int main() { + struct tm tm = {0}; + char buf[BUF_SIZE]; + + char *a = strptime("%", "%%", &tm); + assert(a != NULL); + assert(*a == '\0'); + assert(strftime(buf, BUF_SIZE, "%%", &tm) == 1); + assert(!strcmp(buf, "%")); + memset(&tm, 0, sizeof(tm)); + + a = strptime("1991-11-21", "%F", &tm); + assert(a != NULL); + assert(*a == '\0'); + assert(tm.tm_mday == 21); + assert(tm.tm_mon == 10); + assert(tm.tm_wday == 4); + assert(tm.tm_yday == 324); + assert(strftime(buf, BUF_SIZE, "%F", &tm) == 10); + assert(!strcmp(buf, "1991-11-21")); + memset(&tm, 0, sizeof(tm)); + + a = strptime("10/19/91", "%D", &tm); + assert(a != NULL); + assert(*a == '\0'); + assert(tm.tm_mday == 19); + assert(tm.tm_mon == 9); + assert(tm.tm_year == 91); + assert(tm.tm_wday == 6); + assert(tm.tm_yday == 291); + assert(strftime(buf, BUF_SIZE, "%D", &tm) == 8); + assert(!strcmp(buf, "10/19/91")); + memset(&tm, 0, sizeof(tm)); + + a = strptime("15:23", "%R", &tm); + assert(a != NULL); + assert(*a == '\0'); + assert(tm.tm_min == 23); + assert(tm.tm_hour == 15); + assert(strftime(buf, BUF_SIZE, "%R", &tm) == 5); + assert(!strcmp(buf, "15:23")); + memset(&tm, 0, sizeof(tm)); + + a = strptime("17:12:56", "%T", &tm); + assert(a != NULL); + assert(*a == '\0'); + assert(tm.tm_sec == 56); + assert(tm.tm_min == 12); + assert(tm.tm_hour == 17); + assert(strftime(buf, BUF_SIZE, "%T", &tm) == 8); + assert(!strcmp(buf, "17:12:56")); + memset(&tm, 0, sizeof(tm)); + + a = strptime("10", "%m", &tm); + assert(a != NULL); + assert(*a == '\0'); + assert(tm.tm_yday == 272); + assert(strftime(buf, BUF_SIZE, "%m", &tm) == 2); + assert(!strcmp(buf, "10")); + memset(&tm, 0, sizeof(tm)); + + a = strptime("14 83", "%C %y", &tm); + assert(a != NULL); + assert(*a == '\0'); + assert(tm.tm_year == -417); + assert(strftime(buf, BUF_SIZE, "%C %y", &tm) == 5); + assert(!strcmp(buf, "14 83")); + memset(&tm, 0, sizeof(tm)); + + a = strptime("32 16", "%y %C", &tm); + assert(a != NULL); + assert(*a == '\0'); + assert(tm.tm_year == -268); + assert(tm.tm_wday == 3); + assert(strftime(buf, BUF_SIZE, "%y %C", &tm) == 5); + assert(!strcmp(buf, "32 16")); + memset(&tm, 0, sizeof(tm)); + + a = strptime("12", "%C", &tm); + assert(a != NULL); + assert(*a == '\0'); + assert(tm.tm_year == -700); + assert(tm.tm_wday == 5); + assert(strftime(buf, BUF_SIZE, "%C", &tm) == 2); + assert(!strcmp(buf, "12")); + memset(&tm, 0, sizeof(tm)); + + a = strptime("1683-9-23", "%F", &tm); + assert(a != NULL); + assert(*a == '\0'); + assert(tm.tm_mday == 23); + assert(tm.tm_mon == 8); + assert(tm.tm_year == -217); + assert(tm.tm_wday == 4); + assert(tm.tm_yday == 265); + assert(strftime(buf, BUF_SIZE, "%F", &tm) == 10); + assert(!strcmp(buf, "1683-09-23")); + memset(&tm, 0, sizeof(tm)); + + a = strptime("14 53", "%H%t%S", &tm); + assert(a != NULL); + assert(*a == '\0'); + assert(tm.tm_sec == 53); + assert(tm.tm_hour == 14); + assert(strftime(buf, BUF_SIZE, "%H%t%S", &tm) == 5); + assert(!strcmp(buf, "14 53")); + memset(&tm, 0, sizeof(tm)); + + a = strptime("24", "%H", &tm); + assert(a == NULL); + memset(&tm, 0, sizeof(tm)); + + a = strptime("0", "%I", &tm); + assert(a == NULL); + memset(&tm, 0, sizeof(tm)); + + setlocale(LC_TIME, "en_US.UTF-8"); + a = strptime("10 21 PM", "%I %M %p", &tm); + assert(a != NULL); + assert(*a == '\0'); + assert(tm.tm_hour == 22); + assert(tm.tm_min == 21); + assert(strftime(buf, BUF_SIZE, "%I %M %p", &tm) == 8); + assert(!strcmp(buf, "10 21 PM")); + memset(&tm, 0, sizeof(tm)); + + tm.tm_min = 23; + assert(strftime(buf, BUF_SIZE, "%I %M", &tm) == 5); + assert(!strcmp(buf, "12 23")); + memset(&tm, 0, sizeof(tm)); + + a = strptime("January", "%h", &tm); + assert(a != NULL); + assert(*a == '\0'); + assert(tm.tm_mon == 0); + assert(strftime(buf, BUF_SIZE, "%h %b", &tm) == 7); + assert(!strcmp(buf, "Jan Jan")); + assert(strftime(buf, BUF_SIZE, "%B", &tm) == 7); + assert(!strcmp(buf, "January")); + memset(&tm, 0, sizeof(tm)); + + a = strptime("2", "%j", &tm); + assert(a != NULL); + assert(*a == '\0'); + assert(tm.tm_yday == 1); + assert(strftime(buf, BUF_SIZE, "%j", &tm) == 3); + assert(!strcmp(buf, "002")); + memset(&tm, 0, sizeof(tm)); + + a = strptime("Wednesday", "%A", &tm); + assert(a != NULL); + assert(*a == '\0'); + assert(tm.tm_wday == 3); + assert(strftime(buf, BUF_SIZE, "%A", &tm) == 9); + assert(!strcmp(buf, "Wednesday")); + memset(&tm, 0, sizeof(tm)); + + a = strptime("11:51:13 PM", "%r", &tm); + assert(a != NULL); + assert(*a == '\0'); + assert(tm.tm_hour == 23); + assert(tm.tm_min == 51); + assert(tm.tm_sec == 13); + assert(strftime(buf, BUF_SIZE, "%r", &tm) == 11); + assert(!strcmp(buf, "11:51:13 PM")); + memset(&tm, 0, sizeof(tm)); + + tm.tm_hour = 0; + tm.tm_min = 51; + tm.tm_sec = 13; + assert(strftime(buf, BUF_SIZE, "%r", &tm) == 11); + assert(!strcmp(buf, "12:51:13 AM")); + memset(&tm, 0, sizeof(tm)); + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat" + assert(strftime(buf, BUF_SIZE, "%", &tm) == 1); + fprintf(stderr, "%s\n", buf); + assert(!strcmp(buf, "%")); + memset(&tm, 0, sizeof(tm)); +#pragma GCC diagnostic pop +} diff --git a/lib/mlibc/tests/posix/timer.c b/lib/mlibc/tests/posix/timer.c new file mode 100644 index 0000000..f39e928 --- /dev/null +++ b/lib/mlibc/tests/posix/timer.c @@ -0,0 +1,25 @@ +#include <stdio.h> +#include <sys/time.h> +#include <assert.h> + +int main() { + struct timeval a = {0, 0}; + struct timeval b = {0, 0}; + struct timeval res = {0, 0}; + a.tv_sec = 10; + assert(timerisset(&a) == 1); + timerclear(&a); + assert(timerisset(&a) == 0); + a.tv_sec = 40; + a.tv_usec = 500; + b.tv_sec = 10; + b.tv_usec = 20; + timeradd(&a, &b, &res); + assert(res.tv_sec == 50); + assert(res.tv_usec == 520); + timerclear(&res); + timersub(&a, &b, &res); + assert(res.tv_sec == 30); + assert(res.tv_usec == 480); + return 0; +} diff --git a/lib/mlibc/tests/posix/vfork.c b/lib/mlibc/tests/posix/vfork.c new file mode 100644 index 0000000..5f8b71d --- /dev/null +++ b/lib/mlibc/tests/posix/vfork.c @@ -0,0 +1,28 @@ +#include <assert.h> +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/wait.h> +#include <unistd.h> + +void prevent_atforks(void){ + exit(1); +} + +int main() { + pid_t pid; + int status; + assert(!pthread_atfork(prevent_atforks, NULL, NULL)); + + switch (pid = vfork()) { + case -1: + perror("vfork"); + abort(); + case 0: + _exit(12); + default: break; + } + + assert(wait(&status) == pid); + assert(WIFEXITED(status) && WEXITSTATUS(status) == 12); +} diff --git a/lib/mlibc/tests/posix/waitid.c b/lib/mlibc/tests/posix/waitid.c new file mode 100644 index 0000000..e69b1ef --- /dev/null +++ b/lib/mlibc/tests/posix/waitid.c @@ -0,0 +1,20 @@ +#include <assert.h> +#include <signal.h> +#include <stdlib.h> +#include <sys/wait.h> +#include <unistd.h> + +siginfo_t si; + +int main() { + int pid = fork(); + + if(!pid) + exit(69); + + int ret = waitid(P_ALL, 0, &si, WEXITED); + assert(ret == 0); + assert(si.si_pid == pid); + assert(si.si_signo == SIGCHLD); + assert(si.si_code == CLD_EXITED); +}
\ No newline at end of file diff --git a/lib/mlibc/tests/posix/wcwidth.c b/lib/mlibc/tests/posix/wcwidth.c new file mode 100644 index 0000000..2e6fc8c --- /dev/null +++ b/lib/mlibc/tests/posix/wcwidth.c @@ -0,0 +1,67 @@ +#include <assert.h> +#include <locale.h> +#include <stdint.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <wchar.h> + +#if UINTPTR_MAX == UINT64_MAX +#define WCHAR_SPEC "" +#else +#define WCHAR_SPEC "l" +#endif + +/* + * The code in this test is taken from https://github.com/termux/wcwidth/, + * under the following license: + * + * Copyright (C) Fredrik Fornwall 2016. + * Distributed under the MIT License. + * + * Implementation of wcwidth(3) as a C port of: + * https://github.com/jquast/wcwidth + * + * Report issues at: + * https://github.com/termux/wcwidth + */ + +static int tests_run; +static int test_failures; + +void assertWidthIs(int expected_width, wchar_t c) { + tests_run++; + int actual_width = wcwidth(c); + if (actual_width != expected_width) { + fprintf(stderr, "ERROR: wcwidth(U+%04" WCHAR_SPEC "x, '%lc') returned %d, expected %d\n", c, (wint_t) c, actual_width, expected_width); + test_failures++; + } +} + +int main() { + setlocale(LC_CTYPE, "C.UTF-8"); + assertWidthIs(1, 'a'); + assertWidthIs(1, L'ö'); + + // Some wide: + assertWidthIs(2, L'A'); + assertWidthIs(2, L'ï¼¢'); + assertWidthIs(2, L'ï¼£'); + assertWidthIs(2, L'ä¸'); + assertWidthIs(2, L'æ–‡'); + assertWidthIs(2, 0x679C); + assertWidthIs(2, 0x679D); + assertWidthIs(2, 0x2070E); + assertWidthIs(2, 0x20731); + +#ifndef USE_HOST_LIBC + assertWidthIs(1, 0x11A3); +#endif + + assertWidthIs(2, 0x1F428); // Koala emoji. + assertWidthIs(2, 0x231a); // Watch emoji. + + if (test_failures > 0) printf("%d tests FAILED, ", test_failures); + printf("%d tests OK\n", tests_run - test_failures); + return (test_failures == 0) ? 0 : 1; +} diff --git a/lib/mlibc/tests/posix/wordexp.c b/lib/mlibc/tests/posix/wordexp.c new file mode 100644 index 0000000..063a374 --- /dev/null +++ b/lib/mlibc/tests/posix/wordexp.c @@ -0,0 +1,140 @@ +#include <assert.h> +#include <limits.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <pwd.h> +#include <unistd.h> +#include <wordexp.h> + +static const char *wordexp_return_stringify(int val, int *should_free); + +struct we_test { + int expected_return; + const char *words; + int flags; + const char *wordv[16]; + size_t wordc; +} test_cases[] = { + /* (unbalanced) pairs */ + {WRDE_SYNTAX, "\\", 0, {NULL}, 0}, + {WRDE_SYNTAX, "\'", 0, {NULL}, 0}, + {0, "\'\'", 0, {""}, 1}, + {0, "\'\"\'", 0, {"\""}, 1}, + {WRDE_SYNTAX, "\"\'", 0, {NULL}, 0}, + + /* numbers & calculations */ + {0, "$((5+5))", 0, {"10"}, 1}, + {0, "$((-5+5))", 0, {"0"}, 1}, + {0, "$((010))", 0, {"8"}, 1}, + {0, "$((0x10))", 0, {"16"}, 1}, + + /* errant symbols */ + {WRDE_BADCHAR, "(string", WRDE_NOCMD, {NULL}, 0}, + {WRDE_BADCHAR, "string)", WRDE_NOCMD, {NULL}, 0}, + {WRDE_BADCHAR, "{string", WRDE_NOCMD, {NULL}, 0}, + {WRDE_BADCHAR, "string}", WRDE_NOCMD, {NULL}, 0}, + {WRDE_BADCHAR, "<string", WRDE_NOCMD, {NULL}, 0}, + {WRDE_BADCHAR, "string>", WRDE_NOCMD, {NULL}, 0}, + {WRDE_BADCHAR, "string|", WRDE_NOCMD, {NULL}, 0}, + {WRDE_BADCHAR, "string;", WRDE_NOCMD, {NULL}, 0}, + {WRDE_BADCHAR, "string&", WRDE_NOCMD, {NULL}, 0}, + + /* command substitution with WRDE_NOCMD */ + {WRDE_CMDSUB, "$(pwd)", WRDE_NOCMD, {NULL}, 0}, + + /* env vars */ + {WRDE_BADVAL, "$DOES_NOT_EXIST", WRDE_UNDEF, {NULL}, 0}, + + /* normal arguments */ + {0, "arg1 arg2", 0, {"arg1", "arg2"}, 2}, + {-1, NULL, 0, {NULL}, 0}, +}; + +int main() { + wordexp_t we; + + wordexp("$(pwd)", &we, 0); + char *cwd = getcwd(NULL, 0); + assert(!strcmp(cwd, we.we_wordv[0])); + wordfree(&we); + free(cwd); + + char *home; + assert(asprintf(&home, "%s/.config", getenv("HOME")) != -1); + wordexp("$HOME ~ ~/.config", &we, WRDE_REUSE); + assert(!strcmp(getenv("HOME"), we.we_wordv[0])); + assert(!strcmp(getenv("HOME"), we.we_wordv[1])); + assert(!strcmp(home, we.we_wordv[2])); + free(home); + wordfree(&we); + + struct passwd *pw = getpwnam("root"); + assert(asprintf(&home, "%s/.config", pw->pw_dir) != -1); + wordexp("~root ~root/.config", &we, WRDE_REUSE); + assert(!strcmp(pw->pw_dir, we.we_wordv[0])); + assert(!strcmp(home, we.we_wordv[1])); + free(home); + wordfree(&we); + + size_t i = 0; + + for(struct we_test *test = &test_cases[i]; test->expected_return != -1; test = &test_cases[++i]) { + wordexp_t test_we; + + int should_free; + const char *expected = wordexp_return_stringify(test->expected_return, &should_free); + fprintf(stderr, "TESTCASE %zu: '%s' with flags %d expected to return %s\n", i, test->words, test->flags, expected); + int test_ret = wordexp(test->words, &test_we, test->flags); + + if(test_ret != test->expected_return) { + fprintf(stderr, "\twordexp() returned %s, but we expect %s\n", wordexp_return_stringify(test_ret, &should_free), wordexp_return_stringify(test->expected_return, &should_free)); + } + + assert(test_ret == test->expected_return); + + if(test_ret != 0) + continue; + + assert(test_we.we_wordc == test->wordc); + + for(size_t j = 0; j < 16 && j < test->wordc; j++) { + assert(test->wordv[j] && test_we.we_wordv[j]); + assert(!strcmp(test->wordv[j], test_we.we_wordv[j])); + } + + wordfree(&test_we); + if (should_free) + free((void *)expected); + } + + exit(EXIT_SUCCESS); +} + +struct wordexp_return_val { + int val; + const char *string; +} wordexp_return_vals[] = { + {0, "WRDE_SUCCESS"}, + {WRDE_BADCHAR, "WRDE_BADCHAR"}, + {WRDE_BADVAL, "WRDE_BADVAL"}, + {WRDE_CMDSUB, "WRDE_CMDSUB"}, + {WRDE_SYNTAX, "WRDE_SYNTAX"}, + {-1, NULL}, +}; + +static const char *wordexp_return_stringify(int val, int *should_free) { + for(size_t i = 0; wordexp_return_vals[i].val != -1 || wordexp_return_vals[i].string; i++) { + if(wordexp_return_vals[i].val == val) { + *should_free = 0; + return wordexp_return_vals[i].string; + } + } + + char *unknown; + assert(asprintf(&unknown, "unknown return value %d", val) != -1); + *should_free = 1; + + return unknown; +} diff --git a/lib/mlibc/tests/rtdl/dl_iterate_phdr/libbar.c b/lib/mlibc/tests/rtdl/dl_iterate_phdr/libbar.c new file mode 100644 index 0000000..41ccc56 --- /dev/null +++ b/lib/mlibc/tests/rtdl/dl_iterate_phdr/libbar.c @@ -0,0 +1,3 @@ +// Bar needs to have a relocation against foo in order to set DT_NEEDED. +int foo(void); +int bar() { return foo(); } diff --git a/lib/mlibc/tests/rtdl/dl_iterate_phdr/libfoo.c b/lib/mlibc/tests/rtdl/dl_iterate_phdr/libfoo.c new file mode 100644 index 0000000..9fe07f8 --- /dev/null +++ b/lib/mlibc/tests/rtdl/dl_iterate_phdr/libfoo.c @@ -0,0 +1 @@ +int foo() { return 0; } diff --git a/lib/mlibc/tests/rtdl/dl_iterate_phdr/meson.build b/lib/mlibc/tests/rtdl/dl_iterate_phdr/meson.build new file mode 100644 index 0000000..acb679e --- /dev/null +++ b/lib/mlibc/tests/rtdl/dl_iterate_phdr/meson.build @@ -0,0 +1,7 @@ +libfoo = shared_library('foo', 'libfoo.c') +libbar = shared_library('bar', 'libbar.c', build_rpath: test_rpath, link_with: libfoo) +test_depends = [libfoo, libbar] + +libfoo_native = shared_library('native-foo', 'libfoo.c', native: true) +libbar_native = shared_library('native-bar', 'libbar.c', build_rpath: test_rpath, link_with: libfoo_native, native: true) +test_native_depends = [libfoo_native, libbar_native] diff --git a/lib/mlibc/tests/rtdl/dl_iterate_phdr/test.c b/lib/mlibc/tests/rtdl/dl_iterate_phdr/test.c new file mode 100644 index 0000000..5d48a41 --- /dev/null +++ b/lib/mlibc/tests/rtdl/dl_iterate_phdr/test.c @@ -0,0 +1,91 @@ +#include <assert.h> +#include <link.h> +#include <string.h> +#include <dlfcn.h> +#include <stdio.h> + +#ifdef USE_HOST_LIBC +#define LDSO_PATTERN "ld-linux-" +#define LIBFOO "libnative-foo.so" +#define LIBBAR "libnative-bar.so" +#else +#define LDSO_PATTERN "ld.so" +#define LIBFOO "libfoo.so" +#define LIBBAR "libbar.so" +#endif + +struct result { + int found_ldso; + int found_self; + int found_foo; + int found_bar; +}; + +static int ends_with(const char *suffix, const char *s) { + size_t suffix_len = strlen(suffix); + size_t s_len = strlen(s); + if (s_len < suffix_len) + return 0; + else { + return !strcmp(suffix, s + s_len - suffix_len); + } +} + +static int contains(const char *pattern, const char *s) { + return !!strstr(s, pattern); +} + +static int callback(struct dl_phdr_info *info, size_t size, void *data) { + assert(size == sizeof(struct dl_phdr_info)); + struct result *found = (struct result *) data; + + printf("%s\n", info->dlpi_name); + fflush(stdout); + + if (ends_with("foo.so", info->dlpi_name)) + found->found_foo++; + if (ends_with("bar.so", info->dlpi_name)) + found->found_bar++; + if (contains(LDSO_PATTERN, info->dlpi_name)) + found->found_ldso++; + + if (!strcmp("", info->dlpi_name)) + found->found_self++; + + assert(info->dlpi_phdr); + return 0; +} + +int main() { + struct result found = { 0 }; + assert(!dl_iterate_phdr(callback, &found)); + assert(found.found_ldso == 1); + assert(found.found_self == 1); + assert(found.found_foo == 0); + assert(found.found_bar == 0); + printf("---\n"); + + memset(&found, 0, sizeof(found)); + void *bar = dlopen(LIBBAR, RTLD_LOCAL | RTLD_NOW); + assert(bar); + assert(!dl_iterate_phdr(callback, &found)); + assert(found.found_ldso == 1); + assert(found.found_self == 1); + assert(found.found_bar == 1); + assert(found.found_foo == 1); // Since bar depends on foo. + printf("---\n"); + + memset(&found, 0, sizeof(found)); + void *foo = dlopen(LIBFOO, RTLD_GLOBAL | RTLD_NOW); + assert(foo); + assert(!dl_iterate_phdr(callback, &found)); + assert(found.found_ldso == 1); + assert(found.found_self == 1); + assert(found.found_foo == 1); + assert(found.found_bar == 1); + printf("---\n"); + + dlclose(bar); + dlclose(foo); + return 0; +} diff --git a/lib/mlibc/tests/rtdl/dladdr_local/libfoo.c b/lib/mlibc/tests/rtdl/dladdr_local/libfoo.c new file mode 100644 index 0000000..2903a3d --- /dev/null +++ b/lib/mlibc/tests/rtdl/dladdr_local/libfoo.c @@ -0,0 +1 @@ +char foo_global[] = ""; diff --git a/lib/mlibc/tests/rtdl/dladdr_local/meson.build b/lib/mlibc/tests/rtdl/dladdr_local/meson.build new file mode 100644 index 0000000..4ae6bb3 --- /dev/null +++ b/lib/mlibc/tests/rtdl/dladdr_local/meson.build @@ -0,0 +1,5 @@ +libfoo = shared_library('foo', 'libfoo.c') +test_depends = [libfoo] + +libfoo_native = shared_library('native-foo', 'libfoo.c', native: true) +test_native_depends = [libfoo_native] diff --git a/lib/mlibc/tests/rtdl/dladdr_local/test.c b/lib/mlibc/tests/rtdl/dladdr_local/test.c new file mode 100644 index 0000000..c64d259 --- /dev/null +++ b/lib/mlibc/tests/rtdl/dladdr_local/test.c @@ -0,0 +1,21 @@ +#include <dlfcn.h> +#include <assert.h> + +#ifdef USE_HOST_LIBC +#define LIBFOO "libnative-foo.so" +#else +#define LIBFOO "libfoo.so" +#endif + +int main() { + void *foo_handle = dlopen(LIBFOO, RTLD_LOCAL | RTLD_NOW); + assert(foo_handle); + + char *foo_global = (char *)dlsym(foo_handle, "foo_global"); + assert(foo_global); + + Dl_info info; + assert(dladdr((const void *)foo_global, &info) != 0); + + assert(dlclose(foo_handle) == 0); +} diff --git a/lib/mlibc/tests/rtdl/ld_library_path/libfoo.c b/lib/mlibc/tests/rtdl/ld_library_path/libfoo.c new file mode 100644 index 0000000..85e6cd8 --- /dev/null +++ b/lib/mlibc/tests/rtdl/ld_library_path/libfoo.c @@ -0,0 +1 @@ +void foo() {} diff --git a/lib/mlibc/tests/rtdl/ld_library_path/meson.build b/lib/mlibc/tests/rtdl/ld_library_path/meson.build new file mode 100644 index 0000000..95629be --- /dev/null +++ b/lib/mlibc/tests/rtdl/ld_library_path/meson.build @@ -0,0 +1,19 @@ +# Remove the RPATH and set the LD_LIBRARY_PATH environment variable +# instead when running tests to make sure the library can be found +# in a directory specified by the user at runtime + +test_rpath = '$ORIGIN/' + +test_ld_path = meson.build_root() / 'tests' / 'rtdl' / test_name +test_env += ['LD_LIBRARY_PATH=' + test_ld_path] +test_native_env += ['LD_LIBRARY_PATH=' + test_ld_path] + +libfoo = shared_library('foo', 'libfoo.c', + dependencies: libc_dep, + link_args: test_additional_link_args, +) + +libfoo_native = shared_library('native-foo', 'libfoo.c', + link_args: ['-ldl'] + test_additional_link_args, + native: true +) diff --git a/lib/mlibc/tests/rtdl/ld_library_path/test.c b/lib/mlibc/tests/rtdl/ld_library_path/test.c new file mode 100644 index 0000000..15ca200 --- /dev/null +++ b/lib/mlibc/tests/rtdl/ld_library_path/test.c @@ -0,0 +1,16 @@ +#include <dlfcn.h> +#include <stdio.h> +#include <assert.h> +#include <string.h> + +#ifdef USE_HOST_LIBC +#define LIBFOO "libnative-foo.so" +#else +#define LIBFOO "libfoo.so" +#endif + +int main() { + void *foo = dlopen(LIBFOO, RTLD_NOW); + assert(foo); + dlclose(foo); +} diff --git a/lib/mlibc/tests/rtdl/meson.build b/lib/mlibc/tests/rtdl/meson.build new file mode 100644 index 0000000..1331899 --- /dev/null +++ b/lib/mlibc/tests/rtdl/meson.build @@ -0,0 +1,56 @@ +rtdl_test_cases = [ + 'dl_iterate_phdr', + 'dladdr_local', + 'ld_library_path', + 'noload-promote', + 'rtld_next', + 'soname', + 'preinit', + 'scope1', + 'scope2', + 'scope3', + 'scope4', + 'scope5', + 'tls_align', +] + +foreach test_name : rtdl_test_cases + test_rpath = meson.build_root() / 'tests' / 'rtdl' / test_name / '' + test_rpath += ':$ORIGIN/' # Workaround old and buggy qemu-user on CI + + test_env = [] + test_link_with = [] + test_depends = [] + test_native_env = [] + test_native_link_with = [] + test_native_depends = [] + test_additional_link_args = [] + + # Build the needed DSOs for the test. This sets the variables above. + subdir(test_name) + + exec = executable('rtdl-' + test_name, [test_name / 'test.c', test_sources], + link_with: test_link_with, + dependencies: libc_dep, + build_rpath: test_rpath, + override_options: test_override_options, + c_args: test_c_args, + link_args: test_link_args + test_additional_link_args, + ) + test(test_name, exec, env: test_env, suite: 'rtdl', depends: test_depends) + + if build_tests_host_libc and not host_libc_excluded_test_cases.contains(test_name) + exec = executable('host-libc-' + test_name, test_name / 'test.c', + link_with: test_native_link_with, + dependencies: rtlib_deps, + build_rpath: test_rpath, + # Don't use ASan here, due to a bug that breaks dlopen() + DT_RUNPATH: + # https://bugzilla.redhat.com/show_bug.cgi?id=1449604 + override_options: 'b_sanitize=undefined', + c_args: ['-D_GNU_SOURCE', '-DUSE_HOST_LIBC'], + link_args: ['-ldl'] + test_additional_link_args, + native: true, + ) + test(test_name, exec, env: test_native_env, suite: ['host-libc', 'rtdl'], depends: test_native_depends) + endif +endforeach diff --git a/lib/mlibc/tests/rtdl/noload-promote/libfoo.c b/lib/mlibc/tests/rtdl/noload-promote/libfoo.c new file mode 100644 index 0000000..85e6cd8 --- /dev/null +++ b/lib/mlibc/tests/rtdl/noload-promote/libfoo.c @@ -0,0 +1 @@ +void foo() {} diff --git a/lib/mlibc/tests/rtdl/noload-promote/meson.build b/lib/mlibc/tests/rtdl/noload-promote/meson.build new file mode 100644 index 0000000..4ae6bb3 --- /dev/null +++ b/lib/mlibc/tests/rtdl/noload-promote/meson.build @@ -0,0 +1,5 @@ +libfoo = shared_library('foo', 'libfoo.c') +test_depends = [libfoo] + +libfoo_native = shared_library('native-foo', 'libfoo.c', native: true) +test_native_depends = [libfoo_native] diff --git a/lib/mlibc/tests/rtdl/noload-promote/test.c b/lib/mlibc/tests/rtdl/noload-promote/test.c new file mode 100644 index 0000000..0a6c55c --- /dev/null +++ b/lib/mlibc/tests/rtdl/noload-promote/test.c @@ -0,0 +1,22 @@ +#include <dlfcn.h> +#include <assert.h> +#include <stddef.h> + +#ifdef USE_HOST_LIBC +#define LIBFOO "libnative-foo.so" +#else +#define LIBFOO "libfoo.so" +#endif + +int main() { + void *foo = dlopen(LIBFOO, RTLD_LOCAL | RTLD_NOW); + assert(foo); + + assert(dlsym(RTLD_DEFAULT, "foo") == NULL); + + // Opening a library with RTLD_NOLOAD | RTLD_GLOBAL should promote it to the global scope. + assert(dlopen(LIBFOO, RTLD_NOLOAD | RTLD_GLOBAL | RTLD_NOW) == foo); + assert(dlsym(RTLD_DEFAULT, "foo") != NULL); + + assert(dlopen("does-not-exist.so.1337", RTLD_NOLOAD | RTLD_GLOBAL | RTLD_NOW) == NULL); +} diff --git a/lib/mlibc/tests/rtdl/preinit/libfoo.c b/lib/mlibc/tests/rtdl/preinit/libfoo.c new file mode 100644 index 0000000..9c834ea --- /dev/null +++ b/lib/mlibc/tests/rtdl/preinit/libfoo.c @@ -0,0 +1,18 @@ +#include <stdio.h> +#include <assert.h> + +int fooDone = 0; + +// DSOs do not support pre-initialization functions. + +__attribute__((constructor)) +void fooInit() { + dprintf(1, "initialization function called in foo\n"); + + assert(fooDone == 0); + fooDone++; +} + +int isFooDone() { + return fooDone; +} diff --git a/lib/mlibc/tests/rtdl/preinit/meson.build b/lib/mlibc/tests/rtdl/preinit/meson.build new file mode 100644 index 0000000..1a7f398 --- /dev/null +++ b/lib/mlibc/tests/rtdl/preinit/meson.build @@ -0,0 +1,18 @@ +if host_machine.cpu_family() == 'riscv64' + # gp isn't initialized until after crt1.o runs, so to access + # globals in our pre-initializers we must disable it. + test_additional_link_args = ['-Wl,--no-relax'] +endif + +libfoo = shared_library('foo', 'libfoo.c', + dependencies: libc_dep, + override_options: 'b_sanitize=none', + link_args: test_additional_link_args, +) +test_link_with = [libfoo] + +libfoo_native = shared_library('native-foo', 'libfoo.c', + link_args: ['-ldl'] + test_additional_link_args, + native: true +) +test_native_link_with = [libfoo_native] diff --git a/lib/mlibc/tests/rtdl/preinit/test.c b/lib/mlibc/tests/rtdl/preinit/test.c new file mode 100644 index 0000000..5b5d5e8 --- /dev/null +++ b/lib/mlibc/tests/rtdl/preinit/test.c @@ -0,0 +1,44 @@ +#include <stdio.h> +#include <assert.h> + +int mainDone = 0; + +int isFooDone(); + +void preInit1() { + // Use dprintf because stdout might not be initialized yet. + dprintf(1, "pre-initialization function 1 called in main executable\n"); + + assert(isFooDone() == 0); + assert(mainDone == 0); + mainDone++; +} + +void preInit2() { + dprintf(1, "pre-initialization function 2 called in main executable\n"); + + assert(isFooDone() == 0); + assert(mainDone == 1); + mainDone++; +} + +__attribute__((constructor)) +void mainInit() { + dprintf(1, "initialization function called in main executable\n"); + + assert(isFooDone() == 1); + assert(mainDone == 2); + mainDone++; +} + +// Manually register the pre-initialization functions. +__attribute__((used, section(".preinit_array"))) +static void (*preinitFunctions[])(void) = { + &preInit1, + &preInit2, +}; + +int main() { + assert(isFooDone() == 1); + assert(mainDone == 3); +} diff --git a/lib/mlibc/tests/rtdl/rtld_next/libbar.c b/lib/mlibc/tests/rtdl/rtld_next/libbar.c new file mode 100644 index 0000000..c0950c5 --- /dev/null +++ b/lib/mlibc/tests/rtdl/rtld_next/libbar.c @@ -0,0 +1,16 @@ +#include <dlfcn.h> + +typedef char *charFn(void); + +__attribute__((weak)) +char *definedInBoth() { + return "bar"; +} + +charFn *barGetDefault() { + return (charFn *)dlsym(RTLD_DEFAULT, "definedInBoth"); +} + +charFn *barGetNext() { + return (charFn *)dlsym(RTLD_NEXT, "definedInBoth"); +} diff --git a/lib/mlibc/tests/rtdl/rtld_next/libfoo.c b/lib/mlibc/tests/rtdl/rtld_next/libfoo.c new file mode 100644 index 0000000..1d46b73 --- /dev/null +++ b/lib/mlibc/tests/rtdl/rtld_next/libfoo.c @@ -0,0 +1,17 @@ +#include <dlfcn.h> + +typedef char *charFn(void); + +__attribute__((weak)) +char *definedInBoth() { + return "foo"; +} + +charFn *fooGetDefault() { + return (charFn *)dlsym(RTLD_DEFAULT, "definedInBoth"); +} + +charFn *fooGetNext() { + return (charFn *)dlsym(RTLD_NEXT, "definedInBoth"); +} + diff --git a/lib/mlibc/tests/rtdl/rtld_next/meson.build b/lib/mlibc/tests/rtdl/rtld_next/meson.build new file mode 100644 index 0000000..d156c24 --- /dev/null +++ b/lib/mlibc/tests/rtdl/rtld_next/meson.build @@ -0,0 +1,25 @@ +# Prevent tail calls, as it breaks libc's 'calling object' detection. +no_tail_calls = '-fno-optimize-sibling-calls' + +libfoo = shared_library('foo', 'libfoo.c', + dependencies: libc_dep, + c_args: no_tail_calls, +) +libbar = shared_library('bar', 'libbar.c', + dependencies: libc_dep, + c_args: no_tail_calls, +) +test_link_with = [libfoo, libbar] # foo is linked before bar + +libfoo_native = shared_library('native-foo', 'libfoo.c', + c_args: [no_tail_calls, '-D_GNU_SOURCE'], + link_args: '-ldl', + native: true +) +libbar_native = shared_library('native-bar', 'libbar.c', + c_args: [no_tail_calls, '-D_GNU_SOURCE'], + link_args: '-ldl', + native: true +) +test_native_link_with = [libfoo_native, libbar_native] + diff --git a/lib/mlibc/tests/rtdl/rtld_next/test.c b/lib/mlibc/tests/rtdl/rtld_next/test.c new file mode 100644 index 0000000..3e90a63 --- /dev/null +++ b/lib/mlibc/tests/rtdl/rtld_next/test.c @@ -0,0 +1,26 @@ +#include <string.h> +#include <assert.h> + +typedef char *charFn(void); +charFn *fooGetDefault(void); +charFn *fooGetNext(void); +charFn *barGetDefault(void); +charFn *barGetNext(void); + +int main() { + charFn *ret; + + ret = fooGetDefault(); + assert(ret != NULL); + assert(!strcmp(ret(), "foo")); + + ret = fooGetNext(); + assert(ret != NULL); + assert(!strcmp(ret(), "bar")); + + ret = barGetDefault(); + assert(ret != NULL); + assert(!strcmp(ret(), "foo")); + + assert(barGetNext() == NULL); +} diff --git a/lib/mlibc/tests/rtdl/scope1/libbar.c b/lib/mlibc/tests/rtdl/scope1/libbar.c new file mode 100644 index 0000000..ecf043e --- /dev/null +++ b/lib/mlibc/tests/rtdl/scope1/libbar.c @@ -0,0 +1,14 @@ +char *foo(void); +char *foo_global(void); + +char *bar() { + return "bar"; +} + +char *bar_calls_foo() { + return foo(); +} + +char *bar_calls_foo_global() { + return foo_global(); +} diff --git a/lib/mlibc/tests/rtdl/scope1/libfoo.c b/lib/mlibc/tests/rtdl/scope1/libfoo.c new file mode 100644 index 0000000..b4e1b8c --- /dev/null +++ b/lib/mlibc/tests/rtdl/scope1/libfoo.c @@ -0,0 +1,9 @@ +char *foo() { + return "foo"; +} + +char global[] = "foo global"; + +char *foo_global() { + return global; +} diff --git a/lib/mlibc/tests/rtdl/scope1/meson.build b/lib/mlibc/tests/rtdl/scope1/meson.build new file mode 100644 index 0000000..acb679e --- /dev/null +++ b/lib/mlibc/tests/rtdl/scope1/meson.build @@ -0,0 +1,7 @@ +libfoo = shared_library('foo', 'libfoo.c') +libbar = shared_library('bar', 'libbar.c', build_rpath: test_rpath, link_with: libfoo) +test_depends = [libfoo, libbar] + +libfoo_native = shared_library('native-foo', 'libfoo.c', native: true) +libbar_native = shared_library('native-bar', 'libbar.c', build_rpath: test_rpath, link_with: libfoo_native, native: true) +test_native_depends = [libfoo_native, libbar_native] diff --git a/lib/mlibc/tests/rtdl/scope1/test.c b/lib/mlibc/tests/rtdl/scope1/test.c new file mode 100644 index 0000000..c19915d --- /dev/null +++ b/lib/mlibc/tests/rtdl/scope1/test.c @@ -0,0 +1,90 @@ +#include <stddef.h> +#include <dlfcn.h> +#include <assert.h> +#include <string.h> +#include <stdio.h> + +#ifdef USE_HOST_LIBC +#define LIBFOO "libnative-foo.so" +#define LIBBAR "libnative-bar.so" +#else +#define LIBFOO "libfoo.so" +#define LIBBAR "libbar.so" +#endif + +typedef char *strfn(void); + +int main() { + // We haven't dlopen'd these libs yet, so symbol resolution should fail. + assert(dlsym(RTLD_DEFAULT, "foo") == NULL); + assert(dlsym(RTLD_DEFAULT, "bar") == NULL); + + assert(!dlopen(LIBFOO, RTLD_NOLOAD)); + assert(!dlopen(LIBBAR, RTLD_NOLOAD)); + + void *foo_handle = dlopen(LIBFOO, RTLD_LOCAL | RTLD_NOW); + assert(foo_handle); + assert(dlopen(LIBFOO, RTLD_NOLOAD | RTLD_NOW)); + + strfn *foo_sym = dlsym(foo_handle, "foo"); + assert(foo_sym); + assert(foo_sym()); + assert(!strcmp(foo_sym(), "foo")); + + strfn *foo_global_sym = dlsym(foo_handle, "foo_global"); + assert(foo_global_sym); + assert(foo_global_sym()); + assert(!strcmp(foo_global_sym(), "foo global")); + + assert(dlsym(foo_handle, "doesnotexist") == NULL); + + // Nested opening should work + assert(dlopen(LIBFOO, RTLD_LOCAL | RTLD_NOW) == foo_handle); + assert(dlopen(LIBFOO, RTLD_NOLOAD | RTLD_NOW)); + + // Since we've loaded the same library twice, the addresses should be the same + assert(dlsym(foo_handle, "foo") == foo_sym); + assert(dlsym(foo_handle, "foo_global") == foo_global_sym); + + // libfoo was opened with RTLD_LOCAL, so we shouldn't be able to lookup + // its symbols in the global namespace. + assert(dlsym(RTLD_DEFAULT, "foo") == NULL); + + { + void *bar_handle = dlopen(LIBBAR, RTLD_GLOBAL | RTLD_NOW); + assert(bar_handle); + assert(dlopen(LIBBAR, RTLD_NOLOAD | RTLD_NOW)); + + strfn *bar_sym = dlsym(bar_handle, "bar"); + assert(bar_sym); + assert(bar_sym()); + assert(!strcmp(bar_sym(), "bar")); + + strfn *bar_calls_foo_sym = dlsym(bar_handle, "bar_calls_foo"); + assert(bar_calls_foo_sym); + assert(bar_calls_foo_sym()); + assert(!strcmp(bar_calls_foo_sym(), "foo")); + + strfn *bar_calls_foo_global_sym = dlsym(bar_handle, "bar_calls_foo_global"); + assert(bar_calls_foo_global_sym); + assert(bar_calls_foo_global_sym()); + assert(!strcmp(bar_calls_foo_global_sym(), "foo global")); + + // libbar was opened with RTLD_GLOBAL, so we can find symbols by + // searching in the global scope. + strfn *new_bar_sym = dlsym(RTLD_DEFAULT, "bar"); + assert(new_bar_sym); + assert(new_bar_sym == bar_sym); + + // Note that we loaded libbar with RTLD_GLOBAL, which should pull + // in libfoo's symbols globally too. + strfn *new_foo_sym = dlsym(RTLD_DEFAULT, "foo"); + assert(new_foo_sym); + assert(new_foo_sym == foo_sym); + + assert(dlclose(bar_handle) == 0); + } + + assert(dlclose(foo_handle) == 0); + assert(dlclose(foo_handle) == 0); +} diff --git a/lib/mlibc/tests/rtdl/scope2/libbar.c b/lib/mlibc/tests/rtdl/scope2/libbar.c new file mode 100644 index 0000000..4783c58 --- /dev/null +++ b/lib/mlibc/tests/rtdl/scope2/libbar.c @@ -0,0 +1,5 @@ +char *foo_baz_conflict(void); + +char *bar_calls_foo_baz_conflict() { + return foo_baz_conflict(); +} diff --git a/lib/mlibc/tests/rtdl/scope2/libbaz.c b/lib/mlibc/tests/rtdl/scope2/libbaz.c new file mode 100644 index 0000000..fc73adc --- /dev/null +++ b/lib/mlibc/tests/rtdl/scope2/libbaz.c @@ -0,0 +1,3 @@ +char *foo_baz_conflict() { + return "resolved to baz"; +} diff --git a/lib/mlibc/tests/rtdl/scope2/libfoo.c b/lib/mlibc/tests/rtdl/scope2/libfoo.c new file mode 100644 index 0000000..9f7b881 --- /dev/null +++ b/lib/mlibc/tests/rtdl/scope2/libfoo.c @@ -0,0 +1,3 @@ +char *foo_baz_conflict() { + return "resolved to foo"; +} diff --git a/lib/mlibc/tests/rtdl/scope2/meson.build b/lib/mlibc/tests/rtdl/scope2/meson.build new file mode 100644 index 0000000..938272c --- /dev/null +++ b/lib/mlibc/tests/rtdl/scope2/meson.build @@ -0,0 +1,9 @@ +libfoo = shared_library('foo', 'libfoo.c') +libbar = shared_library('bar', 'libbar.c', build_rpath: test_rpath, link_with: libfoo) +libbaz = shared_library('baz', 'libbaz.c') +test_depends = [libfoo, libbar, libbaz] + +libfoo_native = shared_library('native-foo', 'libfoo.c', native: true) +libbar_native = shared_library('native-bar', 'libbar.c', build_rpath: test_rpath, link_with: libfoo_native, native: true) +libbaz_native = shared_library('native-baz', 'libbaz.c', native: true) +test_native_depends = [libfoo_native, libbar_native, libbaz_native] diff --git a/lib/mlibc/tests/rtdl/scope2/test.c b/lib/mlibc/tests/rtdl/scope2/test.c new file mode 100644 index 0000000..f4f42dc --- /dev/null +++ b/lib/mlibc/tests/rtdl/scope2/test.c @@ -0,0 +1,36 @@ +#include <stddef.h> +#include <dlfcn.h> +#include <assert.h> +#include <string.h> +#include <stdio.h> + +#ifdef USE_HOST_LIBC +#define LIBBAR "libnative-bar.so" +#define LIBBAZ "libnative-baz.so" +#else +#define LIBBAR "libbar.so" +#define LIBBAZ "libbaz.so" +#endif + +typedef char *strfn(void); + +int main() { + void *baz = dlopen(LIBBAZ, RTLD_LAZY | RTLD_GLOBAL); + assert(baz); + + // At this point, baz is loaded in the global scope. When we load bar locally, + // there is a relocation to `foo_baz_conflict` which is defined in both + // foo (which is a dependency of bar), and baz. In this case baz should win + // since we search the global scope first. + + void *bar = dlopen(LIBBAR, RTLD_LAZY | RTLD_LOCAL); + assert(bar); + + strfn *bfn = dlsym(bar, "bar_calls_foo_baz_conflict"); + assert(!strcmp(bfn(), "resolved to baz")); + + // TODO: Test RTLD_DEEPBIND and DT_SYMBOLIC once we implement it. + + dlclose(bar); + dlclose(baz); +} diff --git a/lib/mlibc/tests/rtdl/scope3/libbar.c b/lib/mlibc/tests/rtdl/scope3/libbar.c new file mode 100644 index 0000000..dc377b6 --- /dev/null +++ b/lib/mlibc/tests/rtdl/scope3/libbar.c @@ -0,0 +1,8 @@ +int g = 1; + +int call_foo(); + +int call_bar() { + return call_foo(); +} + diff --git a/lib/mlibc/tests/rtdl/scope3/libbaz.c b/lib/mlibc/tests/rtdl/scope3/libbaz.c new file mode 100644 index 0000000..32524cc --- /dev/null +++ b/lib/mlibc/tests/rtdl/scope3/libbaz.c @@ -0,0 +1,7 @@ +int g = 2; + +int call_foo(); + +int call_baz() { + return call_foo(); +} diff --git a/lib/mlibc/tests/rtdl/scope3/libfoo.c b/lib/mlibc/tests/rtdl/scope3/libfoo.c new file mode 100644 index 0000000..bc86319 --- /dev/null +++ b/lib/mlibc/tests/rtdl/scope3/libfoo.c @@ -0,0 +1,5 @@ +int g = 0; + +int call_foo() { + return g; +} diff --git a/lib/mlibc/tests/rtdl/scope3/meson.build b/lib/mlibc/tests/rtdl/scope3/meson.build new file mode 100644 index 0000000..0c98583 --- /dev/null +++ b/lib/mlibc/tests/rtdl/scope3/meson.build @@ -0,0 +1,9 @@ +libfoo = shared_library('foo', 'libfoo.c') +libbar = shared_library('bar', 'libbar.c', build_rpath: test_rpath, link_with: libfoo) +libbaz = shared_library('baz', 'libbaz.c', build_rpath: test_rpath, link_with: libfoo) +test_depends = [libfoo, libbar, libbaz] + +libfoo_native = shared_library('native-foo', 'libfoo.c', native: true) +libbar_native = shared_library('native-bar', 'libbar.c', build_rpath: test_rpath, link_with: libfoo_native, native: true) +libbaz_native = shared_library('native-baz', 'libbaz.c', build_rpath: test_rpath, link_with: libfoo_native, native: true) +test_native_depends = [libfoo_native, libbar_native, libbaz_native] diff --git a/lib/mlibc/tests/rtdl/scope3/test.c b/lib/mlibc/tests/rtdl/scope3/test.c new file mode 100644 index 0000000..30fc662 --- /dev/null +++ b/lib/mlibc/tests/rtdl/scope3/test.c @@ -0,0 +1,39 @@ +#include <dlfcn.h> +#include <stdio.h> +#include <assert.h> + +#ifdef USE_HOST_LIBC +#define LIBBAR "libnative-bar.so" +#define LIBBAZ "libnative-baz.so" +#else +#define LIBBAR "libbar.so" +#define LIBBAZ "libbaz.so" +#endif + +int main() { + // In this test, we have bar -> foo and baz -> foo (where -> means 'depends on'). + // All three objects contain a definition of a symbol g. Bar calls into foo to retrieve + // what foo thinks g is, but since bar appears earlier in the scope than foo, bar's copy + // of g wins. + // + // Next, we load baz, which is identical to bar. When baz calls into foo to retrieve g, + // foo still sees bar's definition of g, so bar's copy of g wins. + // + // Swapping the load order of bar and baz should therefore change the value of g which + // foo sees. This behaviour is why dlmopen exists. If we ever implement that, we should + // write a similar test and assert that the calls return different results. + + void *libbar = dlopen(LIBBAR, RTLD_LAZY | RTLD_LOCAL); + int (*call_bar)(void) = dlsym(libbar, "call_bar"); + printf("call_bar: %d\n", call_bar()); + assert(call_bar() == 1); + + void *libbaz = dlopen(LIBBAZ, RTLD_LAZY | RTLD_LOCAL); + int (*call_baz)(void) = dlsym(libbaz, "call_baz"); + printf("call_baz: %d\n", call_baz()); + assert(call_baz() == 1); + + + dlclose(libbar); + dlclose(libbaz); +} diff --git a/lib/mlibc/tests/rtdl/scope4/libbar.c b/lib/mlibc/tests/rtdl/scope4/libbar.c new file mode 100644 index 0000000..514e456 --- /dev/null +++ b/lib/mlibc/tests/rtdl/scope4/libbar.c @@ -0,0 +1,3 @@ +// Bar needs to have a relocation against foo in order to set DT_NEEDED. +void foo(void); +void bar() { foo(); } diff --git a/lib/mlibc/tests/rtdl/scope4/libbaz.c b/lib/mlibc/tests/rtdl/scope4/libbaz.c new file mode 100644 index 0000000..256a0e3 --- /dev/null +++ b/lib/mlibc/tests/rtdl/scope4/libbaz.c @@ -0,0 +1 @@ +void baz() {} diff --git a/lib/mlibc/tests/rtdl/scope4/libfoo.c b/lib/mlibc/tests/rtdl/scope4/libfoo.c new file mode 100644 index 0000000..6710db7 --- /dev/null +++ b/lib/mlibc/tests/rtdl/scope4/libfoo.c @@ -0,0 +1,3 @@ +// Foo needs to have a relocation against baz in order to set DT_NEEDED. +void baz(void); +void foo() { baz(); } diff --git a/lib/mlibc/tests/rtdl/scope4/meson.build b/lib/mlibc/tests/rtdl/scope4/meson.build new file mode 100644 index 0000000..804a40c --- /dev/null +++ b/lib/mlibc/tests/rtdl/scope4/meson.build @@ -0,0 +1,9 @@ +libbaz = shared_library('baz', 'libbaz.c') +libfoo = shared_library('foo', 'libfoo.c', build_rpath: test_rpath, link_with: libbaz) +libbar = shared_library('bar', 'libbar.c', build_rpath: test_rpath, link_with: libfoo) +test_depends = [libfoo, libbar, libbaz] + +libbaz_native = shared_library('native-baz', 'libbaz.c', native: true) +libfoo_native = shared_library('native-foo', 'libfoo.c', build_rpath: test_rpath, link_with: libbaz_native, native: true) +libbar_native = shared_library('native-bar', 'libbar.c', build_rpath: test_rpath, link_with: libfoo_native, native: true) +test_native_depends = [libfoo_native, libbar_native, libbaz_native] diff --git a/lib/mlibc/tests/rtdl/scope4/test.c b/lib/mlibc/tests/rtdl/scope4/test.c new file mode 100644 index 0000000..2365e26 --- /dev/null +++ b/lib/mlibc/tests/rtdl/scope4/test.c @@ -0,0 +1,36 @@ +#include <dlfcn.h> +#include <stdio.h> +#include <assert.h> + +#ifdef USE_HOST_LIBC +#define LIBFOO "libnative-foo.so" +#define LIBBAR "libnative-bar.so" +#define LIBBAZ "libnative-baz.so" +#else +#define LIBFOO "libfoo.so" +#define LIBBAR "libbar.so" +#define LIBBAZ "libbaz.so" +#endif + +int main() { + // In this test, we have foo -> baz, bar -> foo (where '->' means 'depends on'). + // We first load foo with RTLD_LOCAL, and then load bar with RTLD_GLOBAL. + // This should bring foo and bar into the global scope. + + void *foo = dlopen(LIBFOO, RTLD_LOCAL | RTLD_NOW); + assert(foo); + assert(dlsym(foo, "foo")); + assert(dlsym(foo, "baz")); + assert(!dlsym(RTLD_DEFAULT, "foo")); + assert(!dlsym(RTLD_DEFAULT, "baz")); + + void *bar = dlopen(LIBBAR, RTLD_GLOBAL | RTLD_NOW); + assert(bar); + assert(dlsym(bar, "bar")); + assert(dlsym(RTLD_DEFAULT, "bar")); + assert(dlsym(RTLD_DEFAULT, "foo")); + assert(dlsym(RTLD_DEFAULT, "baz")); + + dlclose(foo); + dlclose(bar); +} diff --git a/lib/mlibc/tests/rtdl/scope5/libfoo.c b/lib/mlibc/tests/rtdl/scope5/libfoo.c new file mode 100644 index 0000000..85e6cd8 --- /dev/null +++ b/lib/mlibc/tests/rtdl/scope5/libfoo.c @@ -0,0 +1 @@ +void foo() {} diff --git a/lib/mlibc/tests/rtdl/scope5/meson.build b/lib/mlibc/tests/rtdl/scope5/meson.build new file mode 100644 index 0000000..22ade5b --- /dev/null +++ b/lib/mlibc/tests/rtdl/scope5/meson.build @@ -0,0 +1,5 @@ +libfoo = shared_library('foo', 'libfoo.c') +test_link_with = [libfoo] + +libfoo_native = shared_library('native-foo', 'libfoo.c', native: true) +test_native_link_with = [libfoo_native] diff --git a/lib/mlibc/tests/rtdl/scope5/test.c b/lib/mlibc/tests/rtdl/scope5/test.c new file mode 100644 index 0000000..2c11c2e --- /dev/null +++ b/lib/mlibc/tests/rtdl/scope5/test.c @@ -0,0 +1,27 @@ +#include <dlfcn.h> +#include <stdio.h> +#include <assert.h> + +#ifdef USE_HOST_LIBC +#define LIBFOO "libnative-foo.so" +#else +#define LIBFOO "libfoo.so" +#endif + +// We need to have a relocation against foo for DT_NEEDED. +void foo(); +void bar() { foo(); } + +int main() { + // In this test, we have exec -> foo (where '->' means 'depends on'). + // This means that foo is in the global scope due to DT_NEEDED. + // We then dlopen it again with RTLD_LOCAL, which should just return + // the already-loaded object, but used to crash in the mlibc linker instead. + + void *foo = dlopen(LIBFOO, RTLD_LOCAL | RTLD_NOW); + assert(foo); + assert(dlsym(foo, "foo")); + assert(dlsym(RTLD_DEFAULT, "foo")); + + dlclose(foo); +} diff --git a/lib/mlibc/tests/rtdl/soname/libbar.c b/lib/mlibc/tests/rtdl/soname/libbar.c new file mode 100644 index 0000000..7f9f475 --- /dev/null +++ b/lib/mlibc/tests/rtdl/soname/libbar.c @@ -0,0 +1 @@ +char *name() { return "bar"; } diff --git a/lib/mlibc/tests/rtdl/soname/libfoo.c b/lib/mlibc/tests/rtdl/soname/libfoo.c new file mode 100644 index 0000000..abe0999 --- /dev/null +++ b/lib/mlibc/tests/rtdl/soname/libfoo.c @@ -0,0 +1 @@ +char *name() { return "foo"; } diff --git a/lib/mlibc/tests/rtdl/soname/meson.build b/lib/mlibc/tests/rtdl/soname/meson.build new file mode 100644 index 0000000..1eb97a8 --- /dev/null +++ b/lib/mlibc/tests/rtdl/soname/meson.build @@ -0,0 +1,17 @@ +# Create two libraries with the same SONAME. +bar_soname = '-Wl,-soname=libbar.so' + +libfoo = shared_library('foo', 'libfoo.c', link_args: '-Wl,-soname=libbar.so') +libbar = shared_library('bar', 'libbar.c', link_args: '-Wl,-soname=libbar.so') +test_depends = [libfoo, libbar] + +libfoo_native = shared_library('native-foo', 'libfoo.c', + link_args: ['-ldl', '-Wl,-soname=libnative-bar.so'], + native: true +) +libbar_native = shared_library('native-bar', 'libbar.c', + link_args: ['-ldl', '-Wl,-soname=libnative-bar.so'], + native: true +) +test_native_depends = [libfoo_native, libbar_native] + diff --git a/lib/mlibc/tests/rtdl/soname/test.c b/lib/mlibc/tests/rtdl/soname/test.c new file mode 100644 index 0000000..23e5b7a --- /dev/null +++ b/lib/mlibc/tests/rtdl/soname/test.c @@ -0,0 +1,33 @@ +#include <dlfcn.h> +#include <stdio.h> +#include <assert.h> +#include <string.h> + +#ifdef USE_HOST_LIBC +#define LIBFOO "libnative-foo.so" +#define LIBBAR "libnative-bar.so" +#else +#define LIBFOO "libfoo.so" +#define LIBBAR "libbar.so" +#endif + +int main() { + void *foo = dlopen(LIBFOO, RTLD_NOW); + void *bar = dlopen(LIBBAR, RTLD_NOW); + assert(foo); + assert(bar); + + // Since these libraries have the same SONAME, they should return the same thing. + assert(foo == bar); + + char *(*fooSym)(void) = dlsym(foo, "name"); + char *(*barSym)(void) = dlsym(bar, "name"); + assert(fooSym && barSym); + assert(fooSym() && barSym()); + printf("foo: name() = \"%s\"\n", fooSym()); + printf("bar: name() = \"%s\"\n", barSym()); + assert(!strcmp(fooSym(), barSym())); + + dlclose(foo); + dlclose(bar); +} diff --git a/lib/mlibc/tests/rtdl/tls_align/libbar.c b/lib/mlibc/tests/rtdl/tls_align/libbar.c new file mode 100644 index 0000000..3f8d6a7 --- /dev/null +++ b/lib/mlibc/tests/rtdl/tls_align/libbar.c @@ -0,0 +1 @@ +_Thread_local __attribute__((aligned(8))) char bar_thread_local[8] = "Hello!"; diff --git a/lib/mlibc/tests/rtdl/tls_align/libfoo.c b/lib/mlibc/tests/rtdl/tls_align/libfoo.c new file mode 100644 index 0000000..8d98177 --- /dev/null +++ b/lib/mlibc/tests/rtdl/tls_align/libfoo.c @@ -0,0 +1 @@ +_Thread_local __attribute__((aligned(16))) char foo_thread_local[8] = "Hello!"; diff --git a/lib/mlibc/tests/rtdl/tls_align/meson.build b/lib/mlibc/tests/rtdl/tls_align/meson.build new file mode 100644 index 0000000..61d0fd9 --- /dev/null +++ b/lib/mlibc/tests/rtdl/tls_align/meson.build @@ -0,0 +1,7 @@ +libfoo = shared_library('foo', 'libfoo.c') +libbar = shared_library('bar', 'libbar.c') +test_link_with = [libfoo, libbar] + +libfoo_native = shared_library('native-foo', 'libfoo.c', native: true) +libbar_native = shared_library('native-bar', 'libbar.c', native: true) +test_native_link_with = [libfoo_native, libbar_native] diff --git a/lib/mlibc/tests/rtdl/tls_align/test.c b/lib/mlibc/tests/rtdl/tls_align/test.c new file mode 100644 index 0000000..4381831 --- /dev/null +++ b/lib/mlibc/tests/rtdl/tls_align/test.c @@ -0,0 +1,10 @@ +#include <assert.h> +#include <stdint.h> + +extern _Thread_local char foo_thread_local[]; +extern _Thread_local char bar_thread_local[]; + +int main() { + assert(!((uintptr_t)foo_thread_local & (16 - 1))); + assert(!((uintptr_t)bar_thread_local & (8 - 1))); +} |