#include "cunit/cyrunit.h"
#include "xmalloc.h"
#include "bsearch.h"
#include "strarray.h"

static void test_fini_null(void)
{
    /* _fini(NULL) is harmless */
    strarray_fini(NULL);
    /* _free(NULL) is harmless */
    strarray_free(NULL);
}

static void test_auto(void)
{
    strarray_t sa = STRARRAY_INITIALIZER;
    char *s1;
    char *s2;

    CU_ASSERT_EQUAL(strarray_size(&sa), 0);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, 0));
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, -1));

    s1 = xstrdup("lorem ipsum");
    strarray_appendm(&sa, s1);
    CU_ASSERT_EQUAL(strarray_size(&sa), 1);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_PTR_EQUAL((char *)strarray_nth(&sa, 0), s1);
    CU_ASSERT_PTR_EQUAL((char *)strarray_nth(&sa, -1), s1);

    s2 = xstrdup("dolor sit");
    strarray_appendm(&sa, s2);
    CU_ASSERT_EQUAL(strarray_size(&sa), 2);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_PTR_EQUAL((char *)strarray_nth(&sa, 0), s1);
    CU_ASSERT_PTR_EQUAL((char *)strarray_nth(&sa, 1), s2);
    CU_ASSERT_PTR_EQUAL((char *)strarray_nth(&sa, -1), s2);

    strarray_fini(&sa);
    CU_ASSERT_EQUAL(strarray_size(&sa), 0);
    CU_ASSERT_EQUAL(sa.alloc, 0);
    CU_ASSERT_PTR_NULL(sa.data);
}

static void test_heap(void)
{
    strarray_t *sa = strarray_new();
    char *s1;
    char *s2;

    CU_ASSERT_EQUAL(sa->count, 0);
    CU_ASSERT(sa->alloc >= sa->count);
    CU_ASSERT_PTR_NULL((char *)strarray_nth(sa, 0));
    CU_ASSERT_PTR_NULL((char *)strarray_nth(sa, -1));

    s1 = xstrdup("lorem ipsum");
    strarray_appendm(sa, s1);
    CU_ASSERT_EQUAL(sa->count, 1);
    CU_ASSERT(sa->alloc >= sa->count);
    CU_ASSERT_PTR_NOT_NULL(sa->data);
    CU_ASSERT_PTR_EQUAL((char *)strarray_nth(sa, 0), s1);
    CU_ASSERT_PTR_EQUAL((char *)strarray_nth(sa, -1), s1);

    s2 = xstrdup("dolor sit");
    strarray_appendm(sa, s2);
    CU_ASSERT_EQUAL(sa->count, 2);
    CU_ASSERT(sa->alloc >= sa->count);
    CU_ASSERT_PTR_NOT_NULL(sa->data);
    CU_ASSERT_PTR_EQUAL((char *)strarray_nth(sa, 0), s1);
    CU_ASSERT_PTR_EQUAL((char *)strarray_nth(sa, 1), s2);
    CU_ASSERT_PTR_EQUAL((char *)strarray_nth(sa, -1), s2);

    strarray_free(sa);
}

static void test_set(void)
{
    strarray_t sa = STRARRAY_INITIALIZER;
#define WORD0   "lorem"
#define WORD0REP "DeLorean"
#define WORD0REP2 "Jiggawatts"
#define WORD1   "ipsum"
#define WORD2   "dolor"
#define WORD2REP "DOLORES"
#define WORD3   "sit"
#define WORD4   "amet"

    CU_ASSERT_EQUAL(strarray_size(&sa), 0);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, 0));
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, -1));

    strarray_append(&sa, WORD0);
    CU_ASSERT_EQUAL(strarray_size(&sa), 1);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0);

    strarray_set(&sa, 0, WORD0REP);
    CU_ASSERT_EQUAL(strarray_size(&sa), 1);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0REP);

    strarray_set(&sa, -1, WORD0REP2);
    CU_ASSERT_EQUAL(strarray_size(&sa), 1);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0REP2);

    strarray_append(&sa, WORD1);
    strarray_append(&sa, WORD2);
    strarray_append(&sa, WORD3);
    strarray_append(&sa, WORD4);
    CU_ASSERT_EQUAL(strarray_size(&sa), 5);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0REP2);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 2), WORD2);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 3), WORD3);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 4), WORD4);

    strarray_set(&sa, 2, WORD2REP);
    CU_ASSERT_EQUAL(strarray_size(&sa), 5);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0REP2);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 2), WORD2REP);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 3), WORD3);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 4), WORD4);

    strarray_fini(&sa);
#undef WORD0
#undef WORD0REP
#undef WORD0REP2
#undef WORD1
#undef WORD2
#undef WORD2REP
#undef WORD3
#undef WORD4
}

static void test_setm(void)
{
    strarray_t sa = STRARRAY_INITIALIZER;
#define WORD0   "lorem"
#define WORD0REP "DeLorean"
#define WORD0REP2 "Jiggawatts"
#define WORD1   "ipsum"
#define WORD2   "dolor"
#define WORD2REP "DOLORES"
#define WORD3   "sit"
#define WORD4   "amet"

    CU_ASSERT_EQUAL(strarray_size(&sa), 0);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, 0));
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, -1));

    strarray_appendm(&sa, xstrdup(WORD0));
    CU_ASSERT_EQUAL(strarray_size(&sa), 1);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0);

    strarray_setm(&sa, 0, xstrdup(WORD0REP));
    CU_ASSERT_EQUAL(strarray_size(&sa), 1);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0REP);

    strarray_setm(&sa, -1, xstrdup(WORD0REP2));
    CU_ASSERT_EQUAL(strarray_size(&sa), 1);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0REP2);

    strarray_appendm(&sa, xstrdup(WORD1));
    strarray_appendm(&sa, xstrdup(WORD2));
    strarray_appendm(&sa, xstrdup(WORD3));
    strarray_appendm(&sa, xstrdup(WORD4));
    CU_ASSERT_EQUAL(strarray_size(&sa), 5);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0REP2);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 2), WORD2);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 3), WORD3);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 4), WORD4);

    strarray_setm(&sa, 2, xstrdup(WORD2REP));
    CU_ASSERT_EQUAL(strarray_size(&sa), 5);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0REP2);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 2), WORD2REP);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 3), WORD3);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 4), WORD4);

    strarray_fini(&sa);
#undef WORD0
#undef WORD0REP
#undef WORD0REP2
#undef WORD1
#undef WORD2
#undef WORD2REP
#undef WORD3
#undef WORD4
}

static void test_insert(void)
{
    strarray_t sa = STRARRAY_INITIALIZER;
#define WORD0   "lorem"
#define WORD1   "ipsum"
#define WORD2   "dolor"
#define WORD3   "sit"
#define WORD4   "amet"

    CU_ASSERT_EQUAL(strarray_size(&sa), 0);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, 0));
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, -1));

    strarray_insert(&sa, 0, WORD0);
    CU_ASSERT_EQUAL(strarray_size(&sa), 1);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0);

    strarray_insert(&sa, -1, WORD1);
    CU_ASSERT_EQUAL(strarray_size(&sa), 2);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD0);

    strarray_insert(&sa, 0, WORD2);
    CU_ASSERT_EQUAL(strarray_size(&sa), 3);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD2);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 2), WORD0);

    strarray_insert(&sa, -1, WORD3);
    CU_ASSERT_EQUAL(strarray_size(&sa), 4);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD2);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 2), WORD3);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 3), WORD0);

    strarray_insert(&sa, 2, WORD4);
    CU_ASSERT_EQUAL(strarray_size(&sa), 5);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD2);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 2), WORD4);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 3), WORD3);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 4), WORD0);

    strarray_fini(&sa);
#undef WORD0
#undef WORD1
#undef WORD2
#undef WORD3
#undef WORD4
}

static void test_insertm(void)
{
    strarray_t sa = STRARRAY_INITIALIZER;
#define WORD0   "lorem"
#define WORD1   "ipsum"
#define WORD2   "dolor"
#define WORD3   "sit"
#define WORD4   "amet"

    CU_ASSERT_EQUAL(strarray_size(&sa), 0);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, 0));
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, -1));

    strarray_insertm(&sa, 0, xstrdup(WORD0));
    CU_ASSERT_EQUAL(strarray_size(&sa), 1);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0);

    strarray_insertm(&sa, -1, xstrdup(WORD1));
    CU_ASSERT_EQUAL(strarray_size(&sa), 2);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD0);

    strarray_insertm(&sa, 0, xstrdup(WORD2));
    CU_ASSERT_EQUAL(strarray_size(&sa), 3);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD2);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 2), WORD0);

    strarray_insertm(&sa, -1, xstrdup(WORD3));
    CU_ASSERT_EQUAL(strarray_size(&sa), 4);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD2);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 2), WORD3);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 3), WORD0);

    strarray_insertm(&sa, 2, xstrdup(WORD4));
    CU_ASSERT_EQUAL(strarray_size(&sa), 5);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD2);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 2), WORD4);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 3), WORD3);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 4), WORD0);

    strarray_fini(&sa);
#undef WORD0
#undef WORD1
#undef WORD2
#undef WORD3
#undef WORD4
}

/* test that _set(), _setm(), _insert() and _insertm() of a bad
 * index will fail silently and leave no side effects including
 * memory leaks */
static void test_bad_index(void)
{
    strarray_t sa = STRARRAY_INITIALIZER;
#define WORD0   "lorem"
#define WORD1   "ipsum"
#define WORD2   "dolor"
    char *s = xstrdup(WORD0);

    CU_ASSERT_EQUAL(strarray_size(&sa), 0);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, 0));
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, -1));

    /* when the strarray is empty, -1 is a bad index */

    strarray_setm(&sa, -1, s);
    CU_ASSERT_EQUAL(strarray_size(&sa), 0);
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, 0));
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, -1));

    strarray_set(&sa, -1, WORD0);
    CU_ASSERT_EQUAL(strarray_size(&sa), 0);
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, 0));
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, -1));

    strarray_insertm(&sa, -1, s);
    CU_ASSERT_EQUAL(strarray_size(&sa), 0);
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, 0));
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, -1));

    strarray_insert(&sa, -1, WORD0);
    CU_ASSERT_EQUAL(strarray_size(&sa), 0);
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, 0));
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, -1));

    /* a negative number larger than the (non-zero) count is a bad index */
    strarray_append(&sa, WORD1);
    strarray_append(&sa, WORD2);

    strarray_setm(&sa, -4, s);
    CU_ASSERT_EQUAL(strarray_size(&sa), 2);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, -1), WORD2);

    strarray_set(&sa, -4, WORD0);
    CU_ASSERT_EQUAL(strarray_size(&sa), 2);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, -1), WORD2);

    strarray_insertm(&sa, -4, s);
    CU_ASSERT_EQUAL(strarray_size(&sa), 2);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, -1), WORD2);

    strarray_insert(&sa, -4, WORD0);
    CU_ASSERT_EQUAL(strarray_size(&sa), 2);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, -1), WORD2);

    free(s);
    strarray_fini(&sa);
#undef WORD0
#undef WORD1
#undef WORD2
}

/* test building a sparse array with _set() and _setm() */
static void test_sparse_set(void)
{
    strarray_t sa = STRARRAY_INITIALIZER;
#define WORD0   "lorem"
#define WORD1   "ipsum"
#define WORD2   "dolor"

    CU_ASSERT_EQUAL(strarray_size(&sa), 0);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, 0));
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, -1));

    strarray_set(&sa, 3, WORD0);
    CU_ASSERT_EQUAL(strarray_size(&sa), 4);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, 0));
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, 1));
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, 2));
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 3), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, -1), WORD0);

    strarray_fini(&sa);
#undef WORD0
#undef WORD1
#undef WORD2
}

static void test_join(void)
{
    strarray_t sa = STRARRAY_INITIALIZER;
    char *s;
#define WORD0   "lorem"
#define WORD1   "ipsum"
#define WORD2   "dolor"
#define WORD3   "sit"
#define WORD4   "amet"

    CU_ASSERT_EQUAL(strarray_size(&sa), 0);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, 0));
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, -1));

    s = strarray_join(&sa, NULL);
    CU_ASSERT_PTR_NULL(s);

    s = strarray_join(&sa, " ");
    CU_ASSERT_PTR_NULL(s);

    strarray_append(&sa, WORD0);
    strarray_append(&sa, WORD1);
    strarray_append(&sa, WORD2);
    strarray_append(&sa, WORD3);
    strarray_append(&sa, WORD4);
    CU_ASSERT_EQUAL(strarray_size(&sa), 5);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 2), WORD2);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 3), WORD3);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 4), WORD4);

    s = strarray_join(&sa, NULL);
    CU_ASSERT_STRING_EQUAL(s, WORD0""WORD1""WORD2""WORD3""WORD4);
    free(s);

    s = strarray_join(&sa, " ");
    CU_ASSERT_STRING_EQUAL(s, WORD0" "WORD1" "WORD2" "WORD3" "WORD4);
    free(s);

    s = strarray_join(&sa, "-X-");
    CU_ASSERT_STRING_EQUAL(s, WORD0"-X-"WORD1"-X-"WORD2"-X-"WORD3"-X-"WORD4);
    free(s);

    /* check that sa is unharmed by the join */
    CU_ASSERT_EQUAL(strarray_size(&sa), 5);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 2), WORD2);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 3), WORD3);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 4), WORD4);

    strarray_fini(&sa);
#undef WORD0
#undef WORD1
#undef WORD2
#undef WORD3
#undef WORD4
}

/* regression test for a bug discovered while reading the
 * strarray source when triaging Coverity defects. */
static void test_join_initial_null(void)
{
    strarray_t sa = STRARRAY_INITIALIZER;
    char *s;
#define WORD0   "lorem"
#define WORD1   "ipsum"

    CU_ASSERT_EQUAL(strarray_size(&sa), 0);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, 0));
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, -1));

    /* [0] element is NULL */
    strarray_set(&sa, 1, WORD0);
    strarray_set(&sa, 2, WORD1);
    CU_ASSERT_EQUAL(strarray_size(&sa), 3);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, 0));
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 2), WORD1);

    s = strarray_join(&sa, NULL);
    CU_ASSERT_STRING_EQUAL(s, WORD0""WORD1);
    free(s);

    s = strarray_join(&sa, " ");
    CU_ASSERT_STRING_EQUAL(s, WORD0" "WORD1);
    free(s);

    s = strarray_join(&sa, "-X-");
    CU_ASSERT_STRING_EQUAL(s, WORD0"-X-"WORD1);
    free(s);

    /* check that sa is unharmed by the join */
    CU_ASSERT_EQUAL(strarray_size(&sa), 3);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, 0));
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 2), WORD1);

    strarray_fini(&sa);
#undef WORD0
#undef WORD1
}

static void test_split(void)
{
    strarray_t *sa;
#define WORD0   "lorem"
#define WORD1   "ipsum"
#define WORD2   "dolor"
#define WORD3   "sit"
#define WORD4   "amet"

    /* 5 words, space separator */
    sa = strarray_split(WORD0" "WORD1" "WORD2" "WORD3" "WORD4, " ", 0);
    CU_ASSERT_PTR_NOT_NULL(sa);
    CU_ASSERT_EQUAL(sa->count, 5);
    CU_ASSERT(sa->alloc >= sa->count);
    CU_ASSERT_PTR_NOT_NULL(sa->data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 0), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 1), WORD1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 2), WORD2);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 3), WORD3);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 4), WORD4);
    strarray_free(sa);

    /* 5 words, NULL separator (whitespace) */
    sa = strarray_split(WORD0" "WORD1"\t"WORD2"\r"WORD3"\n"WORD4, NULL, 0);
    CU_ASSERT_PTR_NOT_NULL(sa);
    CU_ASSERT_EQUAL(sa->count, 5);
    CU_ASSERT(sa->alloc >= sa->count);
    CU_ASSERT_PTR_NOT_NULL(sa->data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 0), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 1), WORD1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 2), WORD2);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 3), WORD3);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 4), WORD4);
    strarray_free(sa);

    /* 5 words, several separators */
    sa = strarray_split(WORD0"("WORD1")"WORD2"["WORD3"]"WORD4, "[]()", 0);
    CU_ASSERT_PTR_NOT_NULL(sa);
    CU_ASSERT_EQUAL(sa->count, 5);
    CU_ASSERT(sa->alloc >= sa->count);
    CU_ASSERT_PTR_NOT_NULL(sa->data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 0), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 1), WORD1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 2), WORD2);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 3), WORD3);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 4), WORD4);
    strarray_free(sa);

    /* splitm - takes ownership of a strdup()d argument */
    sa = strarray_new();
    strarray_splitm(sa, xstrdup(WORD0" "WORD1" "WORD2" "WORD3" "WORD4), " ", 0);
    CU_ASSERT_PTR_NOT_NULL(sa);
    CU_ASSERT_EQUAL(sa->count, 5);
    CU_ASSERT(sa->alloc >= sa->count);
    CU_ASSERT_PTR_NOT_NULL(sa->data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 0), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 1), WORD1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 2), WORD2);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 3), WORD3);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 4), WORD4);
    strarray_free(sa);

    /* nsplit - specify a byte range to copy and split */
    sa = strarray_nsplit(WORD0" "WORD1" "WORD2" "WORD3" "WORD4,
                         sizeof(WORD0)+sizeof(WORD1)+sizeof(WORD2), " ", 0);
    CU_ASSERT_PTR_NOT_NULL(sa);
    CU_ASSERT_EQUAL(sa->count, 3);
    CU_ASSERT(sa->alloc >= sa->count);
    CU_ASSERT_PTR_NOT_NULL(sa->data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 0), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 1), WORD1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 2), WORD2);
    strarray_free(sa);

    /* split with surrounding whitespace */
    sa = strarray_split(WORD0"| "WORD1" | "WORD2" |"WORD3" | "WORD4"| ", "|", 0);
    CU_ASSERT_PTR_NOT_NULL(sa);
    CU_ASSERT_EQUAL(sa->count, 6);
    CU_ASSERT(sa->alloc >= sa->count);
    CU_ASSERT_PTR_NOT_NULL(sa->data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 0), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 1), " "WORD1" ");
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 2), " "WORD2" ");
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 3), WORD3" ");
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 4), " "WORD4);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 5), " ");
    strarray_free(sa);

    /* trim surrounding whitespace */
    sa = strarray_split(WORD0"| "WORD1" | "WORD2" |"WORD3" | "WORD4"| ", "|", STRARRAY_TRIM);
    CU_ASSERT_PTR_NOT_NULL(sa);
    CU_ASSERT_EQUAL(sa->count, 5);
    CU_ASSERT(sa->alloc >= sa->count);
    CU_ASSERT_PTR_NOT_NULL(sa->data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 0), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 1), WORD1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 2), WORD2);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 3), WORD3);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 4), WORD4);
    strarray_free(sa);

#undef WORD0
#undef WORD1
#undef WORD2
#undef WORD3
#undef WORD4
}

static void test_split_lcase(void)
{
    strarray_t *sa;
#define WORD0   "LORem"
#define WORD1   "ipSUM"
#define WORD2   "DoLoR"
#define WORD3   "siT"
#define WORD4   "Amet"

#define WORD0lc "lorem"
#define WORD1lc "ipsum"
#define WORD2lc "dolor"
#define WORD3lc "sit"
#define WORD4lc "amet"

    /* 5 words, space separator */
    sa = strarray_split(WORD0" "WORD1" "WORD2" "WORD3" "WORD4,
                        " ", STRARRAY_LCASE);
    CU_ASSERT_PTR_NOT_NULL(sa);
    CU_ASSERT_EQUAL(sa->count, 5);
    CU_ASSERT(sa->alloc >= sa->count);
    CU_ASSERT_PTR_NOT_NULL(sa->data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 0), WORD0lc);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 1), WORD1lc);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 2), WORD2lc);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 3), WORD3lc);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 4), WORD4lc);
    strarray_free(sa);

    /* 5 words, NULL separator (whitespace) */
    sa = strarray_split(WORD0" "WORD1"\t"WORD2"\r"WORD3"\n"WORD4,
                        NULL, STRARRAY_LCASE);
    CU_ASSERT_PTR_NOT_NULL(sa);
    CU_ASSERT_EQUAL(sa->count, 5);
    CU_ASSERT(sa->alloc >= sa->count);
    CU_ASSERT_PTR_NOT_NULL(sa->data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 0), WORD0lc);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 1), WORD1lc);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 2), WORD2lc);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 3), WORD3lc);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 4), WORD4lc);
    strarray_free(sa);

    /* 5 words, several separators */
    sa = strarray_split(WORD0"("WORD1")"WORD2"["WORD3"]"WORD4,
                        "[]()", STRARRAY_LCASE);
    CU_ASSERT_PTR_NOT_NULL(sa);
    CU_ASSERT_EQUAL(sa->count, 5);
    CU_ASSERT(sa->alloc >= sa->count);
    CU_ASSERT_PTR_NOT_NULL(sa->data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 0), WORD0lc);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 1), WORD1lc);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 2), WORD2lc);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 3), WORD3lc);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 4), WORD4lc);
    strarray_free(sa);

    /* splitm - takes ownership of a strdup()d argument */
    sa = strarray_new();
    strarray_splitm(sa, xstrdup(WORD0" "WORD1" "WORD2" "WORD3" "WORD4),
                         " ", STRARRAY_LCASE);
    CU_ASSERT_PTR_NOT_NULL(sa);
    CU_ASSERT_EQUAL(sa->count, 5);
    CU_ASSERT(sa->alloc >= sa->count);
    CU_ASSERT_PTR_NOT_NULL(sa->data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 0), WORD0lc);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 1), WORD1lc);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 2), WORD2lc);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 3), WORD3lc);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 4), WORD4lc);
    strarray_free(sa);

    /* nsplit - specify a byte range to copy and split */
    sa = strarray_nsplit(WORD0" "WORD1" "WORD2" "WORD3" "WORD4,
                         sizeof(WORD0)+sizeof(WORD1)+sizeof(WORD2),
                         " ", STRARRAY_LCASE);
    CU_ASSERT_PTR_NOT_NULL(sa);
    CU_ASSERT_EQUAL(sa->count, 3);
    CU_ASSERT(sa->alloc >= sa->count);
    CU_ASSERT_PTR_NOT_NULL(sa->data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 0), WORD0lc);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 1), WORD1lc);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 2), WORD2lc);
    strarray_free(sa);

    /* split with surrounding whitespace */
    sa = strarray_split(WORD0"| "WORD1" | "WORD2" |"WORD3" | "WORD4"| ",
                        "|", STRARRAY_LCASE);
    CU_ASSERT_PTR_NOT_NULL(sa);
    CU_ASSERT_EQUAL(sa->count, 6);
    CU_ASSERT(sa->alloc >= sa->count);
    CU_ASSERT_PTR_NOT_NULL(sa->data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 0), WORD0lc);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 1), " "WORD1lc" ");
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 2), " "WORD2lc" ");
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 3), WORD3lc" ");
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 4), " "WORD4lc);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 5), " ");
    strarray_free(sa);

    /* trim surrounding whitespace */
    sa = strarray_split(WORD0"| "WORD1" | "WORD2" |"WORD3" | "WORD4"| ",
                        "|", STRARRAY_TRIM | STRARRAY_LCASE);
    CU_ASSERT_PTR_NOT_NULL(sa);
    CU_ASSERT_EQUAL(sa->count, 5);
    CU_ASSERT(sa->alloc >= sa->count);
    CU_ASSERT_PTR_NOT_NULL(sa->data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 0), WORD0lc);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 1), WORD1lc);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 2), WORD2lc);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 3), WORD3lc);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 4), WORD4lc);
    strarray_free(sa);

#undef WORD0
#undef WORD1
#undef WORD2
#undef WORD3
#undef WORD4

#undef WORD0lc
#undef WORD1lc
#undef WORD2lc
#undef WORD3lc
#undef WORD4lc
}

static void test_remove(void)
{
    strarray_t sa = STRARRAY_INITIALIZER;
    char *s;
#define WORD0   "lorem"
#define WORD1   "ipsum"
#define WORD2   "dolor"
#define WORD3   "sit"
#define WORD4   "amet"

    CU_ASSERT_EQUAL(strarray_size(&sa), 0);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, 0));
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, -1));

    strarray_append(&sa, WORD0);
    strarray_append(&sa, WORD1);
    strarray_append(&sa, WORD2);
    strarray_append(&sa, WORD3);
    strarray_append(&sa, WORD4);
    CU_ASSERT_EQUAL(strarray_size(&sa), 5);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 2), WORD2);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 3), WORD3);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 4), WORD4);

    s = strarray_remove(&sa, 2);
    CU_ASSERT_STRING_EQUAL(s, WORD2);
    free(s);
    CU_ASSERT_EQUAL(strarray_size(&sa), 4);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 2), WORD3);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 3), WORD4);

    s = strarray_remove(&sa, 0);
    CU_ASSERT_STRING_EQUAL(s, WORD0);
    free(s);
    CU_ASSERT_EQUAL(strarray_size(&sa), 3);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD3);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 2), WORD4);

    s = strarray_remove(&sa, -1);
    CU_ASSERT_STRING_EQUAL(s, WORD4);
    free(s);
    CU_ASSERT_EQUAL(strarray_size(&sa), 2);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD3);

    s = strarray_remove(&sa, 1);
    CU_ASSERT_STRING_EQUAL(s, WORD3);
    free(s);
    CU_ASSERT_EQUAL(strarray_size(&sa), 1);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD1);

    s = strarray_remove(&sa, 0);
    CU_ASSERT_STRING_EQUAL(s, WORD1);
    free(s);
    CU_ASSERT_EQUAL(strarray_size(&sa), 0);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, 0));

    s = strarray_remove(&sa, 0);
    CU_ASSERT_PTR_NULL(s);

    strarray_fini(&sa);
#undef WORD0
#undef WORD1
#undef WORD2
#undef WORD3
#undef WORD4
}

static void test_takevf(void)
{
    strarray_t *sa;
    char **ss;
#define WORD0   "lorem"
#define WORD1   "ipsum"
#define WORD2   "dolor"
#define WORD3   "sit"
#define WORD4   "amet"

    /* 5 words, space separator */
    sa = strarray_split(WORD0" "WORD1" "WORD2" "WORD3" "WORD4, " ", 0);
    CU_ASSERT_PTR_NOT_NULL(sa);
    CU_ASSERT_EQUAL(sa->count, 5);
    CU_ASSERT(sa->alloc >= sa->count);
    CU_ASSERT_PTR_NOT_NULL(sa->data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 0), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 1), WORD1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 2), WORD2);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 3), WORD3);
    CU_ASSERT_STRING_EQUAL(strarray_nth(sa, 4), WORD4);

    ss = strarray_takevf(sa);
    /* note: takevf frees the strarray itself */
    CU_ASSERT_PTR_NOT_NULL(ss);
    CU_ASSERT_STRING_EQUAL(ss[0], WORD0);
    CU_ASSERT_STRING_EQUAL(ss[1], WORD1);
    CU_ASSERT_STRING_EQUAL(ss[2], WORD2);
    CU_ASSERT_STRING_EQUAL(ss[3], WORD3);
    CU_ASSERT_STRING_EQUAL(ss[4], WORD4);
    CU_ASSERT_PTR_NULL(ss[5]);

    free(ss[0]);
    free(ss[1]);
    free(ss[2]);
    free(ss[3]);
    free(ss[4]);
    free(ss);

#undef WORD0
#undef WORD1
#undef WORD2
#undef WORD3
#undef WORD4
}

static void test_truncate(void)
{
    strarray_t sa = STRARRAY_INITIALIZER;
#define WORD0   "lorem"
#define WORD1   "ipsum"
#define WORD2   "dolor"
#define WORD3   "sit"
#define WORD4   "amet"

    CU_ASSERT_EQUAL(strarray_size(&sa), 0);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, 0));
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, -1));

    strarray_append(&sa, WORD0);
    strarray_append(&sa, WORD1);
    strarray_append(&sa, WORD2);
    strarray_append(&sa, WORD3);
    strarray_append(&sa, WORD4);
    CU_ASSERT_EQUAL(strarray_size(&sa), 5);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 2), WORD2);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 3), WORD3);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 4), WORD4);

    /* expand the array */
    strarray_truncate(&sa, 7);
    CU_ASSERT_EQUAL(strarray_size(&sa), 7);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 2), WORD2);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 3), WORD3);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 4), WORD4);
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, 5));
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, 6));

    /* shrink the array */
    strarray_truncate(&sa, 4);
    CU_ASSERT_EQUAL(strarray_size(&sa), 4);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 2), WORD2);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 3), WORD3);

    /* shrink the array harder */
    strarray_truncate(&sa, 3);
    CU_ASSERT_EQUAL(strarray_size(&sa), 3);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 2), WORD2);

    /* shrink the array to nothing */
    strarray_truncate(&sa, 0);
    CU_ASSERT_EQUAL(strarray_size(&sa), 0);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    /* whether sa.data is NULL is undefined at this time */
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, 0));
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, -1));

    strarray_fini(&sa);
#undef WORD0
#undef WORD1
#undef WORD2
#undef WORD3
#undef WORD4
}

static void test_find(void)
{
    strarray_t sa = STRARRAY_INITIALIZER;
    int i;
#define WORD0   "lorem"
#define WORD0   "lorem"
#define WORD1   "ipsum"
#define WORD1u  "IPSUM"
#define WORD1c  "Ipsum"
#define WORD2   "dolor"
#define WORD3   "sit"
#define WORD4   "amet"

    CU_ASSERT_EQUAL(strarray_size(&sa), 0);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, 0));
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, -1));

    strarray_append(&sa, WORD0);
    strarray_append(&sa, WORD1);
    strarray_append(&sa, WORD2);
    strarray_append(&sa, WORD3);
    strarray_append(&sa, WORD0);
    strarray_append(&sa, WORD4);
    CU_ASSERT_EQUAL(strarray_size(&sa), 6);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 2), WORD2);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 3), WORD3);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 4), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 5), WORD4);

    /* search for something which isn't there */
    i = strarray_find(&sa, "NotHere", 0);
    CU_ASSERT_EQUAL(i, -1);

    /* search for something which isn't there, starting off the end */
    i = strarray_find(&sa, "NotHere", 7);
    CU_ASSERT_EQUAL(i, -1);

    /* search for something which is there */
    i = strarray_find(&sa, WORD1, 0);
    CU_ASSERT_EQUAL(i, 1);
    i = strarray_find(&sa, WORD1, i+1);
    CU_ASSERT_EQUAL(i, -1);

    /* search for something which isn't there but whose case analogue is */
    i = strarray_find(&sa, WORD1u, 0);
    CU_ASSERT_EQUAL(i, -1);
    i = strarray_find(&sa, WORD1c, 0);
    CU_ASSERT_EQUAL(i, -1);

    /* search for something which is there, starting off the end */
    i = strarray_find(&sa, WORD1, 7);
    CU_ASSERT_EQUAL(i, -1);

    /* search for something which is there multiple times */
    i = strarray_find(&sa, WORD0, 0);
    CU_ASSERT_EQUAL(i, 0);
    i = strarray_find(&sa, WORD0, i+1);
    CU_ASSERT_EQUAL(i, 4);
    i = strarray_find(&sa, WORD0, i+1);
    CU_ASSERT_EQUAL(i, -1);

    strarray_fini(&sa);
#undef WORD0
#undef WORD1
#undef WORD1u
#undef WORD1c
#undef WORD2
#undef WORD3
#undef WORD4
}

static void test_find_case(void)
{
    strarray_t sa = STRARRAY_INITIALIZER;
    int i;
#define WORD0   "Lorem"
#define WORD0u  "LOREM"
#define WORD0l  "lorem"
#define WORD0i  "lOREM"
#define WORD1   "iPSum"
#define WORD1u  "IPSUM"
#define WORD1l  "ipsum"
#define WORD1i  "IpsUM"
#define WORD2   "DOLOR"
#define WORD3   "sit"
#define WORD4   "ameT"

    CU_ASSERT_EQUAL(strarray_size(&sa), 0);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, 0));
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, -1));

    strarray_append(&sa, WORD0);
    strarray_append(&sa, WORD1);
    strarray_append(&sa, WORD2);
    strarray_append(&sa, WORD3);
    strarray_append(&sa, WORD0);
    strarray_append(&sa, WORD4);
    CU_ASSERT_EQUAL(strarray_size(&sa), 6);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 2), WORD2);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 3), WORD3);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 4), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 5), WORD4);

    /* search for something which isn't there */
    i = strarray_find_case(&sa, "NotHere", 0);
    CU_ASSERT_EQUAL(i, -1);

    /* search for something which isn't there, starting off the end */
    i = strarray_find_case(&sa, "NotHere", 7);
    CU_ASSERT_EQUAL(i, -1);

    /* search for something which is there ... */

    /* ... original capitalisation */
    i = strarray_find_case(&sa, WORD1, 0);
    CU_ASSERT_EQUAL(i, 1);
    i = strarray_find_case(&sa, WORD1, i+1);
    CU_ASSERT_EQUAL(i, -1);

    /* ... lowercase key */
    i = strarray_find_case(&sa, WORD1l, 0);
    CU_ASSERT_EQUAL(i, 1);
    i = strarray_find_case(&sa, WORD1l, i+1);
    CU_ASSERT_EQUAL(i, -1);

    /* ... uppercase key */
    i = strarray_find_case(&sa, WORD1u, 0);
    CU_ASSERT_EQUAL(i, 1);
    i = strarray_find_case(&sa, WORD1u, i+1);
    CU_ASSERT_EQUAL(i, -1);

    /* ... inverted case key */
    i = strarray_find_case(&sa, WORD1i, 0);
    CU_ASSERT_EQUAL(i, 1);
    i = strarray_find_case(&sa, WORD1i, i+1);
    CU_ASSERT_EQUAL(i, -1);

    /* search for something which is there, starting off the end ... */
    /* ... original capitalisation */
    i = strarray_find_case(&sa, WORD1, 7);
    CU_ASSERT_EQUAL(i, -1);
    /* ... lowercase key */
    i = strarray_find_case(&sa, WORD1u, 7);
    CU_ASSERT_EQUAL(i, -1);
    /* ... uppercase key */
    i = strarray_find_case(&sa, WORD1l, 7);
    CU_ASSERT_EQUAL(i, -1);
    /* ... inverted case key */
    i = strarray_find_case(&sa, WORD1i, 7);
    CU_ASSERT_EQUAL(i, -1);

    /* search for something which is there multiple times ... */

    /* ... original capitalisation */
    i = strarray_find_case(&sa, WORD0, 0);
    CU_ASSERT_EQUAL(i, 0);
    i = strarray_find_case(&sa, WORD0, i+1);
    CU_ASSERT_EQUAL(i, 4);
    i = strarray_find_case(&sa, WORD0, i+1);
    CU_ASSERT_EQUAL(i, -1);

    /* ... lowercase key */
    i = strarray_find_case(&sa, WORD0u, 0);
    CU_ASSERT_EQUAL(i, 0);
    i = strarray_find_case(&sa, WORD0u, i+1);
    CU_ASSERT_EQUAL(i, 4);
    i = strarray_find_case(&sa, WORD0u, i+1);
    CU_ASSERT_EQUAL(i, -1);

    /* ... uppercase key */
    i = strarray_find_case(&sa, WORD0l, 0);
    CU_ASSERT_EQUAL(i, 0);
    i = strarray_find_case(&sa, WORD0l, i+1);
    CU_ASSERT_EQUAL(i, 4);
    i = strarray_find_case(&sa, WORD0l, i+1);
    CU_ASSERT_EQUAL(i, -1);

    /* ... inverted case key */
    i = strarray_find_case(&sa, WORD0i, 0);
    CU_ASSERT_EQUAL(i, 0);
    i = strarray_find_case(&sa, WORD0i, i+1);
    CU_ASSERT_EQUAL(i, 4);
    i = strarray_find_case(&sa, WORD0i, i+1);
    CU_ASSERT_EQUAL(i, -1);

    strarray_fini(&sa);
#undef WORD0
#undef WORD0u
#undef WORD0l
#undef WORD0i
#undef WORD1
#undef WORD1u
#undef WORD1l
#undef WORD1i
#undef WORD2
#undef WORD3
#undef WORD4
}


static void test_dup(void)
{
    strarray_t sa = STRARRAY_INITIALIZER;
    strarray_t *dup;
#define WORD0   "lorem"
#define WORD1   "ipsum"
#define WORD2   "dolor"
#define WORD3   "sit"
#define WORD4   "amet"

    CU_ASSERT_EQUAL(strarray_size(&sa), 0);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));

    /* dup an empty array */
    dup = strarray_dup(&sa);
    CU_ASSERT_PTR_NOT_NULL(dup);
    CU_ASSERT_PTR_NOT_EQUAL(dup, &sa);
    CU_ASSERT_EQUAL(dup->count, 0);
    CU_ASSERT(dup->alloc >= dup->count);
    strarray_free(dup);

    /* dup a non-empty array */
    strarray_append(&sa, WORD0);
    strarray_append(&sa, WORD1);
    strarray_append(&sa, WORD2);
    strarray_append(&sa, WORD3);
    strarray_append(&sa, WORD0);
    strarray_append(&sa, WORD4);
    CU_ASSERT_EQUAL(strarray_size(&sa), 6);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 2), WORD2);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 3), WORD3);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 4), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 5), WORD4);

    dup = strarray_dup(&sa);
    CU_ASSERT_PTR_NOT_NULL(dup);
    CU_ASSERT_PTR_NOT_EQUAL(dup, &sa);
    CU_ASSERT_EQUAL(dup->count, 6);
    CU_ASSERT(dup->alloc >= dup->count);
    CU_ASSERT_PTR_NOT_NULL(dup->data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(dup, 0), WORD0);
    CU_ASSERT_PTR_NOT_EQUAL((void *)strarray_nth(dup, 0), (void *)strarray_nth(&sa, 0));
    CU_ASSERT_STRING_EQUAL(strarray_nth(dup, 1), WORD1);
    CU_ASSERT_PTR_NOT_EQUAL((void *)strarray_nth(dup, 1), (void *)strarray_nth(&sa, 1));
    CU_ASSERT_STRING_EQUAL(strarray_nth(dup, 2), WORD2);
    CU_ASSERT_PTR_NOT_EQUAL((void *)strarray_nth(dup, 2), (void *)strarray_nth(&sa, 2));
    CU_ASSERT_STRING_EQUAL(strarray_nth(dup, 3), WORD3);
    CU_ASSERT_PTR_NOT_EQUAL((void *)strarray_nth(dup, 3), (void *)strarray_nth(&sa, 3));
    CU_ASSERT_STRING_EQUAL(strarray_nth(dup, 4), WORD0);
    CU_ASSERT_PTR_NOT_EQUAL((void *)strarray_nth(dup, 4), (void *)strarray_nth(&sa, 4));
    CU_ASSERT_STRING_EQUAL(strarray_nth(dup, 5), WORD4);
    CU_ASSERT_PTR_NOT_EQUAL((void *)strarray_nth(dup, 5), (void *)strarray_nth(&sa, 5));
    strarray_free(dup);

    strarray_fini(&sa);
#undef WORD0
#undef WORD1
#undef WORD2
#undef WORD3
#undef WORD4
}

static void test_remove_all(void)
{
    strarray_t sa = STRARRAY_INITIALIZER;
#define WORD0   "lorem"
#define WORD1   "ipsum"
#define WORD2   "dolor"
#define WORD3   "sit"
#define WORD4   "amet"

    CU_ASSERT_EQUAL(strarray_size(&sa), 0);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));

    /* removing from an empty array */
    strarray_remove_all(&sa, WORD0);
    CU_ASSERT_EQUAL(strarray_size(&sa), 0);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));

    /* removing a single item from a non-empty array */
    strarray_append(&sa, WORD0);
    strarray_append(&sa, WORD1);
    strarray_append(&sa, WORD2);
    strarray_append(&sa, WORD3);
    strarray_append(&sa, WORD0);
    strarray_append(&sa, WORD4);
    CU_ASSERT_EQUAL(strarray_size(&sa), 6);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 2), WORD2);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 3), WORD3);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 4), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 5), WORD4);

    strarray_remove_all(&sa, WORD1);
    CU_ASSERT_EQUAL(strarray_size(&sa), 5);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD2);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 2), WORD3);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 3), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 4), WORD4);

    /* removing an item that appears more than once */
    strarray_remove_all(&sa, WORD0);
    CU_ASSERT_EQUAL(strarray_size(&sa), 3);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD2);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD3);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 2), WORD4);

    strarray_fini(&sa);
#undef WORD0
#undef WORD1
#undef WORD2
#undef WORD3
#undef WORD4
}

static void test_pop(void)
{
    strarray_t sa = STRARRAY_INITIALIZER;
    char *s;
#define WORD0   "lorem"
#define WORD1   "ipsum"
#define WORD2   "dolor"
#define WORD3   "sit"
#define WORD4   "amet"

    CU_ASSERT_EQUAL(strarray_size(&sa), 0);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));

    /* pop from an empty array */
    s = strarray_pop(&sa);
    CU_ASSERT_PTR_NULL(s);
    CU_ASSERT_EQUAL(strarray_size(&sa), 0);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));

    /* popping items from a non-empty array */
    strarray_append(&sa, WORD0);
    strarray_append(&sa, WORD1);
    strarray_append(&sa, WORD2);
    strarray_append(&sa, WORD3);
    strarray_append(&sa, WORD0);
    strarray_append(&sa, WORD4);
    CU_ASSERT_EQUAL(strarray_size(&sa), 6);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 2), WORD2);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 3), WORD3);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 4), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 5), WORD4);

    s = strarray_pop(&sa);
    CU_ASSERT_PTR_NOT_NULL(s);
    CU_ASSERT_EQUAL(strarray_size(&sa), 5);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 2), WORD2);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 3), WORD3);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 4), WORD0);
    CU_ASSERT_STRING_EQUAL(s, WORD4);
    free(s);

    s = strarray_pop(&sa);
    CU_ASSERT_PTR_NOT_NULL(s);
    CU_ASSERT_EQUAL(strarray_size(&sa), 4);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 2), WORD2);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 3), WORD3);
    CU_ASSERT_STRING_EQUAL(s, WORD0);
    free(s);

    s = strarray_pop(&sa);
    CU_ASSERT_PTR_NOT_NULL(s);
    CU_ASSERT_EQUAL(strarray_size(&sa), 3);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 2), WORD2);
    CU_ASSERT_STRING_EQUAL(s, WORD3);
    free(s);

    s = strarray_pop(&sa);
    CU_ASSERT_PTR_NOT_NULL(s);
    CU_ASSERT_EQUAL(strarray_size(&sa), 2);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD1);
    CU_ASSERT_STRING_EQUAL(s, WORD2);
    free(s);

    s = strarray_pop(&sa);
    CU_ASSERT_PTR_NOT_NULL(s);
    CU_ASSERT_EQUAL(strarray_size(&sa), 1);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0);
    CU_ASSERT_STRING_EQUAL(s, WORD1);
    free(s);

    s = strarray_pop(&sa);
    CU_ASSERT_PTR_NOT_NULL(s);
    CU_ASSERT_EQUAL(strarray_size(&sa), 0);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_STRING_EQUAL(s, WORD0);
    free(s);

    s = strarray_pop(&sa);
    CU_ASSERT_PTR_NULL(s);
    CU_ASSERT_EQUAL(strarray_size(&sa), 0);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));

    strarray_fini(&sa);
#undef WORD0
#undef WORD1
#undef WORD2
#undef WORD3
#undef WORD4
}

static void test_sortrawuniq(void)
{
    strarray_t sa = STRARRAY_INITIALIZER;
#define WORD0   "INBOX"
#define WORD1   "INBOX.a folder"
#define WORD2   "INBOX.a.sub"
#define WORD3   "INBOX.a.aaa"
#define WORD4   "INBOX.a.aa"

    /* initialise */
    CU_ASSERT_EQUAL(strarray_size(&sa), 0);
    strarray_append(&sa, WORD0);
    strarray_append(&sa, WORD1);
    strarray_append(&sa, WORD2);
    strarray_append(&sa, WORD3);
    strarray_append(&sa, WORD4);
    /* duplicates */
    strarray_append(&sa, WORD0);
    strarray_append(&sa, WORD2);
    strarray_append(&sa, WORD1);

    CU_ASSERT_EQUAL(strarray_size(&sa), 8);

    /* normal sort */
    strarray_sort(&sa, cmpstringp_raw);

    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 2), WORD1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 3), WORD1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 4), WORD4);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 5), WORD3);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 6), WORD2);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 7), WORD2);

    /* uniq */
    strarray_uniq(&sa);
    CU_ASSERT_EQUAL(strarray_size(&sa), 5);

    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 2), WORD4);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 3), WORD3);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 4), WORD2);

    strarray_fini(&sa);
#undef WORD0
#undef WORD1
#undef WORD2
#undef WORD3
}

static void test_sortmboxuniq(void)
{
    strarray_t sa = STRARRAY_INITIALIZER;
#define WORD0   "INBOX"
#define WORD1   "INBOX.a folder"
#define WORD2   "INBOX.a.sub"
#define WORD3   "INBOX.a.aaa"
#define WORD4   "INBOX.a.aa"

    /* initialise */
    CU_ASSERT_EQUAL(strarray_size(&sa), 0);
    strarray_append(&sa, WORD0);
    strarray_append(&sa, WORD1);
    strarray_append(&sa, WORD2);
    strarray_append(&sa, WORD3);
    strarray_append(&sa, WORD4);
    /* duplicates */
    strarray_append(&sa, WORD0);
    strarray_append(&sa, WORD2);
    strarray_append(&sa, WORD1);

    CU_ASSERT_EQUAL(strarray_size(&sa), 8);

    /* mbox order sort */
    strarray_sort(&sa, cmpstringp_mbox);

    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 2), WORD4);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 3), WORD3);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 4), WORD2);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 5), WORD2);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 6), WORD1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 7), WORD1);

    /* uniq */
    strarray_uniq(&sa);
    CU_ASSERT_EQUAL(strarray_size(&sa), 5);

    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD4);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 2), WORD3);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 3), WORD2);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 4), WORD1);

    strarray_fini(&sa);
#undef WORD0
#undef WORD1
#undef WORD2
#undef WORD3
}

static void test_add(void)
{
    strarray_t sa = STRARRAY_INITIALIZER;
#define WORD0   "lorem"
#define WORD0u  "LOREM"
#define WORD1   "ipsum"

    CU_ASSERT_EQUAL(strarray_size(&sa), 0);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, 0));
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, -1));

    /* _add() on an empty array appends */
    strarray_add(&sa, WORD0);
    CU_ASSERT_EQUAL(strarray_size(&sa), 1);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0);

    /* _add() of an item already present is a no-op */
    strarray_add(&sa, WORD0);
    CU_ASSERT_EQUAL(strarray_size(&sa), 1);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0);

    /* _add() of an item not already present (but whose case
     * analogue is present) appends */
    strarray_add(&sa, WORD0u);
    CU_ASSERT_EQUAL(strarray_size(&sa), 2);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD0u);

    /* _add() of an item not already present appends */
    strarray_add(&sa, WORD1);
    CU_ASSERT_EQUAL(strarray_size(&sa), 3);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD0u);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 2), WORD1);

    /* _add() of an item already present is a no-op */
    strarray_add(&sa, WORD0);
    CU_ASSERT_EQUAL(strarray_size(&sa), 3);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD0u);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 2), WORD1);

    strarray_add(&sa, WORD0u);
    CU_ASSERT_EQUAL(strarray_size(&sa), 3);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD0u);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 2), WORD1);

    strarray_add(&sa, WORD1);
    CU_ASSERT_EQUAL(strarray_size(&sa), 3);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD0u);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 2), WORD1);

    strarray_fini(&sa);
#undef WORD0
#undef WORD0u
#undef WORD1
}

static void test_add_case(void)
{
    strarray_t sa = STRARRAY_INITIALIZER;
#define WORD0   "lorem"
#define WORD0u  "LOREM"
#define WORD1   "ipsum"

    CU_ASSERT_EQUAL(strarray_size(&sa), 0);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, 0));
    CU_ASSERT_PTR_NULL(strarray_nth(&sa, -1));

    /* _add_case() on an empty array appends */
    strarray_add_case(&sa, WORD0);
    CU_ASSERT_EQUAL(strarray_size(&sa), 1);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0);

    /* _add_case() of an item already present is a no-op */
    strarray_add_case(&sa, WORD0);
    CU_ASSERT_EQUAL(strarray_size(&sa), 1);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0);

    /* _add_case() of an item not already present (but whose case
     * analogue is present) is a no-op */
    strarray_add_case(&sa, WORD0u);
    CU_ASSERT_EQUAL(strarray_size(&sa), 1);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0);

    /* _add_case() of an item not already present appends */
    strarray_add_case(&sa, WORD1);
    CU_ASSERT_EQUAL(strarray_size(&sa), 2);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD1);

    /* _add_case() of an item already present is a no-op */
    strarray_add_case(&sa, WORD0);
    CU_ASSERT_EQUAL(strarray_size(&sa), 2);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD1);

    strarray_add_case(&sa, WORD0u);
    CU_ASSERT_EQUAL(strarray_size(&sa), 2);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD1);

    strarray_add_case(&sa, WORD1);
    CU_ASSERT_EQUAL(strarray_size(&sa), 2);
    CU_ASSERT(sa.alloc >= strarray_size(&sa));
    CU_ASSERT_PTR_NOT_NULL(sa.data);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD1);

    strarray_fini(&sa);
#undef WORD0
#undef WORD0u
#undef WORD1
}

static void test_swap(void)
{
    strarray_t sa = STRARRAY_INITIALIZER;
#define WORD0   "lorem"
#define WORD1   "ipsum"

    /* swap is safe for bad index */
    strarray_swap(&sa, 0, 1);

    /* swap is safe for same index */
    strarray_insert(&sa, 0, WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0);
    strarray_swap(&sa, 0, 0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0);
    CU_ASSERT_EQUAL(strarray_size(&sa), 1);

    /* swap */
    strarray_insert(&sa, 1, WORD1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD0);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD1);
    strarray_swap(&sa, 0, 1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 0), WORD1);
    CU_ASSERT_STRING_EQUAL(strarray_nth(&sa, 1), WORD0);
    CU_ASSERT_EQUAL(strarray_size(&sa), 2);

    strarray_fini(&sa);
#undef WORD0
#undef WORD1
}

static void test_appendv(void)
{
    strarray_t sa = STRARRAY_INITIALIZER;
    const char *s;

    s = strarray_appendv(&sa, "lorem");
    CU_ASSERT_PTR_EQUAL(s, strarray_nth(&sa, 0));
    CU_ASSERT_EQUAL(strarray_size(&sa), 1);

    s = strarray_appendv(&sa, "ipsum");
    CU_ASSERT_PTR_EQUAL(s, strarray_nth(&sa, 1));
    CU_ASSERT_EQUAL(strarray_size(&sa), 2);

    strarray_fini(&sa);
}

static void test_addv(void)
{
    strarray_t sa = STRARRAY_INITIALIZER;
    const char *s;

    s = strarray_addv(&sa, "lorem");
    CU_ASSERT_PTR_EQUAL(s, strarray_nth(&sa, 0));
    CU_ASSERT_EQUAL(strarray_size(&sa), 1);

    s = strarray_addv(&sa, "ipsum");
    CU_ASSERT_PTR_EQUAL(s, strarray_nth(&sa, 1));
    CU_ASSERT_EQUAL(strarray_size(&sa), 2);

    s = strarray_addv(&sa, "lorem");
    CU_ASSERT_PTR_EQUAL(s, strarray_nth(&sa, 0));
    CU_ASSERT_EQUAL(strarray_size(&sa), 2);

    strarray_fini(&sa);
}

static void test_add_casev(void)
{
    strarray_t sa = STRARRAY_INITIALIZER;
    const char *s;

    s = strarray_add_casev(&sa, "lorem");
    CU_ASSERT_PTR_EQUAL(s, strarray_nth(&sa, 0));
    CU_ASSERT_EQUAL(strarray_size(&sa), 1);

    s = strarray_add_casev(&sa, "ipsum");
    CU_ASSERT_PTR_EQUAL(s, strarray_nth(&sa, 1));
    CU_ASSERT_EQUAL(strarray_size(&sa), 2);

    s = strarray_add_casev(&sa, "lOREm");
    CU_ASSERT_PTR_EQUAL(s, strarray_nth(&sa, 0));
    CU_ASSERT_EQUAL(strarray_size(&sa), 2);

    strarray_fini(&sa);
}

/* vim: set ft=c: */
