[SSSD] [PATCH] TESTS: Add test for mmap cache client crash
Lukas Slebodnik
lslebodn at redhat.com
Thu Nov 20 10:25:49 UTC 2014
ehlo,
I was not able to mock functions with linker flang -wrap when
they was called indirectly by another function from dynamic library.
(It works with dirrect invocation of function)
mc_client_tests_LDFLAGS = \
-Wl,-wrap,sss_nss_check_header \
-Wl,-wrap,sss_open_cloexec \
$(NULL)
This is a reason why I wrote mocked function in unit test
e.g.
__real_sss_open_cloexec
__wrap_sss_open_cloexec
sss_open_cloexec
If you know better way how to mock functions I will be glad to change test.
LS
-------------- next part --------------
>From 4828a2e273bcf72fb85337b6abd2d2d6a8add9e2 Mon Sep 17 00:00:00 2001
From: Lukas Slebodnik <lslebodn at redhat.com>
Date: Thu, 18 Sep 2014 07:58:36 +0200
Subject: [PATCH 1/2] BUILD: Prepare version of libnss_sss.so for tests.
It is not good idea to link unit test with "libnss_sss.so"
due to warning
*** Warning: Linking the binary xyz against the loadable module
*** libnss_sss.so is not portable!
---
Makefile.am | 12 ++++++++++++
src/tests/dlopen-tests.c | 1 +
2 files changed, 13 insertions(+)
diff --git a/Makefile.am b/Makefile.am
index 156ef3c4eab1510126d2bfb47c06163885b8acfe..6386d9aff8c80181acba7c8e0988a80e1ded6178 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1687,6 +1687,18 @@ TEST_MOCK_PROVIDER_OBJ = \
src/tests/cmocka/common_mock_sdap.c \
src/tests/cmocka/common_mock_sysdb_objects.c
+check_LTLIBRARIES += \
+ libnss_sss_tests.la \
+ $(NULL)
+
+libnss_sss_tests_la_SOURCES = \
+ $(libnss_sss_la_SOURCES)
+libnss_sss_tests_la_LIBADD = \
+ $(libnss_sss_la_LIBADD)
+libnss_sss_tests_la_LDFLAGS = \
+ -shared \
+ -rpath $(libdir)
+
EXTRA_nss_srv_tests_DEPENDENCIES = \
$(ldblib_LTLIBRARIES)
nss_srv_tests_SOURCES = \
diff --git a/src/tests/dlopen-tests.c b/src/tests/dlopen-tests.c
index 7e56d652461155045023fddeb988f4f4ba017277..afe6de8982042458d4fcec2336dc2c96e69afe8f 100644
--- a/src/tests/dlopen-tests.c
+++ b/src/tests/dlopen-tests.c
@@ -43,6 +43,7 @@ struct so {
{ "libsss_idmap.so", { LIBPFX"libsss_idmap.so", NULL } },
{ "libsss_nss_idmap.so", { LIBPFX"libsss_nss_idmap.so", NULL } },
{ "libnss_sss.so", { LIBPFX"libnss_sss.so", NULL } },
+ { "libnss_sss_tests.so", { LIBPFX"libnss_sss_tests.so", NULL } },
{ "pam_sss.so", { LIBPFX"pam_sss.so", NULL } },
#ifdef BUILD_LIBWBCLIENT
{ "libwbclient.so", { LIBPFX"libwbclient.so", NULL } },
--
2.1.0
-------------- next part --------------
>From 9ffdaaacb7ba069dc4a01dfc731cca4ded2c7b2f Mon Sep 17 00:00:00 2001
From: Lukas Slebodnik <lslebodn at redhat.com>
Date: Tue, 23 Sep 2014 15:30:48 +0200
Subject: [PATCH 2/2] TESTS: Add test for mmap cache client crash
Test create race condition between two threads when memory cache is
invalidated(SSS_MC_HEADER_RECYCLED).
Unit test for:
https://fedorahosted.org/sssd/ticket/2445
---
Makefile.am | 17 ++
src/tests/cmocka/test_mc_client.c | 327 ++++++++++++++++++++++++++++++++++++++
2 files changed, 344 insertions(+)
create mode 100644 src/tests/cmocka/test_mc_client.c
diff --git a/Makefile.am b/Makefile.am
index 6386d9aff8c80181acba7c8e0988a80e1ded6178..2eadc4960e56deb515bf24c68212c9dca8ff048f 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -214,6 +214,7 @@ if HAVE_CMOCKA
test_search_bases \
sdap-tests \
test_sysdb_views \
+ mc_client_tests \
$(NULL)
if BUILD_IFP
@@ -2034,6 +2035,22 @@ sdap_tests_LDADD = \
libsss_test_common.la \
$(NULL)
+mc_client_tests_SOURCES = \
+ src/tests/cmocka/test_mc_client.c \
+ $(NULL)
+mc_client_tests_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(NULL)
+mc_client_tests_LDADD = \
+ $(CMOCKA_LIBS) \
+ $(POPT_LIBS) \
+ $(LIBADD_DL) \
+ libsss_debug.la \
+ -lpthread \
+ libnss_sss_tests.la \
+ libsss_test_common.la \
+ $(NULL)
+
if BUILD_IFP
ifp_tests_SOURCES = \
$(TEST_MOCK_RESP_OBJ) \
diff --git a/src/tests/cmocka/test_mc_client.c b/src/tests/cmocka/test_mc_client.c
new file mode 100644
index 0000000000000000000000000000000000000000..4347cce1969763aea85294bd74dae55c1c3f76ab
--- /dev/null
+++ b/src/tests/cmocka/test_mc_client.c
@@ -0,0 +1,327 @@
+/*
+ Authors:
+ Lukas Slebodnik <lslebodn at redhat.com>
+
+ Copyright (C) 2014 Red Hat
+
+ SSSD tests: Memory cache client Tests
+
+ 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 3 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 <http://www.gnu.org/licenses/>.
+*/
+
+/* we need to define extra feature macros for RTLD_NEXT */
+#include "config.h"
+
+#include <popt.h>
+#include <pthread.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <dlfcn.h>
+
+#include "util/io.h"
+#include "sss_client/nss_mc.h"
+#include "tests/cmocka/common_mock.h"
+
+enum {
+ CALL_REAL = 0,
+ CALL_THREAD1,
+ CALL_THREAD2,
+};
+
+/* Barrier variable */
+pthread_barrier_t barr;
+
+static struct sss_cli_mc_ctx mc_ctx = { false, -1, 0, NULL, 0, NULL, 0, NULL,
+ 0 };
+
+/*
+ ***************************************************************************
+ * Mocked functions *
+ ***************************************************************************
+ */
+
+int __real_sss_open_cloexec(const char *pathname, int flags, int *ret)
+{
+ typedef int (*sss_open_cloexec_ptr)(const char *, int, int *);
+
+ sss_open_cloexec_ptr real_fun
+ = (sss_open_cloexec_ptr)dlsym(RTLD_NEXT, "sss_open_cloexec");
+
+ assert_non_null(real_fun);
+ return real_fun(pathname, flags, ret);
+}
+
+int __wrap_sss_open_cloexec(const char *pathname, int flags, int *ret)
+{
+ const char *fake_path = sss_mock_ptr_type(const char *);
+
+ return __real_sss_open_cloexec(fake_path, flags, ret);
+}
+
+int sss_open_cloexec(const char *pathname, int flags, int *ret)
+{
+ return __wrap_sss_open_cloexec(pathname, flags, ret);
+}
+
+/* Intercept header checking of memory cache */
+errno_t sss_nss_check_header_thread1(struct sss_cli_mc_ctx *ctx);
+errno_t sss_nss_check_header_thread2(struct sss_cli_mc_ctx *ctx);
+
+errno_t __real_sss_nss_check_header(struct sss_cli_mc_ctx *ctx)
+{
+ typedef errno_t (*sss_nss_check_header_ptr)(struct sss_cli_mc_ctx * ctx);
+
+ sss_nss_check_header_ptr real_fun
+ = (sss_nss_check_header_ptr)dlsym(RTLD_NEXT, "sss_nss_check_header");
+
+ assert_non_null(real_fun);
+ return real_fun(ctx);
+}
+
+errno_t __wrap_sss_nss_check_header(struct sss_cli_mc_ctx *ctx)
+{
+ int id = sss_mock_type(int);
+ switch (id) {
+ case CALL_REAL:
+ /* execure real implementation */
+ return __real_sss_nss_check_header(ctx);
+ break;
+ case CALL_THREAD1:
+ return sss_nss_check_header_thread1(ctx);
+ case CALL_THREAD2:
+ return sss_nss_check_header_thread2(ctx);
+ default:
+ /* should not be called */
+ fail();
+ break;
+ }
+
+ return 0;
+}
+
+errno_t sss_nss_check_header(struct sss_cli_mc_ctx *ctx)
+{
+ return __wrap_sss_nss_check_header(ctx);
+}
+
+errno_t sss_nss_check_header_thread1(struct sss_cli_mc_ctx *ctx)
+{
+ errno_t ret;
+ int rc;
+
+ ret = __real_sss_nss_check_header(ctx);
+ rc = pthread_barrier_wait(&barr);
+ if (rc != 0 && rc != PTHREAD_BARRIER_SERIAL_THREAD) {
+ fail();
+ }
+
+ assert_int_equal(ret, EINVAL);
+
+ return ret;
+}
+
+errno_t sss_nss_check_header_thread2(struct sss_cli_mc_ctx *ctx)
+{
+ errno_t ret;
+ int rc = pthread_barrier_wait(&barr);
+ if (rc != 0 && rc != PTHREAD_BARRIER_SERIAL_THREAD) {
+ fail();
+ }
+
+ /*
+ * 1 seconds is exaggerated, but it's better to be sure.
+ * 1 second is enough time to unmap file, close file and clear context
+ * With real race condition, crash happened after 1st thread unmapped file
+ * and 2nd thread want to test header of mmaped file
+ */
+ sleep(1);
+ ret = __real_sss_nss_check_header(ctx);
+ assert_int_equal(ret, EINVAL);
+
+ return ret;
+}
+
+void *entry_point(void *arg)
+{
+ errno_t ret;
+ const errno_t expected_ret = (const errno_t)(intptr_t)arg;
+
+ ret = sss_nss_mc_get_ctx("str_will_be_mocked", &mc_ctx);
+ assert_int_equal(ret, expected_ret);
+
+ return NULL;
+}
+
+/*
+ ***************************************************************************
+ * Memory cache related functions *
+ ***************************************************************************
+ */
+
+const char *TEST_MC_CACHE = TEST_DIR "/dummy";
+
+static void create_fake_cache(const char *file_name)
+{
+ int ret;
+ int fd;
+ ssize_t size;
+ struct sss_mc_header h;
+
+ ret = unlink(file_name);
+ if (ret != 0 && errno != ENOENT) {
+ fail();
+ }
+
+ fd = creat(file_name, S_IRUSR | S_IWUSR);
+ assert_return_code(fd, errno);
+
+ h.b1 = h.b2 = 0xf0000001;
+ h.major_vno = SSS_MC_MAJOR_VNO;
+ h.minor_vno = SSS_MC_MINOR_VNO;
+ h.status = SSS_MC_HEADER_ALIVE;
+ h.seed = time(NULL);
+ h.dt_size = 6000000;
+ h.ft_size = 6250;
+ h.ht_size = 400000;
+ h.data_table = MC_HEADER_SIZE;
+ h.free_table = h.data_table + MC_ALIGN64(h.dt_size);
+ h.hash_table = h.free_table + MC_ALIGN64(h.ft_size);
+ h.reserved = 0;
+
+ size = write(fd, &h, sizeof(h));
+ assert_int_equal(size, sizeof(h));
+
+ ret = ftruncate(fd, h.hash_table + MC_ALIGN64(h.ht_size));
+ assert_return_code(ret, errno);
+}
+
+static void set_header_status(struct sss_cli_mc_ctx *ctx,
+ const char *file_name, uint32_t status)
+{
+ int fd;
+ int ret;
+ const off_t STATUS_OFFSET = offsetof(struct sss_mc_header, status);
+
+ fd = open(file_name, O_RDWR);
+ assert_return_code(fd, errno);
+
+ ret = lseek(fd, STATUS_OFFSET, SEEK_SET);
+ assert_return_code(ret, errno);
+
+ ret = write(fd, &status, sizeof(status));
+ assert_return_code(ret, errno);
+
+ ret = close(fd);
+ assert_int_equal(ret, 0);
+}
+
+/*
+ ***************************************************************************
+ * Unit tests *
+ ***************************************************************************
+ */
+
+static void test_client_reinit_race_condition(void **state)
+{
+ errno_t ret;
+ const int THREADS_COUNT = 2;
+ int i;
+ pthread_t thr[THREADS_COUNT];
+
+ create_fake_cache(TEST_MC_CACHE);
+
+ will_return(__wrap_sss_open_cloexec, TEST_MC_CACHE);
+ will_return(__wrap_sss_nss_check_header, CALL_REAL);
+
+ ret = sss_nss_mc_get_ctx("str_will_be_mocked", &mc_ctx);
+ assert_int_equal(ret, ERR_OK);
+
+ will_return(__wrap_sss_nss_check_header, CALL_REAL);
+ ret = sss_nss_check_header(&mc_ctx);
+ assert_int_equal(ret, ERR_OK);
+
+ set_header_status(&mc_ctx, TEST_MC_CACHE, SSS_MC_HEADER_RECYCLED);
+
+ will_return(__wrap_sss_nss_check_header, CALL_REAL);
+ ret = sss_nss_check_header(&mc_ctx);
+ assert_int_equal(ret, EINVAL);
+
+ will_return(__wrap_sss_nss_check_header, CALL_THREAD1);
+ will_return(__wrap_sss_nss_check_header, CALL_THREAD2);
+ sss_will_return_always(__wrap_sss_nss_check_header, 3);
+
+ ret = pthread_barrier_init(&barr, NULL, THREADS_COUNT);
+ assert_int_equal(ret, 0);
+
+ for (i = 0; i < THREADS_COUNT; ++i) {
+ ret = pthread_create(&thr[i], NULL, &entry_point, (void *)EINVAL);
+ assert_int_equal(ret, 0);
+ }
+
+ /* cleanup */
+ for (i = 0; i < THREADS_COUNT; ++i) {
+ ret = pthread_join(thr[i], NULL);
+ assert_int_equal(ret, 0);
+ }
+
+ ret = pthread_barrier_destroy(&barr);
+ assert_int_equal(ret, 0);
+}
+
+int main(int argc, const char *argv[])
+{
+ int no_cleanup = 0;
+ poptContext pc;
+ int opt;
+ int ret;
+ struct poptOption long_options[] = {
+ SSSD_DEBUG_OPTS
+ {"no-cleanup", 'n', POPT_ARG_NONE, &no_cleanup, 0,
+ _("Do not delete the test database after a test run"), NULL },
+ POPT_TABLEEND
+ };
+ const UnitTest tests[] = {
+ unit_test(test_client_reinit_race_condition),
+ };
+
+ /* Set debug level to invalid value so we can deside if -d 0 was used. */
+ debug_level = SSSDBG_INVALID;
+
+ pc = poptGetContext(argv[0], argc, argv, long_options, 0);
+ while ((opt = poptGetNextOpt(pc)) != -1) {
+ switch (opt) {
+ default:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ return 1;
+ }
+ }
+ poptFreeContext(pc);
+
+ DEBUG_CLI_INIT(debug_level);
+
+ /* Even though normally the tests should clean up after themselves
+ * they might not after a failed run. Remove the old db to be sure */
+ tests_set_cwd();
+
+ ret = run_tests(tests);
+ if (ret == 0 && !no_cleanup) {
+ //test_dom_suite_cleanup(TESTS_PATH, TEST_CONF_DB, TEST_SYSDB_FILE);
+ }
+ return ret;
+}
--
2.1.0
More information about the sssd-devel
mailing list