/*
    SSSD

    util-tests.c

    Authors:
        Stephen Gallagher <sgallagh@redhat.com>

    Copyright (C) 2010 Red Hat

    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 <popt.h>
#include <talloc.h>
#include <check.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "util/util.h"
#include "util/sss_utf8.h"
#include "util/murmurhash3.h"
#include "tests/common.h"

#define FILENAME_TEMPLATE "tests-atomicio-XXXXXX"
char *filename;
int atio_fd;

START_TEST(test_parse_args)
{
    struct pa_testcase {
        const char *argstr;
        const char **parsed;
    };

    TALLOC_CTX *test_ctx;
    int i, ii;
    int ret;
    char **parsed;
    char **only_ret;
    char **only_exp;
    char **both;

    test_ctx = talloc_new(NULL);

    /* Positive tests */
    const char *parsed1[] = { "foo", NULL };
    const char *parsed2[] = { "foo", "a", NULL };
    const char *parsed3[] = { "foo", "b", NULL };
    const char *parsed4[] = { "foo", "a c", NULL };
    const char *parsed5[] = { "foo", "a", "d", NULL };
    const char *parsed6[] = { "foo", "a", "e", NULL };
    const char *parsed7[] = { "foo", "a", "f", NULL };
    const char *parsed8[] = { "foo", "a\tg", NULL };
    struct pa_testcase tc[] = {
        { "foo", parsed1 },
        { "foo a", parsed2 },
        { "foo  b", parsed3 },
        { "foo a\\ c", parsed4 },
        { "foo a d ", parsed5 },
        { "foo   a   e   ", parsed6 },
        { "foo	a	 	f 	", parsed7 },
        { "foo a\\\tg", parsed8 },
        { NULL, NULL }
    };

    for (i=0; tc[i].argstr != NULL; i++) {
        parsed = parse_args(tc[i].argstr);
        fail_if(parsed == NULL && tc[i].parsed != NULL,
                "Could not parse correct argument string '%s'\n");

        ret = diff_string_lists(test_ctx, parsed, discard_const(tc[i].parsed),
                                &only_ret, &only_exp, &both);
        fail_unless(ret == EOK, "diff_string_lists returned error [%d]", ret);
        fail_unless(only_ret[0] == NULL, "The parser returned more data than expected\n");
        fail_unless(only_exp[0] == NULL, "The parser returned less data than expected\n");

        for (ii = 0; parsed[ii]; ii++) free(parsed[ii]);
        free(parsed);
    }

    talloc_free(test_ctx);
}
END_TEST

START_TEST(test_diff_string_lists)
{
    TALLOC_CTX *test_ctx;
    char **l1;
    char **l2;
    char **l3;
    char **only_l1;
    char **only_l2;
    char **both;
    int ret;

    test_ctx = talloc_new(NULL);

    /* Test with all values returned */
    l1 = talloc_array(test_ctx, char *, 4);
    l1[0] = talloc_strdup(l1, "a");
    l1[1] = talloc_strdup(l1, "b");
    l1[2] = talloc_strdup(l1, "c");
    l1[3] = NULL;

    l2 = talloc_array(test_ctx, char *, 4);
    l2[0] = talloc_strdup(l1, "d");
    l2[1] = talloc_strdup(l1, "c");
    l2[2] = talloc_strdup(l1, "b");
    l2[3] = NULL;

    ret = diff_string_lists(test_ctx,
                            l1, l2,
                            &only_l1, &only_l2, &both);

    fail_unless(ret == EOK, "diff_string_lists returned error [%d]", ret);
    fail_unless(strcmp(only_l1[0], "a") == 0, "Missing \"a\" from only_l1");
    fail_unless(only_l1[1] == NULL, "only_l1 not NULL-terminated");
    fail_unless(strcmp(only_l2[0], "d") == 0, "Missing \"d\" from only_l2");
    fail_unless(only_l2[1] == NULL, "only_l2 not NULL-terminated");
    fail_unless(strcmp(both[0], "c") == 0, "Missing \"c\" from both");
    fail_unless(strcmp(both[1], "b") == 0, "Missing \"b\" from both");
    fail_unless(both[2] == NULL, "both not NULL-terminated");

    talloc_zfree(only_l1);
    talloc_zfree(only_l2);
    talloc_zfree(both);

    /* Test with restricted return values */
    ret = diff_string_lists(test_ctx,
                            l1, l2,
                            &only_l1, &only_l2, NULL);

    fail_unless(ret == EOK, "diff_string_lists returned error [%d]", ret);
    fail_unless(strcmp(only_l1[0], "a") == 0, "Missing \"a\" from only_l1");
    fail_unless(only_l1[1] == NULL, "only_l1 not NULL-terminated");
    fail_unless(strcmp(only_l2[0], "d") == 0, "Missing \"d\" from only_l2");
    fail_unless(only_l2[1] == NULL, "only_l2 not NULL-terminated");
    fail_unless(both == NULL, "Nothing returned to both");

    talloc_zfree(only_l1);
    talloc_zfree(only_l2);
    talloc_zfree(both);

    ret = diff_string_lists(test_ctx,
                            l1, l2,
                            &only_l1, NULL, NULL);

    fail_unless(ret == EOK, "diff_string_lists returned error [%d]", ret);
    fail_unless(strcmp(only_l1[0], "a") == 0, "Missing \"a\" from only_l1");
    fail_unless(only_l1[1] == NULL, "only_l1 not NULL-terminated");
    fail_unless(only_l2 == NULL, "Nothing returned to only_l2");
    fail_unless(both == NULL, "Nothing returned to both");

    talloc_zfree(only_l1);
    talloc_zfree(only_l2);
    talloc_zfree(both);

    ret = diff_string_lists(test_ctx,
                            l1, l2,
                            NULL, &only_l2, NULL);

    fail_unless(ret == EOK, "diff_string_lists returned error [%d]", ret);
    fail_unless(strcmp(only_l2[0], "d") == 0, "Missing \"d\" from only_l2");
    fail_unless(only_l2[1] == NULL, "only_l2 not NULL-terminated");
    fail_unless(only_l1 == NULL, "Nothing returned to only_l1");
    fail_unless(both == NULL, "Nothing returned to both");

    talloc_zfree(only_l1);
    talloc_zfree(only_l2);
    talloc_zfree(both);

    /* Test with no overlap */
    l3 = talloc_array(test_ctx, char *, 4);
    l3[0] = talloc_strdup(l1, "d");
    l3[1] = talloc_strdup(l1, "e");
    l3[2] = talloc_strdup(l1, "f");
    l3[3] = NULL;

    ret = diff_string_lists(test_ctx,
                            l1, l3,
                            &only_l1, &only_l2, &both);

    fail_unless(ret == EOK, "diff_string_lists returned error [%d]", ret);
    fail_unless(strcmp(only_l1[0], "a") == 0, "Missing \"a\" from only_l1");
    fail_unless(strcmp(only_l1[1], "b") == 0, "Missing \"b\" from only_l1");
    fail_unless(strcmp(only_l1[2], "c") == 0, "Missing \"c\" from only_l1");
    fail_unless(only_l1[3] == NULL, "only_l1 not NULL-terminated");
    fail_unless(strcmp(only_l2[0], "d") == 0, "Missing \"f\" from only_l2");
    fail_unless(strcmp(only_l2[1], "e") == 0, "Missing \"e\" from only_l2");
    fail_unless(strcmp(only_l2[2], "f") == 0, "Missing \"d\" from only_l2");
    fail_unless(only_l2[3] == NULL, "only_l2 not NULL-terminated");
    fail_unless(both[0] == NULL, "both should have zero entries");

    talloc_zfree(only_l1);
    talloc_zfree(only_l2);
    talloc_zfree(both);

    /* Test with 100% overlap */
    ret = diff_string_lists(test_ctx,
                            l1, l1,
                            &only_l1, &only_l2, &both);

    fail_unless(ret == EOK, "diff_string_lists returned error [%d]", ret);
    fail_unless(only_l1[0] == NULL, "only_l1 should have zero entries");
    fail_unless(only_l2[0] == NULL, "only_l2 should have zero entries");
    fail_unless(strcmp(both[0], "a") == 0, "Missing \"a\" from both");
    fail_unless(strcmp(both[1], "b") == 0, "Missing \"b\" from both");
    fail_unless(strcmp(both[2], "c") == 0, "Missing \"c\" from both");
    fail_unless(both[3] == NULL, "both is not NULL-terminated");

    talloc_zfree(only_l1);
    talloc_zfree(only_l2);
    talloc_zfree(both);

    /* Test with no second list */
    ret = diff_string_lists(test_ctx,
                            l1, NULL,
                            &only_l1, &only_l2, &both);

    fail_unless(ret == EOK, "diff_string_lists returned error [%d]", ret);
    fail_unless(strcmp(only_l1[0], "a") == 0, "Missing \"a\" from only_l1");
    fail_unless(strcmp(only_l1[1], "b") == 0, "Missing \"b\" from only_l1");
    fail_unless(strcmp(only_l1[2], "c") == 0, "Missing \"c\" from only_l1");
    fail_unless(only_l1[3] == NULL, "only_l1 not NULL-terminated");
    fail_unless(only_l2[0] == NULL, "only_l2 should have zero entries");
    fail_unless(both[0] == NULL, "both should have zero entries");

    talloc_free(test_ctx);
}
END_TEST


START_TEST(test_sss_filter_sanitize)
{
    errno_t ret;
    char *sanitized = NULL;

    TALLOC_CTX *test_ctx = talloc_new(NULL);
    fail_if (test_ctx == NULL, "Out of memory");

    const char no_specials[] = "username";
    ret = sss_filter_sanitize(test_ctx, no_specials, &sanitized);
    fail_unless(ret == EOK, "no_specials error [%d][%s]",
                ret, strerror(ret));
    fail_unless(strcmp(no_specials, sanitized)==0,
                "Expected [%s], got [%s]",
                no_specials, sanitized);

    const char has_asterisk[] = "*username";
    const char has_asterisk_expected[] = "\\2ausername";
    ret = sss_filter_sanitize(test_ctx, has_asterisk, &sanitized);
    fail_unless(ret == EOK, "has_asterisk error [%d][%s]",
                ret, strerror(ret));
    fail_unless(strcmp(has_asterisk_expected, sanitized)==0,
                "Expected [%s], got [%s]",
                has_asterisk_expected, sanitized);

    const char has_lparen[] = "user(name";
    const char has_lparen_expected[] = "user\\28name";
    ret = sss_filter_sanitize(test_ctx, has_lparen, &sanitized);
    fail_unless(ret == EOK, "has_lparen error [%d][%s]",
                ret, strerror(ret));
    fail_unless(strcmp(has_lparen_expected, sanitized)==0,
                "Expected [%s], got [%s]",
                has_lparen_expected, sanitized);

    const char has_rparen[] = "user)name";
    const char has_rparen_expected[] = "user\\29name";
    ret = sss_filter_sanitize(test_ctx, has_rparen, &sanitized);
    fail_unless(ret == EOK, "has_rparen error [%d][%s]",
                ret, strerror(ret));
    fail_unless(strcmp(has_rparen_expected, sanitized)==0,
                "Expected [%s], got [%s]",
                has_rparen_expected, sanitized);

    const char has_backslash[] = "username\\";
    const char has_backslash_expected[] = "username\\5c";
    ret = sss_filter_sanitize(test_ctx, has_backslash, &sanitized);
    fail_unless(ret == EOK, "has_backslash error [%d][%s]",
                ret, strerror(ret));
    fail_unless(strcmp(has_backslash_expected, sanitized)==0,
                "Expected [%s], got [%s]",
                has_backslash_expected, sanitized);

    const char has_all[] = "\\(user)*name";
    const char has_all_expected[] = "\\5c\\28user\\29\\2aname";
    ret = sss_filter_sanitize(test_ctx, has_all, &sanitized);
    fail_unless(ret == EOK, "has_all error [%d][%s]",
                ret, strerror(ret));
    fail_unless(strcmp(has_all_expected, sanitized)==0,
                "Expected [%s], got [%s]",
                has_all_expected, sanitized);

    talloc_free(test_ctx);
}
END_TEST

START_TEST(test_size_t_overflow)
{
    fail_unless(!SIZE_T_OVERFLOW(1, 1), "unexpected overflow");
    fail_unless(!SIZE_T_OVERFLOW(SIZE_T_MAX, 0), "unexpected overflow");
    fail_unless(!SIZE_T_OVERFLOW(SIZE_T_MAX-10, 10), "unexpected overflow");
    fail_unless(SIZE_T_OVERFLOW(SIZE_T_MAX, 1), "overflow not detected");
    fail_unless(SIZE_T_OVERFLOW(SIZE_T_MAX, SIZE_T_MAX),
                "overflow not detected");
    fail_unless(SIZE_T_OVERFLOW(SIZE_T_MAX, ULLONG_MAX),
                "overflow not detected");
    fail_unless(SIZE_T_OVERFLOW(SIZE_T_MAX, -10), "overflow not detected");
}
END_TEST

START_TEST(test_utf8_lowercase)
{
    const uint8_t munchen_utf8_upcase[] = { 'M', 0xC3, 0x9C, 'N', 'C', 'H', 'E', 'N', 0x0 };
    const uint8_t munchen_utf8_lowcase[] = { 'm', 0xC3, 0xBC, 'n', 'c', 'h', 'e', 'n', 0x0 };
    uint8_t *lcase;
    size_t nlen;

    lcase = sss_utf8_tolower(munchen_utf8_upcase,
                             strlen((const char *)munchen_utf8_upcase),
                             &nlen);
    fail_if(strlen((const char *) munchen_utf8_upcase) != nlen); /* This is not true for utf8 strings in general */
    fail_if(memcmp(lcase, munchen_utf8_lowcase, nlen));
    sss_utf8_free(lcase);
}
END_TEST

START_TEST(test_utf8_talloc_lowercase)
{
    const uint8_t munchen_utf8_upcase[] = { 'M', 0xC3, 0x9C, 'N', 'C', 'H', 'E', 'N', 0x0 };
    const uint8_t munchen_utf8_lowcase[] = { 'm', 0xC3, 0xBC, 'n', 'c', 'h', 'e', 'n', 0x0 };
    uint8_t *lcase;
    size_t nsize;

    TALLOC_CTX *test_ctx;
    test_ctx = talloc_new(NULL);
    fail_if(test_ctx == NULL);

    lcase = sss_tc_utf8_tolower(test_ctx, munchen_utf8_upcase,
                                strlen((const char *) munchen_utf8_upcase),
                                &nsize);
    fail_if(memcmp(lcase, munchen_utf8_lowcase, nsize));
    talloc_free(test_ctx);
}
END_TEST

START_TEST(test_utf8_talloc_str_lowercase)
{
    const uint8_t munchen_utf8_upcase[] = { 'M', 0xC3, 0x9C, 'N', 'C', 'H', 'E', 'N', 0x0 };
    const uint8_t munchen_utf8_lowcase[] = { 'm', 0xC3, 0xBC, 'n', 'c', 'h', 'e', 'n', 0x0 };
    char *lcase;

    TALLOC_CTX *test_ctx;
    test_ctx = talloc_new(NULL);
    fail_if(test_ctx == NULL);

    lcase = sss_tc_utf8_str_tolower(test_ctx, (const char *) munchen_utf8_upcase);
    fail_if(memcmp(lcase, munchen_utf8_lowcase, strlen(lcase)));
    talloc_free(test_ctx);
}
END_TEST

START_TEST(test_utf8_caseeq)
{
    const uint8_t munchen_utf8_upcase[] = { 'M', 0xC3, 0x9C, 'N', 'C', 'H', 'E', 'N', 0x0 };
    const uint8_t munchen_utf8_lowcase[] = { 'm', 0xC3, 0xBC, 'n', 'c', 'h', 'e', 'n', 0x0 };
    const uint8_t czech_utf8_lowcase[] = { 0xC4, 0x8D, 'e', 'c', 'h', 0x0 };
    const uint8_t czech_utf8_upcase[] = { 0xC4, 0x8C, 'e', 'c', 'h', 0x0 };
    const uint8_t czech_utf8_lowcase_neg[] = { 0xC4, 0x8E, 'e', 'c', 'h', 0x0 };
    errno_t ret;

    ret = sss_utf8_case_eq(munchen_utf8_upcase, munchen_utf8_lowcase);
    fail_unless(ret == EOK, "Latin 1 Supplement comparison failed\n");

    ret = sss_utf8_case_eq(czech_utf8_upcase, czech_utf8_lowcase);
    fail_unless(ret == EOK, "Latin Extended A comparison failed\n");

    ret = sss_utf8_case_eq(czech_utf8_upcase, czech_utf8_lowcase_neg);
    fail_if(ret == EOK, "Negative test succeeded\n");
}
END_TEST

START_TEST(test_utf8_check)
{
    const char *invalid = "ad\351la\357d";
    const uint8_t valid[] = { 'M', 0xC3, 0x9C, 'N', 'C', 'H', 'E', 'N', 0x0 };
    bool ret;

    ret = sss_utf8_check(valid, strlen((const char *) valid));
    fail_unless(ret == true, "Positive test failed\n");

    ret = sss_utf8_check((const uint8_t *) invalid, strlen(invalid));
    fail_unless(ret == false, "Negative test succeeded\n");
}
END_TEST

START_TEST(test_murmurhash3_check)
{
    const char *tests[6] = { "1052800007", "1052800008", "1052800000",
                             "abcdefghijk", "abcdefghili", "abcdefgh000" };
    uint32_t results[6];
    int i, j;

    for (i = 0; i< 6; i++) {
        results[i] = murmurhash3(tests[i],
                                 strlen(tests[i]),
                                 0xdeadbeef);
        for (j = 0; j < i; j++) {
            fail_if(results[i] == results[j]);
        }
    }
}
END_TEST

START_TEST(test_murmurhash3_random)
{
    char test[16];
    uint32_t result1;
    uint32_t result2;
    unsigned int init_seed;
    unsigned int seed;
    size_t len;
    int i;

    /* generate a random string so each time we test with different values */
    init_seed = time(0);
    seed = init_seed;
    /* use also random length (min len = 1) */
    len = 1 + rand_r(&seed) % 14;
    for (i = 0; i < len; i++) {
        test[i] = 1 + rand_r(&seed) % 254;
    }
    test[len] = '\0'; /* null terminate */

    fprintf(stdout, "test_murmurhash3_random seed: %d\n", init_seed);

    result1 = murmurhash3(test, len + 1, init_seed);
    result2 = murmurhash3(test, len + 1, init_seed);
    fail_if(result1 != result2);
}
END_TEST

void setup_atomicio(void)
{
    int ret;
    mode_t old_umask;

    filename = strdup(FILENAME_TEMPLATE);
    fail_unless(filename != NULL, "strdup failed");

    atio_fd = -1;
    old_umask = umask(077);
    ret = mkstemp(filename);
    umask(old_umask);
    fail_unless(ret != -1, "mkstemp failed [%d][%s]", errno, strerror(errno));
    atio_fd = ret;
}

void teardown_atomicio(void)
{
    int ret;

    if (atio_fd != -1) {
        ret = close(atio_fd);
        fail_unless(ret == 0, "close failed [%d][%s]", errno, strerror(errno));
    }

    fail_unless(filename != NULL, "unknown filename");
    ret = unlink(filename);
    free(filename);
    fail_unless(ret == 0, "unlink failed [%d][%s]", errno, strerror(errno));
}

START_TEST(test_atomicio_read_from_file)
{
    const ssize_t bufsize = 64;
    char buf[64];
    int fd;
    ssize_t numread;
    errno_t ret;

    fd = open("/dev/zero", O_RDONLY);
    fail_if(fd == -1, "Cannot open /dev/zero");

    errno = 0;
    numread = sss_atomic_read_s(fd, buf, bufsize);
    ret = errno;

    fail_unless(ret == 0, "Error %d while reading\n", ret);
    fail_unless(numread == bufsize,
                "Read %d bytes expected %d\n", numread, bufsize);
    close(fd);
}
END_TEST

START_TEST(test_atomicio_read_from_small_file)
{
    char wbuf[] = "foobar";
    ssize_t wsize = strlen(wbuf)+1;
    ssize_t numwritten;
    char rbuf[64];
    ssize_t numread;
    errno_t ret;

    fail_if(atio_fd < 0, "No fd to test?\n");

    errno = 0;
    numwritten = sss_atomic_write_s(atio_fd, wbuf, wsize);
    ret = errno;

    fail_unless(ret == 0, "Error %d while writing\n", ret);
    fail_unless(numwritten == wsize,
                "Wrote %d bytes expected %d\n", numwritten, wsize);

    fsync(atio_fd);
    lseek(atio_fd, 0, SEEK_SET);

    errno = 0;
    numread = sss_atomic_read_s(atio_fd, rbuf, 64);
    ret = errno;

    fail_unless(ret == 0, "Error %d while reading\n", ret);
    fail_unless(numread == numwritten,
                "Read %d bytes expected %d\n", numread, numwritten);
}
END_TEST

START_TEST(test_atomicio_read_from_large_file)
{
    char wbuf[] = "123456781234567812345678";
    ssize_t wsize = strlen(wbuf)+1;
    ssize_t numwritten;
    char rbuf[8];
    ssize_t numread;
    ssize_t total;
    errno_t ret;

    fail_if(atio_fd < 0, "No fd to test?\n");

    errno = 0;
    numwritten = sss_atomic_write_s(atio_fd, wbuf, wsize);
    ret = errno;

    fail_unless(ret == 0, "Error %d while writing\n", ret);
    fail_unless(numwritten == wsize,
                "Wrote %d bytes expected %d\n", numwritten, wsize);

    fsync(atio_fd);
    lseek(atio_fd, 0, SEEK_SET);

    total = 0;
    do {
        errno = 0;
        numread = sss_atomic_read_s(atio_fd, rbuf, 8);
        ret = errno;

        fail_if(numread == -1, "Read error %d: %s\n", ret, strerror(ret));
        total += numread;
    } while (numread != 0);

    fail_unless(ret == 0, "Error %d while reading\n", ret);
    fail_unless(total == numwritten,
                "Read %d bytes expected %d\n", numread, numwritten);
}
END_TEST

START_TEST(test_atomicio_read_exact_sized_file)
{
    char wbuf[] = "12345678";
    ssize_t wsize = strlen(wbuf)+1;
    ssize_t numwritten;
    char rbuf[9];
    ssize_t numread;
    errno_t ret;

    fail_if(atio_fd < 0, "No fd to test?\n");

    errno = 0;
    numwritten = sss_atomic_write_s(atio_fd, wbuf, wsize);
    ret = errno;

    fail_unless(ret == 0, "Error %d while writing\n", ret);
    fail_unless(numwritten == wsize,
                "Wrote %d bytes expected %d\n", numwritten, wsize);

    fsync(atio_fd);
    lseek(atio_fd, 0, SEEK_SET);

    errno = 0;
    numread = sss_atomic_read_s(atio_fd, rbuf, 9);
    ret = errno;

    fail_unless(ret == 0, "Error %d while reading\n", ret);
    fail_unless(numread == numwritten,
                "Read %d bytes expected %d\n", numread, numwritten);

    fail_unless(rbuf[8] == '\0', "String not NULL terminated?");
    fail_unless(strcmp(wbuf, rbuf) == 0, "Read something else than wrote?");

    /* We've reached end-of-file, next read must return 0 */
    errno = 0;
    numread = sss_atomic_read_s(atio_fd, rbuf, 9);
    ret = errno;

    fail_unless(ret == 0, "Error %d while reading\n", ret);
    fail_unless(numread == 0, "More data to read?");
}
END_TEST

START_TEST(test_atomicio_read_from_empty_file)
{
    char buf[64];
    int fd;
    ssize_t numread;
    errno_t ret;

    fd = open("/dev/null", O_RDONLY);
    fail_if(fd == -1, "Cannot open /dev/null");

    errno = 0;
    numread = sss_atomic_read_s(fd, buf, 64);
    ret = errno;

    fail_unless(ret == 0, "Error %d while reading\n", ret);
    fail_unless(numread == 0,
                "Read %d bytes expected 0\n", numread);
    close(fd);
}
END_TEST

Suite *util_suite(void)
{
    Suite *s = suite_create("util");

    TCase *tc_util = tcase_create("util");

    tcase_add_test (tc_util, test_diff_string_lists);
    tcase_add_test (tc_util, test_sss_filter_sanitize);
    tcase_add_test (tc_util, test_size_t_overflow);
    tcase_add_test (tc_util, test_parse_args);
    tcase_set_timeout(tc_util, 60);

    TCase *tc_utf8 = tcase_create("utf8");
    tcase_add_test (tc_utf8, test_utf8_lowercase);
    tcase_add_test (tc_utf8, test_utf8_talloc_lowercase);
    tcase_add_test (tc_utf8, test_utf8_talloc_str_lowercase);
    tcase_add_test (tc_utf8, test_utf8_caseeq);
    tcase_add_test (tc_utf8, test_utf8_check);

    tcase_set_timeout(tc_utf8, 60);

    TCase *tc_mh3 = tcase_create("murmurhash3");
    tcase_add_test (tc_mh3, test_murmurhash3_check);
    tcase_add_test (tc_mh3, test_murmurhash3_random);
    tcase_set_timeout(tc_mh3, 60);

    TCase *tc_atomicio = tcase_create("atomicio");
    tcase_add_checked_fixture (tc_atomicio,
                               setup_atomicio,
                               teardown_atomicio);
    tcase_add_test(tc_atomicio, test_atomicio_read_from_file);
    tcase_add_test(tc_atomicio, test_atomicio_read_from_small_file);
    tcase_add_test(tc_atomicio, test_atomicio_read_from_large_file);
    tcase_add_test(tc_atomicio, test_atomicio_read_exact_sized_file);
    tcase_add_test(tc_atomicio, test_atomicio_read_from_empty_file);

    suite_add_tcase (s, tc_util);
    suite_add_tcase (s, tc_utf8);
    suite_add_tcase (s, tc_mh3);
    suite_add_tcase (s, tc_atomicio);

    return s;
}

int main(int argc, const char *argv[])
{
    int opt;
    int failure_count;
    poptContext pc;
    Suite *s = util_suite();
    SRunner *sr = srunner_create (s);

    struct poptOption long_options[] = {
        POPT_AUTOHELP
        SSSD_MAIN_OPTS
        POPT_TABLEEND
    };

    /* 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);

    CONVERT_AND_SET_DEBUG_LEVEL(debug_level);

    tests_set_cwd();

    srunner_run_all(sr, CK_ENV);
    failure_count = srunner_ntests_failed (sr);
    srunner_free (sr);
    if (failure_count == 0) {
        return EXIT_SUCCESS;
    }
    return  EXIT_FAILURE;
}