diff options
Diffstat (limited to '')
-rw-r--r-- | src/lib/test-array.c | 441 |
1 files changed, 441 insertions, 0 deletions
diff --git a/src/lib/test-array.c b/src/lib/test-array.c new file mode 100644 index 0000000..e6aade1 --- /dev/null +++ b/src/lib/test-array.c @@ -0,0 +1,441 @@ +/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ + +#include "test-lib.h" +#include "array.h" + + +struct foo { + unsigned int a, b, c; +}; + +static void test_array_elem(void) +{ + ARRAY(struct foo *) foos; + struct foo *nfoo; + struct foo *foo; + struct foo local_foo; + unsigned int i; + + test_begin("array elem"); + t_array_init(&foos, 32); + + foo = &local_foo; + array_foreach_elem(&foos, foo) + test_assert(FALSE); + test_assert(foo == &local_foo); + + for (i = 1; i <= 3; i++) { + nfoo = t_new(struct foo, 1); + nfoo->a = i; + array_push_back(&foos, &nfoo); + } + + struct foo *const *foo_p = array_idx(&foos, 1); + unsigned int idx = 1; + foo = array_idx_elem(&foos, idx++); + /* make sure idx isn't expanded multiple times in the macro */ + test_assert(idx == 2); + test_assert(*foo_p == foo); + + i = 1; + array_foreach_elem(&foos, foo) { + test_assert(foo->a == i); + i++; + } + test_assert(foo->a == i-1); + test_end(); +} + +static void test_array_count(void) +{ + ARRAY(struct foo) foos; + struct foo nfoo; + + test_begin("array count/empty"); + t_array_init(&foos, 32); + + test_assert(array_count(&foos) == 0); + test_assert(array_is_empty(&foos)); + test_assert(!array_not_empty(&foos)); + nfoo.a = nfoo.b = nfoo.c = 9; + array_push_back(&foos, &nfoo); + test_assert(array_count(&foos) == 1); + test_assert(!array_is_empty(&foos)); + test_assert(array_not_empty(&foos)); + + test_end(); +} +static void test_array_foreach(void) +{ + ARRAY(struct foo) foos; + const struct foo *foo; + struct foo nfoo; + unsigned int i; + + test_begin("array foreach"); + t_array_init(&foos, 32); + for (i = 0; i < 10; i++) { + nfoo.a = nfoo.b = nfoo.c = i; + array_push_back(&foos, &nfoo); + } + + array_foreach(&foos, foo) { + i = array_foreach_idx(&foos, foo); + test_assert(foo->a == i); + test_assert(foo->b == i); + test_assert(foo->c == i); + } + /* points past the last element */ + test_assert(foo == array_idx(&foos, i)+1); + test_end(); +} + +static void test_array_foreach_reverse(void) +{ + ARRAY(unsigned int) arr; + const unsigned int *i_p; + unsigned int i, i2, *imod_p; + + test_begin("array foreach reverse"); + t_array_init(&arr, 32); + + /* first test that array_foreach() + array_delete() doesn't really + work as we might hope.. */ + for (i = 1; i <= 5; i++) + array_push_back(&arr, &i); + array_foreach(&arr, i_p) { + i = array_foreach_idx(&arr, i_p); + array_delete(&arr, i, 1); + } + test_assert(array_count(&arr) == 2); + + /* but using array_foreach_reverse() + array_delete() does work: */ + array_clear(&arr); + i2 = 5; + for (i = 1; i <= i2; i++) + array_push_back(&arr, &i); + array_foreach_reverse(&arr, i_p) { + i = array_foreach_idx(&arr, i_p); + test_assert(*i_p == i2); + test_assert(*i_p == i + 1); + array_delete(&arr, i, 1); + i2--; + } + test_assert(array_count(&arr) == 0); + + /* also array_foreach_reverse_modifiable() + array_delete() works: */ + i2 = 5; + for (i = 1; i <= i2; i++) + array_push_back(&arr, &i); + array_foreach_reverse_modifiable(&arr, imod_p) { + i = array_foreach_idx(&arr, imod_p); + test_assert(*imod_p == i2); + test_assert(*imod_p == i + 1); + array_delete(&arr, i, 1); + i2--; + } + test_assert(array_count(&arr) == 0); + + test_end(); +} + +static void test_array_foreach_elem_string(void) +{ + ARRAY(char *) blurbs; + ARRAY(const char *) cblurbs; + char *string; + const char *cstring; + int i; + + test_begin("array foreach_elem ro/rw strings"); + t_array_init(&blurbs, 32); + t_array_init(&cblurbs, 32); + for (i = 0; i < 10; i++) { + cstring = t_strdup_printf("x%iy", i); + string = t_strdup_noconst(cstring); + array_push_back(&blurbs, &string); + array_push_back(&cblurbs, &cstring); + } + + i = 0; + array_foreach_elem(&blurbs, string) { + test_assert_idx(string[0] == 'x' && string[1]-'0' == i && string[2] == 'y', i); + i++; + } + i = 0; + array_foreach_elem(&cblurbs, cstring) { + test_assert_idx(cstring[0] == 'x' && cstring[1]-'0' == i && cstring[2] == 'y', i); + i++; + } + test_end(); +} + +static int test_int_compare(const int *key, const int *elem) +{ + return (*key < *elem) ? -1 : + (*key > *elem) ? 1 : + 0; +} +static void test_array_reverse(void) +{ + ARRAY(int) intarr; + int input[] = { -1234567890, -272585721, 272485922, 824725652 }; + const int tmpi = 999, *output; + unsigned int i, j; + + test_begin("array reverse"); + t_array_init(&intarr, 5); + for (i = 0; i <= N_ELEMENTS(input); i++) { + array_clear(&intarr); + array_append(&intarr, input, i); + array_reverse(&intarr); + + output = i == 0 ? NULL : array_front(&intarr); + for (j = 0; j < i; j++) + test_assert(input[i-j-1] == output[j]); + } + test_end(); + + test_begin("array_lsearch"); + for (i = 0; i < N_ELEMENTS(input); i++) { + output = array_lsearch(&intarr, &input[i], test_int_compare); + test_assert(output != NULL); + j = array_ptr_to_idx(&intarr, output); + test_assert_idx(j == N_ELEMENTS(input) - 1 - i, i); + } + output = array_lsearch(&intarr, &tmpi, test_int_compare); + test_assert(output == NULL); + test_end(); +} +static int test_compare_ushort(const unsigned short *c1, const unsigned short *c2) +{ + return *c1 > *c2 ? 1 + : *c1 < *c2 ? -1 + : 0; +} +static int test_compare_ushort_fuzz(const unsigned short *c1, const unsigned short *c2, const int *pfuzz) +{ + int d = (int)*c1 - (int)*c2; + if (d <= *pfuzz && -d <= *pfuzz) + return 0; + return d; +} +static void test_array_cmp(void) +{ + static const unsigned short deltas[] = { + 0x8000, 0xc000, 0xfe00, 0xff00, 0xff80, 0xffc0, 0xfffe, 0xffff, + 0, 1, 2, 64, 128, 256, 512, 16384, 32768 + }; + +#define NELEMS 5u + ARRAY(unsigned short) arr1, arr2; + unsigned short elems[NELEMS+1]; + unsigned int i; + int fuzz; + + test_begin("array compare (ushort)"); + t_array_init(&arr1, NELEMS); + t_array_init(&arr2, NELEMS); + for (i = 0; i < NELEMS; i++) { + elems[i] = i_rand_ushort(); + array_push_back(&arr2, &elems[i]); + } + array_append(&arr1, elems, NELEMS); + test_assert(array_cmp(&arr1, &arr2) == TRUE); + test_assert(array_equal_fn(&arr1, &arr2, test_compare_ushort) == TRUE); + fuzz = 0; + test_assert(array_equal_fn_ctx(&arr1, &arr2, test_compare_ushort_fuzz, &fuzz) == TRUE); + + for (i = 0; i < 256; i++) { + unsigned int j = i_rand_limit(NELEMS); + const unsigned short *ptmp = array_idx(&arr2, j); + unsigned short tmp = *ptmp; + unsigned short repl = ((unsigned int)tmp + + deltas[i_rand_limit(N_ELEMENTS(deltas))]) & 0xffff; + + array_idx_set(&arr2, j, &repl); + test_assert_idx(array_cmp(&arr1, &arr2) == (tmp == repl), i); + test_assert_idx(array_equal_fn(&arr1, &arr2, test_compare_ushort) == (tmp == repl), i); + fuzz = (int)tmp - (int)repl; + if (fuzz < 0) + fuzz = -fuzz; + test_assert_idx(array_equal_fn_ctx(&arr1, &arr2, test_compare_ushort_fuzz, &fuzz) == TRUE, i); + if (fuzz > 0) { + fuzz--; + test_assert_idx(array_equal_fn_ctx(&arr1, &arr2, test_compare_ushort_fuzz, &fuzz) == FALSE, i); + } + array_idx_set(&arr2, j, &tmp); + test_assert_idx(array_cmp(&arr1, &arr2) == TRUE, i); + test_assert_idx(array_equal_fn(&arr1, &arr2, test_compare_ushort) == TRUE, i); + fuzz = 0; + test_assert_idx(array_equal_fn_ctx(&arr1, &arr2, test_compare_ushort_fuzz, &fuzz) == TRUE, i); + } + elems[NELEMS] = 0; + array_push_back(&arr2, &elems[NELEMS]); + test_assert(array_cmp(&arr1, &arr2) == FALSE); + test_assert(array_equal_fn(&arr1, &arr2, test_compare_ushort) == FALSE); + test_assert_idx(array_equal_fn_ctx(&arr1, &arr2, test_compare_ushort_fuzz, &fuzz) == FALSE, i); + + test_end(); +} + +static void test_array_cmp_str(void) +{ +#define NELEMS 5u + ARRAY(const char *) arr1, arr2; + const char *elemstrs[NELEMS+1]; + unsigned int i; + + test_begin("array compare (char*)"); + t_array_init(&arr1, NELEMS); + t_array_init(&arr2, NELEMS); + for (i = 0; i < NELEMS; i++) { + elemstrs[i] = t_strdup_printf("%x", i_rand()); /* never 0-length */ + array_push_back(&arr2, &elemstrs[i]); + } + array_append(&arr1, elemstrs, NELEMS); + test_assert(array_cmp(&arr1, &arr2) == TRUE); /* pointers shared, so identical */ + test_assert(array_equal_fn(&arr1, &arr2, i_strcmp_p) == TRUE); /* therefore value same */ + for (i = 0; i < 2560; i++) { + unsigned int j = i_rand_limit(NELEMS); + const char *const *ostr_p = array_idx(&arr2, j); + const char *ostr = *ostr_p; + unsigned int olen = strlen(ostr); + unsigned int rc = i_rand_limit(olen + 1); + char ochar = ostr[rc]; + char buf[12]; + const char *bufp = buf; + memcpy(buf, ostr, olen+1); + buf[rc] = (int32_t)i_rand_limit(CHAR_MAX + 1 - CHAR_MIN) + CHAR_MIN; + if(rc == olen) + buf[rc+1] = '\0'; + array_idx_set(&arr2, j, &bufp); + test_assert(array_cmp(&arr1, &arr2) == FALSE); /* pointers now differ */ + test_assert_idx(array_equal_fn(&arr1, &arr2, i_strcmp_p) + == (strcmp(ostr, buf) == 0), i); /* sometimes still the same */ + test_assert_idx(array_equal_fn(&arr1, &arr2, i_strcmp_p) + == (ochar == buf[rc]), i); /* ditto */ + array_idx_set(&arr2, j, &ostr); + test_assert(array_cmp(&arr1, &arr2) == TRUE); /* pointers now same again */ + test_assert_idx(array_equal_fn(&arr1, &arr2, i_strcmp_p) == TRUE, i); /* duh! */ + } + /* length differences being detected are tested in other tests */ + test_end(); +} + +static void +test_array_free_case(bool keep) +{ + pool_t pool = pool_allocfree_create("array test"); + ARRAY(int) r; + int *p; + + test_begin(keep ? "array_free" : "array_free_without_data"); + + p_array_init(&r, pool, 100); + array_append_zero(&r); + if (keep) { + p = array_free_without_data(&r); + test_assert(pool_allocfree_get_total_used_size(pool)>=400); + p_free(pool, p); + } else { + array_free(&r); + test_assert(pool_allocfree_get_total_used_size(pool)==0); + } + pool_unref(&pool); + test_end(); +} +static void +test_array_free(void) +{ + test_array_free_case(FALSE); + test_array_free_case(TRUE); +} + +void test_array(void) +{ + test_array_elem(); + test_array_count(); + test_array_foreach(); + test_array_foreach_reverse(); + test_array_foreach_elem_string(); + test_array_reverse(); + test_array_cmp(); + test_array_cmp_str(); + test_array_free(); +} + +enum fatal_test_state fatal_array(unsigned int stage) +{ + double tmpd[2] = { 42., -42. }; + short tmps[8] = {1,2,3,4,5,6,7,8}; + static const void *useless_ptr; /* persuade gcc to not optimise the tests */ + + switch(stage) { + case 0: { + ARRAY(double) ad; + test_begin("fatal_array"); + t_array_init(&ad, 3); + /* allocation big enough, but memory not initialised */ + test_expect_fatal_string("(array_idx_i): assertion failed: (idx < array->buffer->used / array->element_size)"); + useless_ptr = array_front(&ad); + return FATAL_TEST_FAILURE; + } + + case 1: { + ARRAY(double) ad; + t_array_init(&ad, 2); + array_append(&ad, tmpd, 2); + /* actual out of range address requested */ + test_expect_fatal_string("(array_idx_i): assertion failed: (idx < array->buffer->used / array->element_size)"); + useless_ptr = array_idx(&ad, 2); + return FATAL_TEST_FAILURE; + } + + case 2: { + ARRAY(double) ad; + ARRAY(short) as; + t_array_init(&ad, 2); + t_array_init(&as, 8); + array_append(&as, tmps, 2); + /* can't copy different array sizes */ + test_expect_fatal_string("(array_copy): assertion failed: (dest->element_size == src->element_size)"); + array_copy(&ad.arr, 1, &as.arr, 0, 4); + return FATAL_TEST_FAILURE; + } + case 3: { + ARRAY(uint8_t) arr; + /* Allocate value dynamically, so compiler won't know the + allocated memory size and output a warning that it's too + small for array_append(). */ + uint8_t *value = t_malloc0(1); + + t_array_init(&arr, 2); + array_push_back(&arr, value); + test_expect_fatal_string("Buffer write out of range"); + /* this is supposed to assert-crash before it even attempts to + access value */ + array_append(&arr, value, UINT_MAX); + return FATAL_TEST_FAILURE; + } + case 4: { + ARRAY(uint32_t) arr; + /* Allocate value dynamically (see above for reasoning). */ + uint32_t *value = t_malloc0(1); + + t_array_init(&arr, 2); + array_push_back(&arr, value); + test_expect_fatal_string("Buffer write out of range"); + /* this is supposed to assert-crash before it even attempts to + access value */ + array_append(&arr, value, UINT_MAX); + return FATAL_TEST_FAILURE; + } + } + test_end(); + /* Forces the compiler to check the value of useless_ptr, so that it + must call array_idx (which is marked as pure, and gcc was desperate + to optimise out. Of course, gcc is unaware stage is never UINT_MAX.*/ + return (useless_ptr != NULL && stage == UINT_MAX) + ? FATAL_TEST_FAILURE : FATAL_TEST_FINISHED; +} |