root/usr/src/test/libc-tests/tests/wcsrtombs/wcsrtombs_test.c
/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright 2014 Garrett D'Amore <garrett@damore.org>
 */

/*
 * This program tests that wcsrtombs and friends work properly.
 * In order for it to work, it requires that some additional locales
 * be installed.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <locale.h>
#include <wchar.h>
#include <err.h>
#include <errno.h>
#include <unistd.h>
#include <xlocale.h>
#include <note.h>
#include "test_common.h"

int extra_debug = 0;

#define NUMTHR  300
#define NUMITR  300

/*
 * Note that this file is easiest edited with a UTF-8 capable editor,
 * as there are embedded UTF-8 symbols in some of the strings.
 */
struct wcsrtombs_test {
        char            mbs[32];
        wchar_t         wcs[32];
};

#define TESTING_MBS     "TESTING"
#define TESTING_WCS     { 'T', 'E', 'S', 'T', 'I', 'N', 'G', 0 }
#define HELLO_RU_MBS    "ПРИВЕТ"
#define HELLO_RU_WCS    { 1055, 1056, 1048, 1042, 1045, 1058, 0 }
#define HELLO_EN_MBS    "HELLO"
#define HELLO_EN_WCS    { 'H', 'E', 'L', 'L', 'O', 0 }

/* Unicode values never have the high order bit set */
#define BAD_WCS         { 'B', 'A', 'D', (wchar_t)0xf000f000, 'W', 'C', 'S' }

struct wcsrtombs_test C_data[] = {
        { TESTING_MBS,  TESTING_WCS },
        { HELLO_EN_MBS, HELLO_EN_WCS },
        { 0, 0 },
};

struct wcsrtombs_test utf8_data[] = {
        { TESTING_MBS,  TESTING_WCS },
        { HELLO_EN_MBS, HELLO_EN_WCS },
        { HELLO_RU_MBS, HELLO_RU_WCS },
        { 0, 0 },
};

struct {
        const char *locale;
        struct wcsrtombs_test *test;
} locales[] =  {
        { "C",                  C_data },
        { "en_US.UTF-8",        utf8_data },
        { NULL,                 NULL }
};

void
test_wcsrtombs_1(const char *locale, struct wcsrtombs_test *test)
{
        test_t          t;
        char            *v;
        mbstate_t       ms;

        t = test_start("wcsrtombs (locale %s)", locale);

        v = setlocale(LC_ALL, locale);
        if (v == NULL) {
                test_failed(t, "setlocale failed: %s", strerror(errno));
        }
        if (strcmp(v, locale) != 0) {
                test_failed(t, "setlocale got %s instead of %s", v, locale);
        }

        for (int i = 0; test[i].mbs[0] != 0; i++) {
                char mbs[32];
                const wchar_t *wcs = test[i].wcs;
                size_t cnt;

                (void) memset(&ms, 0, sizeof (ms));
                (void) memset(mbs, 0, sizeof (mbs));
                cnt = wcsrtombs(mbs, &wcs, sizeof (mbs), &ms);
                if (cnt != strlen(test[i].mbs)) {
                        test_failed(t, "incorrect return value: %d != %d",
                            cnt, strlen(test[i].mbs));
                }
                if (strcmp(mbs, test[i].mbs) != 0) {
                        test_failed(t, "wrong result: %s != %s",
                            mbs, test[i].mbs);
                }
                if (extra_debug) {
                        test_debugf(t, "mbs is %s", mbs);
                }
        }
        test_passed(t);
}

void
test_wcsrtombs_l(const char *locale, struct wcsrtombs_test *test)
{
        test_t  t;
        locale_t loc;
        char    *v;
        mbstate_t       ms;

        t = test_start("wcsrtombs_l (locale %s)", locale);

        v = setlocale(LC_ALL, "C");
        if (v == NULL) {
                test_failed(t, "setlocale failed: %s", strerror(errno));
        }
        if (strcmp(v, "C") != 0) {
                test_failed(t, "setlocale got %s instead of %s", v, "C");
        }

        loc = newlocale(LC_ALL_MASK, locale, NULL);
        if (loc == NULL) {
                test_failed(t, "newlocale failed: %s", strerror(errno));
        }

        for (int i = 0; test[i].mbs[0] != 0; i++) {
                char mbs[32];
                const wchar_t *wcs = test[i].wcs;
                size_t cnt;

                (void) memset(&ms, 0, sizeof (ms));
                (void) memset(mbs, 0, sizeof (mbs));
                cnt = wcsrtombs_l(mbs, &wcs, sizeof (mbs), &ms, loc);
                if (cnt != strlen(test[i].mbs)) {
                        test_failed(t, "incorrect return value: %d != %d",
                            cnt, strlen(test[i].mbs));
                }
                if (strcmp(mbs, test[i].mbs) != 0) {
                        test_failed(t, "wrong result: %s != %s", mbs,
                            test[i].mbs);
                }
                if (extra_debug) {
                        test_debugf(t, "mbs is %s", mbs);
                }
        }
        test_passed(t);
}

void
test_wcsrtombs_thr_iter(test_t t, const char *locale,
    struct wcsrtombs_test *test)
{
        locale_t loc;
        mbstate_t       ms;

        loc = newlocale(LC_ALL_MASK, locale, NULL);
        if (loc == NULL) {
                test_failed(t, "newlocale failed: %s", strerror(errno));
        }

        for (int i = 0; test[i].mbs[0] != 0; i++) {
                char mbs[32];
                const wchar_t *wcs = test[i].wcs;
                size_t cnt;

                (void) memset(&ms, 0, sizeof (ms));
                (void) memset(mbs, 0, sizeof (mbs));
                cnt = wcsrtombs_l(mbs, &wcs, sizeof (mbs), &ms, loc);
                if (cnt != strlen(test[i].mbs)) {
                        test_failed(t, "incorrect return value: %d != %d",
                            cnt, strlen(test[i].mbs));
                }
                if (strcmp(mbs, test[i].mbs) != 0) {
                        test_failed(t, "wrong result: %s != %s", mbs,
                            test[i].mbs);
                }
                if (extra_debug) {
                        test_debugf(t, "mbs is %s", mbs);
                }
        }

        freelocale(loc);
}

void
test_wcsrtombs_thr_work(test_t t, void *arg)
{
        _NOTE(ARGUNUSED(arg));
        for (int j = 0; j < NUMITR; j++) {
                test_debugf(t, "iteration %d", j);
                for (int i = 0; locales[i].locale != NULL; i++) {
                        test_wcsrtombs_thr_iter(t, locales[i].locale,
                            locales[i].test);
                }
        }
        test_passed(t);
}

void
test_wcsrtombs_threaded(void)
{
        (void) setlocale(LC_ALL, "C");
        test_run(NUMTHR, test_wcsrtombs_thr_work, NULL, "wcsrtombs_threaded");
}

void
test_wcsrtombs_partial(void)
{
        test_t          t;
        mbstate_t       ms;
        wchar_t         src[32] = HELLO_RU_WCS;
        char            mbs[32];
        char            *dst;
        const wchar_t   *wcs;
        size_t          cnt;


        (void) memset(&ms, 0, sizeof (ms));
        t = test_start("wcsrtombs_partial");

        if (setlocale(LC_ALL, "ru_RU.UTF-8") == NULL) {
                test_failed(t, "setlocale failed: %s", strerror(errno));
        }

        wcs = src;
        dst = mbs;
        cnt = wcsrtombs(dst, &wcs, 1, &ms);
        if (cnt != 0) {
                test_failed(t, "gave back a conversion cnt %d != 0", cnt);
        }
        if (wcs != src) {
                test_failed(t, "incorrectly advanced wcs");
        }

        cnt = wcsrtombs(dst, &wcs, 2, &ms);
        if (cnt != 2) {
                test_failed(t, "gave back a conversion cnt %d != 2", cnt);
        }
        dst += cnt;

        cnt = wcsrtombs(dst, &wcs, 4, &ms);
        dst += cnt;

        cnt = wcsrtombs(dst, &wcs, sizeof (mbs) - strlen(mbs), &ms);
        if (extra_debug) {
                test_debugf(t, "mbs is %s", mbs);
        }
        if (strcmp(mbs, HELLO_RU_MBS) != 0) {
                test_failed(t, "wrong result: %s != %s", mbs, HELLO_RU_MBS);
        }
        test_passed(t);
}

void
test_wcsrtombs_negative(void)
{
        mbstate_t       ms;
        const wchar_t   *wcs;
        char            mbs[32];
        char            *dst;
        int             e;
        wchar_t         src[32] = BAD_WCS;
        test_t          t;
        int             cnt;

        t = test_start("wcsrtombs_negative");

        (void) memset(&ms, 0, sizeof (ms));
        if (setlocale(LC_ALL, "ru_RU.UTF-8") == NULL) {
                test_failed(t, "setlocale failed: %s", strerror(errno));
        }

        wcs = src;
        dst = mbs;
        cnt = wcsrtombs(dst, &wcs, sizeof (mbs), &ms);
        if (cnt != -1) {
                test_failed(t, "bogus success (%d)", cnt);
        }
        if ((e = errno) != EILSEQ) {
                test_failed(t, "wrong errno, wanted %d (EILSEQ), got %d: %s",
                    EILSEQ, e, strerror(e));
        }
        test_passed(t);
}

void
test_wcsnrtombs_partial(void)
{
        test_t          t;
        mbstate_t       ms;
        wchar_t         src[32] = HELLO_RU_WCS;
        char            mbs[32];
        char            *dst;
        const wchar_t   *wcs;
        size_t          cnt;


        (void) memset(&ms, 0, sizeof (ms));
        t = test_start("wcsrntombs_partial");

        if (setlocale(LC_ALL, "ru_RU.UTF-8") == NULL) {
                test_failed(t, "setlocale failed: %s", strerror(errno));
        }

        wcs = src;
        dst = mbs;
        cnt = wcsnrtombs(dst, &wcs, 1, 1, &ms);
        if (cnt != 0) {
                test_failed(t, "gave back a conversion cnt %d != 0", cnt);
        }
        if (wcs != src) {
                test_failed(t, "incorrectly advanced wcs");
        }

        /* we should get just 2 wide characters (expanding to 4 bytes) */
        cnt = wcsnrtombs(dst, &wcs, 2, sizeof (mbs), &ms);
        if (cnt != 4) {
                test_failed(t, "gave back a conversion cnt %d != 4", cnt);
        }
        dst += cnt;

        cnt = wcsnrtombs(dst, &wcs, 32, sizeof (mbs) - strlen(mbs), &ms);
        if (extra_debug) {
                test_debugf(t, "mbs is %s", mbs);
        }
        if (strcmp(mbs, HELLO_RU_MBS) != 0) {
                test_failed(t, "wrong result: %s != %s", mbs, HELLO_RU_MBS);
        }
        test_passed(t);
}

void
test_wcsrtombs(void)
{
        for (int i = 0; locales[i].locale != NULL; i++) {
                test_wcsrtombs_1(locales[i].locale, locales[i].test);
                test_wcsrtombs_l(locales[i].locale, locales[i].test);
        }
}

int
main(int argc, char **argv)
{
        int optc;

        while ((optc = getopt(argc, argv, "dfD")) != EOF) {
                switch (optc) {
                case 'd':
                        test_set_debug();
                        break;
                case 'f':
                        test_set_force();
                        break;
                case 'D':
                        test_set_debug();
                        extra_debug++;
                        break;
                default:
                        (void) fprintf(stderr, "Usage: %s [-dfD]\n", argv[0]);
                        exit(1);
                }
        }

        test_wcsrtombs();
        test_wcsrtombs_partial();
        test_wcsrtombs_negative();
        test_wcsrtombs_threaded();
        test_wcsnrtombs_partial();

        exit(0);
}