[SSSD] [PATCH] RFC: Unit test for the NSS responder based on the cmocka library

Jakub Hrozek jhrozek at redhat.com
Tue Mar 5 16:25:45 UTC 2013


On Fri, Mar 01, 2013 at 02:38:08PM +0100, Pavel Březina wrote:
> On 03/01/2013 02:01 PM, Jakub Hrozek wrote:
> >On Fri, Mar 01, 2013 at 01:39:22PM +0100, Pavel Březina wrote:
> >>>[1] http://cmocka.cryptomilk.org/
> >>
> >>Very nice.
> >>
> >>I would like to increase granularity though. First of all we should
> >>separate cmocka test files from check files on directory/filename
> >>level. I'd say that all cmocka stuff should go into tests/cmocka. For
> >>example, there are files common_check.c, common_dom.c, common.c, but
> >>only common_dom.c is suitable for cmocka. When more common files and
> >>tests comes, it will be hard to distinguish them.
> >>
> >>Testing with cmocka is mainly about mocking structures and creating
> >>wrappers around functions. We should try to create reusable mocks and
> >>wrappers right from the beginning, making them independent on tests.
> >>For example: mock_rctx(), mock_cctx(), sss_dp_get_account_send(),
> >>sss_dp_get_account_recv(), ... I believe these and more functions can
> >>be reused when testing other responders so they should be placed in
> >>e.g. common_responder.c
> >>
> >
> >Definitely. As I said in the original mail, I didn't want to spend too
> >much time without getting some kind of feedback.
> >
> >I will move the definitions to some common file and resubmit.
> >
> >>Why did you include setjmp.h?
> >
> >I will add a comment to clarify this.
> 
> OK, I went through the test code and here are few ideas to improve
> readability.
> 
> A wrapper has the ability to execute the original function. And you use
> will_return(__wrap_fn, true/false) to choose the code flow. A macro
> would be better:
> 
> will_return(__wrap_sss_packet_get_body, CALL_REAL);
> will_return(__wrap_sss_packet_get_body, CALL_WRAPPER);
> 
> or even better:
> call_real(fn)
> call_wrapper(fn)
> 
> and we should standardize wrappers to always have this ability and that
> it should be set before any other will_return() is called.
> 

I was experimenting with a wrapper like this but it looked cumbersome to
me. Mostly because you also need to declare the __real function and if
the wrapper was in a separate module, both wrapper and real function
would have to be in the header file. This looked quite ugly to me. I
would rather keep this suggestion in mind and see in time when other
tests land if they would also need this feature.

> 
> Also will_return() macro is used to set both parameters and return
> value. It would be nice to distinguish between those too on semantic
> level. Something like:
> 
> mock_param(fn, value) /* we are setting parameter */
> will_return(fn, value) /* we are setting return value */
> 
> It would be good if we can provider an order parameter to mock_param()
> and mock() so we don't have to rely on the calling order. But this
> probably needs changes in cmocka.
> 

Yes, this sounds like a good idea if it can be done from cmocka's point
of view.

> 
> Create a macro for:
> *body = (uint8_t *) mock() => mock_as(uint8_t*)

OK, done.

New tests attached.
-------------- next part --------------
>From 1bb20056e23730d751a61d7e51786c78820f44a6 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek at redhat.com>
Date: Tue, 12 Feb 2013 14:47:35 +0100
Subject: [PATCH 1/4] Detect the presence of libcmocka during configure

---
 configure.ac              |  2 ++
 src/external/libcmocka.m4 | 19 +++++++++++++++++++
 2 files changed, 21 insertions(+)
 create mode 100644 src/external/libcmocka.m4

diff --git a/configure.ac b/configure.ac
index af976151ad3c90bfcb056dd7d8d24bbc8b884ca5..0e7712389d99cf1b903ae1dbf063d889be62d7b0 100644
--- a/configure.ac
+++ b/configure.ac
@@ -137,6 +137,7 @@ m4_include([src/external/ldap.m4])
 m4_include([src/external/libpcre.m4])
 m4_include([src/external/krb5.m4])
 m4_include([src/external/libcares.m4])
+m4_include([src/external/libcmocka.m4])
 m4_include([src/external/docbook.m4])
 m4_include([src/external/sizes.m4])
 m4_include([src/external/python.m4])
@@ -269,6 +270,7 @@ AC_PATH_PROG([DOXYGEN], [doxygen], [false])
 AM_CONDITIONAL([HAVE_DOXYGEN], [test x$DOXYGEN != xfalse ])
 
 AM_CONDITIONAL([HAVE_CHECK], [test x$have_check != x])
+AM_CONDITIONAL([HAVE_CMOCKA], [test x$have_cmocka != x])
 
 abs_build_dir=`pwd`
 AC_DEFINE_UNQUOTED([ABS_BUILD_DIR], ["$abs_build_dir"], [Absolute path to the build directory])
diff --git a/src/external/libcmocka.m4 b/src/external/libcmocka.m4
new file mode 100644
index 0000000000000000000000000000000000000000..c57327612e2eec2411f18b640c4e2eaed2b31bc1
--- /dev/null
+++ b/src/external/libcmocka.m4
@@ -0,0 +1,19 @@
+dnl this file will be simplified when cmocka carries a .pc file
+AC_SUBST(CMOCKA_LIBS)
+AC_SUBST(CMOCKA_CFLAGS)
+
+AC_CHECK_HEADERS(
+    [setjmp.h cmocka.h],
+    [AC_CHECK_LIB([cmocka], [_will_return],
+                  [ CMOCKA_LIBS="-lcmocka"
+                    have_cmocka="yes" ],
+                  [AC_MSG_WARN([No libcmocka library found])
+                    have_cmocka="no" ])],
+    [AC_MSG_WARN([libcmocka header files not installed])],
+    [[ #include <stdarg.h>
+     # include <stddef.h>
+     #ifdef HAVE_SETJMP_H
+     # include <setjmp.h>
+     #endif
+    ]]
+)
-- 
1.8.1.4

-------------- next part --------------
>From 3a044154d9d2d92e6bd8971dd503ed13088603d2 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek at redhat.com>
Date: Tue, 12 Feb 2013 13:18:00 +0100
Subject: [PATCH 2/4] Add utility functions for tests that use sysdb or tevent.

There was shared code for several unit tests that connected to their own
sysdb instance. This patch adds common code to run a generic request to
completion or connect to a test sysdb.
---
 src/tests/common.h     |  40 ++++++++++++
 src/tests/common_dom.c | 167 +++++++++++++++++++++++++++++++++++++++++++++++++
 src/tests/common_tev.c |  60 ++++++++++++++++++
 3 files changed, 267 insertions(+)
 create mode 100644 src/tests/common_dom.c
 create mode 100644 src/tests/common_tev.c

diff --git a/src/tests/common.h b/src/tests/common.h
index f13181b751fef517f609c6208977cc82a825014e..b7eee46eff303df475909376c663a23fcc02a1b5 100644
--- a/src/tests/common.h
+++ b/src/tests/common.h
@@ -55,4 +55,44 @@ errno_t
 compare_sdap_attr_maps(struct sdap_attr_map *map1, size_t size1,
                        struct sdap_attr_map *map2);
 
+/* A common test structure for tests that require a domain to be set up. */
+struct sss_test_ctx {
+    struct sysdb_ctx *sysdb;
+    struct confdb_ctx *confdb;
+    struct tevent_context *ev;
+    struct sss_domain_info *dom;
+
+    bool done;
+    int error;
+};
+
+struct sss_test_conf_param {
+    const char *key;
+    const char *value;
+};
+
+struct sss_test_ctx *
+create_dom_test_ctx(TALLOC_CTX *mem_ctx,
+                    const char *tests_path,
+                    const char *confdb_path,
+                    const char *sysdb_path,
+                    const char *domain_name,
+                    const char *id_provider,
+                    struct sss_test_conf_param *params);
+
+void test_dom_suite_setup(const char *tests_path);
+
+void test_dom_suite_cleanup(const char *tests_path,
+                            const char *confdb_path,
+                            const char *sysdb_path);
+
+struct tevent_req *
+test_request_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, errno_t err);
+
+#define test_req_succeed_send(mem_ctx, ev) test_request_send(mem_ctx, ev, 0)
+
+errno_t test_request_recv(struct tevent_req *req);
+
+int test_ev_loop(struct sss_test_ctx *tctx);
+
 #endif /* !__TESTS_COMMON_H__ */
diff --git a/src/tests/common_dom.c b/src/tests/common_dom.c
new file mode 100644
index 0000000000000000000000000000000000000000..f835621004d19259b9d9411da6c1d64893a82ebb
--- /dev/null
+++ b/src/tests/common_dom.c
@@ -0,0 +1,167 @@
+/*
+    Authors:
+        Jakub Hrozek <jhrozek at redhat.com>
+
+    Copyright (C) 2013 Red Hat
+
+    SSSD tests: Common utilities for tests that exercise domains
+
+    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/>.
+*/
+
+#include <talloc.h>
+#include <errno.h>
+
+#include "tests/common.h"
+
+struct sss_test_ctx *
+create_dom_test_ctx(TALLOC_CTX *mem_ctx,
+                    const char *tests_path,
+                    const char *confdb_path,
+                    const char *sysdb_path,
+                    const char *domain_name,
+                    const char *id_provider,
+                    struct sss_test_conf_param *params)
+{
+    struct sss_test_ctx *test_ctx;
+    char *conf_db;
+    size_t i;
+    const char *val[2];
+    val[1] = NULL;
+    errno_t ret;
+    char *dompath;
+
+    test_ctx = talloc_zero(mem_ctx, struct sss_test_ctx);
+    if (test_ctx == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, ("talloc_zero failed\n"));
+        goto fail;
+    }
+
+    /* Create an event context */
+    test_ctx->ev = tevent_context_init(test_ctx);
+    if (test_ctx->ev == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, ("tevent_context_init failed\n"));
+        goto fail;
+    }
+
+    conf_db = talloc_asprintf(test_ctx, "%s/%s", tests_path, confdb_path);
+    if (conf_db == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, ("talloc_asprintf failed\n"));
+        goto fail;
+    }
+
+    /* Connect to the conf db */
+    ret = confdb_init(test_ctx, &test_ctx->confdb, conf_db);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE, ("confdb_init failed: %d\n", ret));
+        goto fail;
+    }
+
+    val[0] = domain_name;
+    ret = confdb_add_param(test_ctx->confdb, true,
+                           "config/sssd", "domains", val);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE, ("cannot add domain: %d\n", ret));
+        goto fail;
+    }
+
+    dompath = talloc_asprintf(test_ctx, "config/domain/%s", domain_name);
+    if (dompath == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, ("talloc_asprintf failed\n"));
+        goto fail;
+    }
+
+    val[0] = id_provider;
+    ret = confdb_add_param(test_ctx->confdb, true,
+                           dompath, "id_provider", val);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE, ("cannot add id_provider: %d\n", ret));
+        goto fail;
+    }
+
+    if (params) {
+        for (i=0; params[i].key; i++) {
+            val[0] = params[i].key;
+            ret = confdb_add_param(test_ctx->confdb, true,
+                                   dompath, params[i].value, val);
+            if (ret != EOK) {
+                DEBUG(SSSDBG_CRIT_FAILURE,
+                      ("cannot add parameter %s: %d\n", params[i].key, ret));
+                goto fail;
+            }
+        }
+    }
+
+    ret = sssd_domain_init(test_ctx, test_ctx->confdb, domain_name,
+                           tests_path, &test_ctx->dom);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE, ("cannot add id_provider: %d\n", ret));
+        goto fail;
+    }
+    test_ctx->sysdb = test_ctx->dom->sysdb;
+
+    return test_ctx;
+
+fail:
+    talloc_free(test_ctx);
+    return NULL;
+}
+
+void test_dom_suite_setup(const char *tests_path)
+{
+    errno_t ret;
+
+    /* Create tests directory if it doesn't exist */
+    /* (relative to current dir) */
+    ret = mkdir(tests_path, 0775);
+    if (ret != 0 && errno != EEXIST) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              ("Could not create test directory\n"));
+    }
+}
+
+void test_dom_suite_cleanup(const char *tests_path,
+                            const char *confdb_path,
+                            const char *sysdb_path)
+{
+    errno_t ret;
+    char *conf_db;
+    char *sys_db;
+
+    conf_db = talloc_asprintf(NULL, "%s/%s", tests_path, confdb_path);
+    sys_db = talloc_asprintf(NULL, "%s/%s", tests_path, sysdb_path);
+    if (!conf_db || !sys_db) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              ("Could not construct db paths\n"));
+    }
+
+    errno = 0;
+    ret = unlink(conf_db);
+    if (ret != 0 && errno != ENOENT) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              ("Could not delete the test config ldb file (%d) (%s)\n",
+               errno, strerror(errno)));
+    }
+
+    errno = 0;
+    ret = unlink(sys_db);
+    if (ret != 0 && errno != ENOENT) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              ("Could not delete the test ldb file (%d) (%s)\n",
+               errno, strerror(errno)));
+    }
+
+    talloc_free(conf_db);
+    talloc_free(sys_db);
+}
diff --git a/src/tests/common_tev.c b/src/tests/common_tev.c
new file mode 100644
index 0000000000000000000000000000000000000000..83ba9fd751616402529d5e03f84e50128365c6da
--- /dev/null
+++ b/src/tests/common_tev.c
@@ -0,0 +1,60 @@
+/*
+    Authors:
+        Jakub Hrozek <jhrozek at redhat.com>
+
+    Copyright (C) 2013 Red Hat
+
+    SSSD tests: Common utilities for tests that exercise domains
+
+    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/>.
+*/
+
+#include <tevent.h>
+#include <talloc.h>
+#include <errno.h>
+
+#include "tests/common.h"
+
+struct tevent_req *
+test_request_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, errno_t err)
+{
+    struct tevent_req *req;
+    int state;
+
+    req = tevent_req_create(mem_ctx, &state, int);
+    if (!req) return NULL;
+
+    if (err == EOK) {
+        tevent_req_done(req);
+    } else {
+        tevent_req_error(req, err);
+    }
+    tevent_req_post(req, ev);
+    return req;
+}
+
+errno_t test_request_recv(struct tevent_req *req)
+{
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+
+    return EOK;
+}
+
+int test_ev_loop(struct sss_test_ctx *tctx)
+{
+    while (!tctx->done)
+        tevent_loop_once(tctx->ev);
+
+    return tctx->error;
+}
-- 
1.8.1.4

-------------- next part --------------
>From c1afe97c4e3d19466df34840a9bf0dc45225a503 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek at redhat.com>
Date: Tue, 12 Feb 2013 13:58:46 +0100
Subject: [PATCH 3/4] Move sss_cmd_execute from client to responder code.

I think it logically belongs there and allows to better exercise the
responder commands from unit tests.
---
 src/responder/common/responder.h        |  4 +++-
 src/responder/common/responder_cmd.c    |  8 +++-----
 src/responder/common/responder_common.c | 10 +++++++++-
 3 files changed, 15 insertions(+), 7 deletions(-)

diff --git a/src/responder/common/responder.h b/src/responder/common/responder.h
index a265d61d920d6f649f99aed0d30c17866c3fa4a4..110bd38d317dbacb10b636a6b56cdae8b7a1af9b 100644
--- a/src/responder/common/responder.h
+++ b/src/responder/common/responder.h
@@ -176,9 +176,11 @@ responder_get_domain(TALLOC_CTX *sd_mem_ctx, struct resp_ctx *rctx,
 int sss_cmd_empty_packet(struct sss_packet *packet);
 int sss_cmd_send_empty(struct cli_ctx *cctx, TALLOC_CTX *freectx);
 int sss_cmd_send_error(struct cli_ctx *cctx, int err);
-int sss_cmd_execute(struct cli_ctx *cctx, struct sss_cmd_table *sss_cmds);
 void sss_cmd_done(struct cli_ctx *cctx, void *freectx);
 int sss_cmd_get_version(struct cli_ctx *cctx);
+int sss_cmd_execute(struct cli_ctx *cctx,
+                    enum sss_cli_command cmd,
+                    struct sss_cmd_table *sss_cmds);
 struct cli_protocol_version *register_cli_protocol_version(void);
 
 struct setent_req_list;
diff --git a/src/responder/common/responder_cmd.c b/src/responder/common/responder_cmd.c
index cb57cba1a446cdf6ce1782b8d772b70c16ef1e07..3a3fca9b00682c23490509ebdd533bc01e9bb005 100644
--- a/src/responder/common/responder_cmd.c
+++ b/src/responder/common/responder_cmd.c
@@ -141,13 +141,12 @@ int sss_cmd_get_version(struct cli_ctx *cctx)
     return EOK;
 }
 
-int sss_cmd_execute(struct cli_ctx *cctx, struct sss_cmd_table *sss_cmds)
+int sss_cmd_execute(struct cli_ctx *cctx,
+                    enum sss_cli_command cmd,
+                    struct sss_cmd_table *sss_cmds)
 {
-    enum sss_cli_command cmd;
     int i;
 
-    cmd = sss_packet_get_cmd(cctx->creq->in);
-
     for (i = 0; sss_cmds[i].cmd != SSS_CLI_NULL; i++) {
         if (cmd == sss_cmds[i].cmd) {
             return sss_cmds[i].fn(cctx);
@@ -156,7 +155,6 @@ int sss_cmd_execute(struct cli_ctx *cctx, struct sss_cmd_table *sss_cmds)
 
     return EINVAL;
 }
-
 struct setent_req_list {
     struct setent_req_list *prev;
     struct setent_req_list *next;
diff --git a/src/responder/common/responder_common.c b/src/responder/common/responder_common.c
index 3bb7b602ccc5c01f0bc0e1b597798116448a9030..c2c34b222b811e407c0c5a12c943c2485561b999 100644
--- a/src/responder/common/responder_common.c
+++ b/src/responder/common/responder_common.c
@@ -262,6 +262,14 @@ static void client_send(struct cli_ctx *cctx)
     return;
 }
 
+static int client_cmd_execute(struct cli_ctx *cctx, struct sss_cmd_table *sss_cmds)
+{
+    enum sss_cli_command cmd;
+
+    cmd = sss_packet_get_cmd(cctx->creq->in);
+    return sss_cmd_execute(cctx, cmd, sss_cmds);
+}
+
 static void client_recv(struct cli_ctx *cctx)
 {
     int ret;
@@ -291,7 +299,7 @@ static void client_recv(struct cli_ctx *cctx)
         /* do not read anymore */
         TEVENT_FD_NOT_READABLE(cctx->cfde);
         /* execute command */
-        ret = sss_cmd_execute(cctx, cctx->rctx->sss_cmds);
+        ret = client_cmd_execute(cctx, cctx->rctx->sss_cmds);
         if (ret != EOK) {
             DEBUG(0, ("Failed to execute request, aborting client!\n"));
             talloc_free(cctx);
-- 
1.8.1.4

-------------- next part --------------
>From 0401ed2dd79f9a261ddd5c929d305618ab3d76a8 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek at redhat.com>
Date: Tue, 12 Feb 2013 17:40:01 +0100
Subject: [PATCH 4/4] CMocka based test for the NSS responder

---
 Makefile.am                         |  43 +++
 src/tests/cmocka/common_mock.h      |  49 ++++
 src/tests/cmocka/common_mock_resp.c | 139 ++++++++++
 src/tests/cmocka/common_mock_resp.h |  62 +++++
 src/tests/cmocka/test_nss_srv.c     | 512 ++++++++++++++++++++++++++++++++++++
 5 files changed, 805 insertions(+)
 create mode 100644 src/tests/cmocka/common_mock.h
 create mode 100644 src/tests/cmocka/common_mock_resp.c
 create mode 100644 src/tests/cmocka/common_mock_resp.h
 create mode 100644 src/tests/cmocka/test_nss_srv.c

diff --git a/Makefile.am b/Makefile.am
index 79e6e571cc12fd38f78ef0b031bb867b0b650eb0..906a2e29a8a132c269cca66b65258e3babd76fba 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -138,9 +138,15 @@ endif
 
 endif
 
+if HAVE_CMOCKA
+    non_interactive_cmocka_based_tests = \
+	nss-srv-tests
+endif
+
 check_PROGRAMS = \
     stress-tests \
     krb5-child-test \
+    $(non_interactive_cmocka_based_tests) \
     $(non_interactive_check_based_tests)
 
 PYTHON_TESTS =
@@ -153,6 +159,7 @@ endif
 
 TESTS = \
     $(PYTHON_TESTS) \
+    $(non_interactive_cmocka_based_tests) \
     $(non_interactive_check_based_tests)
 
 sssdlib_LTLIBRARIES = \
@@ -1152,6 +1159,42 @@ krb5_child_test_LDADD = \
     libsss_util.la \
     libsss_test_common.la
 
+if HAVE_CMOCKA
+TEST_MOCK_OBJ = \
+     src/tests/common.c
+
+TEST_MOCK_RESP_OBJ = \
+     $(TEST_MOCK_OBJ) \
+     src/tests/common_tev.c \
+     src/tests/common_dom.c \
+     src/tests/cmocka/common_mock_resp.c \
+     src/responder/common/responder_packet.c \
+     src/responder/common/responder_cmd.c \
+     src/responder/common/negcache.c \
+     src/responder/common/responder_common.c
+
+nss_srv_tests_DEPENDENCIES = \
+     $(ldblib_LTLIBRARIES)
+nss_srv_tests_SOURCES = \
+     $(TEST_MOCK_RESP_OBJ) \
+     src/tests/cmocka/test_nss_srv.c \
+     src/responder/nss/nsssrv_cmd.c \
+     src/responder/nss/nsssrv_netgroup.c \
+     src/responder/nss/nsssrv_services.c \
+     src/responder/nss/nsssrv_mmap_cache.c
+nss_srv_tests_CFLAGS = \
+    $(AM_CFLAGS)
+nss_srv_tests_LDFLAGS = \
+    -Wl,-wrap,sss_ncache_check_user \
+    -Wl,-wrap,sss_packet_get_body \
+    -Wl,-wrap,sss_packet_get_cmd \
+    -Wl,-wrap,sss_cmd_send_empty \
+    -Wl,-wrap,sss_cmd_done
+nss_srv_tests_LDADD = \
+    $(CMOCKA_LIBS) \
+    libsss_util.la
+endif
+
 noinst_PROGRAMS = pam_test_client
 if BUILD_SUDO
 noinst_PROGRAMS += sss_sudo_cli
diff --git a/src/tests/cmocka/common_mock.h b/src/tests/cmocka/common_mock.h
new file mode 100644
index 0000000000000000000000000000000000000000..fdce3aa7abdc9a2cd9859ade9af5ae280083bac4
--- /dev/null
+++ b/src/tests/cmocka/common_mock.h
@@ -0,0 +1,49 @@
+/*
+    Authors:
+        Jakub Hrozek <jhrozek at redhat.com>
+
+    Copyright (C) 2013 Red Hat
+
+    SSSD tests: Common utilities for tests that exercise domains
+
+    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/>.
+*/
+
+#ifndef __COMMON_MOCK_H_
+#define __COMMON_MOCK_H_
+
+/*
+ * from cmocka.c:
+ * These headers or their equivalents should be included prior to
+ * including
+ * this header file.
+ *
+ * #include <stdarg.h>
+ * #include <stddef.h>
+ * #include <setjmp.h>
+ *
+ * This allows test applications to use custom definitions of C standard
+ * library functions and types.
+ */
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include "tests/common.h"
+
+#define mock_type(type) ((type) mock())
+
+#endif /* __COMMON_MOCK_H_ */
+
diff --git a/src/tests/cmocka/common_mock_resp.c b/src/tests/cmocka/common_mock_resp.c
new file mode 100644
index 0000000000000000000000000000000000000000..970026737ba7fe186b666a5e20cda6abe3a80f1c
--- /dev/null
+++ b/src/tests/cmocka/common_mock_resp.c
@@ -0,0 +1,139 @@
+/*
+    Authors:
+        Jakub Hrozek <jhrozek at redhat.com>
+
+    Copyright (C) 2013 Red Hat
+
+    SSSD tests: Common utilities for tests that exercise domains
+
+    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/>.
+*/
+
+#include "util/util.h"
+#include "tests/cmocka/common_mock_resp.h"
+
+/* Mock a responder context */
+struct resp_ctx *
+mock_rctx(TALLOC_CTX *mem_ctx,
+          struct tevent_context *ev,
+          struct sss_domain_info *domains,
+          void *pvt_ctx)
+{
+    struct resp_ctx *rctx;
+    errno_t ret;
+
+    rctx = talloc_zero(mem_ctx, struct resp_ctx);
+    if (!rctx) return NULL;
+
+    ret = sss_hash_create(rctx, 30, &rctx->dp_request_table);
+    if (ret != EOK) {
+        talloc_free(rctx);
+        return NULL;
+    }
+
+    rctx->ev = ev;
+    rctx->domains = domains;
+    rctx->pvt_ctx = pvt_ctx;
+    return rctx;
+}
+
+/* Mock a client context */
+struct cli_ctx *
+mock_cctx(TALLOC_CTX *mem_ctx, struct resp_ctx *rctx)
+{
+    struct cli_ctx *cctx;
+
+    cctx = talloc_zero(mem_ctx, struct cli_ctx);
+    if (!cctx) return NULL;
+
+    cctx->creq = talloc_zero(cctx, struct cli_request);
+    if (cctx->creq == NULL) {
+        talloc_free(cctx);
+        return NULL;
+    }
+
+    cctx->rctx = rctx;
+    return cctx;
+}
+
+/* Mock DP requests that finish immediatelly and return
+ * mocked values as per previous set by mock_account_recv
+ */
+struct tevent_req *
+sss_dp_get_account_send(TALLOC_CTX *mem_ctx,
+                        struct resp_ctx *rctx,
+                        struct sss_domain_info *dom,
+                        bool fast_reply,
+                        enum sss_dp_acct_type type,
+                        const char *opt_name,
+                        uint32_t opt_id,
+                        const char *extra)
+{
+    return test_req_succeed_send(mem_ctx, rctx->ev);
+}
+
+
+errno_t
+sss_dp_get_account_recv(TALLOC_CTX *mem_ctx,
+                        struct tevent_req *req,
+                        dbus_uint16_t *dp_err,
+                        dbus_uint32_t *dp_ret,
+                        char **err_msg)
+{
+    acct_cb_t cb;
+
+    *dp_err = mock_type(dbus_uint16_t);
+    *dp_ret = mock_type(dbus_uint32_t);
+    *dp_ret = mock_type(dbus_uint32_t);
+
+    cb = mock_type(acct_cb_t);
+    if (cb) {
+        (cb)(mock_type(void *));
+    }
+
+    return test_request_recv(req);
+}
+
+void mock_account_recv(uint16_t dp_err, uint32_t dp_ret, char *msg,
+                       acct_cb_t acct_cb, void *pvt)
+{
+    will_return(sss_dp_get_account_recv, dp_err);
+    will_return(sss_dp_get_account_recv, dp_ret);
+    will_return(sss_dp_get_account_recv, msg);
+
+    will_return(sss_dp_get_account_recv, acct_cb);
+    if (acct_cb) {
+        will_return(sss_dp_get_account_recv, pvt);
+    }
+}
+
+void mock_account_recv_simple(void)
+{
+    return mock_account_recv(0, 0, NULL, NULL, NULL);
+}
+
+/* Mock subdomain requests */
+struct tevent_req *
+sss_dp_get_domains_send(TALLOC_CTX *mem_ctx,
+                        struct resp_ctx *rctx,
+                        bool force,
+                        const char *hint)
+{
+    return test_req_succeed_send(mem_ctx, rctx->ev);
+}
+
+errno_t sss_dp_get_domains_recv(struct tevent_req *req)
+{
+    return test_request_recv(req);
+}
diff --git a/src/tests/cmocka/common_mock_resp.h b/src/tests/cmocka/common_mock_resp.h
new file mode 100644
index 0000000000000000000000000000000000000000..5ddcc236e09c12034a6079b74adeb099d38d22df
--- /dev/null
+++ b/src/tests/cmocka/common_mock_resp.h
@@ -0,0 +1,62 @@
+/*
+    Authors:
+        Jakub Hrozek <jhrozek at redhat.com>
+
+    Copyright (C) 2013 Red Hat
+
+    SSSD tests: Common utilities for tests that exercise domains
+
+    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/>.
+*/
+
+#ifndef __COMMON_MOCK_RESP_H_
+#define __COMMON_MOCK_RESP_H_
+
+#include "util/util.h"
+#include "responder/common/responder.h"
+#include "tests/cmocka/common_mock.h"
+
+/* Mock a responder context */
+struct resp_ctx *
+mock_rctx(TALLOC_CTX *mem_ctx,
+          struct tevent_context *ev,
+          struct sss_domain_info *domains,
+          void *pvt_ctx);
+
+/* Mock a client context */
+struct cli_ctx *
+mock_cctx(TALLOC_CTX *mem_ctx, struct resp_ctx *rctx);
+
+/* When mocking a module that calls sss_dp_get_account_{send,recv}
+ * requests, your test, when linked against this module, will call
+ * the mock functions instead. Then you can simulate results of the
+ * sss_dp_get_account_recv call by calling mock_account_recv.
+ *
+ * The mocked sss_sp_get_account_recv shall return the return values
+ * given with parameters dp_err, dp_ret and msg and optionally also call
+ * the acct_cb_t callback, if given with the pvt pointer as user data.
+ * The callback can for instance populate the cache, thus simulating
+ * Data Provider lookup.
+ *
+ * There is also even simpler wrapper called mock_account_recv_simple
+ * that just finishes the account request with a success.
+ */
+typedef int (*acct_cb_t)(void *);
+
+void mock_account_recv(uint16_t dp_err, uint32_t dp_ret, char *msg,
+                       acct_cb_t acct_cb, void *pvt);
+
+void mock_account_recv_simple(void);
+
+#endif /* __COMMON_MOCK_RESP_H_ */
diff --git a/src/tests/cmocka/test_nss_srv.c b/src/tests/cmocka/test_nss_srv.c
new file mode 100644
index 0000000000000000000000000000000000000000..1a1317d8faa2c930ab8be8c61c1b4220216b2f98
--- /dev/null
+++ b/src/tests/cmocka/test_nss_srv.c
@@ -0,0 +1,512 @@
+/*
+    Authors:
+        Jakub Hrozek <jhrozek at redhat.com>
+
+    Copyright (C) 2013 Red Hat
+
+    SSSD tests: Common utilities for tests that exercise domains
+
+    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/>.
+*/
+
+#include <talloc.h>
+#include <tevent.h>
+#include <errno.h>
+#include <popt.h>
+
+#include "tests/cmocka/common_mock.h"
+#include "tests/cmocka/common_mock_resp.h"
+#include "responder/common/negcache.h"
+#include "responder/nss/nsssrv.h"
+#include "responder/nss/nsssrv_private.h"
+
+#define TESTS_PATH "tests_nss"
+#define TEST_CONF_DB "test_nss_conf.ldb"
+#define TEST_SYSDB_FILE "cache_nss_test.ldb"
+#define TEST_DOM_NAME "nss_test"
+#define TEST_ID_PROVIDER "ldap"
+
+struct nss_test_ctx {
+    struct sss_test_ctx *tctx;
+
+    struct resp_ctx *rctx;
+    struct cli_ctx *cctx;
+    struct sss_cmd_table *nss_cmds;
+    struct nss_ctx *nctx;
+
+    bool ncache_hit;
+};
+
+struct nss_test_ctx *nss_test_ctx;
+
+/* Mock NSS structure */
+struct nss_ctx *
+mock_nctx(TALLOC_CTX *mem_ctx)
+{
+    struct nss_ctx *nctx;
+    errno_t ret;
+
+    nctx = talloc_zero(mem_ctx, struct nss_ctx);
+    if (!nctx) {
+        return NULL;
+    }
+
+    ret = sss_ncache_init(nctx, &nctx->ncache);
+    if (ret != EOK) {
+        talloc_free(nctx);
+        return NULL;
+    }
+    nctx->neg_timeout = 10;
+
+    return nctx;
+}
+
+/* Mock reading requests from a client. Use values passed from mock
+ * instead
+ */
+void __real_sss_packet_get_body(struct sss_packet *packet,
+                                uint8_t **body, size_t *blen);
+
+void __wrap_sss_packet_get_body(struct sss_packet *packet,
+                                uint8_t **body, size_t *blen)
+{
+    bool real = mock_type(bool);
+
+    if (real) {
+        return __real_sss_packet_get_body(packet, body, blen);
+    }
+
+    *body = mock_type(uint8_t *);
+    *blen = strlen((const char *) *body)+1;
+    return;
+}
+
+/* Mock returning result to client. Terminate the unit test instead. */
+typedef int (*cmd_cb_fn_t)(uint8_t *, size_t );
+
+static void set_cmd_cb(cmd_cb_fn_t fn)
+{
+    will_return(__wrap_sss_cmd_done, fn);
+}
+
+void __wrap_sss_cmd_done(struct cli_ctx *cctx, void *freectx)
+{
+    struct sss_packet *packet = cctx->creq->out;
+    uint8_t *body;
+    size_t blen;
+    cmd_cb_fn_t check_cb;
+
+    check_cb = mock_type(cmd_cb_fn_t);
+
+    __real_sss_packet_get_body(packet, &body, &blen);
+
+    nss_test_ctx->tctx->error = check_cb(body, blen);
+    nss_test_ctx->tctx->done = true;
+}
+
+enum sss_cli_command __wrap_sss_packet_get_cmd(struct sss_packet *packet)
+{
+    return mock_type(enum sss_cli_command);
+}
+
+int __wrap_sss_cmd_send_empty(struct cli_ctx *cctx, TALLOC_CTX *freectx)
+{
+    nss_test_ctx->tctx->done = true;
+    nss_test_ctx->tctx->error = ENOENT;
+    return EOK;
+}
+
+/* Intercept negative cache lookups */
+int __real_sss_ncache_check_user(struct sss_nc_ctx *ctx, int ttl,
+                                 struct sss_domain_info *dom, const char *name);
+
+int __wrap_sss_ncache_check_user(struct sss_nc_ctx *ctx, int ttl,
+                                 struct sss_domain_info *dom, const char *name)
+{
+    int ret;
+
+    ret = __real_sss_ncache_check_user(ctx, ttl, dom, name);
+    if (ret == EEXIST) {
+        nss_test_ctx->ncache_hit = true;
+    }
+    return ret;
+}
+
+/* Mock input from the client library */
+static void mock_input_user(const char *username)
+{
+    will_return(__wrap_sss_packet_get_body, false);
+    will_return(__wrap_sss_packet_get_body, username);
+}
+
+static void mock_fill_user(void)
+{
+    /* One packet for the entry and one for num entries */
+    will_return(__wrap_sss_packet_get_body, true);
+    will_return(__wrap_sss_packet_get_body, true);
+}
+
+static int parse_user_packet(uint8_t *body, size_t blen, struct passwd *pwd)
+{
+    size_t rp = 2 * sizeof(uint32_t);
+
+    SAFEALIGN_COPY_UINT32(&pwd->pw_uid, body+rp, &rp);
+    SAFEALIGN_COPY_UINT32(&pwd->pw_gid, body+rp, &rp);
+
+    /* Sequence of null terminated strings (name, passwd, gecos, dir, shell) */
+    pwd->pw_name = (char *) body+rp;
+    rp += strlen(pwd->pw_name) + 1;
+    if (rp >= blen) return EINVAL;
+
+    pwd->pw_gecos = (char *) body+rp;
+    rp += strlen(pwd->pw_gecos) + 1;
+    if (rp >= blen) return EINVAL;
+
+    pwd->pw_dir = (char *) body+rp;
+    rp += strlen(pwd->pw_dir) + 1;
+    if (rp >= blen) return EINVAL;
+
+    pwd->pw_shell = (char *) body+rp;
+    return EOK;
+}
+
+/* ====================== The tests =============================== */
+
+/* Check getting cached and valid user from cache. Account callback will
+ * not be called and test_nss_getpwnam_check will make sure the user is
+ * the same as the test entered before starting
+ */
+static int test_nss_getpwnam_check(uint8_t *body, size_t blen)
+{
+    struct passwd pwd;
+    errno_t ret;
+
+    ret = parse_user_packet(body, blen, &pwd);
+    assert_int_equal(ret, EOK);
+
+    assert_int_equal(pwd.pw_uid, 123);
+    assert_int_equal(pwd.pw_gid, 456);
+    assert_string_equal(pwd.pw_name, "testuser");
+    assert_string_equal(pwd.pw_shell, "/bin/sh");
+    return EOK;
+}
+
+void test_nss_getpwnam(void **state)
+{
+    errno_t ret;
+
+    /* Prime the cache with a valid user */
+    ret = sysdb_add_user(nss_test_ctx->tctx->sysdb,
+                         nss_test_ctx->tctx->dom,
+                         "testuser", 123, 456, "test user",
+                         "/home/testuser", "/bin/sh", NULL,
+                         NULL, 300, 0);
+    assert_int_equal(ret, EOK);
+
+    mock_input_user("testuser");
+    will_return(__wrap_sss_packet_get_cmd, SSS_NSS_GETPWNAM);
+    mock_fill_user();
+
+    /* Query for that user, call a callback when command finishes */
+    set_cmd_cb(test_nss_getpwnam_check);
+    ret = sss_cmd_execute(nss_test_ctx->cctx, SSS_NSS_GETPWNAM,
+                          nss_test_ctx->nss_cmds);
+    assert_int_equal(ret, EOK);
+
+    /* Wait until the test finishes with EOK */
+    ret = test_ev_loop(nss_test_ctx->tctx);
+    assert_int_equal(ret, EOK);
+}
+
+/* Test that searching for a nonexistant user yields ENOENT.
+ * Account callback will be called
+ */
+void test_nss_getpwnam_neg(void **state)
+{
+    errno_t ret;
+
+    mock_input_user("testuser_neg");
+    mock_account_recv_simple();
+
+    assert_true(nss_test_ctx->ncache_hit == false);
+
+    ret = sss_cmd_execute(nss_test_ctx->cctx, SSS_NSS_GETPWNAM,
+                          nss_test_ctx->nss_cmds);
+    assert_int_equal(ret, EOK);
+
+    /* Wait until the test finishes with ENOENT */
+    ret = test_ev_loop(nss_test_ctx->tctx);
+    assert_int_equal(ret, ENOENT);
+    assert_true(nss_test_ctx->ncache_hit == false);
+
+    /* Test that subsequent search for a nonexistent user yields
+     * ENOENT and Account callback is not called, on the other hand
+     * the ncache functions will be called
+     */
+    nss_test_ctx->tctx->done = false;
+
+    mock_input_user("testuser_neg");
+    ret = sss_cmd_execute(nss_test_ctx->cctx, SSS_NSS_GETPWNAM,
+                          nss_test_ctx->nss_cmds);
+    assert_int_equal(ret, EOK);
+
+    /* Wait until the test finishes with ENOENT */
+    ret = test_ev_loop(nss_test_ctx->tctx);
+    assert_int_equal(ret, ENOENT);
+    /* Negative cache was hit this time */
+    assert_true(nss_test_ctx->ncache_hit == true);
+}
+
+static int test_nss_getpwnam_search_acct_cb(void *pvt)
+{
+    errno_t ret;
+    struct nss_test_ctx *ctx = talloc_get_type(pvt, struct nss_test_ctx);
+
+    ret = sysdb_add_user(ctx->tctx->sysdb,
+                         ctx->tctx->dom,
+                         "testuser_search", 567, 890, "test search",
+                         "/home/testsearch", "/bin/sh", NULL,
+                         NULL, 300, 0);
+    assert_int_equal(ret, EOK);
+
+    return EOK;
+}
+
+static int test_nss_getpwnam_search_check(uint8_t *body, size_t blen)
+{
+    struct passwd pwd;
+    errno_t ret;
+
+    ret = parse_user_packet(body, blen, &pwd);
+    assert_int_equal(ret, EOK);
+
+    assert_int_equal(pwd.pw_uid, 567);
+    assert_int_equal(pwd.pw_gid, 890);
+    assert_string_equal(pwd.pw_name, "testuser_search");
+    assert_string_equal(pwd.pw_shell, "/bin/sh");
+    return EOK;
+}
+
+void test_nss_getpwnam_search(void **state)
+{
+    errno_t ret;
+    struct ldb_result *res;
+
+    mock_input_user("testuser_search");
+    mock_account_recv(0, 0, NULL, test_nss_getpwnam_search_acct_cb, nss_test_ctx);
+    will_return(__wrap_sss_packet_get_cmd, SSS_NSS_GETPWNAM);
+    mock_fill_user();
+    set_cmd_cb(test_nss_getpwnam_search_check);
+
+    ret = sysdb_getpwnam(nss_test_ctx, nss_test_ctx->tctx->sysdb,
+                         nss_test_ctx->tctx->dom, "testuser_search",
+                         &res);
+    assert_int_equal(ret, EOK);
+    assert_int_equal(res->count, 0);
+
+    ret = sss_cmd_execute(nss_test_ctx->cctx, SSS_NSS_GETPWNAM,
+                          nss_test_ctx->nss_cmds);
+    assert_int_equal(ret, EOK);
+
+    /* Wait until the test finishes with EOK */
+    ret = test_ev_loop(nss_test_ctx->tctx);
+    assert_int_equal(ret, EOK);
+
+    /* test_nss_getpwnam_search_check will check the user attributes */
+    ret = sysdb_getpwnam(nss_test_ctx, nss_test_ctx->tctx->sysdb,
+                         nss_test_ctx->tctx->dom, "testuser_search",
+                         &res);
+    assert_int_equal(ret, EOK);
+    assert_int_equal(res->count, 1);
+}
+
+/* Test that searching for a user that is expired in the cache goes to the DP
+ * which updates the record and the NSS responder returns the updated record
+ *
+ * The user's shell attribute is updated.
+ */
+static int test_nss_getpwnam_update_acct_cb(void *pvt)
+{
+    errno_t ret;
+    struct nss_test_ctx *ctx = talloc_get_type(pvt, struct nss_test_ctx);
+
+    ret = sysdb_store_user(ctx->tctx->sysdb,
+                           ctx->tctx->dom,
+                           "testuser_update", NULL, 10, 11, "test user",
+                           "/home/testuser", "/bin/ksh", NULL,
+                           NULL, NULL, 300, 0);
+    assert_int_equal(ret, EOK);
+
+    return EOK;
+}
+
+static int test_nss_getpwnam_update_check(uint8_t *body, size_t blen)
+{
+    struct passwd pwd;
+    errno_t ret;
+
+    ret = parse_user_packet(body, blen, &pwd);
+    assert_int_equal(ret, EOK);
+
+    assert_int_equal(pwd.pw_uid, 10);
+    assert_int_equal(pwd.pw_gid, 11);
+    assert_string_equal(pwd.pw_name, "testuser_update");
+    assert_string_equal(pwd.pw_shell, "/bin/ksh");
+    return EOK;
+}
+
+void test_nss_getpwnam_update(void **state)
+{
+    errno_t ret;
+    struct ldb_result *res;
+    const char *shell;
+
+    /* Prime the cache with a valid but expired user */
+    ret = sysdb_add_user(nss_test_ctx->tctx->sysdb,
+                         nss_test_ctx->tctx->dom,
+                         "testuser_update", 10, 11, "test user",
+                         "/home/testuser", "/bin/sh", NULL,
+                         NULL, 1, 1);
+    assert_int_equal(ret, EOK);
+
+    /* Mock client input */
+    mock_input_user("testuser_update");
+    /* Mock client command */
+    will_return(__wrap_sss_packet_get_cmd, SSS_NSS_GETPWNAM);
+    /* Call this function when user is updated by the mock DP request */
+    mock_account_recv(0, 0, NULL, test_nss_getpwnam_update_acct_cb, nss_test_ctx);
+    /* Call this function to check what the responder returned to the client */
+    set_cmd_cb(test_nss_getpwnam_update_check);
+    /* Mock output buffer */
+    mock_fill_user();
+
+    /* Fire the command */
+    ret = sss_cmd_execute(nss_test_ctx->cctx, SSS_NSS_GETPWNAM,
+                          nss_test_ctx->nss_cmds);
+    assert_int_equal(ret, EOK);
+
+    /* Wait until the test finishes with EOK */
+    ret = test_ev_loop(nss_test_ctx->tctx);
+    assert_int_equal(ret, EOK);
+
+    /* Check the user was updated in the cache */
+    ret = sysdb_getpwnam(nss_test_ctx, nss_test_ctx->tctx->sysdb,
+                         nss_test_ctx->tctx->dom, "testuser_update",
+                         &res);
+    assert_int_equal(ret, EOK);
+    assert_int_equal(res->count, 1);
+
+    shell = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_SHELL, NULL);
+    assert_string_equal(shell, "/bin/ksh");
+}
+
+/* Testsuite setup and teardown */
+void nss_test_setup(void **state)
+{
+    errno_t ret;
+    struct sss_test_conf_param params[] = {
+        { "enumerate", "false" },
+        { NULL, NULL },             /* Sentinel */
+    };
+
+    nss_test_ctx = talloc_zero(NULL, struct nss_test_ctx);
+    assert_non_null(nss_test_ctx);
+
+    nss_test_ctx->tctx = create_dom_test_ctx(nss_test_ctx, TESTS_PATH, TEST_CONF_DB,
+                                             TEST_SYSDB_FILE, TEST_DOM_NAME,
+                                             TEST_ID_PROVIDER, params);
+    assert_non_null(nss_test_ctx->tctx);
+
+    nss_test_ctx->nss_cmds = get_nss_cmds();
+    assert_non_null(nss_test_ctx->nss_cmds);
+
+    /* FIXME - perhaps this should be folded into sssd_domain_init or stricty
+     * used together
+     */
+    ret = sss_names_init(nss_test_ctx, nss_test_ctx->tctx->confdb,
+                         TEST_DOM_NAME, &nss_test_ctx->tctx->dom->names);
+    assert_int_equal(ret, EOK);
+
+    /* Initialize the NSS responder */
+    nss_test_ctx->nctx = mock_nctx(nss_test_ctx);
+    assert_non_null(nss_test_ctx->nctx);
+
+    nss_test_ctx->rctx = mock_rctx(nss_test_ctx, nss_test_ctx->tctx->ev,
+                                   nss_test_ctx->tctx->dom, nss_test_ctx->nctx);
+    assert_non_null(nss_test_ctx->rctx);
+
+    /* Create client context */
+    nss_test_ctx->cctx = mock_cctx(nss_test_ctx, nss_test_ctx->rctx);
+    assert_non_null(nss_test_ctx->cctx);
+}
+
+void nss_test_teardown(void **state)
+{
+    talloc_free(nss_test_ctx);
+}
+
+int main(int argc, const char *argv[])
+{
+    int rv;
+    int no_cleanup = 0;
+    poptContext pc;
+    int opt;
+    struct poptOption long_options[] = {
+        POPT_AUTOHELP
+        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_setup_teardown(test_nss_getpwnam,
+                                 nss_test_setup, nss_test_teardown),
+        unit_test_setup_teardown(test_nss_getpwnam_neg,
+                                 nss_test_setup, nss_test_teardown),
+        unit_test_setup_teardown(test_nss_getpwnam_search,
+                                 nss_test_setup, nss_test_teardown),
+        unit_test_setup_teardown(test_nss_getpwnam_update,
+                                 nss_test_setup, nss_test_teardown),
+    };
+
+    /* 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_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();
+    test_dom_suite_cleanup(TESTS_PATH, TEST_CONF_DB, TEST_SYSDB_FILE);
+    test_dom_suite_setup(TESTS_PATH);
+
+    rv = run_tests(tests);
+    if (rv == 0 && !no_cleanup) {
+        test_dom_suite_cleanup(TESTS_PATH, TEST_CONF_DB, TEST_SYSDB_FILE);
+    }
+    return rv;
+}
-- 
1.8.1.4



More information about the sssd-devel mailing list