/*
 * Copyright (c) 2015-2021 Nicholas Fraser and the MPack authors
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

// we need internal to access the utf-8 check functions
#define MPACK_INTERNAL 1

#include "test-common.h"

#include "test.h"

static void test_tags_special(void) {

    // we emit a warning for inlines on tcc because they are static.
    #ifndef __TINYC__
    // for all other compilers, ensure there is only one inline definition (the
    // global address is in test.c)
    TEST_TRUE(fn_mpack_tag_nil == &mpack_tag_nil);
    #endif

    // test comparison with invalid tags
    // (invalid enum values cause undefined behavior in C++)
    #if MPACK_DEBUG && !defined(__cplusplus)
    mpack_tag_t invalid = mpack_tag_nil();
    invalid.type = (mpack_type_t)-1;
    TEST_ASSERT(mpack_tag_cmp(invalid, invalid));
    #endif

}

static void test_tags_simple(void) {

    // ensure tag types are correct
    TEST_TRUE(mpack_tag_nil().type == mpack_type_nil);
    TEST_TRUE(mpack_tag_bool(false).type == mpack_type_bool);
    TEST_TRUE(mpack_tag_int(0).type == mpack_type_int);
    TEST_TRUE(mpack_tag_uint(0).type == mpack_type_uint);

    // uints
    TEST_TRUE(mpack_tag_uint(0).v.u == 0);
    TEST_TRUE(mpack_tag_uint(1).v.u == 1);
    TEST_TRUE(mpack_tag_uint(MPACK_INT32_MAX).v.u == MPACK_INT32_MAX);
    TEST_TRUE(mpack_tag_uint(MPACK_INT64_MAX).v.u == MPACK_INT64_MAX);

    // ints
    TEST_TRUE(mpack_tag_int(0).v.i == 0);
    TEST_TRUE(mpack_tag_int(1).v.i == 1);
    // when using INT32_C() and compiling the test suite as c++, gcc complains:
    // error: this decimal constant is unsigned only in ISO C90 [-Werror]
    TEST_TRUE(mpack_tag_int(MPACK_INT64_C(-2147483648)).v.i == MPACK_INT64_C(-2147483648));
    TEST_TRUE(mpack_tag_int(MPACK_INT64_MIN).v.i == MPACK_INT64_MIN);

    // bools
    TEST_TRUE(mpack_tag_bool(true).v.b == true);
    TEST_TRUE(mpack_tag_bool(false).v.b == false);
    TEST_TRUE(mpack_tag_bool(1).v.b == true);
    TEST_TRUE(mpack_tag_bool(0).v.b == false);

    // comparisons of simple types
    TEST_TRUE(true == mpack_tag_equal(mpack_tag_nil(), mpack_tag_nil()));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_nil(), mpack_tag_bool(false)));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_nil(), mpack_tag_uint(0)));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_bool(false), mpack_tag_int(0)));
    TEST_TRUE(true == mpack_tag_equal(mpack_tag_bool(false), mpack_tag_bool(false)));
    TEST_TRUE(true == mpack_tag_equal(mpack_tag_bool(true), mpack_tag_bool(true)));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_bool(false), mpack_tag_bool(true)));

    // uint/int comparisons
    TEST_TRUE(true == mpack_tag_equal(mpack_tag_uint(0), mpack_tag_uint(0)));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_uint(0), mpack_tag_uint(1)));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_uint(0), mpack_tag_uint(1)));
    TEST_TRUE(true == mpack_tag_equal(mpack_tag_uint(1), mpack_tag_uint(1)));
    TEST_TRUE(true == mpack_tag_equal(mpack_tag_int(0), mpack_tag_int(0)));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_int(0), mpack_tag_int(-1)));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_int(0), mpack_tag_int(-1)));
    TEST_TRUE(true == mpack_tag_equal(mpack_tag_int(-1), mpack_tag_int(-1)));

    // int to uint comparisons
    TEST_TRUE(true == mpack_tag_equal(mpack_tag_uint(0), mpack_tag_int(0)));
    TEST_TRUE(true == mpack_tag_equal(mpack_tag_uint(1), mpack_tag_int(1)));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_uint(0), mpack_tag_int(1)));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_uint(0), mpack_tag_int(1)));
    TEST_TRUE(true == mpack_tag_equal(mpack_tag_int(0), mpack_tag_uint(0)));
    TEST_TRUE(true == mpack_tag_equal(mpack_tag_int(1), mpack_tag_uint(1)));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_int(0), mpack_tag_uint(1)));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_int(0), mpack_tag_uint(1)));

    // ordering

    TEST_TRUE(-1 == mpack_tag_cmp(mpack_tag_uint(0), mpack_tag_uint(1)));
    TEST_TRUE(1 == mpack_tag_cmp(mpack_tag_uint(1), mpack_tag_uint(0)));
    TEST_TRUE(-1 == mpack_tag_cmp(mpack_tag_int(-2), mpack_tag_int(-1)));
    TEST_TRUE(1 == mpack_tag_cmp(mpack_tag_int(-1), mpack_tag_int(-2)));

    TEST_TRUE(-1 == mpack_tag_cmp(mpack_tag_str(0), mpack_tag_str(1)));
    TEST_TRUE(1 == mpack_tag_cmp(mpack_tag_str(1), mpack_tag_str(0)));
    TEST_TRUE(-1 == mpack_tag_cmp(mpack_tag_bin(0), mpack_tag_bin(1)));
    TEST_TRUE(1 == mpack_tag_cmp(mpack_tag_bin(1), mpack_tag_bin(0)));

    TEST_TRUE(-1 == mpack_tag_cmp(mpack_tag_array(0), mpack_tag_array(1)));
    TEST_TRUE(1 == mpack_tag_cmp(mpack_tag_array(1), mpack_tag_array(0)));
    TEST_TRUE(-1 == mpack_tag_cmp(mpack_tag_map(0), mpack_tag_map(1)));
    TEST_TRUE(1 == mpack_tag_cmp(mpack_tag_map(1), mpack_tag_map(0)));

    #if MPACK_EXTENSIONS
    TEST_TRUE(-1 == mpack_tag_cmp(mpack_tag_ext(1, 1), mpack_tag_ext(2, 0)));
    TEST_TRUE(-1 == mpack_tag_cmp(mpack_tag_ext(1, 1), mpack_tag_ext(1, 2)));
    TEST_TRUE(1 == mpack_tag_cmp(mpack_tag_ext(2, 0), mpack_tag_ext(1, 1)));
    TEST_TRUE(1 == mpack_tag_cmp(mpack_tag_ext(1, 2), mpack_tag_ext(1, 1)));
    #endif

}

static void test_tags_reals(void) {

    // types
    #if MPACK_FLOAT
    TEST_TRUE(mpack_tag_float(0.0f).type == mpack_type_float);
    TEST_TRUE(mpack_tag_float((float)NAN).type == mpack_type_float);
    #endif
    #if MPACK_DOUBLE
    TEST_TRUE(mpack_tag_double(0.0).type == mpack_type_double);
    TEST_TRUE(mpack_tag_double((double)NAN).type == mpack_type_double);
    #endif

    // float comparisons
    #if MPACK_FLOAT
    TEST_TRUE(true == mpack_tag_equal(mpack_tag_float(0), mpack_tag_float(0)));
    TEST_TRUE(true == mpack_tag_equal(mpack_tag_float(1), mpack_tag_float(1)));
    TEST_TRUE(true == mpack_tag_equal(mpack_tag_float(MPACK_FLOAT_POSITIVE_INFINITY), mpack_tag_float(MPACK_FLOAT_POSITIVE_INFINITY)));
    TEST_TRUE(true == mpack_tag_equal(mpack_tag_float(MPACK_FLOAT_NEGATIVE_INFINITY), mpack_tag_float(MPACK_FLOAT_NEGATIVE_INFINITY)));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_float(0), mpack_tag_float(1)));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_float(1), mpack_tag_float(MPACK_FLOAT_POSITIVE_INFINITY)));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_float(MPACK_FLOAT_POSITIVE_INFINITY), mpack_tag_float(MPACK_FLOAT_NEGATIVE_INFINITY)));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_float(0), mpack_tag_float(NAN)));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_float(MPACK_FLOAT_POSITIVE_INFINITY), mpack_tag_float(NAN)));
    #endif

    // double comparisons
    #if MPACK_DOUBLE
    TEST_TRUE(true == mpack_tag_equal(mpack_tag_double(0), mpack_tag_double(0)));
    TEST_TRUE(true == mpack_tag_equal(mpack_tag_double(1), mpack_tag_double(1)));
    TEST_TRUE(true == mpack_tag_equal(mpack_tag_double(MPACK_DOUBLE_POSITIVE_INFINITY), mpack_tag_double(MPACK_DOUBLE_POSITIVE_INFINITY)));
    TEST_TRUE(true == mpack_tag_equal(mpack_tag_double(MPACK_DOUBLE_NEGATIVE_INFINITY), mpack_tag_double(MPACK_DOUBLE_NEGATIVE_INFINITY)));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_double(0), mpack_tag_double(1)));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_double(1), mpack_tag_double(MPACK_DOUBLE_POSITIVE_INFINITY)));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_double(MPACK_DOUBLE_POSITIVE_INFINITY), mpack_tag_double(MPACK_DOUBLE_NEGATIVE_INFINITY)));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_double(0), mpack_tag_double(NAN)));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_double(MPACK_DOUBLE_POSITIVE_INFINITY), mpack_tag_double(NAN)));
    #endif

    // float/double comparisons
    #if MPACK_FLOAT && MPACK_DOUBLE
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_double(0), mpack_tag_float(0)));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_double(1), mpack_tag_float(1)));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_double(MPACK_DOUBLE_POSITIVE_INFINITY), mpack_tag_float(MPACK_FLOAT_POSITIVE_INFINITY)));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_double(MPACK_DOUBLE_NEGATIVE_INFINITY), mpack_tag_float(MPACK_FLOAT_NEGATIVE_INFINITY)));
    #endif

    // here we're comparing NaNs and we expect true. this is because we compare
    // floats bit-for-bit, not using operator==
    #if MPACK_FLOAT && MPACK_DOUBLE
    TEST_TRUE(true == mpack_tag_equal(mpack_tag_float(NAN), mpack_tag_float(NAN)));
    TEST_TRUE(true == mpack_tag_equal(mpack_tag_double(NAN), mpack_tag_double(NAN)));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_float(NAN), mpack_tag_double(NAN)));
    #endif

}

static void test_tags_compound(void) {
    TEST_TRUE(mpack_tag_array(0).type == mpack_type_array);
    TEST_TRUE(mpack_tag_map(0).type == mpack_type_map);
    TEST_TRUE(mpack_tag_str(0).type == mpack_type_str);
    TEST_TRUE(mpack_tag_bin(0).type == mpack_type_bin);
    #if MPACK_EXTENSIONS
    TEST_TRUE(mpack_tag_ext(0, 0).type == mpack_type_ext);
    #endif

    TEST_TRUE(true == mpack_tag_equal(mpack_tag_array(0), mpack_tag_array(0)));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_array(0), mpack_tag_array(1)));
    TEST_TRUE(0 == mpack_tag_cmp(mpack_tag_array(0), mpack_tag_array(0)));
    TEST_TRUE(-1 == mpack_tag_cmp(mpack_tag_array(0), mpack_tag_array(1)));
    TEST_TRUE(1 == mpack_tag_cmp(mpack_tag_array(1), mpack_tag_array(0)));

    TEST_TRUE(true == mpack_tag_equal(mpack_tag_map(0), mpack_tag_map(0)));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_map(0), mpack_tag_map(1)));
    TEST_TRUE(0 == mpack_tag_cmp(mpack_tag_map(0), mpack_tag_map(0)));
    TEST_TRUE(-1 == mpack_tag_cmp(mpack_tag_map(0), mpack_tag_map(1)));
    TEST_TRUE(1 == mpack_tag_cmp(mpack_tag_map(1), mpack_tag_map(0)));

    TEST_TRUE(true == mpack_tag_equal(mpack_tag_str(0), mpack_tag_str(0)));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_str(0), mpack_tag_str(1)));
    TEST_TRUE(0 == mpack_tag_cmp(mpack_tag_str(0), mpack_tag_str(0)));
    TEST_TRUE(-1 == mpack_tag_cmp(mpack_tag_str(0), mpack_tag_str(1)));
    TEST_TRUE(1 == mpack_tag_cmp(mpack_tag_str(1), mpack_tag_str(0)));

    TEST_TRUE(true == mpack_tag_equal(mpack_tag_bin(0), mpack_tag_bin(0)));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_bin(0), mpack_tag_bin(1)));
    TEST_TRUE(0 == mpack_tag_cmp(mpack_tag_bin(0), mpack_tag_bin(0)));
    TEST_TRUE(-1 == mpack_tag_cmp(mpack_tag_bin(0), mpack_tag_bin(1)));
    TEST_TRUE(1 == mpack_tag_cmp(mpack_tag_bin(1), mpack_tag_bin(0)));

    #if MPACK_EXTENSIONS
    TEST_TRUE(true == mpack_tag_equal(mpack_tag_ext(0, 0), mpack_tag_ext(0, 0)));
    TEST_TRUE(true == mpack_tag_equal(mpack_tag_ext(0, 1), mpack_tag_ext(0, 1)));
    TEST_TRUE(true == mpack_tag_equal(mpack_tag_ext(127, 0), mpack_tag_ext(127, 0)));
    TEST_TRUE(true == mpack_tag_equal(mpack_tag_ext(127, 1), mpack_tag_ext(127, 1)));
    TEST_TRUE(true == mpack_tag_equal(mpack_tag_ext(-128, 0), mpack_tag_ext(-128, 0)));
    TEST_TRUE(true == mpack_tag_equal(mpack_tag_ext(-128, 1), mpack_tag_ext(-128, 1)));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_ext(0, 0), mpack_tag_ext(127, 0)));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_ext(0, 0), mpack_tag_ext(-128, 0)));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_ext(0, 0), mpack_tag_ext(0, 1)));
    #endif

    TEST_TRUE(false == mpack_tag_equal(mpack_tag_array(0), mpack_tag_map(0)));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_array(0), mpack_tag_str(0)));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_array(0), mpack_tag_bin(0)));

    TEST_TRUE(false == mpack_tag_equal(mpack_tag_map(0), mpack_tag_array(0)));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_map(0), mpack_tag_str(0)));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_map(0), mpack_tag_bin(0)));

    TEST_TRUE(false == mpack_tag_equal(mpack_tag_str(0), mpack_tag_array(0)));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_str(0), mpack_tag_map(0)));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_str(0), mpack_tag_bin(0)));

    TEST_TRUE(false == mpack_tag_equal(mpack_tag_bin(0), mpack_tag_array(0)));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_bin(0), mpack_tag_map(0)));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_bin(0), mpack_tag_str(0)));

    #if MPACK_EXTENSIONS
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_array(0), mpack_tag_ext(0, 0)));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_map(0), mpack_tag_ext(0, 0)));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_str(0), mpack_tag_ext(0, 0)));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_bin(0), mpack_tag_ext(0, 0)));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_ext(0, 0), mpack_tag_array(0)));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_ext(0, 0), mpack_tag_map(0)));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_ext(0, 0), mpack_tag_str(0)));
    TEST_TRUE(false == mpack_tag_equal(mpack_tag_ext(0, 0), mpack_tag_bin(0)));
    #endif
}

static void test_string(const char* str, const char* content) {
    #if MPACK_STRINGS
    // with strings enabled, the string should contain the expected content
    TEST_TRUE(strstr(str, content) != NULL, "string \"%s\" does not contain \"%s\"", str, content);
    #else
    // strings should be blank when disabled
    MPACK_UNUSED(content);
    TEST_TRUE(strcmp(str, "") == 0, "string is not empty: %s", str);
    #endif
}

static void test_strings(void) {
    test_string(mpack_error_to_string(mpack_ok), "ok");
    #define TEST_ERROR_STRING(error) test_string(mpack_error_to_string(mpack_error_##error), #error)
    TEST_ERROR_STRING(io);
    TEST_ERROR_STRING(invalid);
    TEST_ERROR_STRING(type);
    TEST_ERROR_STRING(too_big);
    TEST_ERROR_STRING(memory);
    TEST_ERROR_STRING(bug);
    TEST_ERROR_STRING(data);
    #undef TEST_ERROR_STRING

    #define TEST_ERROR_TYPE(type) test_string(mpack_type_to_string(mpack_type_##type), #type)
    TEST_ERROR_TYPE(nil);
    TEST_ERROR_TYPE(bool);
    #if MPACK_FLOAT
    TEST_ERROR_TYPE(float);
    #endif
    #if MPACK_DOUBLE
    TEST_ERROR_TYPE(double);
    #endif
    TEST_ERROR_TYPE(int);
    TEST_ERROR_TYPE(uint);
    TEST_ERROR_TYPE(str);
    TEST_ERROR_TYPE(bin);
    TEST_ERROR_TYPE(array);
    TEST_ERROR_TYPE(map);
    #if MPACK_EXTENSIONS
    TEST_ERROR_TYPE(ext);
    #endif
    #undef TEST_ERROR_TYPE

    // test strings for invalid enum values
    // (invalid enum values cause undefined behavior in C++)
    #if MPACK_DEBUG && !defined(__cplusplus) && MPACK_STRINGS
    TEST_ASSERT(mpack_error_to_string((mpack_error_t)-1));
    TEST_ASSERT(mpack_type_to_string((mpack_type_t)-1));
    #endif
}

static void test_utf8_check(void) {
    #define EXPAND_STR_ARGS(str) str, sizeof(str) - 1


    // ascii
    TEST_TRUE(true  == mpack_utf8_check(EXPAND_STR_ARGS("")));
    TEST_TRUE(true  == mpack_utf8_check(EXPAND_STR_ARGS("test")));
    TEST_TRUE(true  == mpack_utf8_check(EXPAND_STR_ARGS("\x00")));
    TEST_TRUE(true  == mpack_utf8_check(EXPAND_STR_ARGS("\x7F")));
    TEST_TRUE(true  == mpack_utf8_check(EXPAND_STR_ARGS("\x00""\x7F")));

    // nul
    TEST_TRUE(true  == mpack_utf8_check(EXPAND_STR_ARGS("\x00")));
    TEST_TRUE(true  == mpack_utf8_check(EXPAND_STR_ARGS("test\x00""test")));
    TEST_TRUE(false == mpack_utf8_check_no_null(EXPAND_STR_ARGS("\x00")));
    TEST_TRUE(false == mpack_utf8_check_no_null(EXPAND_STR_ARGS("test\x00""test")));


    // 2-byte sequences
    TEST_TRUE(true  == mpack_utf8_check(EXPAND_STR_ARGS("\xC2\x80")));
    TEST_TRUE(true  == mpack_utf8_check(EXPAND_STR_ARGS("\xDF\xBF")));
    TEST_TRUE(true  == mpack_utf8_check(EXPAND_STR_ARGS("test\xC2\x80""test")));
    TEST_TRUE(true  == mpack_utf8_check(EXPAND_STR_ARGS("test\xDF\xBF""test")));

    // truncated 2-byte sequences
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("\xC2")));
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("\xDF")));
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("test\xC2")));
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("test\xDF")));

    // 2-byte overlong sequences
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("\xC0\xBF")));
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("\xC1\xBF")));
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("test\xC0\xBF""test")));
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("test\xC1\xBF""test")));

    // not continuation bytes
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("\xC2\x02")));
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("\xC2\xC0")));
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("\xC2\xE0")));
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("test\xC2\x02""test")));
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("test\xC2\xC0""test")));
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("test\xC2\xE0""test")));

    // miscellaneous 2-byte sequences
    TEST_TRUE(true  == mpack_utf8_check(EXPAND_STR_ARGS("\xC2\x80\xDF\xBF")));
    TEST_TRUE(true  == mpack_utf8_check(EXPAND_STR_ARGS("test\xC2\x80""test\xDF\xBF""test")));
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("\xC2\x70\xDF\xBF")));
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("\xC2\x80\xDF\xEF")));
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("test\xC2\x00""test\xDF\xBF""test")));
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("test\xC2\x80""test\xDF\xEF""test")));


    // 3-byte sequences
    TEST_TRUE(true  == mpack_utf8_check(EXPAND_STR_ARGS("\xE0\xA0\x80")));
    TEST_TRUE(true  == mpack_utf8_check(EXPAND_STR_ARGS("\xE7\xA0\xBF")));
    TEST_TRUE(true  == mpack_utf8_check(EXPAND_STR_ARGS("\xEF\xBF\xBF")));
    TEST_TRUE(true  == mpack_utf8_check(EXPAND_STR_ARGS("test\xE0\xA0\x80""test")));
    TEST_TRUE(true  == mpack_utf8_check(EXPAND_STR_ARGS("test\xE7\xA0\xBF""test")));
    TEST_TRUE(true  == mpack_utf8_check(EXPAND_STR_ARGS("test\xEF\xBF\xBF""test")));

    // truncated 3-byte sequences
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("\xE0")));
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("\xEF")));
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("\xE7\x80")));
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("\xEA\xBF")));
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("test\xE0")));
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("test\xEA\xBF")));

    // 3-byte overlong sequences
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("\xE0\x80\x80")));
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("\xE0\x9F\xFF")));
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("test\xE0\x80\x80""test")));
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("test\xE0\x9F\xFF""test")));

    // not continuation bytes
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("\xE0\x00\x80")));
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("\xE0\xF0\x80")));
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("\xE0\x80\x00")));
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("\xE0\x80\xF0")));

    // surrogates
    TEST_TRUE(true  == mpack_utf8_check(EXPAND_STR_ARGS("\xED\x9F\xBF")));
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("\xED\xA0\x80")));
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("\xED\xBF\xBF")));
    TEST_TRUE(true  == mpack_utf8_check(EXPAND_STR_ARGS("\xEE\x80\x80")));
    TEST_TRUE(true  == mpack_utf8_check(EXPAND_STR_ARGS("\xED\x9F\xBF\xEE\x80\x80")));
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("\xED\x9F\xBF\xED\xBF\xBF\xEE\x80\x80")));

    // miscellaneous 3-byte sequences
    TEST_TRUE(true  == mpack_utf8_check(EXPAND_STR_ARGS("\xE0\xA0\x80\xE7\xA0\xBF\xEF\xBF\xBF")));
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("\xE0\xA0\x80\xE7\x00\xBF\xEF\xBF\xBF")));
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("\xE0\xA0\x80\xE7\xA0\xBF\xEF\xBF\x7F")));
    TEST_TRUE(true  == mpack_utf8_check(EXPAND_STR_ARGS("test\xE0\xA0\x80""test\xE7\xA0\xBF""test\xEF\xBF\xBF""test")));
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("test\xE0\xA0\x80""test\xE7\xD0\xBF""test\xEF\xBF\xBF""test")));
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("test\xE0\xA0\x80""test\xE7\xA0\xBF""test\xEF\x1F\xBF""test")));


    // 4-byte sequences
    TEST_TRUE(true  == mpack_utf8_check(EXPAND_STR_ARGS("\xF0\x90\x80\x80"))); // U+10000
    TEST_TRUE(true  == mpack_utf8_check(EXPAND_STR_ARGS("\xF4\x8F\xBF\xBF"))); // limit
    TEST_TRUE(true  == mpack_utf8_check(EXPAND_STR_ARGS("\xF0\x90\x80\x80\xF4\x8F\xBF\xBF")));
    TEST_TRUE(true  == mpack_utf8_check(EXPAND_STR_ARGS("test\xF0\x90\x80\x80""test"))); // U+10000
    TEST_TRUE(true  == mpack_utf8_check(EXPAND_STR_ARGS("test\xF4\x8F\xBF\xBF""test"))); // limit

    // truncated 4-byte sequences
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("\xF0\x90")));
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("\xF1\x90\xB0")));

    // 4-byte overlong sequences
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("\xF0\x80\x80\x80"))); // NUL
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("\xF0\x8F\xBF\xBF"))); // U+9999 (overlong)

    // not continuation bytes
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("\xF0\x60\x80\x80")));
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("\xF1\x90\xD0\x80")));
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("\xF2\x90\x80\xF0")));

    // unicode limit
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("\xF4\x90\x80\x80"))); // U+110000 (out of bounds)
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("\xF6\x80\x80\x80")));
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("\xF7\x80\x80\x80")));


    // 5- and 6-byte sequences
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("test\xF8\x80\x80\x80\x80""test")));
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("test\xFB\x80\x80\x80\x80""test")));
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("test\xFD\x80\x80\x80\x80\x80""test")));

    // other invalid bytes
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("test\xC0""testtesttest")));
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("test\xC1""testtesttest")));
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("test\xF5""testtesttest")));
    TEST_TRUE(false == mpack_utf8_check(EXPAND_STR_ARGS("test\xFF""testtesttest")));
}

static void test_shorten_raw_double_to_float(void) {
    #if MPACK_FLOAT && !MPACK_DOUBLE && !defined(__AVR__)
    TEST_DOUBLE doubles[] = {
        0.0,
        3.141592653589793238462643,
        -1.0,
        (TEST_DOUBLE)INFINITY,
        -(TEST_DOUBLE)INFINITY,
        1.234567890123456789e-50,
        -1.234567890123456789e-25,
        1.234567890123456789e-10,
        -1.234567890123456789e10,
        1.234567890123456789e25,
        -1.234567890123456789e50,
        1.234567890123456789e100,
    };

    size_t i;
    for (i = 0; i < sizeof(doubles)/sizeof(*doubles); ++i) {
        union {
            TEST_DOUBLE d;
            uint64_t u;
        } u;
        u.d = doubles[i];

        // Proper float conversion
        float f = (float)u.d;

        // Our shortening conversion
        float s = mpack_shorten_raw_double_to_float(u.u);

        // Make sure they are close enough
        if (isnan(f) || isnan(s)) {
            TEST_TRUE(isnan(f) && isnan(s));
        } else if (isinf(f) || isinf(s)) {
            TEST_TRUE(isinf(f) && isinf(s));
        } else {
            TEST_DOUBLE diff = ((TEST_DOUBLE)f - (TEST_DOUBLE)s) / (u.d + 0.0000001);
            TEST_TRUE(diff < 0.00001);
            #if 0
            printf("%.20f %.20f %.20f diff: %.20f\n", u.d, f, s, diff);
            #endif
        }
    }
    #endif
}

void test_common() {
    test_tags_special();
    test_tags_simple();
    test_tags_reals();
    test_tags_compound();

    test_strings();
    test_utf8_check();
    test_shorten_raw_double_to_float();
}

