/*
 * Unit tests for API functions of libuciconv
 *
 * SPDX-FileType: SOURCE
 * SPDX-FileCopyrightText: Michael Bäuerle
 * SPDX-License-Identifier: BSD-2-Clause
 */

/* Check for XSI extension on POSIX systems older than POSIX.1-2001 */
#if (defined _POSIX_C_SOURCE) && (_POSIX_C_SOURCE < 200112L)
#include <unistd.h>
#if _XOPEN_VERSION >= 500
#define _XOPEN_SOURCE  _XOPEN_VERSION
#endif  /* _XOPEN_VERSION >= 500 */
#endif  /* (defined _POSIX_C_SOURCE) && (_POSIX_C_SOURCE < 200112L) */

/* System headers */
#include <errno.h>
#include <stdio.h>
#include <stddef.h>
#include <string.h>

/* libuciconv */
#include "libuciconv-0/iconv.h"

/* CHEAT unit test framework */
#ifndef __BASE_FILE__
#define __BASE_FILE__ UCIC0_UTFILE
#endif  /* __BASE_FILE__ */
#include <cheat.h>


#define UCIC0_I_TEST_INBUF_SIZE   1024U
#define UCIC0_I_TEST_OUTBUF_SIZE  1024U

/*
 * Print output buffer to stderr before compare with reference file.
 * Attention: Terminal must be configured for UTF-8.
 */
#define UCIC0_I_TEST_VERBOSE  0


CHEAT_DECLARE(
    static size_t rv CHEAT_COMMA inlen CHEAT_COMMA outlen;

    /* Input buffer */
    static char inbuf[UCIC0_I_TEST_INBUF_SIZE];

    /* Output buffer */
    static char outbuf[UCIC0_I_TEST_OUTBUF_SIZE];

    /* Fill input buffer with octet values representing the index */
    void PREPARE_INBUF(void)
    {
        size_t i;

        for (i = 0; 256 > i; ++i)
            inbuf[i] = i;
    }
)


/* Check size and print debug output after error */
CHEAT_DECLARE(
    void CHECK(size_t num, size_t expected)
    {
        if (expected != num)
        {
            /* Treat (size_t)-1 as error from return value */
            if ((size_t)-1 == rv)
            {
                fprintf(stderr, "Return code is (size_t)-1\n");
                perror("Decoded errno");
            }

            fprintf(stderr, "num: %lu, expected: %lu\n",
                    (unsigned long int)num, (unsigned long int)expected);
            fprintf(stderr, "Failed\n");
        }

        cheat_assert(expected == num);
    }
)


/* Load conversion input data from file */
CHEAT_DECLARE(
    void LOAD_INPUT_DATA(char *buf, size_t len, size_t *len_in, const char* pn)
    {
        FILE *fp = fopen(pn, "rb");

        cheat_assert(NULL != fp);
        if (NULL != fp)
        {
            int    rv_load = EOF;
            size_t i       = 0;

            *len_in = 0;
            for ( ; len > i; ++i)
            {
                rv_load = fgetc(fp);
                cheat_assert(EOF != rv_load);
                if (EOF == rv_load)
                    break;
                else
                {
                    buf[i] = (char)rv_load;
                    ++(*len_in);
                }
            }

            /* Check for EOF in reference data file */
            if (EOF != rv_load)
            {
                rv_load = fgetc(fp);
                cheat_assert(EOF == rv_load);
            }

            rv_load = fclose(fp);
            cheat_assert(0 == rv_load);
        }
    }
)


/* Compare conversion output with reference file */
CHEAT_DECLARE(
    void COMPARE_WITH_REFERENCE(const char *buf, size_t len, const char* pn)
    {
        FILE *fp = fopen(pn, "rb");

        /* Print output buffer on request */
        if (UCIC0_I_TEST_VERBOSE)
        {
            size_t i = 0;

            for (; len > i; ++i)
                fprintf(stderr, "%c", buf[i]);
            fprintf(stderr, "\n");
        }

        cheat_assert(NULL != fp);
        if (NULL != fp)
        {
            int    rv_compare = EOF;
            size_t i          = 0;

            /* Compare with reference data */
            for ( ; len > i; ++i)
            {
                unsigned int chk = (unsigned char)buf[i];

                rv_compare = fgetc(fp);
                cheat_assert(EOF != rv_compare);
                {
                    unsigned int ref = (unsigned char)rv_compare;

                    if (chk != (unsigned char)rv_compare)
                    {

                        fprintf( stderr, "Mismatch at index %u (0x%02X): "
                                 "Checked 0x%02X against Reference 0x%02X\n",
                                 (unsigned int) i, (unsigned int) i, chk, ref );
                    }
                    cheat_assert(chk == ref);
                }
            }

            /* Check for EOF in reference data file */
            rv_compare = fgetc(fp);
            cheat_assert(EOF == rv_compare);

            rv_compare = fclose(fp);
            cheat_assert(0 == rv_compare);
        }
    }
)


/* Prepare buffers with defined values */
CHEAT_SET_UP(
    (void)memset(inbuf , 42, UCIC0_I_TEST_INBUF_SIZE);
    (void)memset(outbuf, 42, UCIC0_I_TEST_OUTBUF_SIZE);
)


/* Print CHEAT version */
CHEAT_TEST(version,
    (void)cheat_print(stdout, "%s", 1, "Unit test framework: ");
    cheat_print_version();
    /* Yellow foreground color is unreadable with white background */
    (void)cheat_print(stdout,
                      "%s", 1, "Yellow foreground color patched out.\n\n");
)


CHEAT_TEST(empty_name,
    size_t rv;

    (void)fputs("Testing ucic0_iconvstr() with empty encoding name ... \n",
                stdout);

    inbuf[0] = 0x20;
    inlen    = 1;
    outlen   = UCIC0_I_TEST_OUTBUF_SIZE;
    rv = ucic0_iconvstr("", "CESU-8", inbuf, &inlen, outbuf, &outlen, 0);
    CHECK(rv, (size_t)-1);
    cheat_assert(EBADF == errno);
)


CHEAT_TEST(invalid_name,
    (void)fputs("Testing ucic0_iconvstr() with invalid encoding name ... \n",
                stdout);

    /* Invalid target encoding */
    inbuf[0] = 0x20;
    inlen    = 1;
    outlen   = UCIC0_I_TEST_OUTBUF_SIZE;
    rv = ucic0_iconvstr("INVAL", "UTF-7", inbuf, &inlen, outbuf, &outlen, 0);
    CHECK(rv, (size_t)-1);
    cheat_assert(EBADF == errno);
    cheat_yield();

    /* Invalid source encoding */
    inlen    = 1;
    outlen   = UCIC0_I_TEST_OUTBUF_SIZE;
    rv = ucic0_iconvstr("UTF-8", "INVAL", inbuf, &inlen, outbuf, &outlen, 0);
    CHECK(rv, (size_t)-1);
    cheat_assert(EBADF == errno);
)


/* === CESU-8 to UTF-8 converter === */


CHEAT_TEST(cesu8_ascii,
    (void)fputs("Testing ucic0_iconvstr() with US-ASCII as CESU-8 data ... \n",
                stdout);

    LOAD_INPUT_DATA(inbuf, 15, &inlen, "reference/US-ASCII.raw");
    cheat_yield();
    outlen = UCIC0_I_TEST_OUTBUF_SIZE;
    rv = ucic0_iconvstr("UTF-8", "CESU-8", inbuf, &inlen, outbuf, &outlen, 0);
    CHECK(rv, 0);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 15, "reference/US-ASCII.utf8");
)


CHEAT_TEST(cesu8_utf8,
    (void)fputs("Testing ucic0_iconvstr() with UTF-8 as CESU-8 data ... \n",
                stdout);

    LOAD_INPUT_DATA(inbuf, 17, &inlen, "reference/UTF-8.raw");
    cheat_yield();
    outlen = UCIC0_I_TEST_OUTBUF_SIZE;
    rv = ucic0_iconvstr("UTF-8", "CESU-8", inbuf, &inlen, outbuf, &outlen, 0);
    CHECK(rv, 0);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 17, "reference/UTF-8.utf8");
)


CHEAT_TEST(cesu8,
    (void)fputs("Testing ucic0_iconvstr() with CESU-8 data ... \n",
                stdout);

    LOAD_INPUT_DATA(inbuf, 20, &inlen, "reference/CESU-8.raw");
    cheat_yield();
    outlen = UCIC0_I_TEST_OUTBUF_SIZE;
    rv = ucic0_iconvstr("UTF-8", "CESU-8", inbuf, &inlen, outbuf, &outlen, 0);
    CHECK(rv, 0);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 18, "reference/CESU-8.utf8");
)


CHEAT_TEST(cesu8_nul,
    size_t inlen_orig;

    (void)fputs("Testing ucic0_iconvstr() with NUL control characters "
                "in CESU-8 data ... \n", stdout);

    /* NUL as the first character */
    inlen_orig = 5;
    LOAD_INPUT_DATA(inbuf, inlen_orig, &inlen, "reference/NUL_1.raw");
    cheat_yield();
    outlen = UCIC0_I_TEST_OUTBUF_SIZE;
    rv = ucic0_iconvstr("UTF-8", "CESU-8", inbuf, &inlen, outbuf, &outlen, 0);
    CHECK(rv, 0);
    cheat_yield();
    CHECK(inlen, inlen_orig);  /* Nothing consumed? */
    cheat_yield();
    /* Again with ignored NUL */
    outlen = UCIC0_I_TEST_OUTBUF_SIZE;
    rv = ucic0_iconvstr("UTF-8", "CESU-8", inbuf, &inlen, outbuf, &outlen,
                        UCIC0_ICONV_IGNORE_NULL);
    CHECK(rv, 0);
    cheat_yield();
    CHECK(inlen, 0);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, inlen_orig, "reference/NUL_1.raw");
    cheat_yield();

    /* NUL in the middle */
    inlen_orig = 8;
    LOAD_INPUT_DATA(inbuf, inlen_orig, &inlen, "reference/NUL_2.raw");
    cheat_yield();
    outlen = UCIC0_I_TEST_OUTBUF_SIZE;
    rv = ucic0_iconvstr("UTF-8", "CESU-8", inbuf, &inlen, outbuf, &outlen, 0);
    CHECK(rv, 0);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 3, "reference/NUL_2.utf8");
    cheat_yield();
    /* Again with ignored NUL */
    inlen  = inlen_orig;
    outlen = UCIC0_I_TEST_OUTBUF_SIZE;
    rv = ucic0_iconvstr("UTF-8", "CESU-8", inbuf, &inlen, outbuf, &outlen,
                        UCIC0_ICONV_IGNORE_NULL);
    CHECK(rv, 0);
    cheat_yield();
    CHECK(inlen, 0);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, inlen_orig, "reference/NUL_2.raw");
    cheat_yield();

    /* NUL at the end */
    inlen_orig = 4;
    LOAD_INPUT_DATA(inbuf, inlen_orig, &inlen, "reference/NUL_3.raw");
    cheat_yield();
    outlen = UCIC0_I_TEST_OUTBUF_SIZE;
    rv = ucic0_iconvstr("UTF-8", "CESU-8", inbuf, &inlen, outbuf, &outlen, 0);
    CHECK(rv, 0);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 3, "reference/NUL_3.utf8");
    cheat_yield();
    /* Again with ignored NUL */
    inlen  = inlen_orig;
    outlen = UCIC0_I_TEST_OUTBUF_SIZE;
    rv = ucic0_iconvstr("UTF-8", "CESU-8", inbuf, &inlen, outbuf, &outlen,
                        UCIC0_ICONV_IGNORE_NULL);
    CHECK(rv, 0);
    cheat_yield();
    CHECK(inlen, 0);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, inlen_orig, "reference/NUL_3.raw");
    cheat_yield();

    /* NUL inside UTF-8 sequence (after first octet) */
    inlen_orig = 14;
    LOAD_INPUT_DATA(inbuf, inlen_orig, &inlen, "reference/NUL_4.raw");
    cheat_yield();
    outlen = UCIC0_I_TEST_OUTBUF_SIZE;
    rv = ucic0_iconvstr("UTF-8", "CESU-8", inbuf, &inlen, outbuf, &outlen, 0);
    CHECK(rv, (size_t)-1);
    cheat_assert(EINVAL == errno);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 5, "reference/NUL_4.utf8");
    cheat_yield();

    /* NUL inside UTF-8 sequence (after second octet) */
    inlen_orig = 14;
    LOAD_INPUT_DATA(inbuf, inlen_orig, &inlen, "reference/NUL_5.raw");
    cheat_yield();
    outlen = UCIC0_I_TEST_OUTBUF_SIZE;
    rv = ucic0_iconvstr("UTF-8", "CESU-8", inbuf, &inlen, outbuf, &outlen, 0);
    CHECK(rv, (size_t)-1);
    cheat_assert(EINVAL == errno);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 5, "reference/NUL_5.utf8");
    cheat_yield();

    /* NUL inside low surrogate codepoint (after first octet) */
    inlen_orig = 17;
    LOAD_INPUT_DATA(inbuf, inlen_orig, &inlen, "reference/NUL_6.raw");
    cheat_yield();
    outlen = UCIC0_I_TEST_OUTBUF_SIZE;
    rv = ucic0_iconvstr("UTF-8", "CESU-8", inbuf, &inlen, outbuf, &outlen, 0);
    CHECK(rv, (size_t)-1);
    cheat_assert(EINVAL == errno);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 5, "reference/NUL_6.utf8");
    cheat_yield();

    /* NUL inside low surrogate codepoint (after second octet) */
    inlen_orig = 17;
    LOAD_INPUT_DATA(inbuf, inlen_orig, &inlen, "reference/NUL_7.raw");
    cheat_yield();
    outlen = UCIC0_I_TEST_OUTBUF_SIZE;
    rv = ucic0_iconvstr("UTF-8", "CESU-8", inbuf, &inlen, outbuf, &outlen, 0);
    CHECK(rv, (size_t)-1);
    cheat_assert(EINVAL == errno);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 5, "reference/NUL_7.utf8");
)


CHEAT_TEST(cesu8_nonidentical,
    size_t inlen_orig;

    (void)fputs("Testing ucic0_iconvstr() "
                "with non-identical conversions in CESU-8 data ... \n", stdout);

    /* Broken UTF-8 sequence */
    inlen_orig = 16;
    LOAD_INPUT_DATA(inbuf, inlen_orig, &inlen, "reference/NONIDENT_1.raw");
    cheat_yield();
    outlen = UCIC0_I_TEST_OUTBUF_SIZE;
    rv = ucic0_iconvstr("UTF-8", "CESU-8", inbuf, &inlen, outbuf, &outlen, 0);
    CHECK(rv, (size_t)-1);
    cheat_assert(EILSEQ == errno);
    cheat_yield();
    CHECK(inlen_orig - inlen, 5U);
    cheat_yield();
    /* Again with replacement enabled */
    inlen  = inlen_orig;
    outlen = UCIC0_I_TEST_OUTBUF_SIZE;
    rv = ucic0_iconvstr("UTF-8", "CESU-8", inbuf, &inlen, outbuf, &outlen,
                        UCIC0_ICONV_REPLACE_INVALID);
    CHECK(rv, 3);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 22, "reference/NONIDENT_1.utf8");
    cheat_yield();

    /* NUL (ignored as termination) in UTF-8 sequence */
    inlen_orig = 16;
    LOAD_INPUT_DATA(inbuf, inlen_orig, &inlen, "reference/NONIDENT_2.raw");
    cheat_yield();
    outlen = UCIC0_I_TEST_OUTBUF_SIZE;
    rv = ucic0_iconvstr("UTF-8", "CESU-8", inbuf, &inlen, outbuf, &outlen,
                        UCIC0_ICONV_IGNORE_NULL | UCIC0_ICONV_REPLACE_INVALID);
    CHECK(rv, 2);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 20, "reference/NONIDENT_2.utf8");
    cheat_yield();

    /* Broken surrogate pair (invalid sequence for high surrogate codepoint) */
    inlen_orig = 20;
    LOAD_INPUT_DATA(inbuf, inlen_orig, &inlen, "reference/NONIDENT_3.raw");
    cheat_yield();
    outlen = UCIC0_I_TEST_OUTBUF_SIZE;
    rv = ucic0_iconvstr("UTF-8", "CESU-8", inbuf, &inlen, outbuf, &outlen, 0);
    CHECK(rv, (size_t)-1);
    cheat_assert(EILSEQ == errno);
    cheat_yield();
    CHECK(inlen_orig - inlen, 9U);
    cheat_yield();
    /* Again with replacement enabled */
    inlen  = inlen_orig;
    outlen = UCIC0_I_TEST_OUTBUF_SIZE;
    rv = ucic0_iconvstr("UTF-8", "CESU-8", inbuf, &inlen, outbuf, &outlen,
                        UCIC0_ICONV_REPLACE_INVALID);
    CHECK(rv, 6);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 32, "reference/NONIDENT_3.utf8");
    cheat_yield();

    /* Broken surrogate pair (invalid sequence for low surrogate codepoint) */
    inlen_orig = 20;
    LOAD_INPUT_DATA(inbuf, inlen_orig, &inlen, "reference/NONIDENT_4.raw");
    cheat_yield();
    outlen = UCIC0_I_TEST_OUTBUF_SIZE;
    rv = ucic0_iconvstr("UTF-8", "CESU-8", inbuf, &inlen, outbuf, &outlen, 0);
    CHECK(rv, (size_t)-1);
    cheat_assert(EILSEQ == errno);
    cheat_yield();
    CHECK(inlen_orig - inlen, 9U);
    cheat_yield();
    /* Again with replacement enabled */
    inlen  = inlen_orig;
    outlen = UCIC0_I_TEST_OUTBUF_SIZE;
    rv = ucic0_iconvstr("UTF-8", "CESU-8", inbuf, &inlen, outbuf, &outlen,
                        UCIC0_ICONV_REPLACE_INVALID);
    CHECK(rv, 6);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 32, "reference/NONIDENT_4.utf8");
    cheat_yield();

    /* Broken surrogate pair (swapped surrogate codepoints) */
    inlen_orig = 20;
    LOAD_INPUT_DATA(inbuf, inlen_orig, &inlen, "reference/NONIDENT_5.raw");
    cheat_yield();
    outlen = UCIC0_I_TEST_OUTBUF_SIZE;
    rv = ucic0_iconvstr("UTF-8", "CESU-8", inbuf, &inlen, outbuf, &outlen, 0);
    CHECK(rv, (size_t)-1);
    cheat_assert(EILSEQ == errno);
    cheat_yield();
    CHECK(inlen_orig - inlen, 5U);
    cheat_yield();
    /* Again with replacement enabled */
    inlen  = inlen_orig;
    outlen = UCIC0_I_TEST_OUTBUF_SIZE;
    rv = ucic0_iconvstr("UTF-8", "CESU-8", inbuf, &inlen, outbuf, &outlen,
                        UCIC0_ICONV_REPLACE_INVALID);
    CHECK(rv, 6);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 32, "reference/NONIDENT_5.utf8");
)


/* === UTF-7 to UTF-8 converter === */


CHEAT_TEST(utf7_ascii,
    (void)fputs("Testing ucic0_iconvstr() with US-ASCII as UTF-7 data ... \n",
                stdout);

    LOAD_INPUT_DATA(inbuf, 15, &inlen, "reference/US-ASCII.raw");
    cheat_yield();
    outlen = UCIC0_I_TEST_OUTBUF_SIZE;
    rv = ucic0_iconvstr("UTF-8", "UTF-7", inbuf, &inlen, outbuf, &outlen, 0);
    CHECK(rv, 0);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 15, "reference/US-ASCII.utf8");
)


CHEAT_TEST(utf7_plus,
    (void)fputs("Testing ucic0_iconvstr() with \"+-\" in UTF-7 data ... \n",
                stdout);

    LOAD_INPUT_DATA(inbuf, 12, &inlen, "reference/UTF-7_plus.raw");
    cheat_yield();
    outlen = UCIC0_I_TEST_OUTBUF_SIZE;
    rv = ucic0_iconvstr("UTF-8", "UTF-7", inbuf, &inlen, outbuf, &outlen, 0);
    CHECK(rv, 0);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 11, "reference/UTF-7_plus.utf8");
)


CHEAT_TEST(utf7_shift,
    (void)fputs("Testing ucic0_iconvstr() with UTF-7 shift sequences ... \n",
                stdout);

    LOAD_INPUT_DATA(inbuf, 41, &inlen, "reference/UTF-7_shift.raw");
    cheat_yield();
    outlen = UCIC0_I_TEST_OUTBUF_SIZE;
    rv = ucic0_iconvstr("UTF-8", "UTF-7", inbuf, &inlen, outbuf, &outlen, 0);
    CHECK(rv, 0);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 33, "reference/UTF-7_shift.utf8");
)


CHEAT_TEST(utf7_eoft,
    (void)fputs("Testing ucic0_iconvstr() with UTF-7 shift sequence "
                "at EOF ... \n", stdout);

    LOAD_INPUT_DATA(inbuf, 9, &inlen, "reference/UTF-7_eof.raw");
    cheat_yield();
    outlen = UCIC0_I_TEST_OUTBUF_SIZE;
    rv = ucic0_iconvstr("UTF-8", "UTF-7", inbuf, &inlen, outbuf, &outlen, 0);
    CHECK(rv, 0);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 8, "reference/UTF-7_eof.utf8");
)


CHEAT_TEST(utf7_pair,
    (void)fputs("Testing ucic0_iconvstr() with UTF-7 data containing "
                "surrogate-pair ... \n", stdout);

    LOAD_INPUT_DATA(inbuf, 34, &inlen, "reference/UTF-7_pair.raw");
    cheat_yield();
    outlen = UCIC0_I_TEST_OUTBUF_SIZE;
    rv = ucic0_iconvstr("UTF-8", "UTF-7", inbuf, &inlen, outbuf, &outlen, 0);
    CHECK(rv, 0);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 29, "reference/UTF-7_pair.utf8");
)


CHEAT_TEST(utf7_nul,
    size_t inlen_orig;

    (void)fputs("Testing ucic0_iconvstr() with NUL control characters "
                "in UTF-7 data ... \n", stdout);

    /* NUL as the first character */
    inlen_orig = 5;
    LOAD_INPUT_DATA(inbuf, inlen_orig, &inlen, "reference/NUL_1.raw");
    cheat_yield();
    outlen = UCIC0_I_TEST_OUTBUF_SIZE;
    rv = ucic0_iconvstr("UTF-8", "UTF-7", inbuf, &inlen, outbuf, &outlen, 0);
    CHECK(rv, 0);
    cheat_yield();
    CHECK(inlen, inlen_orig);  /* Nothing consumed? */
    cheat_yield();
    /* Again with ignored NUL */
    outlen = UCIC0_I_TEST_OUTBUF_SIZE;
    rv = ucic0_iconvstr("UTF-8", "UTF-7", inbuf, &inlen, outbuf, &outlen,
                        UCIC0_ICONV_IGNORE_NULL);
    CHECK(rv, (size_t)-1);
    cheat_assert(EILSEQ == errno);
    cheat_yield();
    CHECK(inlen, inlen_orig);  /* Nothing consumed? */

    /* NUL in the middle */
    inlen_orig = 8;
    LOAD_INPUT_DATA(inbuf, inlen_orig, &inlen, "reference/NUL_2.raw");
    cheat_yield();
    outlen = UCIC0_I_TEST_OUTBUF_SIZE;
    rv = ucic0_iconvstr("UTF-8", "UTF-7", inbuf, &inlen, outbuf, &outlen, 0);
    CHECK(rv, 0);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 3, "reference/NUL_2.utf8");
    cheat_yield();
    /* Again with ignored NUL */
    inlen  = inlen_orig;
    outlen = UCIC0_I_TEST_OUTBUF_SIZE;
    rv = ucic0_iconvstr("UTF-8", "UTF-7", inbuf, &inlen, outbuf, &outlen,
                        UCIC0_ICONV_IGNORE_NULL);
    CHECK(rv, (size_t)-1);
    cheat_assert(EILSEQ == errno);
    cheat_yield();
    CHECK(inlen_orig - inlen, 3U);
)


CHEAT_TEST(utf7_empty,
    (void)fputs("Testing ucic0_iconvstr() with empty UTF-7 shift "
                "sequence ... \n", stdout);

    LOAD_INPUT_DATA(inbuf, 11, &inlen, "reference/UTF-7_empty.raw");
    cheat_yield();
    outlen = UCIC0_I_TEST_OUTBUF_SIZE;
    rv = ucic0_iconvstr("UTF-8", "UTF-7", inbuf, &inlen, outbuf, &outlen, 0);
    CHECK(rv, 0);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 10, "reference/UTF-7_empty.utf8");
)


CHEAT_TEST(utf7_nonident1,
    (void)fputs("Testing ucic0_iconvstr() with UTF-7 data and replaced "
                "NUL control character ... \n",  stdout);

    LOAD_INPUT_DATA(inbuf, 11, &inlen, "reference/UTF-7_NI_1.raw");
    cheat_yield();
    outlen = UCIC0_I_TEST_OUTBUF_SIZE;
    rv = ucic0_iconvstr("UTF-8", "UTF-7", inbuf, &inlen, outbuf, &outlen,
                        UCIC0_ICONV_IGNORE_NULL | UCIC0_ICONV_REPLACE_INVALID);
    CHECK(rv, 1);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 13, "reference/UTF-7_NI_1.utf8");
)


CHEAT_TEST(utf7_nonident2,
    (void)fputs("Testing ucic0_iconvstr() with UTF-7 shift sequence and "
                "replaced unpaired low surrogate codepoint ... \n",  stdout);

    LOAD_INPUT_DATA(inbuf, 72, &inlen, "reference/UTF-7_NI_2.raw");
    cheat_yield();
    outlen = UCIC0_I_TEST_OUTBUF_SIZE;
    rv = ucic0_iconvstr("UTF-8", "UTF-7", inbuf, &inlen, outbuf, &outlen,
                        UCIC0_ICONV_REPLACE_INVALID);
    CHECK(rv, 1);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 69, "reference/UTF-7_NI_2.utf8");
)


CHEAT_TEST(utf7_nonident3,
    (void)fputs("Testing ucic0_iconvstr() with UTF-7 shift sequence and "
                "replaced unpaired high surrogate codepoint ... \n",  stdout);

    LOAD_INPUT_DATA(inbuf, 73, &inlen, "reference/UTF-7_NI_3.raw");
    cheat_yield();
    outlen = UCIC0_I_TEST_OUTBUF_SIZE;
    rv = ucic0_iconvstr("UTF-8", "UTF-7", inbuf, &inlen, outbuf, &outlen,
                        UCIC0_ICONV_REPLACE_INVALID);
    CHECK(rv, 1);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 70, "reference/UTF-7_NI_3.utf8");
)
