diff options
Diffstat (limited to 'src/lib/test-file-cache.c')
-rw-r--r-- | src/lib/test-file-cache.c | 293 |
1 files changed, 293 insertions, 0 deletions
diff --git a/src/lib/test-file-cache.c b/src/lib/test-file-cache.c new file mode 100644 index 0000000..6bac9ab --- /dev/null +++ b/src/lib/test-file-cache.c @@ -0,0 +1,293 @@ +/* Copyright (c) 2020 Dovecot authors, see the included COPYING file */ + +#include "test-lib.h" +#include "istream.h" +#include "ostream.h" +#include "file-cache.h" + +#include <sys/resource.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/mman.h> + +#define TEST_FILENAME ".test_file_cache" + +static void test_file_cache_read(void) +{ + test_begin("file_cache_read"); + + /* create a file */ + struct ostream *os = o_stream_create_file(TEST_FILENAME, 0, 0600, 0); + o_stream_nsend_str(os, "initial data\n"); + test_assert(o_stream_finish(os) == 1); + o_stream_destroy(&os); + + int fd = open(TEST_FILENAME, O_RDONLY); + i_assert(fd > -1); + struct file_cache *cache = file_cache_new_path(fd, TEST_FILENAME); + + /* this should be 0 before read */ + size_t size; + const unsigned char *map = file_cache_get_map(cache, &size); + test_assert(map == NULL); + test_assert(size == 0); + test_assert(map == NULL); + + test_assert(file_cache_read(cache, 0, 13) == 13); + map = file_cache_get_map(cache, &size); + test_assert(map != NULL && size == 13 && memcmp(map, "initial data\n", 13) == 0); + + file_cache_free(&cache); + i_close_fd(&fd); + i_unlink(TEST_FILENAME); + + test_end(); +} + +static void test_file_cache_write_read(void) +{ + test_begin("file_cache_write_read"); + + /* create a file */ + struct ostream *os = o_stream_create_file(TEST_FILENAME, 0, 0600, 0); + o_stream_nsend_str(os, "initial data\n"); + test_assert(o_stream_finish(os) == 1); + o_stream_destroy(&os); + + int fd = open(TEST_FILENAME, O_RDONLY); + i_assert(fd > -1); + struct file_cache *cache = file_cache_new_path(fd, TEST_FILENAME); + + /* this should be 0 before read */ + size_t size; + const unsigned char *map = file_cache_get_map(cache, &size); + test_assert(map == NULL); + test_assert(size == 0); + test_assert(map == NULL); + test_assert(file_cache_read(cache, 0, 13) == 13); + file_cache_write(cache, "updated data\n", 13, 0); + map = file_cache_get_map(cache, &size); + test_assert(map != NULL && size == 13 && memcmp(map, "updated data\n", 13) == 0); + file_cache_free(&cache); + i_close_fd(&fd); + + struct istream *is = i_stream_create_file(TEST_FILENAME, SIZE_MAX); + const unsigned char *data; + test_assert(i_stream_read_more(is, &data, &size) > 0 && size == 13); + test_assert(map != NULL && size == 13 && memcmp(data, "initial data\n", 13) == 0); + i_stream_destroy(&is); + i_unlink(TEST_FILENAME); + + test_end(); +} + +static void test_file_cache_read_invalidate(void) +{ + test_begin("file_cache_read_invalidate"); + + /* create a file */ + struct ostream *os = o_stream_create_file(TEST_FILENAME, 0, 0600, 0); + o_stream_nsend_str(os, "initial data\n"); + test_assert(o_stream_finish(os) == 1); + o_stream_destroy(&os); + + int fd = open(TEST_FILENAME, O_RDONLY); + i_assert(fd > -1); + struct file_cache *cache = file_cache_new_path(fd, TEST_FILENAME); + + /* this should be 0 before read */ + size_t size; + test_assert(file_cache_read(cache, 0, 13) == 13); + const unsigned char *map = file_cache_get_map(cache, &size); + test_assert(map != NULL && size == 13 && memcmp(map, "initial data\n", 13) == 0); + + /* update file */ + os = o_stream_create_file(TEST_FILENAME, 0, 0600, 0); + o_stream_nsend_str(os, "updated data\n"); + test_assert(o_stream_finish(os) == 1); + o_stream_destroy(&os); + + map = file_cache_get_map(cache, &size); + test_assert(map != NULL && size == 13 && memcmp(map, "initial data\n", 13) == 0); + + /* invalidate cache */ + file_cache_invalidate(cache, 0, size); + test_assert(file_cache_read(cache, 0, 13) == 13); + map = file_cache_get_map(cache, &size); + test_assert(size == 13); + test_assert(map != NULL && size == 13 && memcmp(map, "updated data\n", 13) == 0); + file_cache_free(&cache); + i_close_fd(&fd); + i_unlink(TEST_FILENAME); + + test_end(); +} + +static void test_file_cache_multipage(void) +{ + test_begin("file_cache_multipage"); + + size_t page_size = getpagesize(); + struct ostream *os = o_stream_create_file(TEST_FILENAME, 0, 0600, 0); + size_t total_size = 0; + for (size_t i = 0; i < page_size * 3 + 100; i += 12) { + o_stream_nsend_str(os, "initial data"); + total_size += 12; + } + test_assert(o_stream_finish(os) == 1); + o_stream_destroy(&os); + + int fd = open(TEST_FILENAME, O_RDONLY); + i_assert(fd > -1); + struct file_cache *cache = file_cache_new_path(fd, TEST_FILENAME); + + /* read everything to memory page at a time */ + test_assert(file_cache_read(cache, 0, page_size) == (ssize_t)page_size); + test_assert(file_cache_read(cache, page_size, page_size) == + (ssize_t)page_size); + test_assert(file_cache_read(cache, page_size*2, page_size) == + (ssize_t)page_size); + test_assert(file_cache_read(cache, page_size*3, page_size) == + (ssize_t)total_size-(ssize_t)page_size*3); + + size_t size; + const unsigned char *map = file_cache_get_map(cache, &size); + test_assert(size == total_size); + test_assert(map != NULL); + + /* write-read-invalidate-read */ + for(size_t i = 0; i < page_size * 3; i+= page_size / 3) { + char orig[13]; + const char *ptr = CONST_PTR_OFFSET(map, i); + memcpy(orig, ptr, 12); + orig[12] = '\0'; + file_cache_write(cache, "updated data", 12, i); + map = file_cache_get_map(cache, &size); + ptr = CONST_PTR_OFFSET(map, i); + test_assert(strncmp(ptr, "updated data", 12) == 0); + /* invalidate cache */ + file_cache_invalidate(cache, i, 12); + /* check that it's back what it was */ + test_assert(file_cache_read(cache, i, 12) == 12); + map = file_cache_get_map(cache, &size); + ptr = CONST_PTR_OFFSET(map, i); + test_assert(strncmp(ptr, orig, 12) == 0); + } + + file_cache_free(&cache); + i_close_fd(&fd); + i_unlink(TEST_FILENAME); + test_end(); +} + +static void test_file_cache_anon(void) +{ + /* file-cache should work as anonymous cache for small files */ + test_begin("file_cache_anon"); + test_assert(access(TEST_FILENAME, F_OK) == -1 && errno == ENOENT); + struct file_cache *cache = file_cache_new_path(-1, TEST_FILENAME); + + test_assert(file_cache_set_size(cache, 1024) == 0); + file_cache_write(cache, "initial data", 12, 0); + + size_t size; + const unsigned char *map = file_cache_get_map(cache, &size); + test_assert(map != NULL && size == 12 && memcmp(map, "initial data", 12) == 0); + + file_cache_free(&cache); + i_unlink_if_exists(TEST_FILENAME); + test_end(); +} + +static void test_file_cache_switch_fd(void) +{ + test_begin("file_cache_switch_fd"); + test_assert(access(TEST_FILENAME, F_OK) == -1 && errno == ENOENT); + struct file_cache *cache = file_cache_new_path(-1, TEST_FILENAME); + + test_assert(file_cache_set_size(cache, 13) == 0); + file_cache_write(cache, "initial data\n", 13, 0); + + /* create a file */ + struct ostream *os = o_stream_create_file(TEST_FILENAME, 0, 0600, 0); + o_stream_nsend_str(os, "updated data\n"); + test_assert(o_stream_finish(os) == 1); + o_stream_destroy(&os); + + int fd = open(TEST_FILENAME, O_RDONLY); + i_assert(fd > -1); + /* map should be invalidated and updated data read + from given file */ + file_cache_set_fd(cache, fd); + test_assert(file_cache_read(cache, 0, 13) == 13); + size_t size; + const unsigned char *map = file_cache_get_map(cache, &size); + test_assert(map != NULL && size == 13 && memcmp(map, "updated data\n", 13) == 0); + + file_cache_free(&cache); + i_close_fd(&fd); + i_unlink(TEST_FILENAME); + test_end(); +} + +static void test_file_cache_errors(void) +{ + test_begin("file_cache_errors"); + + size_t page_size = getpagesize(); + + test_assert(access(TEST_FILENAME, F_OK) == -1 && errno == ENOENT); + int fd = open(TEST_FILENAME, O_RDONLY); + struct file_cache *cache = file_cache_new_path(fd, TEST_FILENAME); + size_t size; + + /* file does not exist and we try large enough mapping */ + test_expect_error_string("fstat(.test_file_cache) failed: " + "Bad file descriptor"); + test_assert(file_cache_read(cache, 0, 2*1024*1024) == -1); + const unsigned char *map = file_cache_get_map(cache, &size); + test_assert(size == 0); + test_assert(map == NULL); + + /* temporarily set a small memory limit to make mmap attempt fail */ + struct rlimit rl_cur; + test_assert(getrlimit(RLIMIT_AS, &rl_cur) == 0); + struct rlimit rl_new = { + .rlim_cur = 1, + .rlim_max = rl_cur.rlim_max + }; + const char *errstr = + t_strdup_printf("mmap_anon(.test_file_cache, %zu) failed: " + "Cannot allocate memory", page_size); + test_assert(setrlimit(RLIMIT_AS, &rl_new) == 0); + test_expect_error_string(errstr); + test_assert(file_cache_set_size(cache, 1024) == -1); + test_assert(setrlimit(RLIMIT_AS, &rl_cur) == 0); + + /* same for mremap */ + errstr = t_strdup_printf("mremap_anon(.test_file_cache, %zu) failed: " + "Cannot allocate memory", page_size*2); + test_assert(file_cache_set_size(cache, 1) == 0); + test_assert(setrlimit(RLIMIT_AS, &rl_new) == 0); + test_expect_error_string(errstr); + test_assert(file_cache_set_size(cache, page_size*2) == -1); + test_assert(setrlimit(RLIMIT_AS, &rl_cur) == 0); + + file_cache_free(&cache); + i_close_fd(&fd); + i_unlink_if_exists(TEST_FILENAME); + test_end(); +} + +void test_file_cache(void) +{ + test_file_cache_read(); + test_file_cache_write_read(); + test_file_cache_read_invalidate(); + test_file_cache_multipage(); + test_file_cache_anon(); + test_file_cache_switch_fd(); + test_file_cache_errors(); +} |