#include "safestr.h"
#include <xxl.h>

#include <stdlib.h>
#include <string.h>

#define FAIL(why)                                                           \
    do {                                                                    \
        fprintf(stderr, "%s\n", (why));                                     \
        exit(1);                                                            \
    } while (0)

static void
test_alloc(void)
{
    int         passed;
    safestr_t   str;

    XXL_ASSET_BLOCK_BEGIN {
        str = safestr_alloc(512, SAFESTR_ASSET_TEMPORARY);
        if (safestr_length(str) != 0)
            FAIL("Allocated string has a length!");
    } XXL_ASSET_BLOCK_END;
    XXL_TRY_BEGIN {
        passed = 0;
        safestr_free(str);
    } XXL_CATCH(SAFESTR_ERROR_BAD_ADDRESS) {
        passed = 1;
    } XXL_TRY_END;
    if (!passed) FAIL("SafeStr asset management didn't clean up properly!");

    str = safestr_alloc(1, 0);
    safestr_reference(str);

    XXL_TRY_BEGIN {
        passed = 1;
        safestr_free(str);
    } XXL_CATCH(SAFESTR_ERROR_BAD_ADDRESS) {
        passed = 0;
    } XXL_TRY_END;
    if (!passed) FAIL("safestr_free(1) failed");

    XXL_TRY_BEGIN {
        passed = 1;
        safestr_free(str);
    } XXL_CATCH(SAFESTR_ERROR_BAD_ADDRESS) {
        passed = 0;
    } XXL_TRY_END;
    if (!passed) FAIL("safestr_free(2) failed");

    XXL_TRY_BEGIN {
        passed = 0;
        safestr_free(str);
    } XXL_CATCH(SAFESTR_ERROR_BAD_ADDRESS) {
        passed = 1;
    } XXL_TRY_END;
    if (!passed) FAIL("safestr_free(3) failed");
}

static void
test_create(void)
{
    int         passed;
    char        *c_str;
    safestr_t   clone, str;
    u_int32_t   i;

    /* safestr_create() */
    c_str = "A quick brown fox jumps over the lazy dog.";
    str = safestr_create(c_str, SAFESTR_IMMUTABLE);
    if (safestr_length(str) != strlen(c_str))
        FAIL("String length after safestr_create() is wrong!");

    /* safestr_charat() */
    for (i = safestr_length(str);  i > 0;  i--) {
        if (safestr_charat(str, i - 1) != c_str[i - 1])
            FAIL("safestr_charat() fails!");
    }
    XXL_TRY_BEGIN {
        passed = 0;
        safestr_charat(str, safestr_length(str));
    } XXL_CATCH(SAFESTR_ERROR_INDEX_OUT_OF_RANGE) {
        passed = 1;
    } XXL_TRY_END;
    if (!passed) FAIL("safestr_charat with index out of range failed to throw!");

    /* safestr_setcharat(), immutability, etc. */
    XXL_TRY_BEGIN {
        passed = 0;
        safestr_setcharat(str, 0, 'a', 1);
    } XXL_CATCH(SAFESTR_ERROR_IMMUTABLE_STRING) {
        passed = 1;
    } XXL_TRY_END;
    if (!passed) FAIL("safestr_setcharat on immutable string failed to throw!");

    safestr_makewritable(str);
    safestr_setcharat(str, 0, 'a', 1);
    if (safestr_charat(str, 0) != 'a')
        FAIL("safestr_setcharat failed to modify the string!");
    XXL_TRY_BEGIN {
        passed = 0;
        safestr_setcharat(str, safestr_length(str), 'X', 1);
    } XXL_CATCH(SAFESTR_ERROR_INDEX_OUT_OF_RANGE) {
        passed = 1;
    } XXL_TRY_END;
    if (!passed) FAIL("safestr_setcharat with index out of range failed to throw!");

    safestr_setcharat(str, 0, 'A', 1);
    if (safestr_charat(str, 0) != 'A')
        FAIL("safestr_setcharat failed to modify the string (2)!");
    safestr_makereadonly(str);
    XXL_TRY_BEGIN {
        passed = 0;
        safestr_setcharat(str, 0, 'X', 1);
    } XXL_CATCH(SAFESTR_ERROR_IMMUTABLE_STRING) {
        passed = 1;
    } XXL_TRY_END;
    if (!passed) FAIL("safestr_makereadonly failed to make the string immutable!");

    /* safestr_clone() */
    clone = safestr_clone(str, 0);
    if (safestr_length(clone) != safestr_length(str))
        FAIL("Cloned string's length does not match source length!");
    if (!safestr_equal(clone, str, 0)) {
        if (strcmp((char *)clone, (char *)str)) {
            printf("str:   [%s]\nclone: [%s]\n", (char *)str, (char *)clone);
            FAIL("Cloned string's content is incorrect.");
        }
        FAIL("safestr_equal() failed to match two equal strings!");
    }

    /* safestr_makereadonly(), safestr_makewritable(), safestr_isreadonly() */
    if (!safestr_isreadonly(str)) FAIL("String isn't read-only as expected!");
    safestr_makewritable(str);
    if (safestr_isreadonly(str)) FAIL("String is read-only and shouldn't be!");
    safestr_makereadonly(str);
    if (!safestr_isreadonly(str)) FAIL("String isn't read-only and should be!");
    safestr_makewritable(str);

    /* safestr_convert() */
    c_str = "A QUICK BROWN FOX JUMPS OVER THE LAZY DOG.";
    safestr_convert(str, SAFESTR_CONVERT_UPPERCASE);
    if (!safestr_equal(str, SAFESTR_TEMP(c_str), 0)) {
        if (strcmp((char *)str, c_str)) {
            printf("str:   [%s]\nc_str: [%s]\n", (char *)str, c_str);
            FAIL("Uppercase converted string's content is incorrect.");
        }
        FAIL("safestr_equal() failed to match two equal strings!");
    }

    c_str = "a quick brown fox jumps over the lazy dog.";
    safestr_convert(str, SAFESTR_CONVERT_LOWERCASE);
    if (!safestr_equal(str, SAFESTR_TEMP(c_str), 0)) {
        if (strcmp((char *)str, c_str)) {
            printf("str:    [%s]\nc_str: [%s]\n", (char *)str, c_str);
            FAIL("Lowercase convert string's content is incorrect.");
        }
        FAIL("safestr_equal() failed to match two equal strings!");
    }

    c_str = "A Quick Brown Fox Jumps Over The Lazy Dog.";
    safestr_convert(str, SAFESTR_CONVERT_TITLECASE);
    if (!safestr_equal(str, SAFESTR_TEMP(c_str), 0)) {
        if (strcmp((char *)str, c_str)) {
            printf("str:   [%s]\nc_str: [%s]\n", (char *)str, c_str);
            FAIL("Titlecase convert string's content is incorrect.");
        }
        FAIL("safestr_equal() failed to match two equal strings!");
    }

    /* safestr_free() */
    safestr_free(clone);
    safestr_free(str);

    XXL_TRY_BEGIN {
        passed = 0;
        safestr_free(str);
    } XXL_CATCH(SAFESTR_ERROR_BAD_ADDRESS) {
        passed = 1;
    } XXL_TRY_END;
    if (!passed) FAIL("safestr_free failed to free 'str'");

    XXL_TRY_BEGIN {
        passed = 0;
        safestr_free(clone);
    } XXL_CATCH(SAFESTR_ERROR_BAD_ADDRESS) {
        passed = 1;
    } XXL_TRY_END;
    if (!passed) FAIL("safestr_free failed to free 'clone'");
}

static void
test_comparison(void)
{
    char        *c_str;
    safestr_t   str1, str2;

    str1 = safestr_create("/Volumes/Music/iTunes/iTunes Music/Fischerspooner/Exclusive/Emerge.aac", 0);
    str2 = safestr_clone(str1, 0);

    if (!safestr_equal(str1, str2, 0))
        FAIL("safestr_equal(1) failed!");
    if (safestr_compare(str1, str2, 0))
        FAIL("safestr_compare(1) failed!");

    safestr_convert(str2, SAFESTR_CONVERT_LOWERCASE);
    if (!safestr_equal(str1, str2, SAFESTR_COMPARE_NOCASE))
        FAIL("safestr_equal(2) failed!");
    if (safestr_compare(str1, str2, SAFESTR_COMPARE_NOCASE))
        FAIL("safestr_compare(2) failed!");

    c_str = "/Volumes/Music/iTunes";
    safestr_duplicate(&str2, SAFESTR_TEMP(c_str), 0);
    if (safestr_length(str2) != strlen(c_str))
        FAIL("String length after safestr_duplicate() doesn't match!");
    if (!safestr_equal(str2, SAFESTR_TEMP(c_str), 0)) {
        printf("str1:  [%s]\nc_str: [%s]\n", (char *)str2, c_str);
        FAIL("Duplicated string contents don't match!");
    }
    if (!safestr_equal(str1, str2, SAFESTR_COMPARE_LIMIT, safestr_length(str2)))
        FAIL("Limited safestr_equal doesn't match!");
    if (safestr_compare(str1, str2, SAFESTR_COMPARE_LIMIT, safestr_length(str2)))
        FAIL("Limited safestr_compare doesn't match!");

    safestr_free(str2);
    safestr_free(str1);
}

static void
test_modify(void)
{
    char        *c_str;
    safestr_t   str1;
    u_int32_t   length;

    c_str = "A brown fox jumped over the stupid dog.";
    str1 = safestr_create(c_str, SAFESTR_TRUSTED);
    if (!safestr_istrusted(str1))
        FAIL("String is not trusted as expected!");
    if (safestr_length(str1) != strlen(c_str))
        FAIL("String lengths don't match after safestr_create()!");

    safestr_replace(&str1, SAFESTR_TEMP("jumped"), SAFESTR_TEMP("jumps"));
    c_str = "A brown fox jumps over the stupid dog.";
    if (!safestr_equal(str1, SAFESTR_TEMP(c_str), 0)) {
        printf("str1:  [%s] length = %u\nc_str: [%s] length = %zu\n",
               (char *)str1, safestr_length(str1), c_str, strlen(c_str));
        FAIL("Strings don't match after safestr_replace()!");
    }
    if (safestr_istrusted(str1))
        FAIL("String is trusted after safestr_replace() and shouldn't be!");
    safestr_trust(str1);
    if (!safestr_istrusted(str1))
        FAIL("String is not trusted after safestr_trust() and should be!");

    safestr_replace(&str1, SAFESTR_TEMP("stupid"), SAFESTR_TEMP_TRUSTED("lazy"));
    if (!safestr_istrusted(str1))
        FAIL("String isn't trusted after safestr_replace() and should be!");
    safestr_untrust(str1);
    if (safestr_istrusted(str1))
        FAIL("String is trusted after safestr_untrust() and shouldn't be!");
    safestr_trust(str1);

    safestr_insert(&str1, 2, SAFESTR_TEMP_TRUSTED("quick and dirty "));
    c_str = "A quick and dirty brown fox jumps over the lazy dog.";
    if (!safestr_equal(str1, SAFESTR_TEMP(c_str), 0)) {
        printf("str1:  [%s]\nc_str: [%s]\n", (char *)str1, c_str);
        FAIL("Strings don't match after safestr_insert()!");
    }
    if (!safestr_istrusted(str1))
        FAIL("String isn't trusted after safestr_insert() and should be!");

    safestr_delete(&str1, 8, 10);
    c_str = "A quick brown fox jumps over the lazy dog.";
    if (!safestr_equal(str1, SAFESTR_TEMP(c_str), 0)) {
        printf("str1:  [%s]\nc_str: [%s]\n", (char *)str1, c_str);
        FAIL("Strings don't match after safestr_delete()!");
    }

    length = safestr_length(str1);
    safestr_append(&str1, SAFESTR_TEMP("  Word."));
    c_str = "A quick brown fox jumps over the lazy dog.  Word.";
    if (!safestr_equal(str1, SAFESTR_TEMP(c_str), 0)) {
        printf("str1:  [%s]\nc_str: [%s]\n", (char *)str1, c_str);
        FAIL("Strings don't match after safestr_concatenate()!");
    }
    if (safestr_istrusted(str1))
        FAIL("String is trusted after safestr_concatenate() and shouldn't be!");

    safestr_delete(&str1, length, length);
    c_str = "A quick brown fox jumps over the lazy dog.";
    if (!safestr_equal(str1, SAFESTR_TEMP(c_str), 0)) {
        printf("str1:  [%s]\nc_str: [%s]\n", (char *)str1, c_str);
        FAIL("Strings don't match after safestr_delete()! (2)");
    }

    safestr_free(str1);
}

static char *c_array[] = {
    "A", "quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog.",
    NULL
};

static void
test_arrays(void)
{
    char        *c_str;
    u_int32_t   c_length, i, safe_length;
    safestr_t   *safe_array, str;

    for (c_length = 0;  c_array[c_length];  c_length++);
    safe_array = safestr_convertarray(c_array, SAFESTR_TRUSTED | SAFESTR_IMMUTABLE);
    for (safe_length = 0;  safe_array[safe_length];  safe_length++);
    if (c_length != safe_length)
        FAIL("safestr_convertarray() array size doesn't match original!");

    for (i = 0;  safe_array[i];  i++) {
        if (!safestr_equal(safe_array[i], SAFESTR_TEMP(c_array[i]), 0))
            FAIL("safestr_convertarray() array contents don't match original!");
    }

    str = safestr_join(safe_array, SAFESTR_TEMP_TRUSTED(" "));
    c_str = "A quick brown fox jumps over the lazy dog.";
    if (!safestr_equal(str, SAFESTR_TEMP(c_str), 0)) {
        printf("str:   [%s]\nc_str: [%s]\n", (char *)str, c_str);
        FAIL("Strings don't match after safestr_join()!");
    }
    if (!safestr_istrusted(str))
        FAIL("String isn't trusted after safestr_join() and should be!");

    for (i = 0;  safe_array[i];  safestr_free(safe_array[i++]));
    free(safe_array);
    safe_array = safestr_split(str, SAFESTR_TEMP(" "));
    for (safe_length = 0;  safe_array[safe_length];  safe_length++);
    if (c_length != safe_length)
        FAIL("safestr_split() array size doesn't match original!");

    for (i = 0;  safe_array[i];  i++) {
        if (!safestr_equal(safe_array[i], SAFESTR_TEMP(c_array[i]), 0))
            FAIL("safestr_split() array contents don't match original!");
    }

    for (i = 0;  safe_array[i];  safestr_free(safe_array[i++]));
    free(safe_array);
    safe_array = safestr_split(str, SAFESTR_TEMP(""));
    for (safe_length = 0;  safe_array[safe_length];  safe_length++);
    if (strlen(c_str) != safe_length)
        FAIL("safestr_split() character array size doesn't match original!");

    for (i = 0;  safe_array[i];  i++) {
        if (safestr_length(safe_array[i]) != 1)
            FAIL("safestr_length() on character array element != 1!");
        if (safestr_charat(safe_array[i], 0) != c_str[i])
            FAIL("safestr_charat() on character array element != original!");
    }

    safestr_free(str);
    for (i = 0;  safe_array[i];  safestr_free(safe_array[i++]));
    free(safe_array);
}

static void
test_misc(void)
{
    char        *c_str;
    safestr_t   slice, str;
    u_int32_t   idx;

    c_str = "A quick brown fox jumps over the lazy dog.";
    str   = safestr_create(c_str, 0);
    idx   = safestr_search(str, SAFESTR_TEMP("fox"), 0);
    if (idx != (u_int32_t)(strstr(c_str, "fox") - c_str))
        FAIL("safestr_search() result not as expected!");
    if (safestr_search(str, SAFESTR_TEMP("FOX"), SAFESTR_FIND_NOMATCHCASE) != idx)
        FAIL("safestr_search() case insensitive search result erroneous!");
    if (safestr_search(str, SAFESTR_TEMP("fox"), SAFESTR_FIND_REVERSE) != idx)
        FAIL("safestr_search() reverse search result erroneous!");
    if (safestr_search(str, SAFESTR_TEMP("foo"), 0) != SAFESTR_ERROR_NOTFOUND)
        FAIL("safestr_search() found non-existant substring!");
    if (safestr_search(str, SAFESTR_TEMP("fox"), SAFESTR_FIND_FROMCHAR, 10) != idx)
        FAIL("safestr_search() w/FROMCHAR search result erroneous!");

    c_str = "The house with the white fence is also painted white.";
    safestr_copy(&str, SAFESTR_TEMP(c_str));
    idx = safestr_search(str, SAFESTR_TEMP("white"), 0);
    if (idx != (u_int32_t)(strstr(c_str, "white") - c_str))
        FAIL("safestr_search() result not as expected! (2)");

    idx = safestr_search(str, SAFESTR_TEMP("white"), SAFESTR_FIND_NTH, 2);
    if (idx != (u_int32_t)(strstr(strstr(c_str, "white") + 5, "white") - c_str))
        FAIL("safestr_search() result not as expected! (3)");

    idx = safestr_search(str, SAFESTR_TEMP("white"), SAFESTR_FIND_FROMNTH, 1);
    if (idx != (u_int32_t)(strstr(strstr(c_str, "white") + 5, "white") - c_str))
        FAIL("safestr_search() result not as expected! (4)");

    idx = safestr_search(str, SAFESTR_TEMP("white"), SAFESTR_FIND_REVERSE | SAFESTR_FIND_NTH, 2);
    if (idx != (u_int32_t)(strstr(c_str, "white") - c_str))
        FAIL("safestr_search() reverse not as expected!");

    c_str = "white.";
    idx = safestr_search(str, SAFESTR_TEMP("white"), SAFESTR_FIND_REVERSE);
    slice = safestr_slice(str, idx, safestr_length(str));
    if (!safestr_equal(slice, SAFESTR_TEMP(c_str), 0)) {
        printf("slice: [%s]\nc_str: [%s]\n", (char *)slice, c_str);
        FAIL("Sliced string is not correct!");
    }
    safestr_free(slice);

    c_str = "house";
    slice = safestr_slice(str, 4, 4 + strlen(c_str));
    if (!safestr_equal(slice, SAFESTR_TEMP(c_str), 0)) {
        printf("slice: [%s]\nc_str: [%s]\n", (char *)slice, c_str);
        FAIL("Sliced string is not correct! (2)");
    }
    safestr_free(slice);

    safestr_truncate(&str, 0);
    if (safestr_length(str) != 0)
        FAIL("String length is wrong after first safestr_truncate()!");

    safestr_truncate(&str, 10);
    if (safestr_length(str) != 10)
        FAIL("String length is wrong after second safestr_truncate()!");
    c_str = "          ";
    if (!safestr_equal(str, SAFESTR_TEMP(c_str), 0)) {
        printf("str:   [%s]\nc_str: [%s]\n", (char *)str, c_str);
        FAIL("String contents are wrong after second safestr_truncate()!");
    }

    c_str = " \t foo\t\t\t    \t\t\t";
    safestr_copy(&str, SAFESTR_TEMP(c_str));
    safestr_trim(str, SAFESTR_TRIM_LEADING);
    if (!safestr_equal(str, SAFESTR_TEMP(c_str + 3), 0)) {
        printf("str:   [%s]\nc_str: [%s]\n", (char *)str, c_str + 3);
        FAIL("String contents are wrong after leading space trim!");
    }

    safestr_copy(&str, SAFESTR_TEMP(c_str));
    safestr_trim(str, SAFESTR_TRIM_TRAILING);
    if (!safestr_equal(str, SAFESTR_TEMP(" \t foo"), 0)) {
        printf("str:   [%s]\nc_str: [ \t foo]\n", (char *)str);
        FAIL("String contents are wrong after trailing space trim!");
    }

    safestr_copy(&str, SAFESTR_TEMP(c_str));
    safestr_trim(str, SAFESTR_TRIM_BOTH);
    if (!safestr_equal(str, SAFESTR_TEMP("foo"), 0)) {
        printf("str:   [%s]\nc_str: [foo]\n", (char *)str);
        FAIL("String contents are wrong after leading AND trailing space trim!");
    }

    safestr_copy(&str, SAFESTR_TEMP("0xdeadbeef"));
    if (safestr_toint32(str, 0) != (int32_t)0xdeadbeef) {
        printf("safestr_toint32(\"0xdeadbeef\", 0) == %#08x\n", safestr_toint32(str, 0));
        FAIL("safestr_toint32(\"0xdeadbeef\", 0) FAILED");
    }
    if (safestr_toint64(str, 0) != (int64_t)0xdeadbeef) {
        printf("safestr_toint64(\"0xdeadbeef\", 0) == %#08llx\n", safestr_toint64(str, 0));
        FAIL("safestr_toint64(\"0xdeadbeef\", 0) FAILED");
    }
    if (safestr_touint32(str, 0) != (u_int32_t)0xdeadbeef) {
        printf("safestr_touint32(\"0xdeadbeef\", 0) == %#08x\n", safestr_touint32(str, 0));
        FAIL("safestr_touint32(\"0xdeadbeef\", 0) FAILED");
    }
    if (safestr_touint64(str, 0) != (u_int64_t)0xdeadbeef) {
        printf("safestr_touint64(\"0xdeadbeef\", 0) == %#08llx\n", safestr_touint64(str, 0));
        FAIL("safestr_touint64(\"0xdeadbeef\", 0) FAILED");
    }

    safestr_copy(&str, SAFESTR_TEMP("0xDEADBEEF"));
    if (safestr_toint32(str, 16) != (int32_t)0xDEADBEEF) {
        printf("safestr_toint32(\"0xDEADBEEF\", 16) == %#08x\n", safestr_toint32(str, 16));
        FAIL("safestr_toint32(\"0xDEADBEEF\", 16) FAILED");
    }
    if (safestr_toint64(str, 16) != (int64_t)0xDEADBEEF) {
        printf("safestr_toint64(\"0xDEADBEEF\", 16) == %#08llx\n", safestr_toint64(str, 16));
        FAIL("safestr_toint64(\"0xDEADBEEF\", 16) FAILED");
    }
    if (safestr_touint32(str, 16) != (u_int32_t)0xDEADBEEF) {
        printf("safestr_touint32(\"0xDEADBEEF\", 16) == %#08x\n", safestr_touint32(str, 16));
        FAIL("safestr_touint32(\"0xDEADBEEF\", 16) FAILED");
    }
    if (safestr_touint64(str, 16) != (u_int64_t)0xDEADBEEF) {
        printf("safestr_touint64(\"0xDEADBEEF\", 16) == %#08llx\n", safestr_touint64(str, 16));
        FAIL("safestr_touint64(\"0xDEADBEEF\", 16) FAILED");
    }

    safestr_copy(&str, SAFESTR_TEMP("0177"));
    if (safestr_toint32(str, 8) != (int32_t)0177) {
        printf("safestr_toint32(\"0177\", 8) == %#08x\n", safestr_toint32(str, 8));
        FAIL("safestr_toint32(\"0177\", 8) FAILED");
    }
    if (safestr_toint64(str, 8) != (int64_t)0177) {
        printf("safestr_toint64(\"0177\", 8) == %#08llx\n", safestr_toint64(str, 8));
        FAIL("safestr_toint64(\"0177\", 8) FAILED");
    }
    if (safestr_touint32(str, 8) != (u_int32_t)0177) {
        printf("safestr_touint32(\"0177\", 8) == %#08x\n", safestr_touint32(str, 8));
        FAIL("safestr_touint32(\"0177\", 8) FAILED");
    }
    if (safestr_touint64(str, 8) != (u_int64_t)0177) {
        printf("safestr_touint64(\"0177\", 8) == %#08llx\n", safestr_touint64(str, 8));
        FAIL("safestr_touint64(\"0177\", 8) FAILED");
    }

    safestr_free(str);
}

typedef struct sprintf_int_t
{
    char *fmt;
    char *expect;
    int  value;
} sprintf_int_t;

static sprintf_int_t int_tests[] =
{
    { "%6d",      "    42",          42 },  /*  7 */
    { "%6d",      "   -42",         -42 },  /*  8 */
    { "%+6d",     "   +42",          42 },  /*  9 */
    { "%+6d",     "   -42",         -42 },  /* 10 */
    { "%-6d",     "42    ",          42 },  /* 11 */
    { "%-6d",     "-42   ",         -42 },  /* 12 */
    { "%-+6d",    "+42   ",          42 },  /* 13 */
    { "%-+6d",    "-42   ",         -42 },  /* 14 */
    { "%12.6d",   "      004217",  4217 },  /* 15 */
    { "%12.6d",   "     -004217", -4217 },  /* 16 */
    { "%+12.6d",  "     +004217",  4217 },  /* 17 */
    { "%+12.6d",  "     -004217", -4217 },  /* 18 */
    { "%-12.6d",  "004217      ",  4217 },  /* 19 */
    { "%-12.6d",  "-004217     ", -4217 },  /* 20 */
    { "%-+12.6d", "+004217     ",  4217 },  /* 21 */
    { "%-+12.6d", "-004217     ", -4217 },  /* 22 */
    { "%06d",     "000042",          42 },  /* 23 */
    { "%06d",     "-00042",         -42 },  /* 24 */
    { "%0+6d",    "+00042",          42 },  /* 25 */
    { "%0+6d",    "-00042",         -42 },  /* 26 */
    { "%012.6d",  "      000042",    42 },  /* 27 */
    { "%012.6d",  "     -000042",   -42 },  /* 28 */
    { "%0+12.6d", "     +000042",    42 },  /* 29 */
    { "%0+12.6d", "     -000042",   -42 },  /* 30 */
    { "%o",       "52",              42 },  /* 31 */
    { "%.o",      "52",              42 },  /* 32 */
    { "%#.o",     "052",             42 },  /* 33 */
    { "%x",       "2a",              42 },  /* 34 */
    { "%X",       "2A",              42 },  /* 35 */
    { "%.x",      "2a",              42 },  /* 36 */
    { "%08x",     "0000002a",        42 },  /* 37 */
    { "%#08x",    "0x00002a",        42 },  /* 38 */
    { "%#8x",     "    0x2a",        42 },  /* 39 */
    { "%#x",      "0x2a",            42 },  /* 40 */
    { "%#.x",     "0x2a",            42 },  /* 41 */
    { "%#.8x",    "0x0000002a",      42 },  /* 42 */
    { "%c",       "*",               42 },  /* 43 */
    { "%p",       "0x00007a69",   31337 },  /* 44 */
    { NULL,       NULL,               0 },
};

typedef struct sprintf_float_t
{
    char   *fmt;
    char   *expect;
    double value;
} sprintf_float_t;

/* Good enough for now since we're just basically testing to make sure that
 * the whole format string gets passed through to the underlying sprintf()
 * function, which is really shitty since not all platforms have %a/%A
 */
static sprintf_float_t float_tests[] =
{
    { "%#e", "4.217000e+01", 42.17 },   /* 45 */
    { "%#f", "42.170000",    42.17 },   /* 46 */
    { "%#g", "42.1700",      42.17 },   /* 47 */
    { NULL, NULL, 0 },
};

#define SPRINTF_TEST(num)                                                   \
    do {                                                                    \
        if (!safestr_equal(str, SAFESTR_TEMP(c_str), 0)) {                  \
            printf("str:   [%s] length = %u\nc_str: [%s] length = %zu\n",   \
                   (char *)str, safestr_length(str),                        \
                   c_str, strlen(c_str));                                   \
            FAIL("safestr_sprintf() test #" #num " FAILED");                \
        }                                                                   \
    } while (0)

static void
test_sprintf(void)
{
    int             i;
    char            *c_str, msg[256];
    safestr_t       str;
    sprintf_int_t   *int_test;
    sprintf_float_t *float_test;

    str = safestr_alloc(0, 0);

    /* String Tests */
    c_str = "This is a simple test.";
    safestr_sprintf(&str, SAFESTR_TEMP(c_str));
    SPRINTF_TEST(1);

    c_str = "Is the SafeStr library working?";
    safestr_sprintf(&str, SAFESTR_TEMP("%s"), SAFESTR_TEMP(c_str));
    SPRINTF_TEST(2);

    c_str = "safestr   ";
    safestr_sprintf(&str, SAFESTR_TEMP("%-10s"), SAFESTR_TEMP("safestr"));
    SPRINTF_TEST(3);

    c_str = "   safestr";
    safestr_sprintf(&str, SAFESTR_TEMP("%10s"), SAFESTR_TEMP("safestr"));
    SPRINTF_TEST(4);

    c_str = "safe";
    safestr_sprintf(&str, SAFESTR_TEMP("%.4s"), SAFESTR_TEMP("safestr"));
    SPRINTF_TEST(5);

    c_str = "";
    safestr_sprintf(&str, SAFESTR_TEMP("%.s"), SAFESTR_TEMP("safestr"));
    SPRINTF_TEST(6);

    /* Integer Tests */
    i = 7;
    for (int_test = int_tests;  int_test->fmt;  int_test++, i++) {
        safestr_sprintf(&str, SAFESTR_TEMP(int_test->fmt), int_test->value);
        if (!safestr_equal(str, SAFESTR_TEMP(int_test->expect), 0)) {
            printf("got:    [%s] length = %u\nexpect: [%s] length = %zu\n",
                   (char *)str, safestr_length(str),
                   int_test->expect, strlen(int_test->expect));
            sprintf(msg, "safestr_sprintf() test #%d FAILED", i);
            FAIL(msg);
        }
    }

    /* Floating Point Tests */
    for (float_test = float_tests;  float_test->fmt;  float_test++, i++) {
        safestr_sprintf(&str, SAFESTR_TEMP(float_test->fmt), float_test->value);
        if (!safestr_equal(str, SAFESTR_TEMP(float_test->expect), 0)) {
            printf("got:    [%s] length = %u\nexpect: [%s] length = %zu\n",
                   (char *)str, safestr_length(str),
                   float_test->expect, strlen(float_test->expect));
            sprintf(msg, "safestr_sprintf() test #%d FAILED", i);
            FAIL(msg);
        }
    }

    safestr_free(str);
}

static void
test_readline(void)
{
    char        c_str[256];
    FILE        *f;
    safestr_t   str;

    /* First create a temporary file to read from with safestr_readline() */
    f = fopen("safetest.txt", "w");
    memset(c_str, 'x', 42);  c_str[42] = '\0';
    fprintf(f, "%s\n", c_str);
    memset(c_str, 'x', 110);  c_str[110] = '\0';
    fprintf(f, "%s\n", c_str);
    memset(c_str, 'x', 250);  c_str[250] = '\0';
    fprintf(f, "%s\n", c_str);
    fclose(f);

    /* Open the file we just created and read from it with safestr_readline() */
    f = fopen("safetest.txt", "r");

    memset(c_str, 'x', 42);  c_str[42] = '\0';
    str = safestr_readline(f);
    if (!safestr_equal(str, SAFESTR_TEMP(c_str), 0)) {
        fclose(f);
        remove("safetest.txt");
        printf("str:   [%s]\nc_str: [%s]\n", (char *)str, c_str);
        FAIL("safestr_readline() length of 42 failed!");
    }
    safestr_free(str);

    memset(c_str, 'x', 110);  c_str[110] = '\0';
    str = safestr_readline(f);
    if (!safestr_equal(str, SAFESTR_TEMP(c_str), 0)) {
        fclose(f);
        remove("safetest.txt");
        printf("str:   [%s]\nc_str: [%s]\n", (char *)str, c_str);
        FAIL("safestr_readline() length of 110 failed!");
    }
    safestr_free(str);

    memset(c_str, 'x', 250);  c_str[250] = '\0';
    str = safestr_readline(f);
    if (!safestr_equal(str, SAFESTR_TEMP(c_str), 0)) {
        fclose(f);
        remove("safetest.txt");
        printf("str:   [%s]\nc_str: [%s]\n", (char *)str, c_str);
        FAIL("safestr_readline() length of 250 failed!");
    }
    safestr_free(str);

    /* cleanup */
    fclose(f);
    remove("safetest.txt");
}

int main(int argc, char *argv[])
{
    char    *tmp;

    tmp = safestr_strdup(argv[0]);
    if (strcmp(tmp, argv[0])) FAIL("safestr_strdup failed");
    free(tmp);

    test_alloc();
    test_create();
    test_comparison();
    test_modify();
    test_arrays();
    test_misc();
    test_sprintf();
    test_readline();

#if 0
    if (isatty(fileno(stdin))) {
        safestr_t   password;

        password = safestr_getpassword(NULL, SAFESTR_TEMP("Enter a password: "));
        printf("Got: [%s]\n", (char *)password);
        safestr_free(password);
    }
#endif

    exit(0);
    return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1