diff --git a/.circleci/config.yml b/.circleci/config.yml index 2bacf55..d482d0f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -38,15 +38,21 @@ jobs: name: Run C tests command: | ./build-gcc/tests + ./build-gcc/malloc_tests + ./build-gcc/io_tests - run: name: Valgrind for C tests. command: | valgrind --leak-check=full --error-exitcode=1 ./build-gcc/tests + valgrind --leak-check=full --error-exitcode=1 ./build-gcc/malloc_tests + valgrind --leak-check=full --error-exitcode=1 ./build-gcc/io_tests - run: name: Run gcov & upload coverage. command: | gcov -pb -o ./python/build/temp.linux*/ python/_kastoremodule.c - find ./build-gcc -type f -name '*.gcno' -exec gcov -pb -o ./build-gcc/tests@exe {} + + gcov -pb ./build-gcc/tests@exe/kastore.c.gcno \ + ./build-gcc/malloc_tests@exe/kastore.c.gcno \ + ./build-gcc/io_tests@exe/kastore.c.gcno codecov -X gcov -F C - run: name: Compile C tests under clang @@ -56,3 +62,5 @@ jobs: name: Run clang C tests command: | ./build-clang/tests + ./build-clang/malloc_tests + ./build-clang/io_tests diff --git a/c/io_tests.c b/c/io_tests.c new file mode 100644 index 0000000..ddd9ff9 --- /dev/null +++ b/c/io_tests.c @@ -0,0 +1,460 @@ +/* These tests simulate failures in IO by wrapping the original definitions + * of the fwrite, etc using the linker and failing after a predefined number + * of calls */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +#include "kastore.h" +#include + +char * _tmp_file_name; + +/* Wrappers used to check for correct error handling. Must be linked with the + * link option, e.g., -Wl,--wrap=fwrite. See 'ld' manpage for details. + */ + +size_t __wrap_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); +size_t __real_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); +int _fwrite_fail_at = -1; +int _fwrite_count = 0; +size_t +__wrap_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) +{ + if (_fwrite_fail_at == _fwrite_count) { + return 0; + } + _fwrite_count++; + return __real_fwrite(ptr, size, nmemb, stream); +} + +size_t __wrap_fread(const void *ptr, size_t size, size_t nmemb, FILE *stream); +size_t __real_fread(const void *ptr, size_t size, size_t nmemb, FILE *stream); +int _fread_fail_at = -1; +int _fread_count = 0; +size_t +__wrap_fread(const void *ptr, size_t size, size_t nmemb, FILE *stream) +{ + if (_fread_fail_at == _fread_count) { + return 0; + } + _fread_count++; + return __real_fread(ptr, size, nmemb, stream); +} + +int __wrap_fseek(FILE *stream, long offset, int whence); +int __real_fseek(FILE *stream, long offset, int whence); +int _fseek_fail_at = -1; +int _fseek_count = 0; +int +__wrap_fseek(FILE *stream, long offset, int whence) +{ + if (_fseek_fail_at == _fseek_count) { + return -1; + } + _fseek_count++; + return __real_fseek(stream, offset, whence); +} + +int __wrap_fclose(FILE *stream); +int __real_fclose(FILE *stream); +int _fclose_fail_at = -1; +int _fclose_count = 0; +int +__wrap_fclose(FILE *stream) +{ + if (_fclose_fail_at == _fclose_count) { + /* Close the file anyway to avoid a memory leak, but return failure. */ + __real_fclose(stream); + return EOF; + } + _fclose_count++; + return __real_fclose(stream); +} + +void *__wrap_mmap64(void *addr, size_t length, int prot, int flags, int fd, off_t offset); +void *__real_mmap64(void *addr, size_t length, int prot, int flags, int fd, off_t offset); +int _mmap_fail_at = -1; +int _mmap_count = 0; +void * +__wrap_mmap64(void *addr, size_t length, int prot, int flags, int fd, off_t offset) +{ + if (_mmap_fail_at == _mmap_count) { + return MAP_FAILED; + } + _mmap_count++; + return __real_mmap64(addr, length, prot, flags, fd, offset); +} + +void *__wrap_stat64(void *addr, size_t length, int prot, int flags, int fd, off_t offset); +void *__real_stat64(void *addr, size_t length, int prot, int flags, int fd, off_t offset); +int _stat_fail_at = -1; +int _stat_count = 0; +void * +__wrap_stat64(void *addr, size_t length, int prot, int flags, int fd, off_t offset) +{ + if (_stat_fail_at == _stat_count) { + return MAP_FAILED; + } + _stat_count++; + return __real_stat64(addr, length, prot, flags, fd, offset); +} + +/* Tests */ + +static void +test_write_empty(void) +{ + kastore_t store; + int ret = 0; + + _fwrite_fail_at = 0; + _fwrite_count = 0; + ret = kastore_open(&store, _tmp_file_name, "w", 0); + CU_ASSERT_EQUAL_FATAL(ret, 0); + ret = kastore_close(&store); + CU_ASSERT_EQUAL_FATAL(ret, KAS_ERR_IO); + + /* Make sure we reset this for other functions */ + _fwrite_fail_at = -1; +} + +static void +test_write(void) +{ + kastore_t store; + int ret = 0; + int8_t array[] = {1}; + bool done; + + /* keep increasing fail_at until we pass. This should catch all possible + * places at which we can fail. */ + _fwrite_fail_at = 0; + _fwrite_count = 0; + done = false; + while (! done) { + ret = kastore_open(&store, _tmp_file_name, "w", 0); + CU_ASSERT_EQUAL_FATAL(ret, 0); + _fwrite_count = 0; + ret = kastore_puts_int8(&store, "a", array, 1, 0); + CU_ASSERT_EQUAL_FATAL(ret, 0); + ret = kastore_close(&store); + if (ret == 0) { + done = true; + } else { + CU_ASSERT_EQUAL_FATAL(ret, KAS_ERR_IO); + } + _fwrite_fail_at++; + } + CU_ASSERT_TRUE(_fwrite_fail_at > 1); + /* Make sure we reset this for other functions */ + _fwrite_fail_at = -1; +} + +static void +test_write_fclose(void) +{ + kastore_t store; + int ret = 0; + int8_t array[] = {1}; + bool done; + + /* keep increasing fail_at until we pass. This should catch all possible + * places at which we can fail. */ + _fclose_fail_at = 0; + _fclose_count = 0; + done = false; + while (! done) { + ret = kastore_open(&store, _tmp_file_name, "w", 0); + CU_ASSERT_EQUAL_FATAL(ret, 0); + _fclose_count = 0; + ret = kastore_puts_int8(&store, "a", array, 1, 0); + CU_ASSERT_EQUAL_FATAL(ret, 0); + ret = kastore_close(&store); + if (ret == 0) { + done = true; + } else { + CU_ASSERT_EQUAL_FATAL(ret, KAS_ERR_IO); + } + _fclose_fail_at++; + } + CU_ASSERT_TRUE(_fclose_fail_at > 1); + /* Make sure we reset this for other functions */ + _fclose_fail_at = -1; +} + +static void +test_read_fclose(void) +{ + kastore_t store; + int ret = 0; + const char *filename = "test-data/v1/all_types_1_elements.kas"; + + _fclose_fail_at = 0; + _fclose_count = 0; + ret = kastore_open(&store, filename, "r", 0); + CU_ASSERT_EQUAL_FATAL(ret, 0); + ret = kastore_close(&store); + CU_ASSERT_EQUAL_FATAL(ret, KAS_ERR_IO); + + _fclose_fail_at = -1; +} + +static void +test_append_fclose(void) +{ + kastore_t store; + int ret = 0; + int8_t array[] = {1}; + bool done; + size_t index = 0; + const char * keys[] = {"b", "c"}; + + ret = kastore_open(&store, _tmp_file_name, "w", 0); + CU_ASSERT_EQUAL_FATAL(ret, 0); + ret = kastore_puts_int8(&store, "a", array, 1, 0); + CU_ASSERT_EQUAL_FATAL(ret, 0); + ret = kastore_close(&store); + CU_ASSERT_EQUAL_FATAL(ret, 0); + + /* keep increasing fail_at until we pass. This should catch all possible + * places at which we can fail. */ + _fclose_fail_at = 0; + _fclose_count = 0; + done = false; + while (! done) { + ret = kastore_open(&store, _tmp_file_name, "a", 0); + CU_ASSERT_EQUAL_FATAL(ret, 0); + _fclose_count = 0; + CU_ASSERT_FATAL(index < sizeof(keys) / (sizeof(*keys))); + ret = kastore_puts_int8(&store, keys[index], array, 1, 0); + CU_ASSERT_EQUAL_FATAL(ret, 0); + ret = kastore_close(&store); + if (ret == 0) { + done = true; + } else { + CU_ASSERT_EQUAL_FATAL(ret, KAS_ERR_IO); + } + _fclose_fail_at++; + index++; + } + CU_ASSERT_TRUE(_fclose_fail_at > 1); + /* Make sure we reset this for other functions */ + _fclose_fail_at = -1; +} + +static void +test_open_read_fread(void) +{ + kastore_t store; + int ret = 0; + const char *filename = "test-data/v1/all_types_1_elements.kas"; + bool done; + size_t f; + int flags[] = {0, KAS_NO_MMAP}; + + _fread_fail_at = 0; + /* Make sure the failing fread setup works first */ + _fread_count = 0; + ret = kastore_open(&store, filename, "r", 0); + CU_ASSERT_EQUAL_FATAL(ret, KAS_ERR_BAD_FILE_FORMAT); + ret = kastore_close(&store); + CU_ASSERT_EQUAL_FATAL(ret, 0); + + /* If we set errno before calling, we should get an IO error back */ + _fread_count = 0; + errno = ENOENT; + ret = kastore_open(&store, filename, "r", 0); + CU_ASSERT_EQUAL_FATAL(ret, KAS_ERR_IO); + errno = 0; + ret = kastore_close(&store); + CU_ASSERT_EQUAL_FATAL(ret, 0); + + /* keep increasing fail_at until we pass. This should catch all possible + * places at which we can fail. */ + for (f = 0; f < sizeof(flags) / sizeof(*flags); f++) { + _fread_fail_at = 0; + done = false; + while (! done) { + _fread_count = 0; + ret = kastore_open(&store, filename, "r", flags[f]); + if (ret == 0) { + done = true; + } else { + CU_ASSERT_EQUAL_FATAL(ret, KAS_ERR_BAD_FILE_FORMAT); + } + ret = kastore_close(&store); + CU_ASSERT_EQUAL_FATAL(ret, 0); + _fread_fail_at++; + } + } + + /* Make sure we reset this for other functions */ + _fread_fail_at = -1; +} + +static void +test_open_read_fseek(void) +{ + kastore_t store; + int ret = 0; + const char *filename = "test-data/v1/all_types_1_elements.kas"; + + /* fseek is only called in the non mmap code path */ + _fseek_fail_at = 0; + _fseek_count = 0; + ret = kastore_open(&store, filename, "r", KAS_NO_MMAP); + CU_ASSERT_EQUAL_FATAL(ret, KAS_ERR_IO); + ret = kastore_close(&store); + CU_ASSERT_EQUAL_FATAL(ret, 0); + + _fseek_fail_at = -1; +} + +static void +test_open_read_mmap(void) +{ + kastore_t store; + int ret = 0; + const char *filename = "test-data/v1/all_types_1_elements.kas"; + + _mmap_fail_at = 0; + _mmap_count = 0; + ret = kastore_open(&store, filename, "r", 0); + CU_ASSERT_EQUAL_FATAL(ret, KAS_ERR_IO); + ret = kastore_close(&store); + CU_ASSERT_EQUAL_FATAL(ret, 0); + + _mmap_fail_at = -1; +} + +static void +test_open_read_stat(void) +{ + kastore_t store; + int ret = 0; + const char *filename = "test-data/v1/all_types_1_elements.kas"; + + _stat_fail_at = 0; + _stat_count = 0; + ret = kastore_open(&store, filename, "r", 0); + CU_ASSERT_EQUAL_FATAL(ret, KAS_ERR_IO); + ret = kastore_close(&store); + CU_ASSERT_EQUAL_FATAL(ret, 0); + + _stat_fail_at = -1; +} + +/*================================================= + Test suite management + ================================================= +*/ + +static int +kastore_suite_init(void) +{ + int fd; + static char template[] = "/tmp/kas_c_test_XXXXXX"; + + _tmp_file_name = NULL; + + _tmp_file_name = malloc(sizeof(template)); + if (_tmp_file_name == NULL) { + return CUE_NOMEMORY; + } + strcpy(_tmp_file_name, template); + fd = mkstemp(_tmp_file_name); + if (fd == -1) { + return CUE_SINIT_FAILED; + } + close(fd); + return CUE_SUCCESS; +} + +static int +kastore_suite_cleanup(void) +{ + if (_tmp_file_name != NULL) { + unlink(_tmp_file_name); + free(_tmp_file_name); + } + return CUE_SUCCESS; +} + +static void +handle_cunit_error(void) +{ + fprintf(stderr, "CUnit error occured: %d: %s\n", + CU_get_error(), CU_get_error_msg()); + exit(EXIT_FAILURE); +} + +int +main(int argc, char **argv) +{ + int ret; + CU_pTest test; + CU_pSuite suite; + CU_TestInfo tests[] = { + {"test_write_empty", test_write_empty}, + {"test_write", test_write}, + {"test_write_fclose", test_write_fclose}, + {"test_read_fclose", test_read_fclose}, + {"test_append_fclose", test_append_fclose}, + {"test_open_read_fread", test_open_read_fread}, + {"test_open_read_fseek", test_open_read_fseek}, + {"test_open_read_mmap", test_open_read_mmap}, + {"test_open_read_stat", test_open_read_stat}, + CU_TEST_INFO_NULL, + }; + + /* We use initialisers here as the struct definitions change between + * versions of CUnit */ + CU_SuiteInfo suites[] = { + { + .pName = "kastore", + .pInitFunc = kastore_suite_init, + .pCleanupFunc = kastore_suite_cleanup, + .pTests = tests + }, + CU_SUITE_INFO_NULL, + }; + if (CUE_SUCCESS != CU_initialize_registry()) { + handle_cunit_error(); + } + if (CUE_SUCCESS != CU_register_suites(suites)) { + handle_cunit_error(); + } + CU_basic_set_mode(CU_BRM_VERBOSE); + + if (argc == 1) { + CU_basic_run_tests(); + } else if (argc == 2) { + suite = CU_get_suite_by_name("kastore", CU_get_registry()); + if (suite == NULL) { + printf("Suite not found\n"); + return EXIT_FAILURE; + } + test = CU_get_test_by_name(argv[1], suite); + if (test == NULL) { + printf("Test '%s' not found\n", argv[1]); + return EXIT_FAILURE; + } + CU_basic_run_test(suite, test); + } else { + printf("usage: ./io_tests \n"); + return EXIT_FAILURE; + } + + ret = EXIT_SUCCESS; + if (CU_get_number_of_tests_failed() != 0) { + printf("Test failed!\n"); + ret = EXIT_FAILURE; + } + CU_cleanup_registry(); + return ret; +} diff --git a/c/kastore.c b/c/kastore.c index 054896e..d172d89 100644 --- a/c/kastore.c +++ b/c/kastore.c @@ -172,10 +172,9 @@ kastore_read_header(kastore_t *self) } /* Compute the locations of the keys and arrays in the file. */ -static int KAS_WARN_UNUSED +static void kastore_pack_items(kastore_t *self) { - int ret = 0; size_t j, offset, remainder; /* Pack the keys */ @@ -194,7 +193,6 @@ kastore_pack_items(kastore_t *self) offset += self->items[j].array_len * type_size(self->items[j].type); } self->file_size = offset; - return ret; } static int KAS_WARN_UNUSED @@ -405,10 +403,7 @@ kastore_write_file(kastore_t *self) int ret = 0; qsort(self->items, self->num_items, sizeof(kaitem_t), compare_items); - ret = kastore_pack_items(self); - if (ret != 0) { - goto out; - } + kastore_pack_items(self); ret = kastore_write_header(self); if (ret != 0) { goto out; @@ -739,7 +734,6 @@ kastore_put(kastore_t *self, const char *key, size_t key_len, } self->items = p; new_item = self->items + self->num_items; - self->num_items++; memset(new_item, 0, sizeof(*new_item)); new_item->type = type; @@ -749,9 +743,12 @@ kastore_put(kastore_t *self, const char *key, size_t key_len, new_item->key = malloc(key_len); new_item->array = malloc(array_size == 0? 1: array_size); if (new_item->key == NULL || new_item->array == NULL) { + kas_safe_free(new_item->key); + kas_safe_free(new_item->array); ret = KAS_ERR_NO_MEMORY; goto out; } + self->num_items++; memcpy(new_item->key, key, key_len); memcpy(new_item->array, array, array_size); diff --git a/c/malloc_tests.c b/c/malloc_tests.c new file mode 100644 index 0000000..37fda91 --- /dev/null +++ b/c/malloc_tests.c @@ -0,0 +1,323 @@ +/* These tests simulate failures in malloc by wrapping the original definitions + * of malloc using the linker and failing after a predefined number of calls */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include + +#include "kastore.h" +#include + +char * _tmp_file_name; + +/* Wrappers used to check for correct error handling. Must be linked with the + * link option, e.g., -Wl,--wrap=malloc. See 'ld' manpage for details. + */ + +/* Consider malloc and realloc as the same thing in terms of the counters */ +void *__wrap_malloc(size_t); +void *__real_malloc(size_t); +void *__wrap_realloc(void *, size_t); +void *__real_realloc(void *, size_t); +void *__wrap_calloc(size_t, size_t); +void *__real_calloc(size_t, size_t); + +int _malloc_fail_at = -1; +int _malloc_count = 0; + +void * +__wrap_malloc(size_t c) +{ + if (_malloc_count == _malloc_fail_at) { + return NULL; + } + _malloc_count++; + return __real_malloc(c); +} + +void * +__wrap_realloc(void *p, size_t c) +{ + if (_malloc_count == _malloc_fail_at) { + return NULL; + } + _malloc_count++; + return __real_realloc(p, c); +} + +void * +__wrap_calloc(size_t s, size_t c) +{ + if (_malloc_count == _malloc_fail_at) { + return NULL; + } + _malloc_count++; + return __real_calloc(s, c); +} + +static void +test_write(void) +{ + kastore_t store; + int ret = 0; + int32_t array[] = {1, 2, 3, 4}; + bool done; + + /* Make sure the failing malloc setup works first */ + _malloc_fail_at = 0; + _malloc_count = 0; + ret = kastore_open(&store, _tmp_file_name, "w", 0); + CU_ASSERT_EQUAL_FATAL(ret, 0); + ret = kastore_puts_int32(&store, "array", array, 4, 0); + CU_ASSERT_EQUAL_FATAL(ret, KAS_ERR_NO_MEMORY); + ret = kastore_close(&store); + CU_ASSERT_EQUAL_FATAL(ret, 0); + + /* keep increasing fail_at until we pass. This should catch all possible + * places at which we can fail. */ + done = false; + while (! done) { + ret = kastore_open(&store, _tmp_file_name, "w", 0); + CU_ASSERT_EQUAL_FATAL(ret, 0); + _malloc_count = 0; + _malloc_fail_at++; + ret = kastore_puts_int32(&store, "array", array, 4, 0); + if (ret == 0) { + done = true; + } else { + CU_ASSERT_EQUAL_FATAL(ret, KAS_ERR_NO_MEMORY); + } + ret = kastore_close(&store); + CU_ASSERT_EQUAL_FATAL(ret, 0); + } + /* Make sure we reset this for other functions */ + _malloc_fail_at = -1; +} + +static void +test_append(void) +{ + kastore_t store; + int ret = 0; + int32_t array[] = {1, 2, 3, 4}; + bool done; + + ret = kastore_open(&store, _tmp_file_name, "w", 0); + CU_ASSERT_EQUAL_FATAL(ret, 0); + ret = kastore_puts_int32(&store, "array", array, 4, 0); + CU_ASSERT_EQUAL_FATAL(ret, 0); + ret = kastore_close(&store); + CU_ASSERT_EQUAL_FATAL(ret, 0); + + /* keep increasing fail_at until we pass. This should catch all possible + * places at which we can fail. */ + _malloc_fail_at = 0; + done = false; + while (! done) { + _malloc_count = 0; + ret = kastore_open(&store, _tmp_file_name, "a", 0); + if (ret == 0) { + done = true; + } else { + CU_ASSERT_EQUAL_FATAL(ret, KAS_ERR_NO_MEMORY); + } + ret = kastore_close(&store); + CU_ASSERT_EQUAL_FATAL(ret, 0); + _malloc_fail_at++; + } + CU_ASSERT(_malloc_fail_at > 1); + /* Make sure we reset this for other functions */ + _malloc_fail_at = -1; +} + + +static void +test_open_read(void) +{ + kastore_t store; + int ret = 0; + const char *filename = "test-data/v1/all_types_1_elements.kas"; + bool done; + size_t f; + int flags[] = {0, KAS_NO_MMAP}; + + /* Make sure the failing malloc setup works first */ + _malloc_fail_at = 0; + _malloc_count = 0; + ret = kastore_open(&store, filename, "r", 0); + CU_ASSERT_EQUAL_FATAL(ret, KAS_ERR_NO_MEMORY); + ret = kastore_close(&store); + CU_ASSERT_EQUAL_FATAL(ret, 0); + + /* keep increasing fail_at until we pass. This should catch all possible + * places at which we can fail. */ + for (f = 0; f < sizeof(flags) / sizeof(*flags); f++) { + _malloc_fail_at = 0; + done = false; + while (! done) { + _malloc_count = 0; + ret = kastore_open(&store, filename, "r", flags[f]); + if (ret == 0) { + done = true; + } else { + CU_ASSERT_EQUAL_FATAL(ret, KAS_ERR_NO_MEMORY); + } + ret = kastore_close(&store); + CU_ASSERT_EQUAL_FATAL(ret, 0); + _malloc_fail_at++; + } + } + + /* Make sure we reset this for other functions */ + _malloc_fail_at = -1; +} + +static void +test_read(void) +{ + kastore_t store; + int ret = 0; + const char *filename = "test-data/v1/all_types_1_elements.kas"; + bool done; + int8_t *array; + size_t size; + + /* Make sure the failing malloc setup works first */ + _malloc_fail_at = 0; + _malloc_count = 0; + ret = kastore_open(&store, filename, "r", KAS_NO_MMAP); + CU_ASSERT_EQUAL_FATAL(ret, KAS_ERR_NO_MEMORY); + ret = kastore_close(&store); + CU_ASSERT_EQUAL_FATAL(ret, 0); + + /* keep increasing fail_at until we pass. This should catch all possible + * places at which we can fail. */ + _malloc_fail_at = -1; + ret = kastore_open(&store, filename, "r", 0); + CU_ASSERT_EQUAL_FATAL(ret, 0); + _malloc_fail_at = 0; + done = false; + while (! done) { + _malloc_count = 0; + ret = kastore_gets_int8(&store, "int8", &array, &size); + if (ret == 0) { + CU_ASSERT_EQUAL_FATAL(size, 1); + done = true; + } else { + CU_ASSERT_EQUAL_FATAL(ret, KAS_ERR_NO_MEMORY); + } + _malloc_fail_at++; + } + ret = kastore_close(&store); + CU_ASSERT_EQUAL_FATAL(ret, 0); + + /* Make sure we reset this for other functions */ + _malloc_fail_at = -1; +} + +/*================================================= + Test suite management + ================================================= +*/ + +static int +kastore_suite_init(void) +{ + int fd; + static char template[] = "/tmp/kas_c_test_XXXXXX"; + + _tmp_file_name = NULL; + + _tmp_file_name = malloc(sizeof(template)); + if (_tmp_file_name == NULL) { + return CUE_NOMEMORY; + } + strcpy(_tmp_file_name, template); + fd = mkstemp(_tmp_file_name); + if (fd == -1) { + return CUE_SINIT_FAILED; + } + close(fd); + return CUE_SUCCESS; +} + +static int +kastore_suite_cleanup(void) +{ + if (_tmp_file_name != NULL) { + unlink(_tmp_file_name); + free(_tmp_file_name); + } + return CUE_SUCCESS; +} + +static void +handle_cunit_error(void) +{ + fprintf(stderr, "CUnit error occured: %d: %s\n", + CU_get_error(), CU_get_error_msg()); + exit(EXIT_FAILURE); +} + +int +main(int argc, char **argv) +{ + int ret; + CU_pTest test; + CU_pSuite suite; + CU_TestInfo tests[] = { + {"test_write", test_write}, + {"test_append", test_append}, + {"test_open_read", test_open_read}, + {"test_read", test_read}, + CU_TEST_INFO_NULL, + }; + + /* We use initialisers here as the struct definitions change between + * versions of CUnit */ + CU_SuiteInfo suites[] = { + { + .pName = "kastore", + .pInitFunc = kastore_suite_init, + .pCleanupFunc = kastore_suite_cleanup, + .pTests = tests + }, + CU_SUITE_INFO_NULL, + }; + if (CUE_SUCCESS != CU_initialize_registry()) { + handle_cunit_error(); + } + if (CUE_SUCCESS != CU_register_suites(suites)) { + handle_cunit_error(); + } + CU_basic_set_mode(CU_BRM_VERBOSE); + + if (argc == 1) { + CU_basic_run_tests(); + } else if (argc == 2) { + suite = CU_get_suite_by_name("kastore", CU_get_registry()); + if (suite == NULL) { + printf("Suite not found\n"); + return EXIT_FAILURE; + } + test = CU_get_test_by_name(argv[1], suite); + if (test == NULL) { + printf("Test '%s' not found\n", argv[1]); + return EXIT_FAILURE; + } + CU_basic_run_test(suite, test); + } else { + printf("usage: ./malloc_tests \n"); + return EXIT_FAILURE; + } + + ret = EXIT_SUCCESS; + if (CU_get_number_of_tests_failed() != 0) { + printf("Test failed!\n"); + ret = EXIT_FAILURE; + } + CU_cleanup_registry(); + return ret; +} diff --git a/c/meson.build b/c/meson.build index 5a6fb1f..79029d9 100644 --- a/c/meson.build +++ b/c/meson.build @@ -5,6 +5,19 @@ add_global_arguments([ '-Wconversion', '-Wshadow', '-Wpointer-arith', '-Wcast-align', '-Wcast-qual', '-Wwrite-strings', '-Wnested-externs', '-fshort-enums', '-fno-common'], language : 'c') - cunit_dep = dependency('cunit') executable('tests', ['tests.c', 'kastore.c'], dependencies: cunit_dep) + +executable('malloc_tests', ['malloc_tests.c', 'kastore.c'], + dependencies: cunit_dep, + link_args:['-Wl,--wrap=malloc', '-Wl,--wrap=realloc', '-Wl,--wrap=calloc']) + +executable('io_tests', ['io_tests.c', 'kastore.c'], + dependencies: cunit_dep, + link_args:[ + '-Wl,--wrap=fwrite', + '-Wl,--wrap=fread', + '-Wl,--wrap=fclose', + '-Wl,--wrap=fseek', + '-Wl,--wrap=stat64', + '-Wl,--wrap=mmap64']) diff --git a/c/tests.c b/c/tests.c index ad94e80..6774011 100644 --- a/c/tests.c +++ b/c/tests.c @@ -3,6 +3,7 @@ #include #include #include +#include #include "kastore.h" @@ -1122,6 +1123,11 @@ test_all_types_n_elements(void) } } +/*================================================= + Test suite management + ================================================= +*/ + static int kastore_suite_init(void) {