diff --git a/.gitignore b/.gitignore index 2fdfceb..47db832 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ *.trs *.dirstamp *.gcno +*.gcda test/unit/core test/unit/uv test/fuzzy/core diff --git a/Makefile.am b/Makefile.am index 40af152..7674215 100644 --- a/Makefile.am +++ b/Makefile.am @@ -55,6 +55,7 @@ libtest_la_SOURCES = \ test/lib/fsm.c \ test/lib/heap.c \ test/lib/munit.c \ + test/lib/munit_mock.c \ test/lib/tracer.c \ test/lib/tcp.c @@ -74,12 +75,13 @@ test_unit_core_SOURCES = \ test/unit/test_log.c \ test/unit/test_queue.c \ test/unit/test_request.c -test_unit_core_CFLAGS = $(AM_CFLAGS) -Wno-conversion +test_unit_core_CFLAGS = $(AM_CFLAGS) -Wno-conversion -std=gnu11 test_unit_core_LDADD = libtest.la ./.libs/libraft.a if LZ4_AVAILABLE test_unit_core_CFLAGS += -DLZ4_AVAILABLE test_unit_core_LDFLAGS = $(LZ4_LIBS) +test_unit_core_LDFLAGS += -Wl,--wrap=LZ4F_createCompressionContext,--wrap=raft_malloc,--wrap=LZ4F_compressBegin libraft_la_CFLAGS += -DLZ4_AVAILABLE libraft_la_LDFLAGS += $(LZ4_LIBS) endif # LZ4_AVAILABLE @@ -121,11 +123,14 @@ test_integration_core_SOURCES = \ test/integration/test_paper.c \ test/integration/test_etcd_migrate_1.c \ test/integration/test_etcd_migrate_2.c \ - test/integration/test_etcd_migrate_3.c + test/integration/test_etcd_migrate_3.c \ + test/integration/mocks.c -test_integration_core_CFLAGS = $(AM_CFLAGS) -Wno-conversion +test_integration_core_CFLAGS = $(AM_CFLAGS) -Wno-conversion -std=gnu11 test_integration_core_LDFLAGS = -no-install test_integration_core_LDADD = libtest.la ./.libs/libraft.a +test_integration_core_LDFLAGS += -Wl,--wrap=logAppendCommands,--wrap=requestRegEnqueue,--wrap=replicationTrigger +test_integration_core_LDFLAGS += -Wl,--wrap=logAppend,--wrap=configurationCopy,--wrap=logAcquire test_fuzzy_core_SOURCES = \ test/fuzzy/main_core.c \ diff --git a/src/replication.c b/src/replication.c index 49c7b6a..8d4fe10 100644 --- a/src/replication.c +++ b/src/replication.c @@ -633,13 +633,14 @@ static int appendLeader(struct raft *r, raft_index index) request->n = n; request->req.data = request; + r->nr_appending_requests += 1; rv = r->io->append(r->io, &request->req, entries, n, appendLeaderCb); if (rv != 0) { + r->nr_appending_requests -= 1; evtErrf("raft(%llx) append failed %d", r->id, rv); ErrMsgTransfer(r->io->errmsg, r->errmsg, "io"); goto err_after_request_alloc; } - r->nr_appending_requests += 1; return 0; @@ -662,6 +663,9 @@ int replicationTrigger(struct raft *r, raft_index index) return rv; } + if (r->state != RAFT_LEADER) + return 0; + return triggerAll(r); } @@ -1279,15 +1283,16 @@ int replicationAppend(struct raft *r, rv = RAFT_SHUTDOWN; goto err_after_acquire_entries; } + r->nr_appending_requests += 1; request->req.data = request; rv = r->io->append(r->io, &request->req, request->args.entries, request->args.n_entries, appendFollowerCb); if (rv != 0) { + r->nr_appending_requests -= 1; ErrMsgTransfer(r->io->errmsg, r->errmsg, "io"); evtErrf("raft(%llx) append failed %d", r->id, rv); goto err_after_acquire_entries; } - r->nr_appending_requests += 1; entryNonBatchDestroyPrefix(args->entries, args->n_entries, i); raft_free(args->entries); @@ -1736,6 +1741,7 @@ static int takeSnapshot(struct raft *r) raft_configuration_close(&snapshot->configuration); abort: r->snapshot.pending.term = 0; + r->snapshot.put.data = NULL; return rv; } diff --git a/test/integration/mocks.c b/test/integration/mocks.c new file mode 100644 index 0000000..13052bb --- /dev/null +++ b/test/integration/mocks.c @@ -0,0 +1,68 @@ +#include "../lib/munit_mock.h" +#include "../../src/log.h" +#include "../../src/request.h" +#include "../../src/replication.h" +#include "../../src/configuration.h" + +extern int __real_logAppendCommands(struct raft_log *l, + const raft_term term, + const struct raft_buffer bufs[], + const unsigned n); + +int __wrap_logAppendCommands(struct raft_log *l, + const raft_term term, + const struct raft_buffer bufs[], + const unsigned n) +{ + return mock_type_args(int, logAppendCommands, l, term, bufs, n); +} + +extern int __real_requestRegEnqueue(struct request_registry *reg, + struct request *req); + +int __wrap_requestRegEnqueue(struct request_registry *reg, struct request *req) +{ + return mock_type_args(int, requestRegEnqueue, reg, req); +} + +extern int __real_replicationTrigger(struct raft *r, raft_index index); + +int __wrap_replicationTrigger(struct raft *r, raft_index index) +{ + return mock_type_args(int, replicationTrigger, r, index); +} + +extern int __real_logAppend(struct raft_log *l, + const raft_term term, + const unsigned short type, + const struct raft_buffer *buf, + void *batch); + +int __wrap_logAppend(struct raft_log *l, + const raft_term term, + const unsigned short type, + const struct raft_buffer *buf, + void *batch) +{ + return mock_type_args(int, logAppend, l, term, type, buf, batch); +} + + +extern int __real_configurationCopy(const struct raft_configuration *src, + struct raft_configuration *dst); + + +int __wrap_configurationCopy(const struct raft_configuration *src, + struct raft_configuration *dst) +{ + return mock_type_args(int, configurationCopy, src, dst); +} + +extern int __real_logAcquire(struct raft_log *l, const raft_index index, + struct raft_entry *entries[], unsigned *n); + +int __wrap_logAcquire(struct raft_log *l, const raft_index index, + struct raft_entry *entries[], unsigned *n) +{ + return mock_type_args(int, logAcquire, l, index, entries, n); +} \ No newline at end of file diff --git a/test/integration/test_apply.c b/test/integration/test_apply.c index a63deec..dc83444 100644 --- a/test/integration/test_apply.c +++ b/test/integration/test_apply.c @@ -1,5 +1,6 @@ #include "../lib/cluster.h" #include "../lib/runner.h" +#include "../lib/munit_mock.h" /****************************************************************************** * @@ -95,6 +96,18 @@ static bool applyCbHasFired(struct raft_fixture *f, void *arg) raft_free(_buf.base); \ } while (0) +/* Submit an apply request. */ +#define APPLY_SUBMIT_ERROR(I, RV) \ + struct raft_buffer _buf; \ + struct raft_apply _req; \ + struct result _result = {RV, false}; \ + int _rv; \ + FsmEncodeSetX(123, &_buf); \ + _req.data = &_result; \ + _rv = raft_apply(CLUSTER_RAFT(I), &_req, &_buf, 1, applyCbAssertResult); \ + munit_assert_int(_rv, ==, 0); + + /****************************************************************************** * * Success scenarios @@ -137,3 +150,74 @@ TEST(raft_apply, leadershipLost, setUp, tearDown, 0, NULL) APPLY_WAIT; return MUNIT_OK; } + +TEST(raft_apply, logAppendCommandsFail, setUp, tearDown, 0, NULL) +{ + struct fixture *f = data; + + will_return(logAppendCommands, RAFT_NOMEM); + APPLY_ERROR(0, RAFT_NOMEM, ""); + return MUNIT_OK; +} + +TEST(raft_apply, requestRegEnqueueFail, setUp, tearDown, 0, NULL) +{ + struct fixture *f = data; + + will_return(requestRegEnqueue, RAFT_NOMEM); + APPLY_ERROR(0, RAFT_NOMEM, ""); + return MUNIT_OK; +} + +TEST(raft_apply, replicationTriggerFail, setUp, tearDown, 0, NULL) +{ + struct fixture *f = data; + + will_return(replicationTrigger, RAFT_NOMEM); + APPLY_ERROR(0, RAFT_NOMEM, ""); + return MUNIT_OK; +} + +static int fakeAppend(struct raft_io *io, struct raft_io_append *req, + const struct raft_entry entries[], unsigned n, + raft_io_append_cb cb) +{ + (void)io; + (void)req; + (void)entries; + (void)n; + (void)cb; + + return RAFT_NOMEM; +} + +static int fakeAppendCb(struct raft_io *io, struct raft_io_append *req, + const struct raft_entry entries[], unsigned n, + raft_io_append_cb cb) +{ + (void)io; + (void)entries; + (void)n; + + cb(req, RAFT_NOMEM); + return 0; +} + +TEST(raft_apply, appendLeaderFail, setUp, tearDown, 0, NULL) +{ + struct fixture *f = data; + typeof(fakeAppend)* append; + + CLUSTER_RAFT(0)->prev_append_status = RAFT_NOMEM; + APPLY_ERROR(0, RAFT_NOMEM, ""); + CLUSTER_RAFT(0)->prev_append_status = 0; + will_return(logAcquire, RAFT_NOMEM); + APPLY_ERROR(0, RAFT_NOMEM, ""); + append = CLUSTER_RAFT(0)->io->append; + CLUSTER_RAFT(0)->io->append = fakeAppend; + APPLY_ERROR(0, RAFT_NOMEM, "io: "); + CLUSTER_RAFT(0)->io->append = fakeAppendCb; + APPLY_SUBMIT_ERROR(0, RAFT_NOMEM); + CLUSTER_RAFT(0)->io->append = append; + return MUNIT_OK; +} \ No newline at end of file diff --git a/test/integration/test_barrier.c b/test/integration/test_barrier.c index 8d95a80..f219711 100644 --- a/test/integration/test_barrier.c +++ b/test/integration/test_barrier.c @@ -1,5 +1,6 @@ #include "../lib/cluster.h" #include "../lib/runner.h" +#include "../lib/munit_mock.h" /****************************************************************************** * @@ -78,6 +79,18 @@ static bool barrierCbHasFired(struct raft_fixture *f, void *arg) BARRIER_WAIT; \ } while (0) +#define BARRIER_ERROR(I, RV, ERRMSG) \ + do { \ + struct raft_barrier _req; \ + struct result _result = {0, false}; \ + int _rv; \ + _req.data = &_result; \ + _rv = raft_barrier(CLUSTER_RAFT(I), &_req, barrierCbAssertResult); \ + munit_assert_int(_rv, ==, RV); \ + munit_assert_string_equal(CLUSTER_ERRMSG(I), ERRMSG); \ + } while (0) + + /****************************************************************************** * * Success scenarios @@ -92,3 +105,30 @@ TEST(raft_barrier, cb, setUp, tearDown, 0, NULL) BARRIER(0); return MUNIT_OK; } + +TEST(raft_barrier, logAppendFail, setUp, tearDown, 0, NULL) +{ + struct fixture *f = data; + + will_return(logAppend, RAFT_NOMEM); + BARRIER_ERROR(0, RAFT_NOMEM, ""); + return MUNIT_OK; +} + +TEST(raft_barrier, requestRegEnqueueFail, setUp, tearDown, 0, NULL) +{ + struct fixture *f = data; + + will_return(requestRegEnqueue, RAFT_NOMEM); + BARRIER_ERROR(0, RAFT_NOMEM, ""); + return MUNIT_OK; +} + +TEST(raft_barrier, replicationTriggerFail, setUp, tearDown, 0, NULL) +{ + struct fixture *f = data; + + will_return(replicationTrigger, RAFT_NOMEM); + BARRIER_ERROR(0, RAFT_NOMEM, ""); + return MUNIT_OK; +} \ No newline at end of file diff --git a/test/integration/test_heap.c b/test/integration/test_heap.c index 2991f81..e2ee27a 100644 --- a/test/integration/test_heap.c +++ b/test/integration/test_heap.c @@ -48,6 +48,15 @@ TEST(raft_heap, aligned_alloc, NULL, NULL, 0, NULL) p = raft_aligned_alloc(1024, 2048); munit_assert_ptr_not_null(p); munit_assert_int((uintptr_t)p % 1024, ==, 0); - raft_free(p); + raft_aligned_free(1024, p); return MUNIT_OK; } + +TEST(raft_heap, entry_malloc, NULL, NULL, 0, NULL) +{ + void *p; + p = raft_entry_malloc(2048); + munit_assert_ptr_not_null(p); + raft_entry_free(p); + return MUNIT_OK; +} \ No newline at end of file diff --git a/test/integration/test_membership.c b/test/integration/test_membership.c index 66ca51e..10330e1 100644 --- a/test/integration/test_membership.c +++ b/test/integration/test_membership.c @@ -89,6 +89,49 @@ static void tear_down(void *data) UNCOMMITTED); \ } +struct result +{ + int status; + bool done; +}; + +static void barrierCbAssertResult(struct raft_barrier *req, int status) +{ + struct result *result = req->data; + munit_assert_int(status, ==, result->status); + result->done = true; +} + +static bool barrierCbHasFired(struct raft_fixture *f, void *arg) +{ + struct result *result = arg; + (void)f; + return result->done; +} +/* Submit a barrier request. */ +#define BARRIER_SUBMIT(I) \ + struct raft_barrier _req; \ + struct result _result = {0, false}; \ + int _rv; \ + _req.data = &_result; \ + _rv = raft_barrier(CLUSTER_RAFT(I), &_req, barrierCbAssertResult); \ + munit_assert_int(_rv, ==, 0); + +/* Expect the barrier callback to fire with the given status. */ +#define BARRIER_EXPECT(STATUS) _result.status = STATUS + +/* Wait until the barrier request completes. */ +#define BARRIER_WAIT CLUSTER_STEP_UNTIL(barrierCbHasFired, &_result, 2000) + +/* Submit to the I'th server a barrier request and wait for the operation to + * succeed. */ +#define BARRIER(I) \ + do { \ + BARRIER_SUBMIT(I); \ + BARRIER_WAIT; \ + } while (0) + + /****************************************************************************** * * raft_add @@ -232,3 +275,28 @@ TEST(raft_remove, badId, setup, tear_down, 0, NULL) REMOVE(0, 3, RAFT_BADID); return MUNIT_OK; } + +/* Rollback configuration. */ +TEST(raft_add, rollBack, setup, tear_down, 0, NULL) +{ + struct fixture *f = data; + GROW; + ADD(0, 3, 0); + CLUSTER_STEP_UNTIL_APPLIED(0, 2, 2000); + ASSIGN(0, 3, RAFT_VOTER); + CLUSTER_STEP_UNTIL_APPLIED(0, 3, 2000); + CLUSTER_STEP_UNTIL_DELIVERED(0, 1, 1000); + CLUSTER_STEP_UNTIL_DELIVERED(0, 2, 1000); + CLUSTER_DISCONNECT(0, 1); + CLUSTER_DISCONNECT(0, 2); + ADD(0, 4, 0); + CLUSTER_STEP_UNTIL_STATE_IS(0, RAFT_FOLLOWER, 2000); + CLUSTER_STEP_UNTIL_HAS_LEADER(2000); + BARRIER(CLUSTER_LEADER); + ADD(CLUSTER_LEADER, 4, 0); + CLUSTER_RECONNECT(0, 1); + CLUSTER_RECONNECT(0, 2); + CLUSTER_STEP_UNTIL_APPLIED(CLUSTER_LEADER, 4, 2000); + CLUSTER_STEP_UNTIL_APPLIED(0, 4, 2000); + return MUNIT_OK; +} \ No newline at end of file diff --git a/test/integration/test_snapshot.c b/test/integration/test_snapshot.c index 5e6ac5f..a45b95b 100644 --- a/test/integration/test_snapshot.c +++ b/test/integration/test_snapshot.c @@ -1,5 +1,7 @@ #include "../lib/cluster.h" #include "../lib/runner.h" +#include "../lib/munit_mock.h" +#include "../../src/snapshot.h" /****************************************************************************** * @@ -545,4 +547,120 @@ TEST(snapshot, reInstallAfterRejected, setUp, tearDown, 0, NULL) munit_assert_int(CLUSTER_N_SEND(0, RAFT_IO_INSTALL_SNAPSHOT), ==, 2); munit_assert_int(CLUSTER_N_RECV(2, RAFT_IO_INSTALL_SNAPSHOT), ==, 2); return MUNIT_OK; -} \ No newline at end of file +} + +static int fakeSnapshot(struct raft_fsm *fsm, struct raft_buffer *bufs[], + unsigned *n_bufs) +{ + (void)fsm; + (void)bufs; + (void)n_bufs; + + return RAFT_BUSY; +} + +static int fakeSnapshotPut(struct raft_io *io, unsigned trailing, + struct raft_io_snapshot_put *req, + const struct raft_snapshot *snapshot, + raft_io_snapshot_put_cb cb) +{ + (void)io; + (void)trailing; + (void)req; + (void)snapshot; + (void)cb; + + return RAFT_NOMEM; +} + +/* Install a snapshot on a follower that has fallen behind. */ +TEST(snapshot, configurationCopyFail, setUp, tearDown, 0, NULL) +{ + struct fixture *f = data; + (void)params; + struct raft_install_snapshot snap = {.term = 2}; + typeof(fakeSnapshot) *snapshot; + typeof(fakeSnapshotPut) *put; + + /* Set very low threshold and trailing entries number */ + SET_SNAPSHOT_THRESHOLD(3); + SET_SNAPSHOT_TRAILING(1); + CLUSTER_SATURATE_BOTHWAYS(0, 2); + + will_return(configurationCopy, RAFT_NOMEM); + /* Apply a few of entries, to force a snapshot to be taken. */ + CLUSTER_MAKE_PROGRESS; + snapshot = CLUSTER_RAFT(0)->fsm->snapshot; + CLUSTER_RAFT(0)->fsm->snapshot = fakeSnapshot; + CLUSTER_MAKE_PROGRESS; + CLUSTER_RAFT(0)->fsm->snapshot = snapshot; + put = CLUSTER_RAFT(0)->io->snapshot_put; + CLUSTER_RAFT(0)->io->snapshot_put = fakeSnapshotPut; + CLUSTER_MAKE_PROGRESS; + CLUSTER_RAFT(0)->io->snapshot_put = put; + CLUSTER_MAKE_PROGRESS; + + /* Reconnect the follower and wait for it to catch up */ + CLUSTER_DESATURATE_BOTHWAYS(0, 2); + CLUSTER_STEP_UNTIL_SNAPSHOT(&f->cluster, 0, 2, &snap, 3000); + + return MUNIT_OK; +} + +static struct raft_snapshot *createSnapshot(struct raft *r) +{ + struct raft_snapshot *snapshot; + + snapshot = raft_malloc(sizeof(*snapshot)); + munit_assert_ptr_not_null(snapshot); + snapshot->n_bufs = 1; + snapshot->bufs = raft_calloc(snapshot->n_bufs, sizeof(struct raft_buffer)); + munit_assert_ptr_not_null(snapshot->bufs); + configurationCopy(&r->configuration, &snapshot->configuration); + snapshot->configuration_index = r->configuration_index; + snapshot->index = r->last_applied; + + return snapshot; +} + +static int fakeRestore(struct raft_fsm *fsm, struct raft_buffer *buf) +{ + (void)fsm; + (void)buf; + + return 0; +} + +TEST(snapshot, restoreSnapshot, setUp, tearDown, 0, NULL) +{ + struct fixture *f = data; + (void)params; + int rv; + struct raft_snapshot *snapshot; + typeof(fakeRestore) *restore; + + /* Set very low threshold and trailing entries number */ + SET_SNAPSHOT_THRESHOLD(3); + SET_SNAPSHOT_TRAILING(1); + CLUSTER_SATURATE_BOTHWAYS(0, 2); + + CLUSTER_STEP_UNTIL_APPLIED(0, 1, 1000); + + restore = CLUSTER_RAFT(0)->fsm->restore; + CLUSTER_RAFT(0)->fsm->restore = fakeRestore; + snapshot = createSnapshot(CLUSTER_RAFT(0)); + munit_assert_ptr_not_null(snapshot); + will_return(configurationCopy, RAFT_NOMEM); + rv = snapshotRestore(CLUSTER_RAFT(0), snapshot); + munit_assert_int(rv, ==, RAFT_NOMEM); + snapshotDestroy(snapshot); + + snapshot = createSnapshot(CLUSTER_RAFT(0)); + munit_assert_ptr_not_null(snapshot); + snapshotRestore(CLUSTER_RAFT(0), snapshot); + CLUSTER_RAFT(0)->fsm->restore = restore; + raft_free(snapshot); + + return MUNIT_OK; +} + diff --git a/test/integration/test_start.c b/test/integration/test_start.c index ecb9e1d..339d0dc 100644 --- a/test/integration/test_start.c +++ b/test/integration/test_start.c @@ -74,6 +74,7 @@ TEST(raft_start, oneSnapshotAndNoEntries, setUp, tearDown, 0, NULL) CLUSTER_SET_TERM(0, 2); BOOTSTRAP(1); CLUSTER_START; + munit_assert_uint64(raft_last_index(CLUSTER_RAFT(1)), ==, 1); CLUSTER_MAKE_PROGRESS; return MUNIT_OK; } diff --git a/test/lib/munit_mock.c b/test/lib/munit_mock.c new file mode 100644 index 0000000..2205030 --- /dev/null +++ b/test/lib/munit_mock.c @@ -0,0 +1,131 @@ +/* Copyright (c) 2013-2018 Evan Nemerson + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "munit_mock.h" +#include "munit.h" +#include +#include + +struct MunitMockItem { + enum MunitMockType type; + const char *name; + union { + MUNIT_MOCK_RETURN_TYPE val; + munit_function_fn func; + }; + TAILQ_ENTRY(MunitMockItem) queue; +}; + +static TAILQ_HEAD(, MunitMockItem) g_mock_items; + +__attribute__((constructor)) static void munit_mock_items_init(void) +{ + TAILQ_INIT(&g_mock_items); +} + +void munit_will_return(const char *name, MUNIT_MOCK_RETURN_TYPE val) +{ + struct MunitMockItem *item = calloc(1, sizeof(*item)); + + munit_assert_ptr_not_null(item); + item->name = name; + item->type = MUNIT_MOCK_TYPE_RETURN; + item->val = val; + TAILQ_INSERT_TAIL(&g_mock_items, item, queue); +} + +void munit_will_call(const char * const name, munit_function_fn func) +{ + struct MunitMockItem *item = calloc(1, sizeof(*item)); + + munit_assert_ptr_not_null(item); + item->name = name; + item->type = MUNIT_MOCK_TYPE_CALL; + item->func = func; + TAILQ_INSERT_TAIL(&g_mock_items, item, queue); +} + +#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = TAILQ_FIRST((head)); \ + (var) && ((tvar) = TAILQ_NEXT((var), field), 1); \ + (var) = (tvar)) + + +static struct MunitMockItem * munit_find_item(const char * const name) +{ + struct MunitMockItem *item; + struct MunitMockItem *tmp; + + TAILQ_FOREACH_SAFE(item, &g_mock_items, queue, tmp) { + if (strcmp(item->name, name)) + continue; + return item; + } + return NULL; +} + +enum MunitMockType munit_mock_type(const char * const name) +{ + struct MunitMockItem *item = munit_find_item(name); + + return item ? item->type : MUNIT_MOCK_TYPE_NULL; +} + +static struct MunitMockItem * munit_remove_item(const char * const name) +{ + struct MunitMockItem *item; + struct MunitMockItem *tmp; + + TAILQ_FOREACH_SAFE(item, &g_mock_items, queue, tmp) { + if (strcmp(item->name, name)) + continue; + TAILQ_REMOVE(&g_mock_items, item, queue); + return item; + } + return NULL; +} + +MUNIT_MOCK_RETURN_TYPE munit_mock_value(const char * const name) +{ + MUNIT_MOCK_RETURN_TYPE val; + struct MunitMockItem *item = munit_remove_item(name); + + munit_assert_ptr_not_null(item); + munit_assert_uint64(item->type, ==, MUNIT_MOCK_TYPE_RETURN); + val = item->val; + free(item); + return val; +} + +munit_function_fn munit_mock_function(const char * const name) +{ + munit_function_fn func; + struct MunitMockItem *item = munit_remove_item(name); + + munit_assert_ptr_not_null(item); + munit_assert_uint64(item->type, ==, MUNIT_MOCK_TYPE_CALL); + func = item->func; + + free(item); + return func; +} diff --git a/test/lib/munit_mock.h b/test/lib/munit_mock.h new file mode 100644 index 0000000..276f3bf --- /dev/null +++ b/test/lib/munit_mock.h @@ -0,0 +1,73 @@ + +/* µnit Testing Framework + * Copyright (c) 2013-2017 Evan Nemerson + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#if !defined(MUNIT_MOCK_H) +#define MUNIT_MOCK_H +#include + +#define MUNIT_MOCK_RETURN_TYPE uint64_t +typedef void (*munit_function_fn)(void); + +enum MunitMockType { + MUNIT_MOCK_TYPE_NULL = 0, + MUNIT_MOCK_TYPE_RETURN = 1, + MUNIT_MOCK_TYPE_CALL = 2 +}; + +void munit_will_return(const char * const name, MUNIT_MOCK_RETURN_TYPE val); +void munit_will_call(const char * const name, munit_function_fn func); + +enum MunitMockType munit_mock_type(const char * const name); + +MUNIT_MOCK_RETURN_TYPE munit_mock_value(const char * const name); +munit_function_fn munit_mock_function(const char * const name); + +#define will_return(func, val) \ + munit_will_return(#func, (val)) + +#define will_return_ptr(func, val) \ + munit_will_return(#func, (uintptr_t)(val)) + +#define will_call(func, wrapper) \ + munit_will_call(#func, (munit_function_fn)wrapper) + +#define mock_type(type, func) \ + (munit_mock_type(#func) == MUNIT_MOCK_TYPE_NULL) \ + ? (type)__real_##func() \ + : (munit_mock_type(#func) == MUNIT_MOCK_TYPE_RETURN) \ + ? (type)munit_mock_value(#func) \ + : (type)((typeof(func) *)munit_mock_function(#func))() + + +#define mock_type_args(type, func, ...) \ + (munit_mock_type(#func) == MUNIT_MOCK_TYPE_NULL) \ + ? (type)__real_##func(__VA_ARGS__) \ + : (munit_mock_type(#func) == MUNIT_MOCK_TYPE_RETURN) \ + ? (type)munit_mock_value(#func) \ + : (type)((typeof(func) *)munit_mock_function(#func))(__VA_ARGS__) + + + +#endif /* !defined(MUNIT_MOCK_H) */ \ No newline at end of file diff --git a/test/unit/test_compress.c b/test/unit/test_compress.c index d2c1a03..9e7b998 100644 --- a/test/unit/test_compress.c +++ b/test/unit/test_compress.c @@ -1,11 +1,13 @@ #include "../../src/byte.h" #include "../../src/compress.h" #include "../lib/munit.h" +#include "../lib/munit_mock.h" #include "../lib/runner.h" #include #ifdef LZ4_AVAILABLE #include +#include #endif SUITE(Compress) @@ -222,6 +224,77 @@ TEST(Compress, compressDecompressCorruption, NULL, NULL, 0, NULL) return MUNIT_OK; } +extern LZ4F_errorCode_t __real_LZ4F_createCompressionContext(LZ4F_cctx **cctxPtr, + unsigned version); +LZ4F_errorCode_t __wrap_LZ4F_createCompressionContext(LZ4F_cctx **cctxPtr, + unsigned version) +{ + return mock_type_args(LZ4F_errorCode_t, LZ4F_createCompressionContext, cctxPtr, version); +} + +TEST(Compress, compressCreateCompressionContextFailed, NULL, NULL, 0, NULL) +{ + char errmsg[RAFT_ERRMSG_BUF_SIZE] = {0}; + struct raft_buffer compressed = {0}; + + /* Fill a buffer with random data */ + size_t len = 2048; + struct raft_buffer buf = getBufWithRandom(len); + + will_return(LZ4F_createCompressionContext, -LZ4F_ERROR_GENERIC); + munit_assert_int(Compress(&buf, 1, &compressed, errmsg), ==, RAFT_NOMEM); + + free(buf.base); + return MUNIT_OK; +} + +extern void *__real_raft_malloc(size_t size); +void *__wrap_raft_malloc(size_t size) +{ + return (void *)(uintptr_t)(mock_type_args(uintptr_t, raft_malloc, size)); +} + +TEST(Compress, compressMallocDestFailed, NULL, NULL, 0, NULL) +{ + char errmsg[RAFT_ERRMSG_BUF_SIZE] = {0}; + struct raft_buffer compressed = {0}; + + /* Fill a buffer with random data */ + size_t len = 2048; + struct raft_buffer buf = getBufWithRandom(len); + + will_return_ptr(raft_malloc, NULL); + munit_assert_int(Compress(&buf, 1, &compressed, errmsg), ==, RAFT_NOMEM); + + free(buf.base); + return MUNIT_OK; +} +extern size_t __real_LZ4F_compressBegin(LZ4F_cctx* cctxPtr, void* dstBuffer, + size_t dstCapacity, + const LZ4F_preferences_t* preferencesPtr); +size_t __wrap_LZ4F_compressBegin(LZ4F_cctx* cctxPtr, void* dstBuffer, + size_t dstCapacity, + const LZ4F_preferences_t* preferencesPtr) +{ + return mock_type_args(LZ4F_errorCode_t, LZ4F_compressBegin, cctxPtr, + dstBuffer, dstCapacity, preferencesPtr); +} + +TEST(Compress, compressBeginFailed, NULL, NULL, 0, NULL) +{ + char errmsg[RAFT_ERRMSG_BUF_SIZE] = {0}; + struct raft_buffer compressed = {0}; + + /* Fill a buffer with random data */ + size_t len = 2048; + struct raft_buffer buf = getBufWithRandom(len); + + will_return_ptr(LZ4F_compressBegin, -LZ4F_ERROR_GENERIC); + munit_assert_int(Compress(&buf, 1, &compressed, errmsg), ==, RAFT_IOERR); + + free(buf.base); + return MUNIT_OK; +} #else TEST(Compress, lz4Disabled, NULL, NULL, 0, NULL)