/* * Copyright (c) 2018 Benjamin Marzinski, Redhat * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include "globals.c" #include "../libmultipath/checkers/directio.c" int test_fd = 111; int ioctx_count = 0; struct io_event mock_events[AIO_GROUP_SIZE]; /* same as the checker max */ int ev_off = 0; struct timespec zero_timeout = {0}; struct timespec full_timeout = { .tv_sec = -1 }; int __real_ioctl(int fd, unsigned long request, void *argp); int __wrap_ioctl(int fd, unsigned long request, void *argp) { #ifdef DIO_TEST_DEV mock_type(int); return __real_ioctl(fd, request, argp); #else int *blocksize = (int *)argp; assert_int_equal(fd, test_fd); assert_int_equal(request, BLKBSZGET); assert_non_null(blocksize); *blocksize = mock_type(int); return 0; #endif } int __real_fcntl(int fd, int cmd, long arg); int __wrap_fcntl(int fd, int cmd, long arg) { #ifdef DIO_TEST_DEV return __real_fcntl(fd, cmd, arg); #else assert_int_equal(fd, test_fd); assert_int_equal(cmd, F_GETFL); return O_DIRECT; #endif } int __real___fxstat(int ver, int fd, struct stat *statbuf); int __wrap___fxstat(int ver, int fd, struct stat *statbuf) { #ifdef DIO_TEST_DEV return __real___fxstat(ver, fd, statbuf); #else assert_int_equal(fd, test_fd); assert_non_null(statbuf); memset(statbuf, 0, sizeof(struct stat)); return 0; #endif } int __real_io_setup(int maxevents, io_context_t *ctxp); int __wrap_io_setup(int maxevents, io_context_t *ctxp) { ioctx_count++; #ifdef DIO_TEST_DEV int ret = mock_type(int); assert_int_equal(ret, __real_io_setup(maxevents, ctxp)); return ret; #else return mock_type(int); #endif } int __real_io_destroy(io_context_t ctx); int __wrap_io_destroy(io_context_t ctx) { ioctx_count--; #ifdef DIO_TEST_DEV int ret = mock_type(int); assert_int_equal(ret, __real_io_destroy(ctx)); return ret; #else return mock_type(int); #endif } int __real_io_submit(io_context_t ctx, long nr, struct iocb *ios[]); int __wrap_io_submit(io_context_t ctx, long nr, struct iocb *ios[]) { #ifdef DIO_TEST_DEV struct timespec dev_delay = { .tv_nsec = 100000 }; int ret = mock_type(int); assert_int_equal(ret, __real_io_submit(ctx, nr, ios)); nanosleep(&dev_delay, NULL); return ret; #else return mock_type(int); #endif } int __real_io_cancel(io_context_t ctx, struct iocb *iocb, struct io_event *evt); int __wrap_io_cancel(io_context_t ctx, struct iocb *iocb, struct io_event *evt) { #ifdef DIO_TEST_DEV mock_type(int); return __real_io_cancel(ctx, iocb, evt); #else return mock_type(int); #endif } int __real_io_getevents(io_context_t ctx, long min_nr, long nr, struct io_event *events, struct timespec *timeout); int __wrap_io_getevents(io_context_t ctx, long min_nr, long nr, struct io_event *events, struct timespec *timeout) { int nr_evs; #ifndef DIO_TEST_DEV struct timespec *sleep_tmo; int i; struct io_event *evs; #endif assert_non_null(timeout); nr_evs = mock_type(int); assert_true(nr_evs <= nr); if (!nr_evs) return 0; #ifdef DIO_TEST_DEV mock_ptr_type(struct timespec *); mock_ptr_type(struct io_event *); assert_int_equal(nr_evs, __real_io_getevents(ctx, min_nr, nr_evs, events, timeout)); #else sleep_tmo = mock_ptr_type(struct timespec *); if (sleep_tmo) { if (sleep_tmo->tv_sec < 0) nanosleep(timeout, NULL); else nanosleep(sleep_tmo, NULL); } if (nr_evs < 0) { errno = -nr_evs; return -1; } evs = mock_ptr_type(struct io_event *); for (i = 0; i < nr_evs; i++) events[i] = evs[i]; #endif ev_off -= nr_evs; return nr_evs; } static void return_io_getevents_none(void) { will_return(__wrap_io_getevents, 0); } static void return_io_getevents_nr(struct timespec *ts, int nr, struct async_req **reqs, int *res) { int i, off = 0; for(i = 0; i < nr; i++) { mock_events[i + ev_off].obj = &reqs[i]->io; if (res[i] == 0) mock_events[i + ev_off].res = reqs[i]->blksize; } while (nr > 0) { will_return(__wrap_io_getevents, (nr > 128)? 128 : nr); will_return(__wrap_io_getevents, ts); will_return(__wrap_io_getevents, &mock_events[off + ev_off]); ts = NULL; off += 128; nr -= 128; } if (nr == 0) will_return(__wrap_io_getevents, 0); ev_off += i; } void do_check_state(struct checker *c, int sync, int timeout, int chk_state) { struct directio_context * ct = (struct directio_context *)c->context; if (!ct->running) will_return(__wrap_io_submit, 1); assert_int_equal(check_state(test_fd, ct, sync, timeout), chk_state); assert_int_equal(ev_off, 0); memset(mock_events, 0, sizeof(mock_events)); } void do_libcheck_reset(int nr_aio_grps) { int count = 0; struct aio_group *aio_grp; list_for_each_entry(aio_grp, &aio_grp_list, node) count++; assert_int_equal(count, nr_aio_grps); for (count = 0; count < nr_aio_grps; count++) will_return(__wrap_io_destroy, 0); libcheck_reset(); assert_true(list_empty(&aio_grp_list)); assert_int_equal(ioctx_count, 0); } static void do_libcheck_init(struct checker *c, int blocksize, struct async_req **req) { struct directio_context * ct; c->fd = test_fd; will_return(__wrap_ioctl, blocksize); assert_int_equal(libcheck_init(c), 0); ct = (struct directio_context *)c->context; assert_non_null(ct); assert_non_null(ct->aio_grp); assert_non_null(ct->req); if (req) *req = ct->req; #ifndef DIO_TEST_DEV /* don't check fake blocksize on real devices */ assert_int_equal(ct->req->blksize, blocksize); #endif } static int is_checker_running(struct checker *c) { struct directio_context * ct = (struct directio_context *)c->context; return ct->running; } static struct aio_group *get_aio_grp(struct checker *c) { struct directio_context * ct = (struct directio_context *)c->context; assert_non_null(ct); return ct->aio_grp; } static void check_aio_grp(struct aio_group *aio_grp, int holders, int orphans) { int count = 0; struct list_head *item; list_for_each(item, &aio_grp->orphans) count++; assert_int_equal(holders, aio_grp->holders); assert_int_equal(orphans, count); } /* simple resetting test */ static void test_reset(void **state) { assert_true(list_empty(&aio_grp_list)); do_libcheck_reset(0); } /* tests initializing, then resetting, and then initializing again */ static void test_init_reset_init(void **state) { struct checker c = {0}; struct aio_group *aio_grp, *tmp_grp; assert_true(list_empty(&aio_grp_list)); will_return(__wrap_io_setup, 0); do_libcheck_init(&c, 4096, NULL); aio_grp = get_aio_grp(&c); check_aio_grp(aio_grp, 1, 0); list_for_each_entry(tmp_grp, &aio_grp_list, node) assert_ptr_equal(aio_grp, tmp_grp); libcheck_free(&c); check_aio_grp(aio_grp, 0, 0); do_libcheck_reset(1); will_return(__wrap_io_setup, 0); do_libcheck_init(&c, 4096, NULL); aio_grp = get_aio_grp(&c); check_aio_grp(aio_grp, 1, 0); list_for_each_entry(tmp_grp, &aio_grp_list, node) assert_ptr_equal(aio_grp, tmp_grp); libcheck_free(&c); check_aio_grp(aio_grp, 0, 0); do_libcheck_reset(1); } /* test initializing and then freeing 4096 checkers */ static void test_init_free(void **state) { int i, count = 0; struct checker c[4096] = {0}; struct aio_group *aio_grp; assert_true(list_empty(&aio_grp_list)); will_return(__wrap_io_setup, 0); will_return(__wrap_io_setup, 0); will_return(__wrap_io_setup, 0); will_return(__wrap_io_setup, 0); for (i = 0; i < 4096; i++) { struct directio_context * ct; if (i % 3 == 0) do_libcheck_init(&c[i], 512, NULL); else if (i % 3 == 1) do_libcheck_init(&c[i], 1024, NULL); else do_libcheck_init(&c[i], 4096, NULL); ct = (struct directio_context *)c[i].context; assert_non_null(ct->aio_grp); if ((i & 1023) == 0) aio_grp = ct->aio_grp; else { assert_ptr_equal(ct->aio_grp, aio_grp); assert_int_equal(aio_grp->holders, (i & 1023) + 1); } } count = 0; list_for_each_entry(aio_grp, &aio_grp_list, node) count++; assert_int_equal(count, 4); for (i = 0; i < 4096; i++) { struct directio_context * ct = (struct directio_context *)c[i].context; aio_grp = ct->aio_grp; libcheck_free(&c[i]); assert_int_equal(aio_grp->holders, 1023 - (i & 1023)); } list_for_each_entry(aio_grp, &aio_grp_list, node) assert_int_equal(aio_grp->holders, 0); do_libcheck_reset(4); } /* check mixed initializing and freeing 4096 checkers */ static void test_multi_init_free(void **state) { int i, count; struct checker c[4096] = {0}; struct aio_group *aio_grp; assert_true(list_empty(&aio_grp_list)); will_return(__wrap_io_setup, 0); will_return(__wrap_io_setup, 0); will_return(__wrap_io_setup, 0); will_return(__wrap_io_setup, 0); for (count = 0, i = 0; i < 4096; count++) { /* usually init, but occasionally free checkers */ if (count == 0 || (count % 5 != 0 && count % 7 != 0)) { do_libcheck_init(&c[i], 4096, NULL); i++; } else { i--; libcheck_free(&c[i]); } } count = 0; list_for_each_entry(aio_grp, &aio_grp_list, node) { assert_int_equal(aio_grp->holders, 1024); count++; } assert_int_equal(count, 4); for (count = 0, i = 4096; i > 0; count++) { /* usually free, but occasionally init checkers */ if (count == 0 || (count % 5 != 0 && count % 7 != 0)) { i--; libcheck_free(&c[i]); } else { do_libcheck_init(&c[i], 4096, NULL); i++; } } do_libcheck_reset(4); } /* simple single checker sync test */ static void test_check_state_simple(void **state) { struct checker c = {0}; struct async_req *req; int res = 0; assert_true(list_empty(&aio_grp_list)); will_return(__wrap_io_setup, 0); do_libcheck_init(&c, 4096, &req); return_io_getevents_nr(NULL, 1, &req, &res); do_check_state(&c, 1, 30, PATH_UP); libcheck_free(&c); do_libcheck_reset(1); } /* test sync timeout */ static void test_check_state_timeout(void **state) { struct checker c = {0}; struct aio_group *aio_grp; assert_true(list_empty(&aio_grp_list)); will_return(__wrap_io_setup, 0); do_libcheck_init(&c, 4096, NULL); aio_grp = get_aio_grp(&c); return_io_getevents_none(); will_return(__wrap_io_cancel, 0); do_check_state(&c, 1, 30, PATH_DOWN); check_aio_grp(aio_grp, 1, 0); #ifdef DIO_TEST_DEV /* io_cancel will return negative value on timeout, so it happens again * when freeing the checker */ will_return(__wrap_io_cancel, 0); #endif libcheck_free(&c); do_libcheck_reset(1); } /* test async timeout */ static void test_check_state_async_timeout(void **state) { struct checker c = {0}; struct aio_group *aio_grp; assert_true(list_empty(&aio_grp_list)); will_return(__wrap_io_setup, 0); do_libcheck_init(&c, 4096, NULL); aio_grp = get_aio_grp(&c); return_io_getevents_none(); do_check_state(&c, 0, 3, PATH_PENDING); return_io_getevents_none(); do_check_state(&c, 0, 3, PATH_PENDING); return_io_getevents_none(); do_check_state(&c, 0, 3, PATH_PENDING); return_io_getevents_none(); will_return(__wrap_io_cancel, 0); do_check_state(&c, 0, 3, PATH_DOWN); check_aio_grp(aio_grp, 1, 0); #ifdef DIO_TEST_DEV will_return(__wrap_io_cancel, 0); #endif libcheck_free(&c); do_libcheck_reset(1); } /* test freeing checkers with outstanding requests */ static void test_free_with_pending(void **state) { struct checker c[2] = {0}; struct aio_group *aio_grp; struct async_req *req; int res = 0; assert_true(list_empty(&aio_grp_list)); will_return(__wrap_io_setup, 0); do_libcheck_init(&c[0], 4096, &req); do_libcheck_init(&c[1], 4096, NULL); aio_grp = get_aio_grp(c); return_io_getevents_none(); do_check_state(&c[0], 0, 30, PATH_PENDING); return_io_getevents_nr(NULL, 1, &req, &res); return_io_getevents_none(); do_check_state(&c[1], 0, 30, PATH_PENDING); assert_true(is_checker_running(&c[0])); assert_true(is_checker_running(&c[1])); check_aio_grp(aio_grp, 2, 0); libcheck_free(&c[0]); check_aio_grp(aio_grp, 1, 0); will_return(__wrap_io_cancel, 0); libcheck_free(&c[1]); #ifdef DIO_TEST_DEV check_aio_grp(aio_grp, 1, 1); /* real cancel doesn't remove request */ #else check_aio_grp(aio_grp, 0, 0); #endif do_libcheck_reset(1); } /* test removing orpahed aio_group on free */ static void test_orphaned_aio_group(void **state) { struct checker c[AIO_GROUP_SIZE] = {0}; struct aio_group *aio_grp, *tmp_grp; int i; assert_true(list_empty(&aio_grp_list)); will_return(__wrap_io_setup, 0); for (i = 0; i < AIO_GROUP_SIZE; i++) { do_libcheck_init(&c[i], 4096, NULL); return_io_getevents_none(); do_check_state(&c[i], 0, 30, PATH_PENDING); } aio_grp = get_aio_grp(c); check_aio_grp(aio_grp, AIO_GROUP_SIZE, 0); i = 0; list_for_each_entry(tmp_grp, &aio_grp_list, node) i++; assert_int_equal(i, 1); for (i = 0; i < AIO_GROUP_SIZE; i++) { assert_true(is_checker_running(&c[i])); will_return(__wrap_io_cancel, -1); if (i == AIO_GROUP_SIZE - 1) { /* remove the orphaned group and create a new one */ will_return(__wrap_io_destroy, 0); } libcheck_free(&c[i]); } do_libcheck_reset(0); } /* test sync timeout with failed cancel and cleanup by another * checker */ static void test_timeout_cancel_failed(void **state) { struct checker c[2] = {0}; struct aio_group *aio_grp; struct async_req *reqs[2]; int res[] = {0,0}; int i; assert_true(list_empty(&aio_grp_list)); will_return(__wrap_io_setup, 0); for (i = 0; i < 2; i++) do_libcheck_init(&c[i], 4096, &reqs[i]); aio_grp = get_aio_grp(c); return_io_getevents_none(); will_return(__wrap_io_cancel, -1); do_check_state(&c[0], 1, 30, PATH_DOWN); assert_true(is_checker_running(&c[0])); check_aio_grp(aio_grp, 2, 0); return_io_getevents_none(); will_return(__wrap_io_cancel, -1); do_check_state(&c[0], 1, 30, PATH_DOWN); assert_true(is_checker_running(&c[0])); return_io_getevents_nr(NULL, 1, &reqs[0], &res[0]); return_io_getevents_nr(NULL, 1, &reqs[1], &res[1]); do_check_state(&c[1], 1, 30, PATH_UP); do_check_state(&c[0], 1, 30, PATH_UP); for (i = 0; i < 2; i++) { assert_false(is_checker_running(&c[i])); libcheck_free(&c[i]); } do_libcheck_reset(1); } /* test async timeout with failed cancel and cleanup by another * checker */ static void test_async_timeout_cancel_failed(void **state) { struct checker c[2] = {0}; struct async_req *reqs[2]; int res[] = {0,0}; int i; assert_true(list_empty(&aio_grp_list)); will_return(__wrap_io_setup, 0); for (i = 0; i < 2; i++) do_libcheck_init(&c[i], 4096, &reqs[i]); return_io_getevents_none(); do_check_state(&c[0], 0, 2, PATH_PENDING); return_io_getevents_none(); do_check_state(&c[1], 0, 2, PATH_PENDING); return_io_getevents_none(); do_check_state(&c[0], 0, 2, PATH_PENDING); return_io_getevents_none(); do_check_state(&c[1], 0, 2, PATH_PENDING); return_io_getevents_none(); will_return(__wrap_io_cancel, -1); do_check_state(&c[0], 0, 2, PATH_DOWN); #ifndef DIO_TEST_DEV /* can't pick which even gets returned on real devices */ return_io_getevents_nr(NULL, 1, &reqs[1], &res[1]); do_check_state(&c[1], 0, 2, PATH_UP); #endif return_io_getevents_none(); will_return(__wrap_io_cancel, -1); do_check_state(&c[0], 0, 2, PATH_DOWN); assert_true(is_checker_running(&c[0])); return_io_getevents_nr(NULL, 2, reqs, res); do_check_state(&c[1], 0, 2, PATH_UP); do_check_state(&c[0], 0, 2, PATH_UP); for (i = 0; i < 2; i++) { assert_false(is_checker_running(&c[i])); libcheck_free(&c[i]); } do_libcheck_reset(1); } /* test orphaning a request, and having another checker clean it up */ static void test_orphan_checker_cleanup(void **state) { struct checker c[2] = {0}; struct async_req *reqs[2]; int res[] = {0,0}; struct aio_group *aio_grp; int i; assert_true(list_empty(&aio_grp_list)); will_return(__wrap_io_setup, 0); for (i = 0; i < 2; i++) do_libcheck_init(&c[i], 4096, &reqs[i]); aio_grp = get_aio_grp(c); return_io_getevents_none(); do_check_state(&c[0], 0, 30, PATH_PENDING); will_return(__wrap_io_cancel, -1); check_aio_grp(aio_grp, 2, 0); libcheck_free(&c[0]); check_aio_grp(aio_grp, 2, 1); return_io_getevents_nr(NULL, 2, reqs, res); do_check_state(&c[1], 0, 2, PATH_UP); check_aio_grp(aio_grp, 1, 0); libcheck_free(&c[1]); check_aio_grp(aio_grp, 0, 0); do_libcheck_reset(1); } /* test orphaning a request, and having reset clean it up */ static void test_orphan_reset_cleanup(void **state) { struct checker c; struct aio_group *orphan_aio_grp, *tmp_aio_grp; int found, count; assert_true(list_empty(&aio_grp_list)); will_return(__wrap_io_setup, 0); do_libcheck_init(&c, 4096, NULL); orphan_aio_grp = get_aio_grp(&c); return_io_getevents_none(); do_check_state(&c, 0, 30, PATH_PENDING); will_return(__wrap_io_cancel, -1); check_aio_grp(orphan_aio_grp, 1, 0); libcheck_free(&c); check_aio_grp(orphan_aio_grp, 1, 1); found = count = 0; list_for_each_entry(tmp_aio_grp, &aio_grp_list, node) { count++; if (tmp_aio_grp == orphan_aio_grp) found = 1; } assert_int_equal(count, 1); assert_int_equal(found, 1); do_libcheck_reset(1); } /* test checkers with different blocksizes */ static void test_check_state_blksize(void **state) { int i; struct checker c[3] = {0}; int blksize[] = {4096, 1024, 512}; struct async_req *reqs[3]; int res[] = {0,1,0}; #ifdef DIO_TEST_DEV /* can't pick event return state on real devices */ int chk_state[] = {PATH_UP, PATH_UP, PATH_UP}; #else int chk_state[] = {PATH_UP, PATH_DOWN, PATH_UP}; #endif assert_true(list_empty(&aio_grp_list)); will_return(__wrap_io_setup, 0); for (i = 0; i < 3; i++) do_libcheck_init(&c[i], blksize[i], &reqs[i]); for (i = 0; i < 3; i++) { return_io_getevents_nr(NULL, 1, &reqs[i], &res[i]); do_check_state(&c[i], 1, 30, chk_state[i]); } for (i = 0; i < 3; i++) { assert_false(is_checker_running(&c[i])); libcheck_free(&c[i]); } do_libcheck_reset(1); } /* test async checkers pending and getting resovled by another checker * as well as the loops for getting multiple events */ static void test_check_state_async(void **state) { int i; struct checker c[257] = {0}; struct async_req *reqs[257]; int res[257] = {0}; assert_true(list_empty(&aio_grp_list)); will_return(__wrap_io_setup, 0); for (i = 0; i < 257; i++) do_libcheck_init(&c[i], 4096, &reqs[i]); for (i = 0; i < 256; i++) { return_io_getevents_none(); do_check_state(&c[i], 0, 30, PATH_PENDING); assert_true(is_checker_running(&c[i])); } return_io_getevents_nr(&full_timeout, 256, reqs, res); return_io_getevents_nr(NULL, 1, &reqs[256], &res[256]); do_check_state(&c[256], 0, 30, PATH_UP); assert_false(is_checker_running(&c[256])); libcheck_free(&c[256]); for (i = 0; i < 256; i++) { do_check_state(&c[i], 0, 30, PATH_UP); assert_false(is_checker_running(&c[i])); libcheck_free(&c[i]); } do_libcheck_reset(1); } static int setup(void **state) { #ifdef DIO_TEST_DEV test_fd = open(DIO_TEST_DEV, O_RDONLY); if (test_fd < 0) fail_msg("cannot open %s: %m", DIO_TEST_DEV); #endif return 0; } static int teardown(void **state) { #ifdef DIO_TEST_DEV assert_true(test_fd > 0); assert_int_equal(close(test_fd), 0); #endif return 0; } int test_directio(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_reset), cmocka_unit_test(test_init_reset_init), cmocka_unit_test(test_init_free), cmocka_unit_test(test_multi_init_free), cmocka_unit_test(test_check_state_simple), cmocka_unit_test(test_check_state_timeout), cmocka_unit_test(test_check_state_async_timeout), cmocka_unit_test(test_free_with_pending), cmocka_unit_test(test_timeout_cancel_failed), cmocka_unit_test(test_async_timeout_cancel_failed), cmocka_unit_test(test_orphan_checker_cleanup), cmocka_unit_test(test_orphan_reset_cleanup), cmocka_unit_test(test_check_state_blksize), cmocka_unit_test(test_check_state_async), cmocka_unit_test(test_orphaned_aio_group), }; return cmocka_run_group_tests(tests, setup, teardown); } int main(void) { int ret = 0; conf.verbosity = 2; ret += test_directio(); return ret; }