From c4821c8e6bf1f81c6ea31e11ebc0fc1666e9337b Mon Sep 17 00:00:00 2001 From: David Robillard Date: Thu, 25 Feb 2021 10:27:59 -0500 Subject: Add exess from git@gitlab.com:drobilla/exess.git 4638b1f --- subprojects/exess/test/.clang-tidy | 11 + subprojects/exess/test/float_test_data.h | 130 +++++ subprojects/exess/test/int_test_data.h | 42 ++ subprojects/exess/test/meson.build | 76 +++ subprojects/exess/test/num_test_utils.h | 85 ++++ subprojects/exess/test/test_base64.c | 196 +++++++ subprojects/exess/test/test_bigint.c | 841 +++++++++++++++++++++++++++++++ subprojects/exess/test/test_boolean.c | 114 +++++ subprojects/exess/test/test_byte.c | 98 ++++ subprojects/exess/test/test_canonical.c | 412 +++++++++++++++ subprojects/exess/test/test_coerce.c | 519 +++++++++++++++++++ subprojects/exess/test/test_datatype.c | 81 +++ subprojects/exess/test/test_date.c | 257 ++++++++++ subprojects/exess/test/test_datetime.c | 459 +++++++++++++++++ subprojects/exess/test/test_decimal.c | 260 ++++++++++ subprojects/exess/test/test_double.c | 246 +++++++++ subprojects/exess/test/test_duration.c | 310 ++++++++++++ subprojects/exess/test/test_float.c | 240 +++++++++ subprojects/exess/test/test_hex.c | 180 +++++++ subprojects/exess/test/test_int.c | 129 +++++ subprojects/exess/test/test_int_math.c | 97 ++++ subprojects/exess/test/test_long.c | 144 ++++++ subprojects/exess/test/test_short.c | 100 ++++ subprojects/exess/test/test_strerror.c | 35 ++ subprojects/exess/test/test_time.c | 222 ++++++++ subprojects/exess/test/test_timezone.c | 178 +++++++ subprojects/exess/test/test_ubyte.c | 100 ++++ subprojects/exess/test/test_uint.c | 128 +++++ subprojects/exess/test/test_ulong.c | 126 +++++ subprojects/exess/test/test_ushort.c | 100 ++++ subprojects/exess/test/test_variant.c | 301 +++++++++++ subprojects/exess/test/time_test_utils.h | 52 ++ 32 files changed, 6269 insertions(+) create mode 100644 subprojects/exess/test/.clang-tidy create mode 100644 subprojects/exess/test/float_test_data.h create mode 100644 subprojects/exess/test/int_test_data.h create mode 100644 subprojects/exess/test/meson.build create mode 100644 subprojects/exess/test/num_test_utils.h create mode 100644 subprojects/exess/test/test_base64.c create mode 100644 subprojects/exess/test/test_bigint.c create mode 100644 subprojects/exess/test/test_boolean.c create mode 100644 subprojects/exess/test/test_byte.c create mode 100644 subprojects/exess/test/test_canonical.c create mode 100644 subprojects/exess/test/test_coerce.c create mode 100644 subprojects/exess/test/test_datatype.c create mode 100644 subprojects/exess/test/test_date.c create mode 100644 subprojects/exess/test/test_datetime.c create mode 100644 subprojects/exess/test/test_decimal.c create mode 100644 subprojects/exess/test/test_double.c create mode 100644 subprojects/exess/test/test_duration.c create mode 100644 subprojects/exess/test/test_float.c create mode 100644 subprojects/exess/test/test_hex.c create mode 100644 subprojects/exess/test/test_int.c create mode 100644 subprojects/exess/test/test_int_math.c create mode 100644 subprojects/exess/test/test_long.c create mode 100644 subprojects/exess/test/test_short.c create mode 100644 subprojects/exess/test/test_strerror.c create mode 100644 subprojects/exess/test/test_time.c create mode 100644 subprojects/exess/test/test_timezone.c create mode 100644 subprojects/exess/test/test_ubyte.c create mode 100644 subprojects/exess/test/test_uint.c create mode 100644 subprojects/exess/test/test_ulong.c create mode 100644 subprojects/exess/test/test_ushort.c create mode 100644 subprojects/exess/test/test_variant.c create mode 100644 subprojects/exess/test/time_test_utils.h (limited to 'subprojects/exess/test') diff --git a/subprojects/exess/test/.clang-tidy b/subprojects/exess/test/.clang-tidy new file mode 100644 index 00000000..98645dc0 --- /dev/null +++ b/subprojects/exess/test/.clang-tidy @@ -0,0 +1,11 @@ +Checks: > + *, + -*-magic-numbers, + -*-uppercase-literal-suffix, + -clang-analyzer-nullability.NullableDereferenced, + -clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling, + -llvm-header-guard, + -llvmlibc-*, +WarningsAsErrors: '*' +HeaderFilterRegex: '.*' +FormatStyle: file diff --git a/subprojects/exess/test/float_test_data.h b/subprojects/exess/test/float_test_data.h new file mode 100644 index 00000000..eaea65c4 --- /dev/null +++ b/subprojects/exess/test/float_test_data.h @@ -0,0 +1,130 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "attributes.h" +#include "ieee_float.h" +#include "warnings.h" + +#include +#include +#include +#include + +/// Return the float with representation `rep` +static inline float +float_from_rep(const uint32_t rep) +{ + float f = 0.0f; + memcpy(&f, &rep, sizeof(f)); + return f; +} + +/// Return the double with representation `rep` +static inline double +double_from_rep(const uint64_t rep) +{ + double d = 0.0; + memcpy(&d, &rep, sizeof(d)); + return d; +} + +/// Return the distance between two doubles in ULPs +static EXESS_I_PURE_FUNC uint64_t +double_ulp_distance(const double a, const double b) +{ + assert(a >= 0.0); + assert(b >= 0.0); + + const uint64_t ia = double_to_rep(a); + const uint64_t ib = double_to_rep(b); + if (ia == ib) { + return 0; + } + + EXESS_DISABLE_CONVERSION_WARNINGS + if (isnan(a) || isnan(b) || isinf(a) || isinf(b)) { + return UINT64_MAX; + } + EXESS_RESTORE_WARNINGS + + return ia > ib ? ia - ib : ib - ia; +} + +/// Return the distance between two floats in ULPs +static EXESS_I_PURE_FUNC uint32_t +float_ulp_distance(const float a, const float b) +{ + assert(a >= 0.0f); + assert(b >= 0.0f); + + const uint32_t ia = float_to_rep(a); + const uint32_t ib = float_to_rep(b); + if (ia == ib) { + return 0; + } + + EXESS_DISABLE_CONVERSION_WARNINGS + if (isnan(a) || isnan(b) || isinf(a) || isinf(b)) { + return UINT32_MAX; + } + EXESS_RESTORE_WARNINGS + + return ia > ib ? ia - ib : ib - ia; +} + +static inline bool +float_matches(const float a, const float b) +{ + EXESS_DISABLE_CONVERSION_WARNINGS + const bool a_is_nan = isnan(a); + const bool a_is_negative = signbit(a); + const bool b_is_nan = isnan(b); + const bool b_is_negative = signbit(b); + EXESS_RESTORE_WARNINGS + + if (a_is_nan && b_is_nan) { + return true; + } + + if (a_is_nan || b_is_nan || a_is_negative != b_is_negative) { + return false; + } + + return a_is_negative ? float_ulp_distance(-a, -b) == 0 + : float_ulp_distance(a, b) == 0; +} + +static inline bool +double_matches(const double a, const double b) +{ + EXESS_DISABLE_CONVERSION_WARNINGS + const bool a_is_nan = isnan(a); + const bool a_is_negative = signbit(a); + const bool b_is_nan = isnan(b); + const bool b_is_negative = signbit(b); + EXESS_RESTORE_WARNINGS + + if (a_is_nan && b_is_nan) { + return true; + } + + if (a_is_nan || b_is_nan || a_is_negative != b_is_negative) { + return false; + } + + return a_is_negative ? double_ulp_distance(-a, -b) == 0 + : double_ulp_distance(a, b) == 0; +} diff --git a/subprojects/exess/test/int_test_data.h b/subprojects/exess/test/int_test_data.h new file mode 100644 index 00000000..978f863b --- /dev/null +++ b/subprojects/exess/test/int_test_data.h @@ -0,0 +1,42 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#ifndef INT_TEST_DATA_H +#define INT_TEST_DATA_H + +#include + +/// Linear Congruential Generator for making random 32-bit integers +static inline uint32_t +lcg32(const uint32_t i) +{ + static const uint32_t a = 134775813u; + static const uint32_t c = 1u; + + return (a * i) + c; +} + +/// Linear Congruential Generator for making random 64-bit integers +static inline uint64_t +lcg64(const uint64_t i) +{ + static const uint64_t a = 6364136223846793005ull; + static const uint64_t c = 1ull; + + return (a * i) + c; +} + +#endif // INT_TEST_DATA_H diff --git a/subprojects/exess/test/meson.build b/subprojects/exess/test/meson.build new file mode 100644 index 00000000..972793f0 --- /dev/null +++ b/subprojects/exess/test/meson.build @@ -0,0 +1,76 @@ +autoship = find_program('autoship', required: false) + +test_args = [] + +if get_option('strict') + if cc.get_id() == 'clang' + test_args = [ + '-Wno-float-equal', + ] + elif cc.get_id() == 'gcc' + test_args = [ + '-Wno-float-equal', + '-Wno-suggest-attribute=pure', + ] + elif cc.get_id() == 'msvc' + test_args = [ + '/wd4996', # POSIX name is deprecated + ] + endif +endif + +private_tests = [ + 'bigint', + 'int_math', +] + +foreach unit : private_tests + test(unit, + executable('test_@0@'.format(unit), + 'test_@0@.c'.format(unit), + c_args: exess_c_args + prog_args + test_args, + dependencies: exess_static_dep, + include_directories: include_directories('../src')), + suite: 'private') +endforeach + +public_tests = [ + 'base64', + 'boolean', + 'byte', + 'canonical', + 'coerce', + 'datatype', + 'date', + 'datetime', + 'decimal', + 'double', + 'duration', + 'float', + 'hex', + 'int', + 'long', + 'short', + 'strerror', + 'time', + 'timezone', + 'ubyte', + 'uint', + 'ulong', + 'ushort', + 'variant', +] + +foreach unit : public_tests + test(unit, + executable('test_@0@'.format(unit), + 'test_@0@.c'.format(unit), + c_args: exess_c_args + prog_args + test_args, + dependencies: [m_dep, exess_dep], + include_directories: include_directories('../src')), + suite: 'public') +endforeach + +if autoship.found() + test('autoship', autoship, args: ['test', exess_src_root], suite: 'data') +endif diff --git a/subprojects/exess/test/num_test_utils.h b/subprojects/exess/test/num_test_utils.h new file mode 100644 index 00000000..ca29a479 --- /dev/null +++ b/subprojects/exess/test/num_test_utils.h @@ -0,0 +1,85 @@ +/* + Copyright 2011-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include +#include +#include +#include +#include + +#ifdef _WIN32 +# include +#else +# include +#endif + +typedef struct { + size_t n_tests; + uint32_t seed; + bool exhaustive; + bool error; +} ExessNumTestOptions; + +static bool +print_num_test_usage(const char* const name) +{ + fprintf(stderr, "Usage: %s [OPTION]...\n", name); + fprintf(stderr, " -n NUM_TESTS Number of random tests to run.\n"); + fprintf(stderr, " -s SEED Use random seed.\n"); + fprintf(stderr, " -x Exhaustively test numbers.\n"); + return true; +} + +static ExessNumTestOptions +parse_num_test_options(const int argc, char* const* const argv) +{ + ExessNumTestOptions opts = { + 16384u, (uint32_t)time(NULL) + (uint32_t)getpid(), false, false}; + + int a = 1; + for (; a < argc && argv[a][0] == '-'; ++a) { + if (argv[a][1] == 'x') { + opts.exhaustive = true; + } else if (argv[a][1] == 's') { + if (++a == argc) { + opts.error = print_num_test_usage(argv[0]); + break; + } + + opts.seed = (uint32_t)strtol(argv[a], NULL, 10); + } else if (argv[a][1] == 'n') { + if (++a == argc) { + opts.error = print_num_test_usage(argv[0]); + break; + } + + opts.n_tests = (uint32_t)strtol(argv[a], NULL, 10); + } else { + opts.error = print_num_test_usage(argv[0]); + break; + } + } + + return opts; +} + +static void +print_num_test_progress(const uint64_t i, const uint64_t n_tests) +{ + if (i % (n_tests / 20) == 1) { + fprintf(stderr, "%f%%\n", (double)i / (double)n_tests * 100.0); + } +} diff --git a/subprojects/exess/test/test_base64.c b/subprojects/exess/test/test_base64.c new file mode 100644 index 00000000..760f44fe --- /dev/null +++ b/subprojects/exess/test/test_base64.c @@ -0,0 +1,196 @@ +/* + Copyright 2011-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#undef NDEBUG + +#include "exess/exess.h" + +#include +#include +#include +#include +#include + +static void +check_read(const char* const string, + const ExessStatus expected_status, + const size_t expected_value_length, + const char* const expected_value, + const size_t expected_value_size, + const size_t expected_count) +{ + char buf[9] = {0, 0, 0, 0, 0, 0, 0, 0, 0}; + ExessBlob blob = {9, buf}; + + ExessResult r = exess_read_base64(&blob, string); + assert(r.status == expected_status); + assert(r.count == expected_count); + assert(r.status || blob.size == expected_value_size); + if (expected_value_length > 0) { + assert(!strncmp(buf, expected_value, expected_value_length)); + assert(blob.size <= exess_base64_decoded_size(strlen(string))); + } +} + +static void +test_rfc4648_cases(void) +{ + char buf[9] = {1, 2, 3, 4, 5, 6, 7, 8, 9}; + + assert(!exess_write_base64(6, "foobar", sizeof(buf), buf).status); + assert(!strcmp(buf, "Zm9vYmFy")); + + assert(!exess_write_base64(5, "fooba", sizeof(buf), buf).status); + assert(!strcmp(buf, "Zm9vYmE=")); + + assert(!exess_write_base64(4, "foob", sizeof(buf), buf).status); + assert(!strcmp(buf, "Zm9vYg==")); + + assert(!exess_write_base64(3, "foo", sizeof(buf), buf).status); + assert(!strcmp(buf, "Zm9v")); + + assert(!exess_write_base64(2, "fo", sizeof(buf), buf).status); + assert(!strcmp(buf, "Zm8=")); + + assert(!exess_write_base64(1, "f", sizeof(buf), buf).status); + assert(!strcmp(buf, "Zg==")); +} + +static void +test_whitespace(void) +{ + check_read("Zm9vYmFy", EXESS_SUCCESS, 6, "foobar", 6, 8); + check_read(" Zm9vYmFy", EXESS_SUCCESS, 6, "foobar", 6, 9); + check_read("Z\fm9vYmFy", EXESS_SUCCESS, 6, "foobar", 6, 9); + check_read("Zm\n9vYmFy", EXESS_SUCCESS, 6, "foobar", 6, 9); + check_read("Zm9\rvYmFy", EXESS_SUCCESS, 6, "foobar", 6, 9); + check_read("Zm9v\tYmFy", EXESS_SUCCESS, 6, "foobar", 6, 9); + check_read("Zm9vY\vmFy", EXESS_SUCCESS, 6, "foobar", 6, 9); + check_read(" \f\n\r\t\vZm9vYmFy", EXESS_SUCCESS, 6, "foobar", 6, 14); + check_read("Zm9vYmFy \f\n\r\t\v", EXESS_SUCCESS, 6, "foobar", 6, 14); +} + +static void +test_syntax_errors(void) +{ + check_read("Z", EXESS_EXPECTED_BASE64, 0, NULL, 0, 1); + check_read("ZZ", EXESS_EXPECTED_BASE64, 0, NULL, 0, 2); + check_read("ZZZ", EXESS_EXPECTED_BASE64, 0, NULL, 0, 3); + + check_read("=ZZZ", EXESS_BAD_VALUE, 0, NULL, 0, 4); + check_read("Z=ZZ", EXESS_BAD_VALUE, 0, NULL, 0, 4); + check_read("ZZ=Z", EXESS_BAD_VALUE, 0, NULL, 0, 4); + + check_read("!m9vYmFy", EXESS_EXPECTED_BASE64, 0, NULL, 0, 0); + check_read("Z!9vYmFy", EXESS_EXPECTED_BASE64, 0, NULL, 0, 1); + check_read("Zm!vYmFy", EXESS_EXPECTED_BASE64, 0, NULL, 0, 2); + check_read("Zm9!YmFy", EXESS_EXPECTED_BASE64, 0, NULL, 0, 3); + check_read("Zm9v!mFy", EXESS_EXPECTED_BASE64, 0, NULL, 3, 4); + check_read("Zm9vY!Fy", EXESS_EXPECTED_BASE64, 0, NULL, 3, 5); + check_read("Zm9vYm!y", EXESS_EXPECTED_BASE64, 0, NULL, 3, 6); + check_read("Zm9vYmF!", EXESS_EXPECTED_BASE64, 0, NULL, 3, 7); +} + +static void +test_read_overflow(void) +{ + char buf[3] = {0, 0, 0}; + ExessBlob blob0 = {0, buf}; + ExessBlob blob1 = {1, buf}; + ExessBlob blob2 = {2, buf}; + ExessBlob blob3 = {3, buf}; + + ExessResult r = exess_read_base64(&blob0, "Zm9v"); + assert(r.status == EXESS_NO_SPACE); + assert(r.count == 4); + assert(blob0.size == 0); + + r = exess_read_base64(&blob1, "Zm9v"); + assert(r.status == EXESS_NO_SPACE); + assert(r.count == 4); + assert(!buf[0]); + + r = exess_read_base64(&blob2, "Zm9v"); + assert(r.status == EXESS_NO_SPACE); + assert(r.count == 4); + assert(!buf[0]); + + r = exess_read_base64(&blob3, "Zm9v"); + assert(r.status == EXESS_SUCCESS); + assert(r.count == 4); + assert(blob3.size == 3); + assert(!strncmp(buf, "foo", 3)); +} + +static void +test_write_overflow(void) +{ + char buf[5] = {1, 2, 3, 4, 5}; + + assert(exess_write_base64(3, "foo", 0, buf).status == EXESS_NO_SPACE); + assert(exess_write_base64(3, "foo", 1, buf).status == EXESS_NO_SPACE); + assert(exess_write_base64(3, "foo", 2, buf).status == EXESS_NO_SPACE); + assert(exess_write_base64(3, "foo", 3, buf).status == EXESS_NO_SPACE); + assert(exess_write_base64(3, "foo", 4, buf).status == EXESS_NO_SPACE); + assert(exess_write_base64(3, "foo", 5, buf).status == EXESS_SUCCESS); +} + +static void +test_round_trip(void) +{ + for (size_t size = 1; size < 256; ++size) { + // Allocate and generate data + uint8_t* const data = (uint8_t*)malloc(size); + for (size_t i = 0; i < size; ++i) { + data[i] = (uint8_t)((size + i) % 256); + } + + // Allocate buffer for encoding with minimum required size + const size_t str_len = exess_write_base64(size, data, 0, NULL).count; + char* const str = (char*)malloc(str_len + 1); + + // Encode data to string buffer + assert(!exess_write_base64(size, data, str_len + 1, str).status); + assert(strlen(str) == str_len); + assert(str_len % 4 == 0); + + // Allocate buffer for decoded data with the same size as the input + uint8_t* const decoded = (uint8_t*)malloc(size); + ExessBlob decoded_blob = {size, decoded}; + + // Decode and check that data matches the original input + assert(!exess_read_base64(&decoded_blob, str).status); + assert(decoded_blob.size == size); + assert(!memcmp(decoded, data, size)); + + free(decoded); + free(str); + free(data); + } +} + +int +main(void) +{ + test_rfc4648_cases(); + test_whitespace(); + test_syntax_errors(); + test_read_overflow(); + test_write_overflow(); + test_round_trip(); + + return 0; +} diff --git a/subprojects/exess/test/test_bigint.c b/subprojects/exess/test/test_bigint.c new file mode 100644 index 00000000..3a74bb92 --- /dev/null +++ b/subprojects/exess/test/test_bigint.c @@ -0,0 +1,841 @@ +/* + Copyright 2011-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#undef NDEBUG + +#include "bigint.h" + +#include +#include +#include + +/* Some test data borrowed from http://github.com/google/double-conversion + which uses a completely different bigint representation (so if these agree, + everything is probably fine). Others cases are either made up to hit the + edges of the implementation, or interesting cases collected from testing the + decimal implementation. Almost everything here uses the hex representation + so it is easy to dump into Python as a sanity check. */ + +static ExessBigint +bigint_from_hex(const char* str) +{ + ExessBigint num; + exess_bigint_set_hex_string(&num, str); + return num; +} + +static bool +check_hex_equals(const char* const str, const ExessBigint* const num) +{ + const ExessBigint expected = bigint_from_hex(str); + + return exess_bigint_compare(&expected, num) == 0; +} + +#define CHECK_HEXEQ(str, num) assert(check_hex_equals(str, num)) + +#define CHECK_SET(setter, value, expected) \ + do { \ + ExessBigint num; \ + exess_bigint_set_##setter(&num, value); \ + CHECK_HEXEQ(expected, &num); \ + } while (0) + +static void +test_set(void) +{ + CHECK_SET(u32, 0, "0"); + CHECK_SET(u32, 0xA, "A"); + CHECK_SET(u32, 0x20, "20"); + CHECK_SET(u32, 0x12345678, "12345678"); + + CHECK_SET(u64, 0, "0"); + CHECK_SET(u64, 0xA, "A"); + CHECK_SET(u64, 0x20, "20"); + CHECK_SET(u64, 0x12345678, "12345678"); + CHECK_SET(u64, 0xFFFFFFFFFFFFFFFFull, "FFFFFFFFFFFFFFFF"); + CHECK_SET(u64, 0x123456789ABCDEF0ull, "123456789ABCDEF0"); + CHECK_SET(u64, 0x123456789ABCDEF0ull, "123456789ABCDEF0"); + + CHECK_SET(decimal_string, "0", "0"); + CHECK_SET(decimal_string, "1", "1"); + CHECK_SET(decimal_string, "01234567890", "499602D2"); + CHECK_SET(decimal_string, "12345.67890", "499602D2"); + CHECK_SET(decimal_string, "12345.67890EOF", "499602D2"); + CHECK_SET(decimal_string, "012345678901", "2DFDC1C35"); + CHECK_SET(decimal_string, "12345.678901", "2DFDC1C35"); + CHECK_SET(decimal_string, + "340282366920938463463374607431768211456", + "100000000000000000000000000000000"); + + CHECK_SET(hex_string, "0", "0"); + CHECK_SET(hex_string, "123456789ABCDEF0", "123456789ABCDEF0"); + + const ExessBigint orig = bigint_from_hex("123456789ABCDEF01"); + ExessBigint copy; + exess_bigint_set(©, &orig); + CHECK_HEXEQ("123456789ABCDEF01", ©); +} + +static void +check_left_shifted_bigit(const char* value, + const unsigned amount, + const unsigned index, + const Bigit expected) +{ + const ExessBigint num = bigint_from_hex(value); + const Bigit actual = exess_bigint_left_shifted_bigit(&num, amount, index); + + assert(expected == actual); +} + +static void +test_left_shifted_bigit(void) +{ + check_left_shifted_bigit("0", 100, 1, 0x0); + check_left_shifted_bigit("1", 0, 0, 0x1); + check_left_shifted_bigit("1", 1, 0, 0x2); + check_left_shifted_bigit("1", 4, 0, 0x10); + check_left_shifted_bigit("1", 32, 0, 0x0); + check_left_shifted_bigit("1", 32, 1, 0x1); + check_left_shifted_bigit("1", 64, 0, 0x0); + check_left_shifted_bigit("1", 64, 1, 0x0); + check_left_shifted_bigit("1", 64, 2, 0x1); + check_left_shifted_bigit("123456789ABCDEF", 64, 0, 0x0); + check_left_shifted_bigit("123456789ABCDEF", 64, 1, 0x0); + check_left_shifted_bigit("123456789ABCDEF", 64, 2, 0x89ABCDEF); + check_left_shifted_bigit("123456789ABCDEF", 64, 3, 0x1234567); + check_left_shifted_bigit("123456789ABCDEF", 64, 4, 0x0); + check_left_shifted_bigit("123456789ABCDEF", 65, 0, 0x0); + check_left_shifted_bigit("123456789ABCDEF", 65, 1, 0x0); + check_left_shifted_bigit("123456789ABCDEF", 65, 2, 0x13579BDE); + check_left_shifted_bigit("123456789ABCDEF", 65, 3, 0x2468ACF); + check_left_shifted_bigit("123456789ABCDEF", 65, 4, 0x0); +} + +static void +check_shift_left(const char* value, const unsigned amount, const char* expected) +{ + ExessBigint num = bigint_from_hex(value); + exess_bigint_shift_left(&num, amount); + CHECK_HEXEQ(expected, &num); +} + +static void +test_shift_left(void) +{ + check_shift_left("0", 100, "0"); + check_shift_left("1", 1, "2"); + check_shift_left("1", 4, "10"); + check_shift_left("1", 32, "100000000"); + check_shift_left("1", 64, "10000000000000000"); + check_shift_left("123456789ABCDEF", 0, "123456789ABCDEF"); + check_shift_left("123456789ABCDEF", 64, "123456789ABCDEF0000000000000000"); + check_shift_left("123456789ABCDEF", 65, "2468ACF13579BDE0000000000000000"); + check_shift_left("16B8B5E06EDC79", 23, "B5C5AF0376E3C800000"); +} + +static void +check_add_u32(const char* value, const uint32_t rhs, const char* expected) +{ + ExessBigint num = bigint_from_hex(value); + exess_bigint_add_u32(&num, rhs); + CHECK_HEXEQ(expected, &num); +} + +static void +test_add_u32(void) +{ + check_add_u32("0", 1, "1"); + check_add_u32("1", 1, "2"); + check_add_u32("FFFFFFF", 1, "10000000"); + check_add_u32("FFFFFFFFFFFFFF", 1, "100000000000000"); + + check_add_u32("10000000000000000000000000000000000080000000", + 0x80000000, + "10000000000000000000000000000000000100000000"); + + check_add_u32("10000000000000000000000000000000000000000000", + 0x1, + "10000000000000000000000000000000000000000001"); +} + +static void +check_add(const char* lhs_hex, const char* rhs_hex, const char* expected) +{ + ExessBigint lhs = bigint_from_hex(lhs_hex); + const ExessBigint rhs = bigint_from_hex(rhs_hex); + + exess_bigint_add(&lhs, &rhs); + CHECK_HEXEQ(expected, &lhs); +} + +static void +test_add(void) +{ + check_add("1", "0", "1"); + check_add("1", "1", "2"); + check_add("FFFFFFF", "1", "10000000"); + check_add("FFFFFFFFFFFFFF", "1", "100000000000000"); + check_add("1", "1000000000000", "1000000000001"); + check_add("FFFFFFF", "1000000000000", "100000FFFFFFF"); + + check_add("10000000000000000000000000000000000000000000", + "1", + "10000000000000000000000000000000000000000001"); + + check_add("10000000000000000000000000000000000000000000", + "1000000000000", + "10000000000000000000000000000001000000000000"); + + check_add("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", + "1000000000000", + "1000000000000000000000000000000FFFFFFFFFFFF"); + + check_add("10000000000000000000000000", + "1000000000000", + "10000000000001000000000000"); + + check_add( + "1", "10000000000000000000000000000", "10000000000000000000000000001"); + + check_add("FFFFFFF", + "10000000000000000000000000000", + "1000000000000000000000FFFFFFF"); + + check_add("10000000000000000000000000000000000000000000", + "10000000000000000000000000000", + "10000000000000010000000000000000000000000000"); + + check_add("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", + "10000000000000000000000000000", + "100000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFF"); + + check_add("10000000000000000000000000", + "10000000000000000000000000000", + "10010000000000000000000000000"); +} + +static void +check_subtract(const char* lhs_hex, const char* rhs_hex, const char* expected) +{ + ExessBigint lhs = bigint_from_hex(lhs_hex); + const ExessBigint rhs = bigint_from_hex(rhs_hex); + + exess_bigint_subtract(&lhs, &rhs); + CHECK_HEXEQ(expected, &lhs); +} + +static void +test_subtract(void) +{ + check_subtract("1", "0", "1"); + check_subtract("2", "0", "2"); + check_subtract("10000000", "1", "FFFFFFF"); + check_subtract("1FFFFFFFF00000000", "FFFFFFFF", "1FFFFFFFE00000001"); + check_subtract("100000000000000", "1", "FFFFFFFFFFFFFF"); + check_subtract("1000000000001", "1000000000000", "1"); + check_subtract("100000FFFFFFF", "1000000000000", "FFFFFFF"); + + check_subtract( + "11F2678326EA00000000", "0878678326EAC9000000", "979FFFFFFFF37000000"); + + check_subtract("10000000000000000000000000000000000000000001", + "00000000000000000000000000000000000000000001", + "10000000000000000000000000000000000000000000"); + + check_subtract("10000000000000000000000000000001000000000000", + "00000000000000000000000000000001000000000000", + "10000000000000000000000000000000000000000000"); + + check_subtract("1000000000000000000000000000000FFFFFFFFFFFF", + "0000000000000000000000000000001000000000000", + " FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); + + check_subtract("10000000000000000000000000", + "00000000000001000000000000", + " FFFFFFFFFFFFF000000000000"); + + check_subtract("10000000000000000000000000", + "1000000000000000000000000", + "F000000000000000000000000"); + + check_subtract("FFFFFFF000000000000000", + "0000000000000800000000", + "FFFFFFEFFFFFF800000000"); + + check_subtract("10000000000000000000000000000000000000000000", + "00000000000000000000000000000000000800000000", + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF800000000"); + + check_subtract("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", + "000000000000000000000000000000000800000000", + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7FFFFFFFF"); +} + +static void +check_subtract_left_shifted(const char* lhs_hex, + const char* rhs_hex, + const unsigned amount, + const char* expected) +{ + ExessBigint lhs = bigint_from_hex(lhs_hex); + const ExessBigint rhs = bigint_from_hex(rhs_hex); + + exess_bigint_subtract_left_shifted(&lhs, &rhs, amount); + CHECK_HEXEQ(expected, &lhs); +} + +static void +test_subtract_left_shifted(void) +{ + check_subtract_left_shifted("1", "0", 1, "1"); + check_subtract_left_shifted("10000000", "1", 1, "FFFFFFE"); + check_subtract_left_shifted("100000000", "40000000", 2, "0"); + check_subtract_left_shifted("1000000000000000", "400000000000000", 2, "0"); + check_subtract_left_shifted("1000000000000000", "800000000000000", 1, "0"); + check_subtract_left_shifted("1000000000000000", "F", 16, "FFFFFFFFFF10000"); + check_subtract_left_shifted("1000000000000000", "F", 24, "FFFFFFFF1000000"); + check_subtract_left_shifted("100000000000000", "1", 0, "FFFFFFFFFFFFFF"); + check_subtract_left_shifted("100000000000000", "1", 56, "0"); + + check_subtract_left_shifted( + "11F2678326EA00000000", "43C33C1937564800000", 1, "979FFFFFFFF37000000"); +} + +static void +check_multiply_u32(const char* value, const uint32_t rhs, const char* expected) +{ + ExessBigint num = bigint_from_hex(value); + exess_bigint_multiply_u32(&num, rhs); + CHECK_HEXEQ(expected, &num); +} + +static void +test_multiply_u32(void) +{ + check_multiply_u32("0", 0x25, "0"); + check_multiply_u32("123456789ABCDEF", 0, "0"); + check_multiply_u32("2", 0x5, "A"); + check_multiply_u32("10000000", 0x9, "90000000"); + check_multiply_u32("100000000000000", 0xFFFF, "FFFF00000000000000"); + check_multiply_u32("100000000000000", 0xFFFFFFFF, "FFFFFFFF00000000000000"); + check_multiply_u32("1234567ABCD", 0xFFF, "12333335552433"); + check_multiply_u32("1234567ABCD", 0xFFFFFFF, "12345679998A985433"); + check_multiply_u32("FFFFFFFFFFFFFFFF", 0x2, "1FFFFFFFFFFFFFFFE"); + check_multiply_u32("FFFFFFFFFFFFFFFF", 0x4, "3FFFFFFFFFFFFFFFC"); + check_multiply_u32("FFFFFFFFFFFFFFFF", 0xF, "EFFFFFFFFFFFFFFF1"); + check_multiply_u32("FFFFFFFFFFFFFFFF", 0xFFFFFF, "FFFFFEFFFFFFFFFF000001"); + check_multiply_u32("377654D193A171", 10000000, "210EDD6D4CDD2580EE80"); + check_multiply_u32("2E3F36D108373C00000", 10, "1CE78242A52285800000"); + + check_multiply_u32( + "10000000000000000000000000", 0x00000002, "20000000000000000000000000"); + + check_multiply_u32( + "10000000000000000000000000", 0x0000000F, "F0000000000000000000000000"); + + check_multiply_u32("FFFF0000000000000000000000000", + 0xFFFF, + "FFFE00010000000000000000000000000"); + + check_multiply_u32("FFFF0000000000000000000000000", + 0xFFFFFFFF, + "FFFEFFFF00010000000000000000000000000"); + + check_multiply_u32("FFFF0000000000000000000000000", + 0xFFFFFFFF, + "FFFEFFFF00010000000000000000000000000"); +} + +static void +check_multiply_u64(const char* value, const uint64_t rhs, const char* expected) +{ + ExessBigint num = bigint_from_hex(value); + exess_bigint_multiply_u64(&num, rhs); + CHECK_HEXEQ(expected, &num); +} + +static void +test_multiply_u64(void) +{ + check_multiply_u64("0", 0x25, "0"); + check_multiply_u64("123456789ABCDEF", 0, "0"); + check_multiply_u64("123456789ABCDEF", 1, "123456789ABCDEF"); + check_multiply_u64("2", 0x5, "A"); + check_multiply_u64("10000000", 0x9, "90000000"); + check_multiply_u64("100000000000000", 0xFFFF, "FFFF00000000000000"); + check_multiply_u64("1234567ABCD", 0xFFF, "12333335552433"); + check_multiply_u64("1234567ABCD", 0xFFFFFFFFFFull, "1234567ABCBDCBA985433"); + check_multiply_u64("FFFFFFFFFFFFFFFF", 0x2, "1FFFFFFFFFFFFFFFE"); + check_multiply_u64("FFFFFFFFFFFFFFFF", 0x4, "3FFFFFFFFFFFFFFFC"); + check_multiply_u64("FFFFFFFFFFFFFFFF", 0xF, "EFFFFFFFFFFFFFFF1"); + + check_multiply_u64( + "100000000000000", 0xFFFFFFFFFFFFFFFFull, "FFFFFFFFFFFFFFFF00000000000000"); + + check_multiply_u64("FFFFFFFFFFFFFFFF", + 0xFFFFFFFFFFFFFFFFull, + "FFFFFFFFFFFFFFFE0000000000000001"); + + check_multiply_u64( + "10000000000000000000000000", 0x00000002, "20000000000000000000000000"); + + check_multiply_u64( + "10000000000000000000000000", 0x0000000F, "F0000000000000000000000000"); + + check_multiply_u64("FFFF0000000000000000000000000", + 0xFFFF, + "FFFE00010000000000000000000000000"); + + check_multiply_u64("FFFF0000000000000000000000000", + 0xFFFFFFFF, + "FFFEFFFF00010000000000000000000000000"); + + check_multiply_u64("FFFF0000000000000000000000000", + 0xFFFFFFFFFFFFFFFFull, + "FFFEFFFFFFFFFFFF00010000000000000000000000000"); + + check_multiply_u64( + "377654D193A171", 0x8AC7230489E80000ull, "1E10EE4B11D15A7F3DE7F3C7680000"); +} + +static void +check_multiply_pow10(const char* value, + const unsigned exponent, + const char* expected) +{ + ExessBigint num = bigint_from_hex(value); + exess_bigint_multiply_pow10(&num, exponent); + CHECK_HEXEQ(expected, &num); +} + +static void +test_multiply_pow10(void) +{ + check_multiply_pow10("0", 10, "0"); + check_multiply_pow10("1234", 0, "1234"); + check_multiply_pow10("4D2", 1, "3034"); + check_multiply_pow10("4D2", 2, "1E208"); + check_multiply_pow10("4D2", 3, "12D450"); + check_multiply_pow10("4D2", 4, "BC4B20"); + check_multiply_pow10("4D2", 5, "75AEF40"); + check_multiply_pow10("4D2", 6, "498D5880"); + check_multiply_pow10("4D2", 7, "2DF857500"); + check_multiply_pow10("4D2", 8, "1CBB369200"); + check_multiply_pow10("4D2", 9, "11F5021B400"); + check_multiply_pow10("4D2", 10, "B3921510800"); + check_multiply_pow10("4D2", 11, "703B4D2A5000"); + check_multiply_pow10("4D2", 12, "4625103A72000"); + check_multiply_pow10("4D2", 13, "2BD72A24874000"); + check_multiply_pow10("4D2", 14, "1B667A56D488000"); + check_multiply_pow10("4D2", 15, "11200C7644D50000"); + check_multiply_pow10("4D2", 16, "AB407C9EB0520000"); + check_multiply_pow10("4D2", 17, "6B084DE32E3340000"); + check_multiply_pow10("4D2", 18, "42E530ADFCE0080000"); + check_multiply_pow10("4D2", 19, "29CF3E6CBE0C0500000"); + check_multiply_pow10("4D2", 20, "1A218703F6C783200000"); + check_multiply_pow10("4D2", 21, "1054F4627A3CB1F400000"); + check_multiply_pow10("4D2", 22, "A3518BD8C65EF38800000"); + check_multiply_pow10("4D2", 23, "6612F7677BFB5835000000"); + check_multiply_pow10("4D2", 24, "3FCBDAA0AD7D17212000000"); + check_multiply_pow10("4D2", 25, "27DF68A46C6E2E74B4000000"); + check_multiply_pow10("4D2", 26, "18EBA166C3C4DD08F08000000"); + check_multiply_pow10("4D2", 27, "F9344E03A5B0A259650000000"); + check_multiply_pow10("4D2", 28, "9BC0B0C2478E6577DF20000000"); + check_multiply_pow10("4D2", 29, "61586E796CB8FF6AEB740000000"); + check_multiply_pow10("4D2", 30, "3CD7450BE3F39FA2D32880000000"); + check_multiply_pow10("4D2", 31, "26068B276E7843C5C3F9500000000"); + + check_multiply_pow10("4D2", + 50, + "149D1B4CFED03B23AB5F4E1196EF45C0" + "8000000000000"); + + check_multiply_pow10("4D2", + 100, + "5827249F27165024FBC47DFCA9359BF3" + "16332D1B91ACEECF471FBAB06D9B2000" + "0000000000000000000000"); + + check_multiply_pow10("4D2", + 305, + "AFBA390D657B0829339F5B98DC852A89" + "682758E01829EADFD016D1528D4D548B" + "80894B9ED9C2EC6A9CABB4881302A637" + "9FF3058908FEAC310C52FCA009799718" + "8260B0B2E2EC96E471B7892AD9B4F9F9" + "A448CBF150D2E87F3934000000000000" + "00000000000000000000000000000000" + "00000000000000000000000000000000"); + + check_multiply_pow10("123456789ABCDEF0", 0, "123456789ABCDEF0"); + check_multiply_pow10("123456789ABCDEF0", + 44, + "51A1AD66ACE4E5C79209330F58F52DE3" + "7CEFFF1F000000000000"); + check_multiply_pow10("123456789ABCDEF0", + 88, + "16E0C6D18F4BFA7D0289B88382F56151" + "EB9DA5DB09D56C9BA5D8305619CEE057" + "4F00000000000000000000000"); + check_multiply_pow10("123456789ABCDEF0", + 132, + "6696B1DA27BEA173B5EFCAABBB8492A9" + "2AE3D97F7EE3C7314FB7E2FF8AEFD329" + "F5F8202C22650BB79A7D9F3867F00000" + "00000000000000000000000000000"); + check_multiply_pow10("123456789ABCDEF0", + 176, + "1CC05FF0499D8BC7D8EBE0C6DC2FDC09" + "E93765F3448235FB16AD09D98BBB3A0A" + "843372D33A318EE63DAE6998DA59EF34" + "B15C40A65B9B65ABF3CAF00000000000" + "00000000000000000000000000000000" + "00"); + check_multiply_pow10("123456789ABCDEF0", + 220, + "80ED0FD9A6C0F56A495F466320D34E22" + "507FAA83F0519E7FF909FDDBDA184682" + "BB70D38D43284C828A3681540722E550" + "960567BAB1C25389C1BE7705228BE8CC" + "AF3EBD382829DF000000000000000000" + "00000000000000000000000000000000" + "000000"); + check_multiply_pow10("123456789ABCDEF0", + 264, + "2421FD0F55C486D05211339D45EC2DC4" + "12AE7A64DDFE619DA81B73C069088D3E" + "83D7AA9F99B571815DE939A5275FB4A6" + "9D8930798C01FB96781B9D633BB59AD5" + "A7F322A7EC14154D1B8B5DF1718779A5" + "2291FE0F000000000000000000000000" + "00000000000000000000000000000000" + "00000000000"); + check_multiply_pow10("123456789ABCDEF0", + 308, + "A206620F35C83E9E780ECC07DCAF13BB" + "0A7EE2E213747914340BC172D783BA56" + "661E8DCFFD03C398BD66F5570F445AC6" + "737126283C64AE1A289B9D8BB4531033" + "8C3E34DE2D534187092ABA1F4706100E" + "ECF66D14059461A05A9BEBBCCBA0F693" + "F0000000000000000000000000000000" + "00000000000000000000000000000000" + "000000000000000"); +} + +static void +check_divmod(const char* lhs_hex, + const char* rhs_hex, + const uint32_t expected_divisor, + const char* expected_mod_hex) +{ + ExessBigint lhs = bigint_from_hex(lhs_hex); + const ExessBigint rhs = bigint_from_hex(rhs_hex); + const uint32_t divisor = exess_bigint_divmod(&lhs, &rhs); + + assert(divisor == expected_divisor); + CHECK_HEXEQ(expected_mod_hex, &lhs); +} + +static void +test_divmod(void) +{ + check_divmod("A", "2", 5, "0"); + check_divmod("B", "2", 5, "1"); + check_divmod("C", "2", 6, "0"); + check_divmod("A", "1234567890", 0, "A"); + check_divmod("FFFFFFFF", "3", 0x55555555, "0"); + check_divmod("12345678", "3789012", 5, "D9861E"); + check_divmod("70000001", "1FFFFFFF", 3, "10000004"); + check_divmod("28000000", "12A05F20", 2, "2BF41C0"); + check_divmod("FFFFFFFFF", "FFFFFFFF", 16, "F"); + check_divmod("100000000000001", "FFFFFFF", 0x10000001, "2"); + check_divmod("40000000000002", "2FAF0800000000", 1, "1050F800000002"); + check_divmod("40000000000000", "40000000000000", 1, "0"); + + check_divmod("43DE72C3DF858FC278A361EEB5A000000", + "80000000000000000000000000000000", + 8, + "3DE72C3DF858FC278A361EEB5A000000"); + + check_divmod( + "B5C5AF0376E3C800000", "43C33C1937564800000", 2, "2E3F36D108373800000"); + + check_divmod("A0000000000000000000000000000000" + "00000000000000000000000000000000" + "00000000000000000000000000000000" + "000000000000000000000000000000", + "20000000000000000000000000000000" + "00000000000000000000000000000000" + "00000000000000000000000000000000" + "000000000000000000000000000000", + 5, + "0"); + + check_divmod("A0000000000000000000000000000000" + "00000000000000000000000000000000" + "00000000000000000000000000000000" + "000000000000000000000000000001", + "20000000000000000000000000000000" + "00000000000000000000000000000000" + "00000000000000000000000000000000" + "000000000000000000000000000000", + 5, + "1"); + + check_divmod("B6080000000000000000000000000000" + "00000000000000000000000000000000" + "00000000000000000000000000000000" + "000000000000000000000000000000FF" + "F", + "A0000000000000000000000000000000" + "00000000000000000000000000000000" + "00000000000000000000000000000000" + "000000000000000000000000000000", + 0x1234, + "FFF"); + + check_divmod("B6080000000000000000000000000000" + "00000000000000000000000000000000" + "00000000000000000000000000000000" + "00000000000000000000000000000000" + "0", + "9FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + "FFFFFFFFFFFFFFFFFFFFFFFFFFF001", + 0x1234, + "1232DCC"); +} + +static void +check_compare(const char* lhs_hex, const char* rhs_hex, const int expected_cmp) +{ + const ExessBigint lhs = bigint_from_hex(lhs_hex); + const ExessBigint rhs = bigint_from_hex(rhs_hex); + const int cmp = exess_bigint_compare(&lhs, &rhs); + + assert(cmp == expected_cmp); + + if (cmp) { + const int rcmp = exess_bigint_compare(&rhs, &lhs); + assert(rcmp == -cmp); + } +} + +static void +test_compare(void) +{ + check_compare("1", "1", 0); + check_compare("0", "1", -1); + check_compare("F", "FF", -1); + check_compare("F", "FFFFFFFFF", -1); + check_compare("10000000000000000", "10000000000000000", 0); + check_compare("10000000000000000", "10000000000000001", -1); + check_compare("FFFFFFFFFFFFFFFFF", "100000000000000000", -1); + check_compare("10000000000000000", "10000000000000001", -1); + check_compare("1234567890ABCDEF12345", "1234567890ABCDEF12345", 0); + check_compare("1234567890ABCDEF12345", "1234567890ABCDEF12346", -1); + + const char* const huge = "123456789ABCDEF0123456789ABCDEF0" + "123456789ABCDEF0123456789ABCDEF0" + "123456789ABCDEF0123456789ABCDEF0" + "123456789ABCDEF0123456789ABCDEF0"; + + const char* const huger = "123456789ABCDEF0123456789ABCDEF0" + "123456789ABCDEF0123456789ABCDEF0" + "123456789ABCDEF0123456789ABCDEF0" + "123456789ABCDEF0123456789ABCDEF1"; + + check_compare(huge, huge, 0); + check_compare(huger, huger, 0); + check_compare(huge, huger, -1); +} + +static void +check_plus_compare(const char* l_hex, + const char* p_hex, + const char* c_hex, + const int expected_cmp) +{ + const ExessBigint l = bigint_from_hex(l_hex); + const ExessBigint p = bigint_from_hex(p_hex); + const ExessBigint c = bigint_from_hex(c_hex); + const int cmp = exess_bigint_plus_compare(&l, &p, &c); + const int rcmp = exess_bigint_plus_compare(&p, &l, &c); + + assert(cmp == expected_cmp); + assert(rcmp == expected_cmp); +} + +static void +test_plus_compare(void) +{ + check_plus_compare("1", "0", "1", 0); + check_plus_compare("0", "0", "1", -1); + check_plus_compare("FFFFFFFFF", "F", "F", 1); + check_plus_compare("F", "F", "800000000", -1); + check_plus_compare("F", "F", "80000000000000000", -1); + check_plus_compare("800000000", "F", "80000000000000000", -1); + check_plus_compare("2D79883D20000", "2D79883D20000", "5AF3107A40000", 0); + check_plus_compare("20000000000000", "1", "20000000000000", +1); + + check_plus_compare( + "0588A503282FE00000", "0588A503282FE00000", "0AD78EBC5AC6200000", +1); + + check_plus_compare("2F06018572BEADD1280000000", + "0204FCE5E3E25026110000000", + "4000000000000000000000000", + -1); + + check_plus_compare("1234567890ABCDEF12345", + "000000000000000000001", + "1234567890ABCDEF12345", + +1); + + check_plus_compare("1234567890ABCDEF12344", + "000000000000000000001", + "1234567890ABCDEF12345", + 0); + + check_plus_compare("123456789000000000000", + "0000000000ABCDEF12345", + "1234567890ABCDEF12345", + 0); + + check_plus_compare("123456789000000000000", + "0000000000ABCDEF12344", + "1234567890ABCDEF12345", + -1); + + check_plus_compare("123456789000000000000", + "0000000000ABCDEF12346", + "1234567890ABCDEF12345", + 1); + + check_plus_compare("123456789100000000000", + "0000000000ABCDEF12345", + "1234567890ABCDEF12345", + 1); + + check_plus_compare("123456788900000000000", + "0000000000ABCDEF12345", + "1234567890ABCDEF12345", + -1); + + check_plus_compare("12345678900000000000000000000", + "0000000000ABCDEF1234500000000", + "1234567890ABCDEF1234500000000", + 0); + + check_plus_compare("12345678900000000000000000000", + "0000000000ABCDEF1234400000000", + "1234567890ABCDEF1234500000000", + -1); + + check_plus_compare("12345678900000000000000000000", + "0000000000ABCDEF1234600000000", + "1234567890ABCDEF1234500000000", + 1); + + check_plus_compare("12345678910000000000000000000", + "0000000000ABCDEF1234500000000", + "1234567890ABCDEF1234500000000", + 1); + check_plus_compare("12345678890000000000000000000", + "0000000000ABCDEF1234500000000", + "1234567890ABCDEF1234500000000", + -1); + + check_plus_compare("12345678900000000000000000000", + "000000000000000000ABCDEF12345", + "123456789000000000ABCDEF12345", + 0); + + check_plus_compare("12345678900000000000000000000", + "000000000000000000ABCDEF12346", + "123456789000000000ABCDEF12345", + 1); + + check_plus_compare("12345678900000000000000000000", + "000000000000000000ABCDEF12344", + "123456789000000000ABCDEF12345", + -1); + + check_plus_compare("12345678900000000000000000000", + "000000000000000000ABCDEF12345", + "12345678900000ABCDEF123450000", + -1); + + check_plus_compare("12345678900000000000000000000", + "000000000000000000ABCDEF12344", + "12345678900000ABCDEF123450000", + -1); + + check_plus_compare("12345678900000000000000000000", + "000000000000000000ABCDEF12345", + "12345678900000ABCDEF123450001", + -1); + + check_plus_compare("12345678900000000000000000000", + "00000000000000ABCDEF123460000", + "12345678900000ABCDEF123450000", + 1); +} + +static void +check_pow10(const unsigned exponent, const char* expected) +{ + ExessBigint num; + exess_bigint_set_pow10(&num, exponent); + CHECK_HEXEQ(expected, &num); +} + +static void +test_set_pow10(void) +{ + check_pow10(0, "1"); + check_pow10(1, "A"); + check_pow10(2, "64"); + check_pow10(5, "186A0"); + check_pow10(8, "5F5E100"); + check_pow10(16, "2386F26FC10000"); + check_pow10(30, "C9F2C9CD04674EDEA40000000"); + check_pow10(31, "7E37BE2022C0914B2680000000"); +} + +int +main(void) +{ + test_set(); + test_left_shifted_bigit(); + test_shift_left(); + test_add_u32(); + test_add(); + test_subtract(); + test_subtract_left_shifted(); + test_multiply_u32(); + test_multiply_u64(); + test_multiply_pow10(); + test_divmod(); + test_compare(); + test_plus_compare(); + test_set_pow10(); + + return 0; +} diff --git a/subprojects/exess/test/test_boolean.c b/subprojects/exess/test/test_boolean.c new file mode 100644 index 00000000..65eb9b24 --- /dev/null +++ b/subprojects/exess/test/test_boolean.c @@ -0,0 +1,114 @@ +/* + Copyright 2011-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#undef NDEBUG + +#include "exess/exess.h" + +#include +#include +#include +#include + +static void +check_read(const char* const string, + const ExessStatus expected_status, + const bool expected_value, + const size_t expected_count) +{ + bool value = false; + + const ExessResult r = exess_read_boolean(&value, string); + assert(value == expected_value); + assert(r.status == expected_status); + assert(r.count == expected_count); +} + +static void +test_read_boolean(void) +{ + // No input + check_read("", EXESS_EXPECTED_BOOLEAN, false, 0); + check_read(" \f\n\r\t\v", EXESS_EXPECTED_BOOLEAN, false, 6); + + // Canonical form + check_read("false", EXESS_SUCCESS, false, 5); + check_read("true", EXESS_SUCCESS, true, 4); + + // Non-canonical form + check_read("0", EXESS_SUCCESS, false, 1); + check_read("1", EXESS_SUCCESS, true, 1); + check_read(" \f\n\r\t\vfalse ", EXESS_SUCCESS, false, 11); + check_read(" \f\n\r\t\vtrue ", EXESS_SUCCESS, true, 10); + check_read(" \f\n\r\t\v0 ", EXESS_SUCCESS, false, 7); + check_read(" \f\n\r\t\v1 ", EXESS_SUCCESS, true, 7); + + // Trailing garbage + check_read("falsely", EXESS_EXPECTED_END, false, 5); + check_read("truely", EXESS_EXPECTED_END, true, 4); + check_read("0no", EXESS_EXPECTED_END, false, 1); + check_read("1yes", EXESS_EXPECTED_END, true, 1); + + // Garbage + check_read("twue", EXESS_EXPECTED_BOOLEAN, false, 0); + check_read("fawse", EXESS_EXPECTED_BOOLEAN, false, 0); + check_read("tr", EXESS_EXPECTED_BOOLEAN, false, 0); + check_read("fa", EXESS_EXPECTED_BOOLEAN, false, 0); + check_read("yes", EXESS_EXPECTED_BOOLEAN, false, 0); + check_read("no", EXESS_EXPECTED_BOOLEAN, false, 0); +} + +static void +check_write(const bool value, + const ExessStatus expected_status, + const size_t buf_size, + const char* const expected_string) +{ + char buf[EXESS_MAX_BOOLEAN_LENGTH + 1] = {1, 2, 3, 4, 5, 6}; + + assert(buf_size <= sizeof(buf)); + + const ExessResult r = exess_write_boolean(value, buf_size, buf); + assert(!strcmp(buf, expected_string)); + assert(r.status == expected_status); + assert(r.count == strlen(buf)); + assert(r.status || exess_write_boolean(value, 0, NULL).count == r.count); +} + +static void +test_write_boolean(void) +{ + check_write(true, EXESS_SUCCESS, 5, "true"); + check_write(false, EXESS_SUCCESS, 6, "false"); + + check_write(true, EXESS_NO_SPACE, 4, ""); + check_write(false, EXESS_NO_SPACE, 5, ""); + + // Check that nothing is written when there isn't enough space + char c = 42; + const ExessResult r = exess_write_boolean(false, 0, &c); + assert(c == 42); + assert(r.status == EXESS_NO_SPACE); + assert(r.count == 0); +} + +int +main(void) +{ + test_read_boolean(); + test_write_boolean(); + return 0; +} diff --git a/subprojects/exess/test/test_byte.c b/subprojects/exess/test/test_byte.c new file mode 100644 index 00000000..a67fd779 --- /dev/null +++ b/subprojects/exess/test/test_byte.c @@ -0,0 +1,98 @@ +/* + Copyright 2011-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#undef NDEBUG + +#include "exess/exess.h" + +#include +#include +#include +#include + +static void +check_read(const char* const string, + const ExessStatus expected_status, + const int8_t expected_value, + const size_t expected_count) +{ + int8_t value = 0; + const ExessResult r = exess_read_byte(&value, string); + + assert(r.status == expected_status); + assert(r.count == expected_count); + assert(value == expected_value); +} + +static void +test_read_byte(void) +{ + // Limits + check_read("-128", EXESS_SUCCESS, INT8_MIN, EXESS_MAX_BYTE_LENGTH); + check_read("127", EXESS_SUCCESS, INT8_MAX, 3); + + // Out of range + check_read("-129", EXESS_OUT_OF_RANGE, 0, 4); + check_read("128", EXESS_OUT_OF_RANGE, 0, 3); + + // Garbage + check_read("+", EXESS_EXPECTED_DIGIT, 0, 1); +} + +static void +check_write(const int8_t value, + const ExessStatus expected_status, + const size_t buf_size, + const char* const expected_string) +{ + char buf[EXESS_MAX_BYTE_LENGTH + 1] = {1, 2, 3, 4, 5}; + assert(buf_size <= sizeof(buf)); + + const ExessResult r = exess_write_byte(value, buf_size, buf); + assert(r.status == expected_status); + assert(!strcmp(buf, expected_string)); + assert(r.count == strlen(buf)); + assert(r.status || exess_write_byte(value, 0, NULL).count == r.count); +} + +static void +test_write_byte(void) +{ + check_write(INT8_MIN, EXESS_SUCCESS, 5, "-128"); + check_write(INT8_MAX, EXESS_SUCCESS, 4, "127"); +} + +static void +test_round_trip(void) +{ + int8_t value = 0; + char buf[EXESS_MAX_BYTE_LENGTH + 1] = {1, 2, 3, 4, 5}; + + for (int16_t i = INT8_MIN; i <= INT8_MAX; ++i) { + assert(!exess_write_byte((int8_t)i, sizeof(buf), buf).status); + assert(!exess_read_byte(&value, buf).status); + assert(value == i); + } +} + +int +main(void) +{ + test_read_byte(); + test_write_byte(); + test_round_trip(); + return 0; +} diff --git a/subprojects/exess/test/test_canonical.c b/subprojects/exess/test/test_canonical.c new file mode 100644 index 00000000..77167fcf --- /dev/null +++ b/subprojects/exess/test/test_canonical.c @@ -0,0 +1,412 @@ +/* + Copyright 2011-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#undef NDEBUG + +#include "exess/exess.h" + +#include +#include +#include + +static void +check_write(const ExessDatatype datatype, + const char* const value, + const ExessStatus expected_status, + const size_t buf_size, + const char* const expected_string) +{ + char buf[328] = {42}; + + assert(buf_size <= sizeof(buf)); + + const ExessResult r = exess_write_canonical(value, datatype, buf_size, buf); + assert(r.status == expected_status); + assert(r.count == strlen(buf)); + assert(!strcmp(buf, expected_string)); + assert((r.status && r.status != EXESS_EXPECTED_END) || + exess_write_canonical(value, datatype, 0, NULL).count == r.count); +} + +static void +test_decimal(void) +{ + check_write(EXESS_DECIMAL, "", EXESS_EXPECTED_DIGIT, 1, ""); + check_write(EXESS_DECIMAL, " \f\n\r\t\v", EXESS_EXPECTED_DIGIT, 1, ""); + + check_write(EXESS_DECIMAL, " -001 ", EXESS_SUCCESS, 5, "-1.0"); + check_write(EXESS_DECIMAL, " -000 ", EXESS_SUCCESS, 5, "-0.0"); + check_write(EXESS_DECIMAL, " 000 ", EXESS_SUCCESS, 4, "0.0"); + check_write(EXESS_DECIMAL, " 001 ", EXESS_SUCCESS, 4, "1.0"); + check_write(EXESS_DECIMAL, " +001 ", EXESS_SUCCESS, 4, "1.0"); + + check_write(EXESS_DECIMAL, " -.123", EXESS_SUCCESS, 7, "-0.123"); + check_write(EXESS_DECIMAL, " +.123", EXESS_SUCCESS, 6, "0.123"); + check_write(EXESS_DECIMAL, " -0 ", EXESS_SUCCESS, 5, "-0.0"); + check_write(EXESS_DECIMAL, " +0 ", EXESS_SUCCESS, 4, "0.0"); + + check_write(EXESS_DECIMAL, " +00.10 ", EXESS_SUCCESS, 4, "0.1"); + check_write(EXESS_DECIMAL, " +01 ", EXESS_SUCCESS, 4, "1.0"); + + check_write(EXESS_DECIMAL, + " 36893488147419103232 ", + EXESS_SUCCESS, + 23, + "36893488147419103232.0"); + + check_write(EXESS_DECIMAL, + " 0036893488147419103232 ", + EXESS_SUCCESS, + 23, + "36893488147419103232.0"); + + check_write(EXESS_DECIMAL, + " +36893488147419103232 ", + EXESS_SUCCESS, + 23, + "36893488147419103232.0"); + + check_write(EXESS_DECIMAL, + " -36893488147419103232 ", + EXESS_SUCCESS, + 24, + "-36893488147419103232.0"); + + check_write(EXESS_DECIMAL, + " +0036893488147419103232 ", + EXESS_SUCCESS, + 23, + "36893488147419103232.0"); + + check_write(EXESS_DECIMAL, + " +0036893488147419103232. ", + EXESS_SUCCESS, + 23, + "36893488147419103232.0"); + + check_write(EXESS_DECIMAL, + " +0036893488147419103232.00 ", + EXESS_SUCCESS, + 23, + "36893488147419103232.0"); + + check_write(EXESS_DECIMAL, + " +0036893488147419103232.12300 ", + EXESS_SUCCESS, + 25, + "36893488147419103232.123"); + + check_write(EXESS_DECIMAL, + " -0036893488147419103232 ", + EXESS_SUCCESS, + 24, + "-36893488147419103232.0"); + + check_write(EXESS_DECIMAL, + " -0036893488147419103232. ", + EXESS_SUCCESS, + 24, + "-36893488147419103232.0"); + + check_write(EXESS_DECIMAL, + " -0036893488147419103232.00 ", + EXESS_SUCCESS, + 24, + "-36893488147419103232.0"); + + check_write(EXESS_DECIMAL, + " -0036893488147419103232.12300 ", + EXESS_SUCCESS, + 26, + "-36893488147419103232.123"); + + check_write(EXESS_DECIMAL, " -1234extra", EXESS_EXPECTED_END, 8, "-1234.0"); + check_write(EXESS_DECIMAL, " 1234extra", EXESS_EXPECTED_END, 7, "1234.0"); + + check_write(EXESS_DECIMAL, " f", EXESS_EXPECTED_DIGIT, 2, ""); + check_write(EXESS_DECIMAL, "", EXESS_EXPECTED_DIGIT, 1, ""); +} + +static void +test_integer(void) +{ + check_write(EXESS_INTEGER, "", EXESS_EXPECTED_DIGIT, 1, ""); + check_write(EXESS_INTEGER, " \f\n\r\t\v", EXESS_EXPECTED_DIGIT, 1, ""); + + // Integer + + check_write(EXESS_INTEGER, " -001 ", EXESS_SUCCESS, 3, "-1"); + check_write(EXESS_INTEGER, " 000 ", EXESS_SUCCESS, 2, "0"); + check_write(EXESS_INTEGER, " 001 ", EXESS_SUCCESS, 2, "1"); + check_write(EXESS_INTEGER, " +001 ", EXESS_SUCCESS, 2, "1"); + + check_write(EXESS_INTEGER, " junk 987654321 ", EXESS_EXPECTED_DIGIT, 2, ""); + check_write( + EXESS_INTEGER, " 987654321 junk ", EXESS_EXPECTED_END, 10, "987654321"); + + check_write(EXESS_INTEGER, + " 36893488147419103232 ", + EXESS_SUCCESS, + 21, + "36893488147419103232"); + + check_write(EXESS_INTEGER, + " 0036893488147419103232 ", + EXESS_SUCCESS, + 21, + "36893488147419103232"); + + check_write(EXESS_INTEGER, + " +36893488147419103232 ", + EXESS_SUCCESS, + 21, + "36893488147419103232"); + + check_write(EXESS_INTEGER, + " +0036893488147419103232 ", + EXESS_SUCCESS, + 21, + "36893488147419103232"); + + check_write(EXESS_INTEGER, + " -36893488147419103232 ", + EXESS_SUCCESS, + 22, + "-36893488147419103232"); + + check_write(EXESS_INTEGER, + " -0036893488147419103232 ", + EXESS_SUCCESS, + 22, + "-36893488147419103232"); + + // NonPositiveInteger + + check_write(EXESS_NON_POSITIVE_INTEGER, " -001 ", EXESS_SUCCESS, 3, "-1"); + check_write(EXESS_NON_POSITIVE_INTEGER, " 000 ", EXESS_SUCCESS, 2, "0"); + check_write(EXESS_NON_POSITIVE_INTEGER, " 001 ", EXESS_BAD_VALUE, 3, ""); + check_write(EXESS_NON_POSITIVE_INTEGER, " +001 ", EXESS_BAD_VALUE, 3, ""); + + check_write(EXESS_NON_POSITIVE_INTEGER, + " -36893488147419103232 ", + EXESS_SUCCESS, + 22, + "-36893488147419103232"); + + check_write(EXESS_NON_POSITIVE_INTEGER, + " -0036893488147419103232 ", + EXESS_SUCCESS, + 22, + "-36893488147419103232"); + + // NegativeInteger + + check_write(EXESS_NEGATIVE_INTEGER, " -001 ", EXESS_SUCCESS, 3, "-1"); + check_write(EXESS_NEGATIVE_INTEGER, " 000 ", EXESS_BAD_VALUE, 3, ""); + check_write(EXESS_NEGATIVE_INTEGER, " 001 ", EXESS_BAD_VALUE, 3, ""); + check_write(EXESS_NEGATIVE_INTEGER, " +001 ", EXESS_BAD_VALUE, 3, ""); + + check_write(EXESS_NEGATIVE_INTEGER, + " -36893488147419103232 ", + EXESS_SUCCESS, + 22, + "-36893488147419103232"); + + check_write(EXESS_NEGATIVE_INTEGER, + " -0036893488147419103232 ", + EXESS_SUCCESS, + 22, + "-36893488147419103232"); + + // NonNegativeInteger + + check_write(EXESS_NON_NEGATIVE_INTEGER, " -001 ", EXESS_BAD_VALUE, 3, ""); + check_write(EXESS_NON_NEGATIVE_INTEGER, " 000 ", EXESS_SUCCESS, 2, "0"); + check_write(EXESS_NON_NEGATIVE_INTEGER, " 001 ", EXESS_SUCCESS, 2, "1"); + check_write(EXESS_NON_NEGATIVE_INTEGER, " +001 ", EXESS_SUCCESS, 2, "1"); + + check_write(EXESS_NON_NEGATIVE_INTEGER, + " 36893488147419103232 ", + EXESS_SUCCESS, + 21, + "36893488147419103232"); + + check_write(EXESS_NON_NEGATIVE_INTEGER, + " 0036893488147419103232 ", + EXESS_SUCCESS, + 21, + "36893488147419103232"); + + // PositiveInteger + + check_write(EXESS_POSITIVE_INTEGER, " -001 ", EXESS_BAD_VALUE, 3, ""); + check_write(EXESS_POSITIVE_INTEGER, " 000 ", EXESS_BAD_VALUE, 3, ""); + check_write(EXESS_POSITIVE_INTEGER, " 001 ", EXESS_SUCCESS, 2, "1"); + check_write(EXESS_POSITIVE_INTEGER, " +001 ", EXESS_SUCCESS, 2, "1"); + + check_write(EXESS_POSITIVE_INTEGER, + " 36893488147419103232 ", + EXESS_SUCCESS, + 21, + "36893488147419103232"); + + check_write(EXESS_POSITIVE_INTEGER, + " 0036893488147419103232 ", + EXESS_SUCCESS, + 21, + "36893488147419103232"); +} + +static void +test_fixed_numbers(void) +{ + check_write(EXESS_DOUBLE, " +00.10 ", EXESS_SUCCESS, 7, "1.0E-1"); + check_write(EXESS_FLOAT, " +00.10 ", EXESS_SUCCESS, 14, "1.00000001E-1"); + check_write(EXESS_BOOLEAN, " 0 ", EXESS_SUCCESS, 6, "false"); + check_write(EXESS_BOOLEAN, " 1 ", EXESS_SUCCESS, 5, "true"); + check_write(EXESS_LONG, " +012 ", EXESS_SUCCESS, 3, "12"); + check_write(EXESS_INT, " +012 ", EXESS_SUCCESS, 3, "12"); + check_write(EXESS_SHORT, " +012 ", EXESS_SUCCESS, 3, "12"); + check_write(EXESS_BYTE, " +012 ", EXESS_SUCCESS, 3, "12"); + check_write(EXESS_ULONG, " 012 ", EXESS_SUCCESS, 3, "12"); + check_write(EXESS_UINT, " 012 ", EXESS_SUCCESS, 3, "12"); + check_write(EXESS_USHORT, " 012 ", EXESS_SUCCESS, 3, "12"); + check_write(EXESS_UBYTE, " 012 ", EXESS_SUCCESS, 3, "12"); +} + +static void +test_time(void) +{ + check_write(EXESS_DURATION, " P0Y6M ", EXESS_SUCCESS, 4, "P6M"); + check_write(EXESS_DURATION, " P1Y6M0D ", EXESS_SUCCESS, 6, "P1Y6M"); + check_write(EXESS_TIME, " 12:15:01+00:00 ", EXESS_SUCCESS, 14, "12:15:01Z"); + check_write( + EXESS_DATE, " 02004-04-12+00:00 ", EXESS_SUCCESS, 12, "2004-04-12Z"); +} + +static void +test_datetime(void) +{ + // Local + check_write(EXESS_DATETIME, + " 02001-02-03T04:05:06.007 ", + EXESS_SUCCESS, + 26, + "2001-02-03T04:05:06.007"); + + // Positive carry: minute => hour + check_write(EXESS_DATETIME, + " 02001-02-03T04:46:59-00:15 ", + EXESS_SUCCESS, + 21, + "2001-02-03T05:01:59Z"); + + // Positive carry: minute => hour => day + check_write(EXESS_DATETIME, + " 02001-02-03T23:46:59-00:15 ", + EXESS_SUCCESS, + 21, + "2001-02-04T00:01:59Z"); + + // Positive carry: minute => hour => day => month (common year) + check_write(EXESS_DATETIME, + " 02001-02-28T23:46:59-00:15 ", + EXESS_SUCCESS, + 21, + "2001-03-01T00:01:59Z"); + + // Positive carry: minute => hour => day => month (leap year) + check_write(EXESS_DATETIME, + " 02000-02-29T23:46:59-00:15 ", + EXESS_SUCCESS, + 21, + "2000-03-01T00:01:59Z"); + + // Positive carry: minute => hour => day => month => year + check_write(EXESS_DATETIME, + " 02001-12-31T23:46:59-00:15 ", + EXESS_SUCCESS, + 21, + "2002-01-01T00:01:59Z"); + + // Negative carry: minute => hour + check_write(EXESS_DATETIME, + " 02001-02-03T04:14:59+00:15 ", + EXESS_SUCCESS, + 21, + "2001-02-03T03:59:59Z"); + + // Negative carry: minute => hour => day + check_write(EXESS_DATETIME, + " 02001-02-02T00:14:59+00:15 ", + EXESS_SUCCESS, + 21, + "2001-02-01T23:59:59Z"); + + // Negative carry: minute => hour => day => month (common year) + check_write(EXESS_DATETIME, + " 02001-03-01T00:14:59+00:15 ", + EXESS_SUCCESS, + 21, + "2001-02-28T23:59:59Z"); + + // Negative carry: minute => hour => day => month (leap year) + check_write(EXESS_DATETIME, + " 02000-03-01T00:14:59+00:15 ", + EXESS_SUCCESS, + 21, + "2000-02-29T23:59:59Z"); + + // Negative carry: minute => hour => day => month => year + check_write(EXESS_DATETIME, + " 02001-01-01T00:14:59+00:15 ", + EXESS_SUCCESS, + 21, + "2000-12-31T23:59:59Z"); +} + +static void +test_binary(void) +{ + check_write(EXESS_HEX, " D EA D B3 3F", EXESS_SUCCESS, 9, "DEADB33F"); + check_write(EXESS_HEX, "invalid", EXESS_EXPECTED_HEX, 1, ""); + check_write(EXESS_HEX, "1A2B3", EXESS_EXPECTED_HEX, 5, ""); + check_write(EXESS_HEX, "1", EXESS_EXPECTED_HEX, 1, ""); + check_write(EXESS_HEX, "", EXESS_EXPECTED_HEX, 5, ""); + + check_write( + EXESS_BASE64, " Z\fm\n9\rv\tY\vmFy", EXESS_SUCCESS, 9, "Zm9vYmFy"); + check_write(EXESS_BASE64, "!nvalid", EXESS_EXPECTED_BASE64, 1, ""); + check_write(EXESS_BASE64, "Z", EXESS_EXPECTED_BASE64, 1, ""); + check_write(EXESS_BASE64, "Zm", EXESS_EXPECTED_BASE64, 2, ""); + check_write(EXESS_BASE64, "Zm9", EXESS_EXPECTED_BASE64, 3, ""); + check_write(EXESS_BASE64, "", EXESS_EXPECTED_BASE64, 5, ""); +} + +int +main(void) +{ + check_write(EXESS_NOTHING, "?", EXESS_UNSUPPORTED, 1, ""); + + test_decimal(); + test_integer(); + test_fixed_numbers(); + test_time(); + test_datetime(); + test_binary(); + + return 0; +} diff --git a/subprojects/exess/test/test_coerce.c b/subprojects/exess/test/test_coerce.c new file mode 100644 index 00000000..dc5ad060 --- /dev/null +++ b/subprojects/exess/test/test_coerce.c @@ -0,0 +1,519 @@ +/* + Copyright 2011-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#undef NDEBUG + +#include "exess/exess.h" + +#include +#include + +static const char* const min_long_str = "-9223372036854775808"; +static const char* const max_long_str = "9223372036854775807"; +static const char* const max_ulong_str = "18446744073709551615"; + +static void +check_from_to(const ExessDatatype from_datatype, + const char* const from_string, + const ExessDatatype to_datatype, + const char* const to_string) +{ + char value_buf[4] = {0, 0, 0, 0}; + char buf[328] = {42}; + + // Read original value + ExessVariant value = {EXESS_HEX, {.as_blob = {sizeof(value_buf), value_buf}}}; + assert(!exess_read_variant(&value, from_datatype, from_string).status); + + // Coerce to target datatype + const ExessVariant coerced = exess_coerce(value, to_datatype, EXESS_LOSSLESS); + assert(coerced.datatype == to_datatype); + + // Write coerced value and check string against expectation + assert(!exess_write_variant(coerced, sizeof(buf), buf).status); + assert(!strcmp(buf, to_string)); + + // Coerce the value back to the original type + const ExessVariant tripped = + exess_coerce(coerced, from_datatype, EXESS_LOSSLESS); + assert(tripped.datatype == from_datatype); + + // Write round-tripped value and check string against the original + assert(!exess_write_variant(tripped, sizeof(buf), buf).status); + assert(!strcmp(buf, from_string)); +} + +static void +check_one_way(const ExessDatatype from_datatype, + const char* const from_string, + const ExessCoercionFlags coercions, + const ExessDatatype to_datatype, + const char* const to_string) +{ + char buf[328] = {42}; + + // Read original value + ExessVariant value = {EXESS_NOTHING, {EXESS_SUCCESS}}; + assert(!exess_read_variant(&value, from_datatype, from_string).status); + + // Coerce to target datatype + ExessVariant coerced = exess_coerce(value, to_datatype, coercions); + assert(coerced.datatype == to_datatype); + + // Write coerced value and check string against expectation + assert(!exess_write_variant(coerced, sizeof(buf), buf).status); + assert(!strcmp(buf, to_string)); +} + +static void +check_failure(const ExessDatatype from_datatype, + const char* const from_string, + const ExessDatatype to_datatype, + const ExessStatus expected_status) +{ + // Read original value + ExessVariant value = {EXESS_NOTHING, {EXESS_SUCCESS}}; + assert(!exess_read_variant(&value, from_datatype, from_string).status); + + // Try to coerce to target datatype + const ExessVariant coerced = exess_coerce(value, to_datatype, EXESS_LOSSLESS); + assert(coerced.datatype == EXESS_NOTHING); + assert(exess_get_status(&coerced) == expected_status); +} + +static void +test_unknown(void) +{ + ExessVariant long_value = exess_make_long(1); + ExessVariant ulong_value = exess_make_ulong(1u); + ExessVariant unknown_value = exess_make_nothing(EXESS_SUCCESS); + + assert(exess_coerce(unknown_value, EXESS_LONG, EXESS_LOSSLESS).datatype == + EXESS_NOTHING); + + assert(exess_coerce(unknown_value, EXESS_ULONG, EXESS_LOSSLESS).datatype == + EXESS_NOTHING); + + assert(exess_coerce(long_value, EXESS_NOTHING, EXESS_LOSSLESS).datatype == + EXESS_NOTHING); + + assert(exess_coerce(ulong_value, EXESS_NOTHING, EXESS_LOSSLESS).datatype == + EXESS_NOTHING); +} + +static void +test_boolean(void) +{ + // Exactly from 0 or 1, lossy from 0 or non-zero + check_from_to(EXESS_BOOLEAN, "false", EXESS_FLOAT, "0.0E0"); + check_from_to(EXESS_BOOLEAN, "true", EXESS_FLOAT, "1.0E0"); + check_from_to(EXESS_BOOLEAN, "false", EXESS_DOUBLE, "0.0E0"); + check_from_to(EXESS_BOOLEAN, "true", EXESS_DOUBLE, "1.0E0"); + check_from_to(EXESS_BOOLEAN, "false", EXESS_LONG, "0"); + check_from_to(EXESS_BOOLEAN, "true", EXESS_LONG, "1"); + check_failure(EXESS_LONG, "-1", EXESS_BOOLEAN, EXESS_WOULD_TRUNCATE); + check_failure(EXESS_LONG, "2", EXESS_BOOLEAN, EXESS_WOULD_TRUNCATE); + check_one_way(EXESS_LONG, "42", EXESS_TRUNCATE, EXESS_BOOLEAN, "true"); + check_one_way(EXESS_LONG, "-1", EXESS_TRUNCATE, EXESS_BOOLEAN, "true"); + check_from_to(EXESS_BOOLEAN, "false", EXESS_ULONG, "0"); + check_from_to(EXESS_BOOLEAN, "true", EXESS_ULONG, "1"); + check_failure(EXESS_LONG, "2", EXESS_BOOLEAN, EXESS_WOULD_TRUNCATE); + check_one_way(EXESS_ULONG, "42", EXESS_TRUNCATE, EXESS_BOOLEAN, "true"); + + // Not convertible to any time types + check_failure(EXESS_BOOLEAN, "true", EXESS_DURATION, EXESS_UNSUPPORTED); + check_failure(EXESS_BOOLEAN, "true", EXESS_DATETIME, EXESS_UNSUPPORTED); + check_failure(EXESS_BOOLEAN, "true", EXESS_TIME, EXESS_UNSUPPORTED); + check_failure(EXESS_BOOLEAN, "true", EXESS_DATE, EXESS_UNSUPPORTED); +} + +static void +test_long(void) +{ + // Truncating boolean conversion + check_one_way(EXESS_LONG, "42", EXESS_TRUNCATE, EXESS_BOOLEAN, "true"); + check_one_way(EXESS_LONG, "-1", EXESS_TRUNCATE, EXESS_BOOLEAN, "true"); + + // All smaller integer types + check_from_to(EXESS_LONG, "-2147483648", EXESS_INT, "-2147483648"); + check_failure(EXESS_LONG, "-2147483649", EXESS_INT, EXESS_OUT_OF_RANGE); + check_from_to(EXESS_LONG, "-32768", EXESS_SHORT, "-32768"); + check_failure(EXESS_LONG, "-32769", EXESS_SHORT, EXESS_OUT_OF_RANGE); + check_from_to(EXESS_LONG, "-128", EXESS_BYTE, "-128"); + check_failure(EXESS_LONG, "-129", EXESS_BYTE, EXESS_OUT_OF_RANGE); + + // Positive values to/from all unsigned types + check_from_to(EXESS_LONG, max_long_str, EXESS_ULONG, max_long_str); + check_failure(EXESS_LONG, "-1", EXESS_ULONG, EXESS_OUT_OF_RANGE); + check_from_to(EXESS_LONG, "4294967295", EXESS_UINT, "4294967295"); + check_failure(EXESS_LONG, "-1", EXESS_UINT, EXESS_OUT_OF_RANGE); + check_from_to(EXESS_LONG, "65535", EXESS_USHORT, "65535"); + check_failure(EXESS_LONG, "-1", EXESS_USHORT, EXESS_OUT_OF_RANGE); + check_from_to(EXESS_LONG, "255", EXESS_UBYTE, "255"); + check_failure(EXESS_LONG, "-1", EXESS_UBYTE, EXESS_OUT_OF_RANGE); + + // Any value to/from integer + check_from_to(EXESS_LONG, min_long_str, EXESS_INTEGER, min_long_str); + check_from_to(EXESS_LONG, max_long_str, EXESS_INTEGER, max_long_str); + + // Non-positive values to/from nonPositiveInteger + check_from_to( + EXESS_LONG, min_long_str, EXESS_NON_POSITIVE_INTEGER, min_long_str); + check_from_to(EXESS_LONG, "0", EXESS_NON_POSITIVE_INTEGER, "0"); + check_failure( + EXESS_LONG, "1", EXESS_NON_POSITIVE_INTEGER, EXESS_OUT_OF_RANGE); + + // Negative values to/from negativeInteger + check_from_to(EXESS_LONG, min_long_str, EXESS_NEGATIVE_INTEGER, min_long_str); + check_from_to(EXESS_LONG, "-1", EXESS_NEGATIVE_INTEGER, "-1"); + check_failure(EXESS_LONG, "0", EXESS_NEGATIVE_INTEGER, EXESS_OUT_OF_RANGE); + + // Non-negative values to/from nonNegativeInteger + check_failure( + EXESS_LONG, "-1", EXESS_NON_NEGATIVE_INTEGER, EXESS_OUT_OF_RANGE); + check_from_to(EXESS_LONG, "0", EXESS_NON_NEGATIVE_INTEGER, "0"); + check_from_to( + EXESS_LONG, max_long_str, EXESS_NON_NEGATIVE_INTEGER, max_long_str); + + // Positive values to/from positiveInteger + check_failure(EXESS_LONG, "-1", EXESS_POSITIVE_INTEGER, EXESS_OUT_OF_RANGE); + check_failure(EXESS_LONG, "0", EXESS_POSITIVE_INTEGER, EXESS_OUT_OF_RANGE); + check_from_to(EXESS_LONG, max_long_str, EXESS_POSITIVE_INTEGER, max_long_str); + check_failure(EXESS_POSITIVE_INTEGER, + "9223372036854775808", + EXESS_LONG, + EXESS_OUT_OF_RANGE); + + // Float + check_failure(EXESS_FLOAT, "1.5", EXESS_LONG, EXESS_WOULD_ROUND); + check_from_to(EXESS_LONG, "-16777215", EXESS_FLOAT, "-1.6777215E7"); + check_failure(EXESS_LONG, "-16777216", EXESS_FLOAT, EXESS_OUT_OF_RANGE); + check_failure(EXESS_FLOAT, "-16777216", EXESS_LONG, EXESS_OUT_OF_RANGE); + check_from_to(EXESS_LONG, "16777215", EXESS_FLOAT, "1.6777215E7"); + check_failure(EXESS_LONG, "16777216", EXESS_FLOAT, EXESS_OUT_OF_RANGE); + check_failure(EXESS_FLOAT, "16777216", EXESS_LONG, EXESS_OUT_OF_RANGE); + check_one_way(EXESS_FLOAT, "1.0", EXESS_LOSSLESS, EXESS_LONG, "1"); + check_one_way(EXESS_FLOAT, "1.5", EXESS_ROUND, EXESS_LONG, "2"); + check_one_way(EXESS_FLOAT, "2.5", EXESS_ROUND, EXESS_LONG, "2"); + check_one_way(EXESS_FLOAT, "3.5", EXESS_ROUND, EXESS_LONG, "4"); + + // Double + check_failure(EXESS_DOUBLE, "1.5", EXESS_LONG, EXESS_WOULD_ROUND); + check_from_to( + EXESS_LONG, "-9007199254740991", EXESS_DOUBLE, "-9.007199254740991E15"); + check_failure( + EXESS_LONG, "-9007199254740992", EXESS_DOUBLE, EXESS_OUT_OF_RANGE); + check_failure( + EXESS_DOUBLE, "-9007199254740992", EXESS_LONG, EXESS_OUT_OF_RANGE); + check_from_to( + EXESS_LONG, "9007199254740991", EXESS_DOUBLE, "9.007199254740991E15"); + check_failure( + EXESS_LONG, "9007199254740992", EXESS_DOUBLE, EXESS_OUT_OF_RANGE); + check_failure( + EXESS_DOUBLE, "9007199254740992", EXESS_LONG, EXESS_OUT_OF_RANGE); + check_one_way(EXESS_DOUBLE, "1.0", EXESS_LOSSLESS, EXESS_LONG, "1"); + check_one_way(EXESS_DOUBLE, "1.5", EXESS_ROUND, EXESS_LONG, "2"); + check_one_way(EXESS_DOUBLE, "2.5", EXESS_ROUND, EXESS_LONG, "2"); + check_one_way(EXESS_DOUBLE, "3.5", EXESS_ROUND, EXESS_LONG, "4"); +} + +static void +test_ulong(void) +{ + ExessVariant unknown = {EXESS_NOTHING, {EXESS_SUCCESS}}; + + assert(exess_coerce(unknown, EXESS_ULONG, EXESS_LOSSLESS).datatype == + EXESS_NOTHING); + + // Truncating boolean conversion + check_one_way(EXESS_ULONG, "42", EXESS_TRUNCATE, EXESS_BOOLEAN, "true"); + + // All integer types + check_from_to(EXESS_ULONG, max_long_str, EXESS_LONG, max_long_str); + check_failure(EXESS_ULONG, max_ulong_str, EXESS_LONG, EXESS_OUT_OF_RANGE); + check_from_to(EXESS_ULONG, "2147483647", EXESS_INT, "2147483647"); + check_failure(EXESS_ULONG, "2147483648", EXESS_INT, EXESS_OUT_OF_RANGE); + check_failure(EXESS_INT, "-1", EXESS_ULONG, EXESS_OUT_OF_RANGE); + check_from_to(EXESS_ULONG, "32767", EXESS_SHORT, "32767"); + check_failure(EXESS_ULONG, "32768", EXESS_SHORT, EXESS_OUT_OF_RANGE); + check_failure(EXESS_SHORT, "-1", EXESS_ULONG, EXESS_OUT_OF_RANGE); + check_from_to(EXESS_ULONG, "127", EXESS_BYTE, "127"); + check_failure(EXESS_ULONG, "128", EXESS_BYTE, EXESS_OUT_OF_RANGE); + check_failure(EXESS_BYTE, "-1", EXESS_ULONG, EXESS_OUT_OF_RANGE); + + // All unsigned types + check_from_to(EXESS_ULONG, "4294967295", EXESS_UINT, "4294967295"); + check_failure(EXESS_ULONG, "4294967296", EXESS_UINT, EXESS_OUT_OF_RANGE); + check_from_to(EXESS_ULONG, "65535", EXESS_USHORT, "65535"); + check_failure(EXESS_ULONG, "65536", EXESS_USHORT, EXESS_OUT_OF_RANGE); + check_from_to(EXESS_ULONG, "255", EXESS_UBYTE, "255"); + check_failure(EXESS_ULONG, "256", EXESS_UBYTE, EXESS_OUT_OF_RANGE); + + // Small enough value Any value to/from integer + check_from_to(EXESS_ULONG, "0", EXESS_INTEGER, "0"); + check_failure( + EXESS_ULONG, "9223372036854775808", EXESS_INTEGER, EXESS_OUT_OF_RANGE); + + // Only zero to/from nonPositiveInteger + check_from_to(EXESS_ULONG, "0", EXESS_NON_POSITIVE_INTEGER, "0"); + check_failure( + EXESS_ULONG, "1", EXESS_NON_POSITIVE_INTEGER, EXESS_OUT_OF_RANGE); + + // Not convertible to/from negativeInteger + check_failure(EXESS_ULONG, "0", EXESS_NEGATIVE_INTEGER, EXESS_OUT_OF_RANGE); + check_failure(EXESS_ULONG, "1", EXESS_NEGATIVE_INTEGER, EXESS_OUT_OF_RANGE); + + // Any value to/from nonNegativeInteger + check_from_to(EXESS_ULONG, "0", EXESS_NON_NEGATIVE_INTEGER, "0"); + check_from_to( + EXESS_ULONG, max_ulong_str, EXESS_NON_NEGATIVE_INTEGER, max_ulong_str); + + // Positive values to/from positiveInteger + check_failure(EXESS_ULONG, "0", EXESS_POSITIVE_INTEGER, EXESS_OUT_OF_RANGE); + check_from_to(EXESS_ULONG, "1", EXESS_POSITIVE_INTEGER, "1"); + + // Float + check_failure(EXESS_FLOAT, "-1", EXESS_ULONG, EXESS_OUT_OF_RANGE); + check_failure(EXESS_FLOAT, "1.5", EXESS_ULONG, EXESS_WOULD_ROUND); + check_from_to(EXESS_ULONG, "0", EXESS_FLOAT, "0.0E0"); + check_from_to(EXESS_ULONG, "16777215", EXESS_FLOAT, "1.6777215E7"); + check_failure(EXESS_ULONG, "16777216", EXESS_FLOAT, EXESS_OUT_OF_RANGE); + check_failure(EXESS_FLOAT, "16777216", EXESS_ULONG, EXESS_OUT_OF_RANGE); + check_one_way(EXESS_FLOAT, "1.0", EXESS_LOSSLESS, EXESS_ULONG, "1"); + check_one_way(EXESS_FLOAT, "1.5", EXESS_ROUND, EXESS_ULONG, "2"); + check_one_way(EXESS_FLOAT, "2.5", EXESS_ROUND, EXESS_ULONG, "2"); + check_one_way(EXESS_FLOAT, "3.5", EXESS_ROUND, EXESS_ULONG, "4"); + + // Double + check_failure(EXESS_DOUBLE, "-1", EXESS_ULONG, EXESS_OUT_OF_RANGE); + check_failure(EXESS_DOUBLE, "1.5", EXESS_ULONG, EXESS_WOULD_ROUND); + check_from_to(EXESS_ULONG, "0", EXESS_DOUBLE, "0.0E0"); + check_from_to( + EXESS_ULONG, "9007199254740991", EXESS_DOUBLE, "9.007199254740991E15"); + check_failure( + EXESS_ULONG, "9007199254740992", EXESS_DOUBLE, EXESS_OUT_OF_RANGE); + check_failure( + EXESS_DOUBLE, "9007199254740992", EXESS_ULONG, EXESS_OUT_OF_RANGE); + check_one_way(EXESS_DOUBLE, "1.0", EXESS_LOSSLESS, EXESS_ULONG, "1"); + check_one_way(EXESS_DOUBLE, "1.5", EXESS_ROUND, EXESS_ULONG, "2"); + check_one_way(EXESS_DOUBLE, "2.5", EXESS_ROUND, EXESS_ULONG, "2"); + check_one_way(EXESS_DOUBLE, "3.5", EXESS_ROUND, EXESS_ULONG, "4"); +} + +static void +test_large_integers(void) +{ + check_failure(EXESS_TIME, "00:00:00", EXESS_INTEGER, EXESS_UNSUPPORTED); + check_failure( + EXESS_TIME, "00:00:00", EXESS_NON_POSITIVE_INTEGER, EXESS_UNSUPPORTED); + check_failure( + EXESS_TIME, "00:00:00", EXESS_NEGATIVE_INTEGER, EXESS_UNSUPPORTED); + check_failure( + EXESS_TIME, "00:00:00", EXESS_NON_NEGATIVE_INTEGER, EXESS_UNSUPPORTED); + check_failure( + EXESS_TIME, "00:00:00", EXESS_POSITIVE_INTEGER, EXESS_UNSUPPORTED); +} + +static void +test_coerce(void) +{ + check_one_way(EXESS_DOUBLE, + "1.0000000000001", + EXESS_REDUCE_PRECISION, + EXESS_FLOAT, + "1.0E0"); + + check_failure( + EXESS_DOUBLE, "1.0000000000001", EXESS_FLOAT, EXESS_WOULD_REDUCE_PRECISION); + + check_one_way(EXESS_FLOAT, "1.5", EXESS_LOSSLESS, EXESS_DOUBLE, "1.5E0"); + + /* check_failure( */ + /* EXESS_LONG, "9007199254740993", EXESS_DOUBLE, EXESS_OUT_OF_RANGE); */ + + /* check_failure(EXESS_FLOAT, "1.0", EXESS_LONG, EXESS_SUCCESS); */ + /* check_from_to(EXESS_BYTE, "-128", EXESS_LONG, "-128"); */ + + // DateTime +} + +static void +test_date_time(void) +{ + check_failure( + EXESS_DATETIME, "2001-02-03T04:05:06", EXESS_TIME, EXESS_WOULD_TRUNCATE); + + check_one_way(EXESS_DATETIME, + "2001-02-03T04:05:06", + EXESS_TRUNCATE, + EXESS_TIME, + "04:05:06"); + + check_one_way(EXESS_DATETIME, + "2001-02-03T04:05:06Z", + EXESS_TRUNCATE, + EXESS_TIME, + "04:05:06Z"); + + check_failure( + EXESS_DATETIME, "2001-02-03T04:05:06", EXESS_DATE, EXESS_WOULD_TRUNCATE); + + check_one_way(EXESS_DATETIME, + "2001-02-03T04:05:06", + EXESS_TRUNCATE, + EXESS_DATE, + "2001-02-03"); + + check_one_way(EXESS_DATETIME, + "2001-02-03T04:05:06Z", + EXESS_TRUNCATE, + EXESS_DATE, + "2001-02-03Z"); +} + +static void +test_number_to_time(void) +{ + check_failure(EXESS_BOOLEAN, "true", EXESS_DURATION, EXESS_UNSUPPORTED); + check_failure(EXESS_BOOLEAN, "true", EXESS_DATETIME, EXESS_UNSUPPORTED); + check_failure(EXESS_BOOLEAN, "true", EXESS_TIME, EXESS_UNSUPPORTED); + check_failure(EXESS_BOOLEAN, "true", EXESS_DATE, EXESS_UNSUPPORTED); + + check_failure(EXESS_LONG, "1", EXESS_DURATION, EXESS_UNSUPPORTED); + check_failure(EXESS_LONG, "1", EXESS_DATETIME, EXESS_UNSUPPORTED); + check_failure(EXESS_LONG, "1", EXESS_TIME, EXESS_UNSUPPORTED); + check_failure(EXESS_LONG, "1", EXESS_DATE, EXESS_UNSUPPORTED); + + check_failure(EXESS_INT, "1", EXESS_DURATION, EXESS_UNSUPPORTED); + check_failure(EXESS_INT, "1", EXESS_DATETIME, EXESS_UNSUPPORTED); + check_failure(EXESS_INT, "1", EXESS_TIME, EXESS_UNSUPPORTED); + check_failure(EXESS_INT, "1", EXESS_DATE, EXESS_UNSUPPORTED); + + check_failure(EXESS_SHORT, "1", EXESS_DURATION, EXESS_UNSUPPORTED); + check_failure(EXESS_SHORT, "1", EXESS_DATETIME, EXESS_UNSUPPORTED); + check_failure(EXESS_SHORT, "1", EXESS_TIME, EXESS_UNSUPPORTED); + check_failure(EXESS_SHORT, "1", EXESS_DATE, EXESS_UNSUPPORTED); + + check_failure(EXESS_BYTE, "1", EXESS_DURATION, EXESS_UNSUPPORTED); + check_failure(EXESS_BYTE, "1", EXESS_DATETIME, EXESS_UNSUPPORTED); + check_failure(EXESS_BYTE, "1", EXESS_TIME, EXESS_UNSUPPORTED); + check_failure(EXESS_BYTE, "1", EXESS_DATE, EXESS_UNSUPPORTED); + + check_failure(EXESS_ULONG, "1", EXESS_DURATION, EXESS_UNSUPPORTED); + check_failure(EXESS_ULONG, "1", EXESS_DATETIME, EXESS_UNSUPPORTED); + check_failure(EXESS_ULONG, "1", EXESS_TIME, EXESS_UNSUPPORTED); + check_failure(EXESS_ULONG, "1", EXESS_DATE, EXESS_UNSUPPORTED); + + check_failure(EXESS_UINT, "1", EXESS_DURATION, EXESS_UNSUPPORTED); + check_failure(EXESS_UINT, "1", EXESS_DATETIME, EXESS_UNSUPPORTED); + check_failure(EXESS_UINT, "1", EXESS_TIME, EXESS_UNSUPPORTED); + check_failure(EXESS_UINT, "1", EXESS_DATE, EXESS_UNSUPPORTED); + + check_failure(EXESS_USHORT, "1", EXESS_DURATION, EXESS_UNSUPPORTED); + check_failure(EXESS_USHORT, "1", EXESS_DATETIME, EXESS_UNSUPPORTED); + check_failure(EXESS_USHORT, "1", EXESS_TIME, EXESS_UNSUPPORTED); + check_failure(EXESS_USHORT, "1", EXESS_DATE, EXESS_UNSUPPORTED); + + check_failure(EXESS_UBYTE, "1", EXESS_DURATION, EXESS_UNSUPPORTED); + check_failure(EXESS_UBYTE, "1", EXESS_DATETIME, EXESS_UNSUPPORTED); + check_failure(EXESS_UBYTE, "1", EXESS_TIME, EXESS_UNSUPPORTED); + check_failure(EXESS_UBYTE, "1", EXESS_DATE, EXESS_UNSUPPORTED); +} + +static void +test_time_to_number(void) +{ + static const char* const duration_str = "P1Y"; + static const char* const datetime_str = "2001-02-03T04:05:06"; + static const char* const time_str = "04:05:06"; + static const char* const date_str = "2001-02-03"; + + check_failure(EXESS_DURATION, duration_str, EXESS_BOOLEAN, EXESS_UNSUPPORTED); + check_failure(EXESS_DATETIME, datetime_str, EXESS_BOOLEAN, EXESS_UNSUPPORTED); + check_failure(EXESS_TIME, time_str, EXESS_BOOLEAN, EXESS_UNSUPPORTED); + check_failure(EXESS_DATE, date_str, EXESS_BOOLEAN, EXESS_UNSUPPORTED); + + check_failure(EXESS_DURATION, duration_str, EXESS_INT, EXESS_UNSUPPORTED); + check_failure(EXESS_DATETIME, datetime_str, EXESS_INT, EXESS_UNSUPPORTED); + check_failure(EXESS_TIME, time_str, EXESS_INT, EXESS_UNSUPPORTED); + check_failure(EXESS_DATE, date_str, EXESS_INT, EXESS_UNSUPPORTED); + + check_failure(EXESS_DURATION, duration_str, EXESS_SHORT, EXESS_UNSUPPORTED); + check_failure(EXESS_DATETIME, datetime_str, EXESS_SHORT, EXESS_UNSUPPORTED); + check_failure(EXESS_TIME, time_str, EXESS_SHORT, EXESS_UNSUPPORTED); + check_failure(EXESS_DATE, date_str, EXESS_SHORT, EXESS_UNSUPPORTED); + + check_failure(EXESS_DURATION, duration_str, EXESS_BYTE, EXESS_UNSUPPORTED); + check_failure(EXESS_DATETIME, datetime_str, EXESS_BYTE, EXESS_UNSUPPORTED); + check_failure(EXESS_TIME, time_str, EXESS_BYTE, EXESS_UNSUPPORTED); + check_failure(EXESS_DATE, date_str, EXESS_BYTE, EXESS_UNSUPPORTED); + + check_failure(EXESS_DURATION, duration_str, EXESS_ULONG, EXESS_UNSUPPORTED); + check_failure(EXESS_DATETIME, datetime_str, EXESS_ULONG, EXESS_UNSUPPORTED); + check_failure(EXESS_TIME, time_str, EXESS_ULONG, EXESS_UNSUPPORTED); + check_failure(EXESS_DATE, date_str, EXESS_ULONG, EXESS_UNSUPPORTED); + + check_failure(EXESS_DURATION, duration_str, EXESS_UINT, EXESS_UNSUPPORTED); + check_failure(EXESS_DATETIME, datetime_str, EXESS_UINT, EXESS_UNSUPPORTED); + check_failure(EXESS_TIME, time_str, EXESS_UINT, EXESS_UNSUPPORTED); + check_failure(EXESS_DATE, date_str, EXESS_UINT, EXESS_UNSUPPORTED); + + check_failure(EXESS_DURATION, duration_str, EXESS_USHORT, EXESS_UNSUPPORTED); + check_failure(EXESS_DATETIME, datetime_str, EXESS_USHORT, EXESS_UNSUPPORTED); + check_failure(EXESS_TIME, time_str, EXESS_USHORT, EXESS_UNSUPPORTED); + check_failure(EXESS_DATE, date_str, EXESS_USHORT, EXESS_UNSUPPORTED); + + check_failure(EXESS_DURATION, duration_str, EXESS_UBYTE, EXESS_UNSUPPORTED); + check_failure(EXESS_DATETIME, datetime_str, EXESS_UBYTE, EXESS_UNSUPPORTED); + check_failure(EXESS_TIME, time_str, EXESS_UBYTE, EXESS_UNSUPPORTED); + check_failure(EXESS_DATE, date_str, EXESS_UBYTE, EXESS_UNSUPPORTED); +} + +static void +test_binary(void) +{ + check_from_to(EXESS_HEX, "666F6F", EXESS_BASE64, "Zm9v"); + + check_failure(EXESS_LONG, "-2147483649", EXESS_HEX, EXESS_UNSUPPORTED); + check_failure(EXESS_LONG, "-2147483649", EXESS_BASE64, EXESS_UNSUPPORTED); + + /* check_from_to(EXESS_BASE64, "Zm9v", EXESS_HEX, "666F6F"); */ + + /* ////////// */ + + /* check_one_way(EXESS_LONG, "-1", EXESS_TRUNCATE, EXESS_BOOLEAN, "true"); */ + + /* // All smaller integer types */ + /* check_from_to(EXESS_LONG, "-2147483648", EXESS_INT, "-2147483648"); */ + /* check_failure(EXESS_LONG, "-2147483649", EXESS_INT, EXESS_OUT_OF_RANGE); */ +} + +int +main(void) +{ + test_unknown(); + test_boolean(); + test_long(); + test_ulong(); + test_large_integers(); + test_coerce(); + test_date_time(); + test_number_to_time(); + test_time_to_number(); + test_binary(); + + return 0; +} diff --git a/subprojects/exess/test/test_datatype.c b/subprojects/exess/test/test_datatype.c new file mode 100644 index 00000000..7ad3fb5e --- /dev/null +++ b/subprojects/exess/test/test_datatype.c @@ -0,0 +1,81 @@ +/* + Copyright 2011-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#undef NDEBUG + +#include "exess/exess.h" + +#include + +static void +test_datatype_uris(void) +{ + assert(!exess_datatype_uri(EXESS_NOTHING)); + assert(!exess_datatype_uri((ExessDatatype)(EXESS_BASE64 + 1))); + + for (unsigned i = 1; i <= EXESS_BASE64; ++i) { + const char* const uri = exess_datatype_uri((ExessDatatype)i); + + assert(uri); + assert(exess_datatype_from_uri(uri) == i); + } + + assert(exess_datatype_from_uri(EXESS_XSD_URI) == EXESS_NOTHING); + assert(exess_datatype_from_uri(EXESS_XSD_URI "unknown") == EXESS_NOTHING); + assert(exess_datatype_from_uri("garbage") == EXESS_NOTHING); + assert(exess_datatype_from_uri("http://example.org/very/long/unknown/uri") == + EXESS_NOTHING); +} + +static void +test_datatype_is_bounded(void) +{ + assert(!exess_datatype_is_bounded(EXESS_NOTHING)); + assert(exess_datatype_is_bounded(EXESS_BOOLEAN)); + assert(!exess_datatype_is_bounded(EXESS_DECIMAL)); + assert(exess_datatype_is_bounded(EXESS_DOUBLE)); + assert(exess_datatype_is_bounded(EXESS_FLOAT)); + assert(!exess_datatype_is_bounded(EXESS_INTEGER)); + assert(!exess_datatype_is_bounded(EXESS_NON_POSITIVE_INTEGER)); + assert(!exess_datatype_is_bounded(EXESS_NEGATIVE_INTEGER)); + assert(exess_datatype_is_bounded(EXESS_LONG)); + assert(exess_datatype_is_bounded(EXESS_INT)); + assert(exess_datatype_is_bounded(EXESS_SHORT)); + assert(exess_datatype_is_bounded(EXESS_BYTE)); + assert(!exess_datatype_is_bounded(EXESS_NON_NEGATIVE_INTEGER)); + assert(exess_datatype_is_bounded(EXESS_ULONG)); + assert(exess_datatype_is_bounded(EXESS_UINT)); + assert(exess_datatype_is_bounded(EXESS_USHORT)); + assert(exess_datatype_is_bounded(EXESS_UBYTE)); + assert(!exess_datatype_is_bounded(EXESS_POSITIVE_INTEGER)); + assert(exess_datatype_is_bounded(EXESS_DURATION)); + assert(exess_datatype_is_bounded(EXESS_DATETIME)); + assert(exess_datatype_is_bounded(EXESS_TIME)); + assert(exess_datatype_is_bounded(EXESS_DATE)); + assert(!exess_datatype_is_bounded(EXESS_HEX)); + assert(!exess_datatype_is_bounded(EXESS_BASE64)); +} + +int +main(int argc, char** argv) +{ + (void)argv; + + test_datatype_uris(); + test_datatype_is_bounded(); + + return argc == 1 ? 0 : 1; +} diff --git a/subprojects/exess/test/test_date.c b/subprojects/exess/test/test_date.c new file mode 100644 index 00000000..0ac8129a --- /dev/null +++ b/subprojects/exess/test/test_date.c @@ -0,0 +1,257 @@ +/* + Copyright 2011-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#undef NDEBUG + +#include "date_utils.h" +#include "int_test_data.h" +#include "macros.h" +#include "num_test_utils.h" +#include "time_test_utils.h" + +#include "exess/exess.h" + +#include +#include +#include +#include +#include + +static const ExessDate nozone = {2001, 1, 2, {EXESS_LOCAL}}; +static const ExessDate utc = {2002, 2, 3, {0}}; +static const ExessDate zoned = {2003, 3, 4, INIT_ZONE(11, 30)}; +static const ExessDate early = {99, 3, 4, INIT_ZONE(11, 30)}; +static const ExessDate future = {12345, 3, 4, INIT_ZONE(11, 30)}; +static const ExessDate lowest = {INT16_MIN, 1, 1, INIT_ZONE(-14, 0)}; +static const ExessDate highest = {INT16_MAX, 1, 1, INIT_ZONE(14, 0)}; +static const ExessDate garbage1 = {2004, 0, 1, INIT_ZONE(11, 30)}; +static const ExessDate garbage2 = {2005, 13, 1, INIT_ZONE(11, 30)}; +static const ExessDate garbage3 = {2006, 1, 0, INIT_ZONE(11, 30)}; +static const ExessDate garbage4 = {2006, 1, 32, INIT_ZONE(11, 30)}; + +static void +check_read(const char* const string, + const ExessStatus expected_status, + const int64_t expected_year, + const uint8_t expected_month, + const uint8_t expected_day, + const int8_t expected_tz_hour, + const int8_t expected_tz_minute, + const bool expected_tz_is_present, + const size_t expected_count) +{ + ExessDate value = {0, 0, 0, {EXESS_LOCAL}}; + + const ExessResult r = exess_read_date(&value, string); + assert(r.status == expected_status); + assert(r.count == expected_count); + assert(value.year == expected_year); + assert(value.month == expected_month); + assert(value.day == expected_day); + assert((!expected_tz_is_present && value.zone.quarter_hours == EXESS_LOCAL) || + value.zone.quarter_hours == + 4 * expected_tz_hour + expected_tz_minute / 15); +} + +static void +test_read_date(void) +{ + // No input + check_read("", EXESS_EXPECTED_DIGIT, 0, 0, 0, 0, 0, false, 0); + check_read(" \f\n\r\t\v", EXESS_EXPECTED_DIGIT, 0, 0, 0, 0, 0, false, 6); + + // Good values + check_read("2004-04-12", EXESS_SUCCESS, 2004, 4, 12, 0, 0, false, 10); + check_read("-0045-01-01", EXESS_SUCCESS, -45, 1, 1, 0, 0, false, 11); + check_read("12004-04-12", EXESS_SUCCESS, 12004, 4, 12, 0, 0, false, 11); + check_read("2004-04-12-05:00", EXESS_SUCCESS, 2004, 4, 12, -5, 0, true, 16); + check_read("2004-04-12Z", EXESS_SUCCESS, 2004, 4, 12, 0, 0, true, 11); + check_read("2001-10-26", EXESS_SUCCESS, 2001, 10, 26, 0, 0, false, 10); + check_read("2001-10-26+02:00", EXESS_SUCCESS, 2001, 10, 26, 2, 0, true, 16); + check_read("2001-10-26Z", EXESS_SUCCESS, 2001, 10, 26, 0, 0, true, 11); + check_read("2001-10-26+00:00", EXESS_SUCCESS, 2001, 10, 26, 0, 0, true, 16); + check_read("-2001-10-26", EXESS_SUCCESS, -2001, 10, 26, 0, 0, false, 11); + check_read("-20000-04-01", EXESS_SUCCESS, -20000, 04, 01, 0, 0, false, 12); + + // Non-canonical + check_read("02004-04-12", EXESS_SUCCESS, 2004, 4, 12, 0, 0, false, 11); + check_read(" 02004-04-12 ", EXESS_SUCCESS, 2004, 4, 12, 0, 0, false, 12); + + // Good common year values + check_read("1900-02-28", EXESS_SUCCESS, 1900, 2, 28, 0, 0, false, 10); + + // Good leap year values + check_read("2000-02-29", EXESS_SUCCESS, 2000, 2, 29, 0, 0, false, 10); + check_read("2004-02-29", EXESS_SUCCESS, 2004, 2, 29, 0, 0, false, 10); + + // Longest possible string + check_read("-32768-01-01-14:00", + EXESS_SUCCESS, + -32768, + 1, + 1, + -14, + 0, + true, + EXESS_MAX_DATE_LENGTH); + + // Limits + check_read("-32768-01-01", EXESS_SUCCESS, -32768, 1, 1, 0, 0, false, 12); + check_read("32767-01-01", EXESS_SUCCESS, 32767, 1, 1, 0, 0, false, 11); + + // Out of range years + check_read("-32769-01-01", EXESS_OUT_OF_RANGE, 0, 0, 0, 0, 0, false, 6); + check_read("32768-01-01", EXESS_OUT_OF_RANGE, 0, 0, 0, 0, 0, false, 5); + + // Out of range months + check_read("2001-00-26", EXESS_OUT_OF_RANGE, 2001, 0, 0, 0, 0, false, 7); + check_read("2001-13-26", EXESS_OUT_OF_RANGE, 2001, 13, 0, 0, 0, false, 7); + check_read("2001-10-00", EXESS_OUT_OF_RANGE, 2001, 10, 0, 0, 0, false, 10); + check_read("2001-10-32", EXESS_OUT_OF_RANGE, 2001, 10, 32, 0, 0, false, 10); + + // Out of range days + check_read("2001-01-32", EXESS_OUT_OF_RANGE, 2001, 1, 32, 0, 0, false, 10); + check_read("2001-02-29", EXESS_OUT_OF_RANGE, 2001, 2, 29, 0, 0, false, 10); + check_read("2001-03-32", EXESS_OUT_OF_RANGE, 2001, 3, 32, 0, 0, false, 10); + check_read("2001-04-31", EXESS_OUT_OF_RANGE, 2001, 4, 31, 0, 0, false, 10); + check_read("2001-05-32", EXESS_OUT_OF_RANGE, 2001, 5, 32, 0, 0, false, 10); + check_read("2001-06-31", EXESS_OUT_OF_RANGE, 2001, 6, 31, 0, 0, false, 10); + check_read("2001-07-32", EXESS_OUT_OF_RANGE, 2001, 7, 32, 0, 0, false, 10); + check_read("2001-08-32", EXESS_OUT_OF_RANGE, 2001, 8, 32, 0, 0, false, 10); + check_read("2001-09-31", EXESS_OUT_OF_RANGE, 2001, 9, 31, 0, 0, false, 10); + check_read("2001-10-32", EXESS_OUT_OF_RANGE, 2001, 10, 32, 0, 0, false, 10); + check_read("2001-11-31", EXESS_OUT_OF_RANGE, 2001, 11, 31, 0, 0, false, 10); + check_read("2001-12-32", EXESS_OUT_OF_RANGE, 2001, 12, 32, 0, 0, false, 10); + + // Garbage + check_read("f", EXESS_EXPECTED_DIGIT, 0, 0, 0, 0, 0, false, 0); + check_read("99-04-12", EXESS_EXPECTED_DIGIT, 99, 0, 0, 0, 0, false, 2); + check_read("2004-4-2", EXESS_EXPECTED_DIGIT, 2004, 4, 0, 0, 0, false, 6); + check_read("2004/04/02", EXESS_EXPECTED_DASH, 2004, 0, 0, 0, 0, false, 4); + check_read("04-12-2004", EXESS_EXPECTED_DIGIT, 4, 0, 0, 0, 0, false, 2); + check_read("2001-10", EXESS_EXPECTED_DASH, 2001, 10, 0, 0, 0, false, 7); + check_read("01-10-26", EXESS_EXPECTED_DIGIT, 1, 0, 0, 0, 0, false, 2); + check_read("2004-04-12A", EXESS_EXPECTED_SIGN, 2004, 4, 12, 0, 0, false, 10); +} + +static void +check_write(const ExessDate value, + const ExessStatus expected_status, + const size_t buf_size, + const char* const expected_string) +{ + char buf[EXESS_MAX_DATE_LENGTH + 1] = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}; + + assert(buf_size <= sizeof(buf)); + + const ExessResult r = exess_write_date(value, buf_size, buf); + assert(r.status == expected_status); + assert(r.count == strlen(buf)); + assert(!strcmp(buf, expected_string)); + assert(r.status || exess_write_date(value, 0, NULL).count == r.count); +} + +static void +test_write_date(void) +{ + check_write(nozone, EXESS_SUCCESS, 11, "2001-01-02"); + check_write(utc, EXESS_SUCCESS, 12, "2002-02-03Z"); + check_write(zoned, EXESS_SUCCESS, 17, "2003-03-04+11:30"); + check_write(early, EXESS_SUCCESS, 17, "0099-03-04+11:30"); + check_write(future, EXESS_SUCCESS, 18, "12345-03-04+11:30"); + check_write(lowest, EXESS_SUCCESS, 19, "-32768-01-01-14:00"); + check_write(highest, EXESS_SUCCESS, 18, "32767-01-01+14:00"); + + check_write(garbage1, EXESS_BAD_VALUE, 14, ""); + check_write(garbage2, EXESS_BAD_VALUE, 14, ""); + check_write(garbage3, EXESS_BAD_VALUE, 14, ""); + check_write(garbage4, EXESS_BAD_VALUE, 14, ""); + + check_write(nozone, EXESS_NO_SPACE, 10, ""); + check_write(future, EXESS_NO_SPACE, 5, ""); + check_write(lowest, EXESS_NO_SPACE, 5, ""); + check_write(highest, EXESS_NO_SPACE, 5, ""); + check_write(utc, EXESS_NO_SPACE, 11, ""); + check_write(zoned, EXESS_NO_SPACE, 16, ""); + + // Check that nothing is written when there isn't enough space + char c = 42; + const ExessResult r = exess_write_date(nozone, 0, &c); + assert(c == 42); + assert(r.status == EXESS_NO_SPACE); + assert(r.count == 0); +} + +static void +check_round_trip(const ExessDate value) +{ + ExessDate parsed_value = {0, 0, 0, {0}}; + char buf[EXESS_MAX_DATE_LENGTH + 1] = {0}; + + assert(!exess_write_date(value, sizeof(buf), buf).status); + assert(!exess_read_date(&parsed_value, buf).status); + assert(parsed_value.year == value.year); + assert(parsed_value.month == value.month); + assert(parsed_value.day == value.day); + assert(parsed_value.zone.quarter_hours == value.zone.quarter_hours); +} + +static void +test_round_trip(const ExessNumTestOptions opts) +{ + fprintf(stderr, "Testing xsd:gDate randomly with seed %u\n", opts.seed); + + const uint64_t n_tests = MAX(256, opts.n_tests / 16); + + uint32_t rng = opts.seed; + for (uint64_t i = 0; i < n_tests; ++i) { + rng = lcg32(rng); + + const int16_t year = (int16_t)(rng % UINT16_MAX); + for (uint8_t month = 1; month < 13; ++month) { + for (uint8_t day = 1; day <= days_in_month(year, month); ++day) { + const ExessDate no_zone = {year, month, day, {EXESS_LOCAL}}; + const ExessDate lowest_zone = {year, month, day, {4 * -14 + 0}}; + const ExessDate highest_zone = {year, month, day, {4 * 14}}; + + check_round_trip(no_zone); + check_round_trip(lowest_zone); + check_round_trip(highest_zone); + + const ExessDate value = {year, month, day, random_timezone(&rng)}; + check_round_trip(value); + } + } + + print_num_test_progress(i, n_tests); + } +} + +int +main(int argc, char** argv) +{ + const ExessNumTestOptions opts = parse_num_test_options(argc, argv); + if (opts.error) { + return 1; + } + + test_read_date(); + test_write_date(); + test_round_trip(opts); + + return 0; +} diff --git a/subprojects/exess/test/test_datetime.c b/subprojects/exess/test/test_datetime.c new file mode 100644 index 00000000..d6d2b378 --- /dev/null +++ b/subprojects/exess/test/test_datetime.c @@ -0,0 +1,459 @@ +/* + Copyright 2011-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#undef NDEBUG + +#include "exess/exess.h" + +#include +#include +#include +#include + +static const ExessDateTime local = {2001, 2, 3, false, 4, 5, 6, 0}; +static const ExessDateTime utc = {2001, 2, 3, true, 4, 5, 6, 0}; +static const ExessDateTime lowest = {INT16_MIN, 1, 1, false, 0, 0, 0, 0}; +static const ExessDateTime highest = {INT16_MAX, 12, 31, false, 24, 0, 0, 0}; +static const ExessDateTime utc_min = {INT16_MIN, 1, 1, true, 0, 0, 0, 0}; +static const ExessDateTime utc_max = {INT16_MAX, 12, 31, true, 24, 0, 0, 0}; +static const ExessDateTime nano = {2001, 1, 1, false, 0, 0, 0, 1}; +static const ExessDateTime garbage1 = {2004, 0, 1, false, 12, 0, 0, 0}; +static const ExessDateTime garbage2 = {2005, 13, 1, false, 12, 0, 0, 0}; +static const ExessDateTime garbage3 = {2006, 1, 0, false, 12, 0, 0, 0}; +static const ExessDateTime garbage4 = {2006, 1, 32, false, 12, 0, 0, 0}; +static const ExessDateTime garbage5 = {2001, 2, 3, false, 0, 0, 0, 1000000000}; +static const ExessDateTime garbage6 = {2001, 2, 3, false, 0, 0, 60, 0}; +static const ExessDateTime garbage7 = {2001, 2, 3, false, 0, 60, 0, 0}; +static const ExessDateTime garbage8 = {2001, 2, 3, false, 24, 0, 0, 1}; +static const ExessDateTime garbage9 = {2001, 2, 3, false, 24, 0, 1, 0}; +static const ExessDateTime garbage10 = {2001, 2, 3, false, 24, 1, 0, 0}; +static const ExessDateTime garbage11 = {2001, 2, 3, false, 25, 0, 0, 0}; + +static void +check_add(const char* const datetime_string, + const char* const duration_string, + const char* const result_string) +{ + ExessDateTime datetime = {0, 0u, 0u, false, 0u, 0u, 0u, 0u}; + ExessDuration duration = {0u, 0u, 0u}; + + ExessResult r = exess_read_datetime(&datetime, datetime_string); + assert(!r.status); + + r = exess_read_duration(&duration, duration_string); + assert(!r.status); + + const ExessDateTime result = exess_add_datetime_duration(datetime, duration); + char buf[28] = {0}; + + r = exess_write_datetime(result, sizeof(buf), buf); + assert(!r.status); + assert(!strcmp(buf, result_string)); +} + +static void +check_is_underflow(const ExessDateTime datetime, const bool is_utc) +{ + assert(datetime.year == INT16_MIN); + assert(datetime.month == 0); + assert(datetime.day == 0); + assert(datetime.is_utc == is_utc); + assert(datetime.hour == 0); + assert(datetime.minute == 0); + assert(datetime.second == 0); + assert(datetime.nanosecond == 0); +} + +static void +check_is_overflow(const ExessDateTime datetime, const bool is_utc) +{ + assert(datetime.year == INT16_MAX); + assert(datetime.month == UINT8_MAX); + assert(datetime.day == UINT8_MAX); + assert(datetime.is_utc == is_utc); + assert(datetime.hour == UINT8_MAX); + assert(datetime.minute == UINT8_MAX); + assert(datetime.second == UINT8_MAX); + assert(datetime.nanosecond == UINT32_MAX); +} + +static void +test_add(void) +{ + // Simple cases + check_add("2001-01-01T00:00:00", "PT1.5S", "2001-01-01T00:00:01.5"); + check_add("2001-01-01T00:00:00", "PT1M", "2001-01-01T00:01:00"); + check_add("2001-01-01T00:00:00", "PT1H", "2001-01-01T01:00:00"); + check_add("2001-01-01T00:00:00", "P1D", "2001-01-02T00:00:00"); + check_add("2001-01-01T00:00:00", "P1M", "2001-02-01T00:00:00"); + check_add("2001-01-01T00:00:00", "P1Y", "2002-01-01T00:00:00"); + check_add("2001-02-02T02:02:02", "-PT1.5S", "2001-02-02T02:02:00.5"); + check_add("2001-02-02T02:02:02", "-PT1M", "2001-02-02T02:01:02"); + check_add("2001-02-02T02:02:02", "-PT1H", "2001-02-02T01:02:02"); + check_add("2001-02-02T02:02:02", "-P1D", "2001-02-01T02:02:02"); + check_add("2001-02-02T02:02:02", "-P1M", "2001-01-02T02:02:02"); + check_add("2001-02-02T02:02:02", "-P1Y", "2000-02-02T02:02:02"); + + // Positive carrying + check_add("2001-01-01T00:00:59", "PT1S", "2001-01-01T00:01:00"); + check_add("2001-01-01T00:59:00", "PT1M", "2001-01-01T01:00:00"); + check_add("2001-01-01T23:00:00", "PT1H", "2001-01-02T00:00:00"); + check_add("2001-01-31T00:00:00", "P1D", "2001-02-01T00:00:00"); + check_add("2001-12-01T00:00:00", "P1M", "2002-01-01T00:00:00"); + + // Negative carrying + check_add("2001-01-01T00:01:00", "-PT1S", "2001-01-01T00:00:59"); + check_add("2001-02-01T01:00:00", "-PT1M", "2001-02-01T00:59:00"); + check_add("2001-02-02T00:00:00", "-PT1H", "2001-02-01T23:00:00"); + check_add("2001-02-01T00:00:00", "-P1D", "2001-01-31T00:00:00"); + check_add("2001-01-01T00:00:00", "-P1M", "2000-12-01T00:00:00"); + + // Underflow and overflow + + static const ExessDuration minus_month = {-1, 0, 0}; + static const ExessDuration minus_second = {0, -1, 0}; + static const ExessDuration minus_nanosecond = {0, 0, -1}; + static const ExessDuration plus_month = {1, 0, 0}; + static const ExessDuration plus_second = {0, 1, 0}; + static const ExessDuration plus_nanosecond = {0, 0, 1}; + + check_is_underflow(exess_add_datetime_duration(lowest, minus_month), false); + check_is_underflow(exess_add_datetime_duration(lowest, minus_second), false); + check_is_underflow(exess_add_datetime_duration(lowest, minus_nanosecond), + false); + + check_is_underflow(exess_add_datetime_duration(utc_min, minus_month), true); + check_is_underflow(exess_add_datetime_duration(utc_min, minus_second), true); + check_is_underflow(exess_add_datetime_duration(utc_min, minus_nanosecond), + true); + + check_is_overflow(exess_add_datetime_duration(highest, plus_month), false); + check_is_overflow(exess_add_datetime_duration(highest, plus_second), false); + check_is_overflow(exess_add_datetime_duration(highest, plus_nanosecond), + false); + + check_is_overflow(exess_add_datetime_duration(utc_max, plus_month), true); + check_is_overflow(exess_add_datetime_duration(utc_max, plus_second), true); + check_is_overflow(exess_add_datetime_duration(utc_max, plus_nanosecond), + true); +} + +static void +check_read(const char* const string, + const ExessStatus expected_status, + const size_t expected_count, + const int64_t expected_year, + const uint8_t expected_month, + const uint8_t expected_day, + const uint8_t expected_hour, + const uint8_t expected_minute, + const uint8_t expected_second, + const uint32_t expected_nanosecond, + const bool expected_is_utc) +{ + ExessDateTime value = {0, 0, 0, false, 0, 0, 0, 0}; + + const ExessResult r = exess_read_datetime(&value, string); + assert(r.status == expected_status); + assert(r.count == expected_count); + assert(value.year == expected_year); + assert(value.month == expected_month); + assert(value.day == expected_day); + assert(value.hour == expected_hour); + assert(value.minute == expected_minute); + assert(value.second == expected_second); + assert(value.nanosecond == expected_nanosecond); + assert(value.is_utc == expected_is_utc); +} + +static void +test_read_datetime(void) +{ + // Simple values + + check_read( + "2001-02-03T04:05:06", EXESS_SUCCESS, 19, 2001, 2, 3, 4, 5, 6, 0, false); + + check_read( + "2001-02-03T04:05:06Z", EXESS_SUCCESS, 20, 2001, 2, 3, 4, 5, 6, 0, true); + + check_read("2004-04-12T13:20:15.5", + EXESS_SUCCESS, + 21, + 2004, + 4, + 12, + 13, + 20, + 15, + 500000000, + false); + + check_read("-32768-01-01T00:00:00.000000001Z", + EXESS_SUCCESS, + EXESS_MAX_DATETIME_LENGTH, + -32768, + 1, + 1, + 0, + 0, + 0, + 1, + true); + + // Simple timezones + + check_read("2001-02-03T04:05:06-00:30", + EXESS_SUCCESS, + 25, + 2001, + 2, + 3, + 4, + 35, + 6, + 0, + true); + + check_read("2001-02-03T04:05:06-01:00", + EXESS_SUCCESS, + 25, + 2001, + 2, + 3, + 5, + 5, + 6, + 0, + true); + + check_read("2001-02-03T04:05:06+00:30", + EXESS_SUCCESS, + 25, + 2001, + 2, + 3, + 3, + 35, + 6, + 0, + true); + + check_read("2001-02-03T04:05:06+01:00", + EXESS_SUCCESS, + 25, + 2001, + 2, + 3, + 3, + 5, + 6, + 0, + true); + + // Positive timezone carry + + // Minute => hour + check_read("2001-02-03T04:46:00-00:15", + EXESS_SUCCESS, + 25, + 2001, + 2, + 3, + 5, + 1, + 0, + 0, + true); + + // Minute => hour => day + check_read("2001-02-03T23:46:00-00:15", + EXESS_SUCCESS, + 25, + 2001, + 2, + 4, + 0, + 1, + 0, + 0, + true); + + // Minute => hour => day => month + check_read("2001-02-28T23:46:00-00:15", + EXESS_SUCCESS, + 25, + 2001, + 3, + 1, + 0, + 1, + 0, + 0, + true); + + // Minute => hour => day => month => year + check_read("2001-12-31T23:46:00-00:15", + EXESS_SUCCESS, + 25, + 2002, + 1, + 1, + 0, + 1, + 0, + 0, + true); + + // Negative timezone carry + + // Minute => hour + check_read("2001-02-03T04:14:00+00:15", + EXESS_SUCCESS, + 25, + 2001, + 2, + 3, + 3, + 59, + 0, + 0, + true); + + // Minute => hour => day + check_read("2001-02-03T00:14:00+00:15", + EXESS_SUCCESS, + 25, + 2001, + 2, + 2, + 23, + 59, + 0, + 0, + true); + + // Minute => hour => day => month + check_read("2001-02-01T00:14:00+00:15", + EXESS_SUCCESS, + 25, + 2001, + 1, + 31, + 23, + 59, + 0, + 0, + true); + + // Garbage + + check_read( + "2004-04-12T13:00", EXESS_EXPECTED_COLON, 16, 0, 0, 0, 0, 0, 0, 0, false); + + check_read("2004-04-1213:20:00", + EXESS_EXPECTED_TIME_SEP, + 10, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + false); + + check_read( + "99-04-12T13:00", EXESS_EXPECTED_DIGIT, 2, 0, 0, 0, 0, 0, 0, 0, false); + + check_read( + "2004-04-12", EXESS_EXPECTED_TIME_SEP, 10, 0, 0, 0, 0, 0, 0, 0, false); + + check_read("2004-04-12-05:00", + EXESS_EXPECTED_TIME_SEP, + 10, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + false); +} + +static void +check_write(const ExessDateTime value, + const ExessStatus expected_status, + const size_t buf_size, + const char* const expected_string) +{ + char buf[EXESS_MAX_DATETIME_LENGTH + 1] = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}; + + assert(buf_size <= sizeof(buf)); + + const ExessResult r = exess_write_datetime(value, buf_size, buf); + assert(r.status == expected_status); + assert(r.count == strlen(buf)); + assert(!strcmp(buf, expected_string)); + assert(r.status || exess_write_datetime(value, 0, NULL).count == r.count); +} + +static void +test_write_datetime(void) +{ + check_write(local, EXESS_SUCCESS, 20, "2001-02-03T04:05:06"); + check_write(utc, EXESS_SUCCESS, 21, "2001-02-03T04:05:06Z"); + check_write(lowest, EXESS_SUCCESS, 22, "-32768-01-01T00:00:00"); + check_write(highest, EXESS_SUCCESS, 21, "32767-12-31T24:00:00"); + check_write(nano, EXESS_SUCCESS, 30, "2001-01-01T00:00:00.000000001"); + + check_write(garbage1, EXESS_BAD_VALUE, 20, ""); + check_write(garbage2, EXESS_BAD_VALUE, 20, ""); + check_write(garbage3, EXESS_BAD_VALUE, 20, ""); + check_write(garbage4, EXESS_BAD_VALUE, 20, ""); + check_write(garbage5, EXESS_BAD_VALUE, 20, ""); + check_write(garbage6, EXESS_BAD_VALUE, 20, ""); + check_write(garbage7, EXESS_BAD_VALUE, 20, ""); + check_write(garbage8, EXESS_BAD_VALUE, 20, ""); + check_write(garbage9, EXESS_BAD_VALUE, 20, ""); + check_write(garbage10, EXESS_BAD_VALUE, 20, ""); + check_write(garbage11, EXESS_BAD_VALUE, 20, ""); + + check_write(lowest, EXESS_NO_SPACE, 12, ""); + check_write(lowest, EXESS_NO_SPACE, 17, ""); + check_write(lowest, EXESS_NO_SPACE, 18, ""); + check_write(lowest, EXESS_NO_SPACE, 21, ""); + + // Check that nothing is written when there isn't enough space + char c = 42; + const ExessResult r = exess_write_datetime(highest, 0, &c); + assert(c == 42); + assert(r.status == EXESS_NO_SPACE); + assert(r.count == 0); +} + +int +main(void) +{ + test_add(); + test_read_datetime(); + test_write_datetime(); + + return 0; +} diff --git a/subprojects/exess/test/test_decimal.c b/subprojects/exess/test/test_decimal.c new file mode 100644 index 00000000..2a49565d --- /dev/null +++ b/subprojects/exess/test/test_decimal.c @@ -0,0 +1,260 @@ +/* + Copyright 2011-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#undef NDEBUG + +#include "float_test_data.h" +#include "int_test_data.h" +#include "num_test_utils.h" +#include "string_utils.h" + +#include "exess/exess.h" + +#include +#include +#include +#include +#include +#include + +static void +check_read(const char* const string, + const ExessStatus expected_status, + const double expected_value, + const size_t expected_count) +{ + double value = 0; + const ExessResult r = exess_read_decimal(&value, string); + + assert(r.status == expected_status); + assert(r.count == expected_count); + assert(double_matches(value, expected_value)); +} + +static void +test_read_decimal(void) +{ + // No value + check_read("", EXESS_EXPECTED_DIGIT, (double)NAN, 0); + check_read(" \f\n\r\t\v", EXESS_EXPECTED_DIGIT, (double)NAN, 6); + + // Basic values + check_read("1.2", EXESS_SUCCESS, 1.2, 3); + check_read("0.01", EXESS_SUCCESS, 0.01, 4); + check_read("10.0", EXESS_SUCCESS, 10.0, 4); + + // Non-canonical form + check_read(" \f\n\r\t\v42.24 ", EXESS_SUCCESS, 42.24, 11); + check_read("12.", EXESS_SUCCESS, 12., 3); + check_read(".34", EXESS_SUCCESS, 0.34, 3); + check_read("+.56", EXESS_SUCCESS, 0.56, 4); + check_read("-.78", EXESS_SUCCESS, -0.78, 4); + + // Limits + check_read( + "0." + "00000000000000000000000000000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000022250738585072014", + EXESS_SUCCESS, + DBL_MIN, + 326); + + check_read("1797693134862315700000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000000.0", + EXESS_SUCCESS, + DBL_MAX, + 311); + + // Superfluous digits + check_read("12345678901234567890", EXESS_SUCCESS, 12345678901234568000.0, 20); + check_read("1.2345678901234567890", EXESS_SUCCESS, 1.2345678901234568, 21); + + // Special values + check_read("-0.0E0", EXESS_EXPECTED_END, -0.0, 4); + check_read("0.0E0", EXESS_EXPECTED_END, 0.0, 3); + check_read("+0.0E0", EXESS_EXPECTED_END, 0.0, 4); + + // No exponent + check_read("1", EXESS_SUCCESS, 1.0, 1); + check_read("2.3", EXESS_SUCCESS, 2.3, 3); + check_read("-4.5", EXESS_SUCCESS, -4.5, 4); + + // Garbage + check_read("NaN", EXESS_EXPECTED_DIGIT, (double)NAN, 0); + check_read("INF", EXESS_EXPECTED_DIGIT, (double)NAN, 0); + check_read("-INF", EXESS_EXPECTED_DIGIT, (double)NAN, 1); + check_read("true", EXESS_EXPECTED_DIGIT, (double)NAN, 0); + check_read("+true", EXESS_EXPECTED_DIGIT, (double)NAN, 1); + check_read("-false", EXESS_EXPECTED_DIGIT, (double)NAN, 1); +} + +static void +test_decimal_string_length(void) +{ + // Basic values + assert(exess_write_decimal(-1.0, 0, NULL).count == 4); + assert(exess_write_decimal(-0.0, 0, NULL).count == 4); + assert(exess_write_decimal(0.0, 0, NULL).count == 3); + assert(exess_write_decimal(1.0, 0, NULL).count == 3); + + // Limits + assert(exess_write_decimal(DBL_MIN, 0, NULL).count == 326); + assert(exess_write_decimal(DBL_MAX, 0, NULL).count == 311); + + // Special values + assert(exess_write_decimal((double)NAN, 0, NULL).count == 0); + assert(exess_write_decimal(-0.0, 0, NULL).count == 4); + assert(exess_write_decimal(0.0, 0, NULL).count == 3); + assert(exess_write_decimal((double)INFINITY, 0, NULL).count == 0); + assert(exess_write_decimal((double)-INFINITY, 0, NULL).count == 0); +} + +/// Check that `str` is a canonical xsd:double string +static void +check_canonical(const char* const str) +{ + assert(strlen(str) > 2); // Shortest possible is something like 1.2 + assert(str[0] == '-' || is_digit(str[0])); + + const int first_digit = str[0] == '-' ? 1 : 0; + + for (const char* s = str + first_digit; *s; ++s) { + assert(*s == '.' || is_digit(*s)); + } +} + +static void +check_write(const double value, + const ExessStatus expected_status, + const size_t buf_size, + const char* const expected_string) +{ + char buf[EXESS_MAX_DECIMAL_LENGTH + 1] = {42}; + + assert(buf_size <= sizeof(buf)); + + const ExessResult r = exess_write_decimal(value, buf_size, buf); + assert(r.status == expected_status); + if (expected_string) { + assert(r.count == strlen(buf)); + assert(!strcmp(buf, expected_string)); + assert(r.status || exess_write_decimal(value, 0, NULL).count == r.count); + + if (expected_string[0]) { + check_canonical(buf); + } + } +} + +static void +test_write_decimal(void) +{ + check_write((double)NAN, EXESS_BAD_VALUE, 4, ""); + check_write((double)-INFINITY, EXESS_BAD_VALUE, 5, ""); + + check_write( + DBL_MIN, + EXESS_SUCCESS, + 327, + "0." + "00000000000000000000000000000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000022250738585072014"); + + check_write(-1.2, EXESS_SUCCESS, 5, "-1.2"); + check_write(-0.0, EXESS_SUCCESS, 5, "-0.0"); + check_write(0.0, EXESS_SUCCESS, 4, "0.0"); + check_write(1.2, EXESS_SUCCESS, 4, "1.2"); + + check_write(DBL_MAX, + EXESS_SUCCESS, + 312, + "1797693134862315700000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000000.0"); + + check_write((double)INFINITY, EXESS_BAD_VALUE, 4, ""); + + check_write(DBL_MIN, EXESS_NO_SPACE, 326, ""); + check_write(-1.2, EXESS_NO_SPACE, 4, ""); + check_write(-0.0, EXESS_NO_SPACE, 4, ""); + check_write(0.0, EXESS_NO_SPACE, 3, ""); + check_write(1.2, EXESS_NO_SPACE, 3, ""); + check_write(DBL_MAX, EXESS_NO_SPACE, 311, ""); + + check_write(-1.0, EXESS_NO_SPACE, 1, ""); + check_write(-1.0, EXESS_NO_SPACE, 0, NULL); +} + +static void +check_round_trip(const double value) +{ + double parsed_value = 0.0; + char buf[EXESS_MAX_DECIMAL_LENGTH + 1] = {42}; + + /* fprintf(stderr, "%f\n", value); */ + assert(!exess_write_decimal(value, sizeof(buf), buf).status); + /* fprintf(stderr, "Buf: %s\n", buf); */ + assert(!exess_read_decimal(&parsed_value, buf).status); + assert(double_matches(parsed_value, value)); +} + +static void +test_round_trip(const ExessNumTestOptions opts) +{ + check_round_trip(DBL_MIN); + check_round_trip(-0.0); + check_round_trip(0.0); + check_round_trip(DBL_MAX); + + fprintf(stderr, "Testing xsd:double randomly with seed %u\n", opts.seed); + + uint32_t rep = opts.seed; + for (uint64_t i = 0; i < opts.n_tests; ++i) { + rep = lcg32(rep); + + const double value = double_from_rep(rep); + + check_round_trip(value); + print_num_test_progress(i, opts.n_tests); + } +} + +int +main(int argc, char** argv) +{ + const ExessNumTestOptions opts = parse_num_test_options(argc, argv); + if (opts.error) { + return 1; + } + + test_read_decimal(); + test_decimal_string_length(); + test_write_decimal(); + test_round_trip(opts); + + return 0; +} diff --git a/subprojects/exess/test/test_double.c b/subprojects/exess/test/test_double.c new file mode 100644 index 00000000..d4901c5a --- /dev/null +++ b/subprojects/exess/test/test_double.c @@ -0,0 +1,246 @@ +/* + Copyright 2011-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#undef NDEBUG + +#include "float_test_data.h" +#include "int_test_data.h" +#include "num_test_utils.h" +#include "string_utils.h" + +#include "exess/exess.h" + +#include +#include +#include +#include +#include +#include + +static void +check_read(const char* const string, + const ExessStatus expected_status, + const double expected_value, + const size_t expected_count) +{ + double value = (double)NAN; + const ExessResult r = exess_read_double(&value, string); + + assert(r.status == expected_status); + assert(r.count == expected_count); + assert(double_matches(value, expected_value)); +} + +static void +test_read_double(void) +{ + // Limits + check_read("-1.7976931348623157E308", EXESS_SUCCESS, -DBL_MAX, 23); + check_read("-2.2250738585072014E-308", EXESS_SUCCESS, -DBL_MIN, 24); + check_read("2.2250738585072014E-308", EXESS_SUCCESS, DBL_MIN, 23); + check_read("1.7976931348623157E308", EXESS_SUCCESS, DBL_MAX, 22); + + // Beyond limits + check_read("1e-326", EXESS_SUCCESS, 0.0, 6); + check_read("12345678901234567123", EXESS_SUCCESS, 12345678901234567000.0, 20); + check_read("1e309", EXESS_SUCCESS, (double)INFINITY, 5); + + // Non-canonical form + check_read("+1E3", EXESS_SUCCESS, 1e3, 4); + check_read("1E+3", EXESS_SUCCESS, 1e3, 4); + check_read("+1.5E3", EXESS_SUCCESS, 1.5e3, 6); + check_read(".5E3", EXESS_SUCCESS, 0.5e3, 4); + check_read("+.5E3", EXESS_SUCCESS, 0.5e3, 5); + check_read("-.5E3", EXESS_SUCCESS, -0.5e3, 5); + check_read("1.E3", EXESS_SUCCESS, 1e3, 4); + check_read("+1.E3", EXESS_SUCCESS, 1e3, 5); + check_read("-1.E3", EXESS_SUCCESS, -1e3, 5); + + // Special values + check_read("NaN", EXESS_SUCCESS, (double)NAN, 3); + check_read("-INF", EXESS_SUCCESS, (double)-INFINITY, 4); + check_read("-0.0E0", EXESS_SUCCESS, -0.0, 6); + check_read("0.0E0", EXESS_SUCCESS, 0.0, 5); + check_read("+0.0E0", EXESS_SUCCESS, 0.0, 6); + check_read("INF", EXESS_SUCCESS, (double)INFINITY, 3); + check_read("+INF", EXESS_SUCCESS, (double)INFINITY, 4); + + // No exponent + check_read("1", EXESS_SUCCESS, 1.0, 1); + check_read("2.3", EXESS_SUCCESS, 2.3, 3); + check_read("-4.5", EXESS_SUCCESS, -4.5, 4); + + // Leading whitespace + check_read(" \f\n\r\t\v1.2", EXESS_SUCCESS, 1.2, 9); + + // Garbage + check_read("true", EXESS_EXPECTED_DIGIT, (double)NAN, 0); + check_read("+true", EXESS_EXPECTED_DIGIT, (double)NAN, 1); + check_read("-false", EXESS_EXPECTED_DIGIT, (double)NAN, 1); + check_read("1.0eX", EXESS_EXPECTED_DIGIT, (double)NAN, 4); + check_read("1.0EX", EXESS_EXPECTED_DIGIT, (double)NAN, 4); +} + +/// Check that `str` is a canonical xsd:double string +static void +check_canonical(const char* const str) +{ + if (!strcmp(str, "NaN") || !strcmp(str, "-INF") || !strcmp(str, "INF")) { + return; + } + + assert(strlen(str) > 4); // Shortest possible is something like 1.2E3 + assert(str[0] == '-' || is_digit(str[0])); + + const int first_digit = str[0] == '-' ? 1 : 0; + assert(is_digit(str[first_digit])); + assert(str[first_digit + 1] == '.'); + assert(is_digit(str[first_digit + 2])); + + const char* const e = strchr(str, 'E'); + assert(e); + assert(*e == 'E'); + assert(*(e + 1) == '-' || is_digit(*(e + 1))); +} + +static void +check_write(const double value, + const ExessStatus expected_status, + const size_t buf_size, + const char* const expected_string) +{ + char buf[EXESS_MAX_DOUBLE_LENGTH + 1] = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // + 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, // + 21, 22, 23}; + + assert(buf_size <= sizeof(buf)); + + const ExessResult r = exess_write_double(value, buf_size, buf); + assert(r.status == expected_status); + assert(r.count == strlen(buf)); + assert(!strcmp(buf, expected_string)); + assert((r.status && r.status != EXESS_NO_SPACE) || + exess_write_double(value, 0, NULL).count == r.count); + + if (!r.status) { + check_canonical(buf); + } +} + +static void +test_write_double(void) +{ + check_write((double)NAN, EXESS_SUCCESS, 4, "NaN"); + check_write(DBL_MIN, EXESS_SUCCESS, 24, "2.2250738585072014E-308"); + check_write(-0.0, EXESS_SUCCESS, 7, "-0.0E0"); + check_write(0.0, EXESS_SUCCESS, 6, "0.0E0"); + check_write(DBL_MAX, EXESS_SUCCESS, 23, "1.7976931348623157E308"); + + /* check_write((double)NAN, EXESS_NO_SPACE, 3, ""); */ + /* check_write(DBL_MIN, EXESS_SUCCESS, 24, "2.2250738585072014E-308"); */ + /* check_write(-0.0, EXESS_SUCCESS, 7, "-0.0E0"); */ + /* check_write(0.0, EXESS_SUCCESS, 6, "0.0E0"); */ + /* check_write(DBL_MAX, EXESS_SUCCESS, 23, "1.7976931348623157E308"); */ +} + +static void +check_round_trip(const double value) +{ + double parsed_value = 0.0; + + char buf[EXESS_MAX_DOUBLE_LENGTH + 1] = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // + 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, // + 21, 22, 23}; + + assert(!exess_write_double(value, sizeof(buf), buf).status); + assert(!exess_read_double(&parsed_value, buf).status); + assert(double_matches(parsed_value, value)); +} + +static void +test_round_trip(const ExessNumTestOptions opts) +{ + check_round_trip((double)NAN); + check_round_trip(-(double)INFINITY); + check_round_trip(DBL_MIN); + check_round_trip(-0.0); + check_round_trip(0.0); + check_round_trip(DBL_MAX); + check_round_trip((double)INFINITY); + + check_round_trip(5.0); + check_round_trip(50.0); + check_round_trip(500000000000000000000.0); + check_round_trip(-0.5); + check_round_trip(0.5); + check_round_trip(0.05); + check_round_trip(0.005); + check_round_trip(0.00000000000000000005); + + // Normal limits + check_round_trip(nextafter(DBL_MIN, (double)INFINITY)); + check_round_trip(nextafter(DBL_EPSILON, (double)INFINITY)); + check_round_trip(nextafter(DBL_MAX, -(double)INFINITY)); + + // Subnormals + check_round_trip(nextafter(0.0, 1.0)); + check_round_trip(nextafter(nextafter(0.0, 1.0), 2.0)); + check_round_trip(nextafter(0.0, -1.0)); + check_round_trip(nextafter(nextafter(0.0, -1.0), -2.0)); + + // Various tricky cases + check_round_trip(1e23); + check_round_trip(6.02951420360127e-309); + check_round_trip(9.17857104364115e+288); + check_round_trip(2.68248422823759e+22); + + // Powers of two (where the lower boundary is closer) + for (int i = -1023; i <= 1023; ++i) { + check_round_trip(pow(2, i)); + } + + fprintf(stderr, "Testing xsd:double randomly with seed %u\n", opts.seed); + + uint64_t rep = opts.seed; + for (uint64_t i = 0; i < opts.n_tests; ++i) { + rep = lcg64(rep); + + const double value = double_from_rep(rep); + + check_round_trip(nextafter(value, -(double)INFINITY)); + check_round_trip(value); + check_round_trip(nextafter(value, (double)INFINITY)); + + print_num_test_progress(i, opts.n_tests); + } +} + +int +main(int argc, char** argv) +{ + const ExessNumTestOptions opts = parse_num_test_options(argc, argv); + if (opts.error) { + return 1; + } + + test_read_double(); + test_write_double(); + test_round_trip(opts); + + return 0; +} diff --git a/subprojects/exess/test/test_duration.c b/subprojects/exess/test/test_duration.c new file mode 100644 index 00000000..3d964e80 --- /dev/null +++ b/subprojects/exess/test/test_duration.c @@ -0,0 +1,310 @@ +/* + Copyright 2011-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#undef NDEBUG + +#include "int_test_data.h" +#include "macros.h" +#include "num_test_utils.h" + +#include "exess/exess.h" + +#include +#include +#include +#include +#include + +static const ExessDuration zero = {0, 0, 0}; +static const ExessDuration lowest = {-INT32_MAX, -INT32_MAX, -999999999}; +static const ExessDuration highest = {INT32_MAX, INT32_MAX, 999999999}; +static const ExessDuration year = {12, 0, 0}; +static const ExessDuration month = {1, 0, 0}; +static const ExessDuration day = {0, 24 * 60 * 60, 0}; +static const ExessDuration hour = {0, 60 * 60, 0}; +static const ExessDuration minute = {0, 60, 0}; +static const ExessDuration second = {0, 1, 0}; +static const ExessDuration nanosecond = {0, 0, 1}; + +static const ExessDuration n_year = {-12, 0, 0}; +static const ExessDuration n_month = {-1, 0, 0}; +static const ExessDuration n_day = {0, -24 * 60 * 60, 0}; +static const ExessDuration n_hour = {0, -60 * 60, 0}; +static const ExessDuration n_minute = {0, -60, 0}; +static const ExessDuration n_second = {0, -1, 0}; +static const ExessDuration n_nanosecond = {0, 0, -1}; + +static const ExessDuration garbage1 = {1, 1, -1}; +static const ExessDuration garbage2 = {1, -1, 1}; +static const ExessDuration garbage3 = {1, -1, -1}; +static const ExessDuration garbage4 = {-1, 1, 1}; +static const ExessDuration garbage5 = {-1, 1, -1}; +static const ExessDuration garbage6 = {-1, -1, 1}; +static const ExessDuration garbage7 = {INT32_MIN, 0, -999999999}; +static const ExessDuration garbage8 = {0, INT32_MIN, -999999999}; +static const ExessDuration garbage9 = {INT32_MIN, INT32_MIN, -999999999}; + +static void +check_read(const char* const string, + const ExessStatus expected_status, + const size_t expected_count, + const int32_t expected_years, + const int32_t expected_months, + const int32_t expected_days, + const int32_t expected_hours, + const int32_t expected_minutes, + const int32_t expected_seconds, + const int32_t expected_nanoseconds, + const bool expected_is_negative) +{ + ExessDuration value = {0, 0, 0.0f}; + const ExessResult r = exess_read_duration(&value, string); + + assert(r.status == expected_status); + assert(r.count == expected_count); + + assert(value.months == (expected_is_negative ? -1 : 1) * 12 * expected_years + + expected_months); + + assert(value.seconds == + (expected_is_negative ? -1 : 1) * + ((expected_seconds + (60 * expected_minutes) + + (60 * 60 * expected_hours) + (24 * 60 * 60 * expected_days)))); + + assert(value.nanoseconds == expected_nanoseconds); +} + +static void +test_read_duration(void) +{ + // No input + check_read("", EXESS_EXPECTED_DURATION, 0, 0, 0, 0, 0, 0, 0, 0, false); + check_read( + " \f\n\r\t\v", EXESS_EXPECTED_DURATION, 6, 0, 0, 0, 0, 0, 0, 0, false); + + // Good values + + check_read( + "P2Y6M5DT12H35M30S", EXESS_SUCCESS, 17, 2, 6, 5, 12, 35, 30, 0, false); + + check_read("P1DT2H", EXESS_SUCCESS, 6, 0, 0, 1, 2, 0, 0, 0, false); + check_read("P20M", EXESS_SUCCESS, 4, 0, 20, 0, 0, 0, 0, 0, false); + check_read("PT20M", EXESS_SUCCESS, 5, 0, 0, 0, 0, 20, 0, 0, false); + check_read("P0Y20M0D", EXESS_SUCCESS, 8, 0, 20, 0, 0, 0, 0, 0, false); + check_read("P0Y", EXESS_SUCCESS, 3, 0, 0, 0, 0, 0, 0, 0, false); + check_read("-P60D", EXESS_SUCCESS, 5, 0, 0, 60, 0, 0, 0, 0, true); + check_read( + "PT1M30.5S", EXESS_SUCCESS, 9, 0, 0, 0, 0, 1, 30, 500000000, false); + + // Leading and trailing whitespace + check_read(" \f\n\r\t\vP1Y", EXESS_SUCCESS, 9, 1, 0, 0, 0, 0, 0, 0, false); + check_read("P1MT2H \f\n\r\t\v", EXESS_SUCCESS, 6, 0, 1, 0, 2, 0, 0, 0, false); + check_read(" \f\n\r\t\vP1Y", EXESS_SUCCESS, 9, 1, 0, 0, 0, 0, 0, 0, false); + check_read("P1YT2H \f\n\r\t\v", EXESS_SUCCESS, 6, 1, 0, 0, 2, 0, 0, 0, false); + + // Non-canonical form + check_read("P06D", EXESS_SUCCESS, 4, 0, 0, 6, 0, 0, 0, 0, false); + check_read("PT7.0S", EXESS_SUCCESS, 6, 0, 0, 0, 0, 0, 7, 0, false); + check_read( + "P0Y0M01DT06H00M00S", EXESS_SUCCESS, 18, 0, 0, 1, 6, 0, 0, 0, false); + + // Out of range fields + check_read( + "P2147483647Y", EXESS_OUT_OF_RANGE, 11, 0, 0, 0, 0, 0, 0, 0, false); + check_read( + "P2147483647M", EXESS_OUT_OF_RANGE, 11, 0, 0, 0, 0, 0, 0, 0, false); + check_read( + "P2147483647D", EXESS_OUT_OF_RANGE, 11, 0, 0, 0, 0, 0, 0, 0, false); + check_read( + "PT2147483647H", EXESS_OUT_OF_RANGE, 12, 0, 0, 0, 0, 0, 0, 0, false); + check_read( + "PT2147483647M", EXESS_OUT_OF_RANGE, 12, 0, 0, 0, 0, 0, 0, 0, false); + check_read( + "PT2147483647S", EXESS_OUT_OF_RANGE, 12, 0, 0, 0, 0, 0, 0, 0, false); + + // Garbage + check_read("P-20M", EXESS_EXPECTED_DIGIT, 1, 0, 0, 0, 0, 0, 0, 0, false); + check_read("P20MT", EXESS_EXPECTED_DIGIT, 5, 0, 20, 0, 0, 0, 0, 0, false); + check_read("P1YM5D", EXESS_EXPECTED_DIGIT, 3, 1, 0, 0, 0, 0, 0, 0, false); + check_read("P15.5Y", EXESS_EXPECTED_DATE_TAG, 3, 0, 0, 0, 0, 0, 0, 0, false); + check_read("P1D2H", EXESS_EXPECTED_TIME_SEP, 3, 0, 0, 1, 0, 0, 0, 0, false); + check_read("1Y2M", EXESS_EXPECTED_DURATION, 0, 0, 0, 0, 0, 0, 0, 0, false); + check_read("P2M1Y", EXESS_BAD_ORDER, 4, 0, 2, 0, 0, 0, 0, 0, false); + check_read("P2D1Y", EXESS_EXPECTED_TIME_SEP, 3, 0, 0, 2, 0, 0, 0, 0, false); + check_read("P2D1M", EXESS_EXPECTED_TIME_SEP, 3, 0, 0, 2, 0, 0, 0, 0, false); + check_read("P", EXESS_EXPECTED_DIGIT, 1, 0, 0, 0, 0, 0, 0, 0, false); + check_read("PT15.5H", EXESS_EXPECTED_TIME_TAG, 6, 0, 0, 0, 0, 0, 0, 0, false); + check_read("PT2M1H", EXESS_BAD_ORDER, 5, 0, 0, 0, 0, 2, 0, 0, false); + check_read("PT2S1H", EXESS_EXPECTED_END, 4, 0, 0, 0, 0, 0, 2, 0, false); + check_read("PT2S1M", EXESS_EXPECTED_END, 4, 0, 0, 0, 0, 0, 2, 0, false); + check_read("PT15.S", EXESS_EXPECTED_DIGIT, 5, 0, 0, 0, 0, 0, 0, 0, false); + check_read("P1Q", EXESS_EXPECTED_DATE_TAG, 2, 0, 0, 0, 0, 0, 0, 0, false); + check_read("PT1Q", EXESS_EXPECTED_TIME_TAG, 3, 0, 0, 0, 0, 0, 0, 0, false); + check_read("P-1Y", EXESS_EXPECTED_DIGIT, 1, 0, 0, 0, 0, 0, 0, 0, false); + check_read("P-1M", EXESS_EXPECTED_DIGIT, 1, 0, 0, 0, 0, 0, 0, 0, false); + check_read("P-1D", EXESS_EXPECTED_DIGIT, 1, 0, 0, 0, 0, 0, 0, 0, false); + check_read("PT-1H", EXESS_EXPECTED_DIGIT, 2, 0, 0, 0, 0, 0, 0, 0, false); + check_read("PT-1M", EXESS_EXPECTED_DIGIT, 2, 0, 0, 0, 0, 0, 0, 0, false); + check_read("PT-1S", EXESS_EXPECTED_DIGIT, 2, 0, 0, 0, 0, 0, 0, 0, false); + check_read( + "P4294967296Y", EXESS_OUT_OF_RANGE, 11, 0, 0, 0, 0, 0, 0, 0, false); + check_read( + "P4294967296M", EXESS_OUT_OF_RANGE, 11, 0, 0, 0, 0, 0, 0, 0, false); + check_read( + "P4294967296D", EXESS_OUT_OF_RANGE, 11, 0, 0, 0, 0, 0, 0, 0, false); + check_read( + "PT4294967296H", EXESS_OUT_OF_RANGE, 12, 0, 0, 0, 0, 0, 0, 0, false); + check_read( + "PT4294967296M", EXESS_OUT_OF_RANGE, 12, 0, 0, 0, 0, 0, 0, 0, false); + check_read("", EXESS_EXPECTED_DURATION, 0, 0, 0, 0, 0, 0, 0, 0, false); +} + +static void +check_write(const ExessDuration value, + const ExessStatus expected_status, + const size_t buf_size, + const char* const expected_string) +{ + char buf[EXESS_MAX_DURATION_LENGTH + 1] = {42}; + + assert(buf_size <= sizeof(buf)); + + const ExessResult r = exess_write_duration(value, buf_size, buf); + assert(r.status == expected_status); + assert(r.count == strlen(buf)); + assert(!strcmp(buf, expected_string)); + assert(r.status || exess_write_duration(value, 0, NULL).count == r.count); +} + +static void +test_write_duration(void) +{ + check_write(zero, EXESS_SUCCESS, 4, "P0Y"); + + check_write( + lowest, EXESS_SUCCESS, 39, "-P178956970Y7M24855DT3H14M7.999999999S"); + + check_write( + highest, EXESS_SUCCESS, 38, "P178956970Y7M24855DT3H14M7.999999999S"); + + check_write(year, EXESS_SUCCESS, 4, "P1Y"); + check_write(month, EXESS_SUCCESS, 4, "P1M"); + check_write(day, EXESS_SUCCESS, 4, "P1D"); + check_write(hour, EXESS_SUCCESS, 5, "PT1H"); + check_write(minute, EXESS_SUCCESS, 5, "PT1M"); + check_write(second, EXESS_SUCCESS, 5, "PT1S"); + check_write(nanosecond, EXESS_SUCCESS, 15, "PT0.000000001S"); + + check_write(n_year, EXESS_SUCCESS, 5, "-P1Y"); + check_write(n_month, EXESS_SUCCESS, 5, "-P1M"); + check_write(n_day, EXESS_SUCCESS, 5, "-P1D"); + check_write(n_hour, EXESS_SUCCESS, 6, "-PT1H"); + check_write(n_minute, EXESS_SUCCESS, 6, "-PT1M"); + check_write(n_second, EXESS_SUCCESS, 6, "-PT1S"); + check_write(n_nanosecond, EXESS_SUCCESS, 16, "-PT0.000000001S"); + + check_write(garbage1, EXESS_BAD_VALUE, 41, ""); + check_write(garbage2, EXESS_BAD_VALUE, 41, ""); + check_write(garbage3, EXESS_BAD_VALUE, 41, ""); + check_write(garbage4, EXESS_BAD_VALUE, 41, ""); + check_write(garbage5, EXESS_BAD_VALUE, 41, ""); + check_write(garbage6, EXESS_BAD_VALUE, 41, ""); + check_write(garbage7, EXESS_OUT_OF_RANGE, 41, ""); + check_write(garbage8, EXESS_OUT_OF_RANGE, 41, ""); + check_write(garbage9, EXESS_OUT_OF_RANGE, 41, ""); + + check_write(zero, EXESS_NO_SPACE, 3, ""); + check_write(lowest, EXESS_NO_SPACE, 24, ""); + check_write(highest, EXESS_NO_SPACE, 4, ""); + check_write(highest, EXESS_NO_SPACE, 10, ""); + check_write(highest, EXESS_NO_SPACE, 13, ""); + check_write(highest, EXESS_NO_SPACE, 16, ""); + check_write(highest, EXESS_NO_SPACE, 20, ""); + check_write(highest, EXESS_NO_SPACE, 23, ""); + check_write(year, EXESS_NO_SPACE, 3, ""); + check_write(month, EXESS_NO_SPACE, 3, ""); + check_write(day, EXESS_NO_SPACE, 3, ""); + check_write(hour, EXESS_NO_SPACE, 4, ""); + check_write(minute, EXESS_NO_SPACE, 4, ""); + check_write(second, EXESS_NO_SPACE, 4, ""); + + // Check that nothing is written when there isn't enough space + char c = 42; + const ExessResult r = exess_write_duration(zero, 0, &c); + assert(c == 42); + assert(r.status == EXESS_NO_SPACE); + assert(r.count == 0); +} + +static void +check_round_trip(const ExessDuration value) +{ + ExessDuration parsed_value = {0, 0, 0}; + char buf[EXESS_MAX_DURATION_LENGTH + 1] = {0}; + + assert(exess_write_duration(value, 0, NULL).count <= + EXESS_MAX_DURATION_LENGTH); + + assert(!exess_write_duration(value, sizeof(buf), buf).status); + assert(!exess_read_duration(&parsed_value, buf).status); + assert(parsed_value.months == value.months); + assert(parsed_value.seconds == value.seconds); + assert(parsed_value.nanoseconds == value.nanoseconds); +} + +static void +test_round_trip(const ExessNumTestOptions opts) +{ + fprintf(stderr, "Testing xsd:duration randomly with seed %u\n", opts.seed); + + const uint64_t n_tests = MAX(256, opts.n_tests / 16); + + uint32_t rng = opts.seed; + for (size_t i = 0; i < n_tests; ++i) { + rng = lcg32(rng); + + const int32_t months = (int32_t)rng; + + rng = lcg32(rng); + + const int32_t seconds = (months < 0 ? -1 : 1) * (int32_t)(rng % INT32_MAX); + + rng = lcg32(rng); + + const int32_t nanoseconds = + (months < 0 ? -1 : 1) * (int32_t)(rng % 1000000000); + + const ExessDuration value = {months, seconds, nanoseconds}; + check_round_trip(value); + + print_num_test_progress(i, n_tests); + } +} + +int +main(int argc, char** argv) +{ + const ExessNumTestOptions opts = parse_num_test_options(argc, argv); + if (opts.error) { + return 1; + } + + test_read_duration(); + test_write_duration(); + test_round_trip(opts); + + return 0; +} diff --git a/subprojects/exess/test/test_float.c b/subprojects/exess/test/test_float.c new file mode 100644 index 00000000..e8cf9141 --- /dev/null +++ b/subprojects/exess/test/test_float.c @@ -0,0 +1,240 @@ +/* + Copyright 2011-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#undef NDEBUG + +#include "float_test_data.h" +#include "int_test_data.h" +#include "num_test_utils.h" +#include "string_utils.h" + +#include "exess/exess.h" + +#include +#include +#include +#include +#include +#include + +static void +check_read(const char* const string, + const ExessStatus expected_status, + const float expected_value, + const size_t expected_count) +{ + float value = NAN; + const ExessResult r = exess_read_float(&value, string); + + assert(r.status == expected_status); + assert(r.count == expected_count); + assert(float_matches(value, expected_value)); +} + +static void +test_read_float(void) +{ + // Limits + check_read("-3.40282347E38", EXESS_SUCCESS, -FLT_MAX, 14); + check_read("-1.17549435E-38", EXESS_SUCCESS, -FLT_MIN, 15); + check_read("1.17549435E-38", EXESS_SUCCESS, FLT_MIN, 14); + check_read("3.40282347E38", EXESS_SUCCESS, FLT_MAX, 13); + + // Special values + check_read("NaN", EXESS_SUCCESS, NAN, 3); + check_read("-INF", EXESS_SUCCESS, -INFINITY, 4); + check_read("-0.0E0", EXESS_SUCCESS, -0.0f, 6); + check_read("0.0E0", EXESS_SUCCESS, 0.0f, 5); + check_read("+0.0E0", EXESS_SUCCESS, 0.0f, 6); + check_read("INF", EXESS_SUCCESS, INFINITY, 3); + check_read("+INF", EXESS_SUCCESS, INFINITY, 4); + + // Various normal cases + check_read("-1.0E0", EXESS_SUCCESS, -1.0f, 6); + check_read("1.0E0", EXESS_SUCCESS, +1.0f, 5); + check_read("5.0E0", EXESS_SUCCESS, 5.0f, 5); + check_read("5.0E1", EXESS_SUCCESS, 50.0f, 5); + check_read("5.0E9", EXESS_SUCCESS, 5000000000.0f, 5); + check_read("-5.0E-1", EXESS_SUCCESS, -0.5f, 7); + check_read("5.0E-1", EXESS_SUCCESS, 0.5f, 6); + check_read("6.25E-2", EXESS_SUCCESS, 0.0625f, 7); + check_read("7.8125E-3", EXESS_SUCCESS, 0.0078125f, 9); + + // No exponent + check_read("1", EXESS_SUCCESS, 1.0f, 1); + check_read("2.3", EXESS_SUCCESS, 2.3f, 3); + check_read("-4.5", EXESS_SUCCESS, -4.5f, 4); + + // Trailing garbage + check_read("1.2.", EXESS_SUCCESS, 1.2f, 3); + + // Garbage + check_read("true", EXESS_EXPECTED_DIGIT, NAN, 0); + check_read("+true", EXESS_EXPECTED_DIGIT, NAN, 1); + check_read("-false", EXESS_EXPECTED_DIGIT, NAN, 1); + check_read("1.0eX", EXESS_EXPECTED_DIGIT, NAN, 4); + check_read("1.0EX", EXESS_EXPECTED_DIGIT, NAN, 4); +} + +static void +test_float_string_length(void) +{ + // Limits + assert(exess_write_float(FLT_MIN, 0, NULL).count == 14); + assert(exess_write_float(FLT_MAX, 0, NULL).count == 13); + + // Special values + assert(exess_write_float((float)NAN, 0, NULL).count == 3); + assert(exess_write_float(-1.0f, 0, NULL).count == 6); + assert(exess_write_float(-0.0f, 0, NULL).count == 6); + assert(exess_write_float(0.0f, 0, NULL).count == 5); + assert(exess_write_float(1.0f, 0, NULL).count == 5); + assert(exess_write_float((float)INFINITY, 0, NULL).count == 3); + assert(exess_write_float((float)-INFINITY, 0, NULL).count == 4); +} + +/// Check that `str` is a canonical xsd:float string +static void +check_canonical(const char* const str) +{ + if (!strcmp(str, "NaN") || !strcmp(str, "-INF") || !strcmp(str, "INF")) { + return; + } + + assert(strlen(str) > 4); // Shortest possible is something like 1.2E3 + assert(str[0] == '-' || is_digit(str[0])); + + const int first_digit = str[0] == '-' ? 1 : 0; + assert(is_digit(str[first_digit])); + assert(str[first_digit + 1] == '.'); + assert(is_digit(str[first_digit + 2])); + + const char* const e = strchr(str, 'E'); + assert(e); + assert(*e == 'E'); + assert(*(e + 1) == '-' || is_digit(*(e + 1))); +} + +static void +check_write(const float value, + const ExessStatus expected_status, + const size_t buf_size, + const char* const expected_string) +{ + char buf[EXESS_MAX_FLOAT_LENGTH + 1] = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; + + assert(buf_size <= sizeof(buf)); + + const ExessResult r = exess_write_float(value, buf_size, buf); + assert(r.status == expected_status); + if (expected_string) { + assert(r.count == strlen(buf)); + assert(!expected_string || !strcmp(buf, expected_string)); + assert(r.status || exess_write_float(value, 0, NULL).count == r.count); + check_canonical(buf); + } else { + assert(r.count == 0); + } +} + +static void +test_write_float(void) +{ + check_write(NAN, EXESS_SUCCESS, 4, "NaN"); + check_write(-INFINITY, EXESS_SUCCESS, 5, "-INF"); + check_write(FLT_MIN, EXESS_SUCCESS, 15, "1.17549435E-38"); + check_write(-0.0f, EXESS_SUCCESS, 7, "-0.0E0"); + check_write(0.0f, EXESS_SUCCESS, 6, "0.0E0"); + check_write(100.25f, EXESS_SUCCESS, 9, "1.0025E2"); + check_write(FLT_MAX, EXESS_SUCCESS, 14, "3.40282346E38"); + check_write(INFINITY, EXESS_SUCCESS, 4, "INF"); + + check_write(NAN, EXESS_NO_SPACE, 3, NULL); + check_write(-INFINITY, EXESS_NO_SPACE, 4, NULL); + check_write(FLT_MIN, EXESS_NO_SPACE, 13, NULL); + check_write(-1.0f, EXESS_NO_SPACE, 2, NULL); + check_write(-0.0f, EXESS_NO_SPACE, 6, NULL); + check_write(0.0f, EXESS_NO_SPACE, 5, NULL); + check_write(100.25f, EXESS_NO_SPACE, 5, NULL); + check_write(100.25f, EXESS_NO_SPACE, 8, NULL); + check_write(FLT_MAX, EXESS_NO_SPACE, 13, NULL); + check_write(INFINITY, EXESS_NO_SPACE, 3, NULL); +} + +static void +check_round_trip(const float value) +{ + float parsed_value = 0.0f; + char buf[EXESS_MAX_FLOAT_LENGTH + 1] = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; + + assert(!exess_write_float(value, sizeof(buf), buf).status); + assert(!exess_read_float(&parsed_value, buf).status); + assert(float_matches(parsed_value, value)); +} + +static void +test_round_trip(const ExessNumTestOptions opts) +{ + check_round_trip(NAN); + check_round_trip(FLT_MIN); + check_round_trip(-0.0f); + check_round_trip(0.0f); + check_round_trip(FLT_MAX); + + if (opts.exhaustive) { + fprintf(stderr, "Testing xsd:float exhaustively\n"); + + for (int64_t i = 0; i <= UINT32_MAX; ++i) { + const float value = float_from_rep((uint32_t)i); + + check_round_trip(value); + print_num_test_progress((uint64_t)(i - (int64_t)INT32_MIN), UINT32_MAX); + } + } else { + fprintf(stderr, "Testing xsd:float randomly with seed %u\n", opts.seed); + + uint32_t rep = opts.seed; + for (uint64_t i = 0; i < opts.n_tests; ++i) { + rep = lcg32(rep); + + const float value = float_from_rep(rep); + + check_round_trip(nextafterf(value, -INFINITY)); + check_round_trip(value); + check_round_trip(nextafterf(value, INFINITY)); + + print_num_test_progress(i, opts.n_tests); + } + } +} + +int +main(int argc, char** argv) +{ + const ExessNumTestOptions opts = parse_num_test_options(argc, argv); + if (opts.error) { + return 1; + } + + test_read_float(); + test_float_string_length(); + test_write_float(); + test_round_trip(opts); + + return 0; +} diff --git a/subprojects/exess/test/test_hex.c b/subprojects/exess/test/test_hex.c new file mode 100644 index 00000000..5ceec90b --- /dev/null +++ b/subprojects/exess/test/test_hex.c @@ -0,0 +1,180 @@ +/* + Copyright 2011-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#undef NDEBUG + +#include "exess/exess.h" + +#include +#include +#include +#include +#include + +static void +check_read(const char* const string, + const ExessStatus expected_status, + const size_t expected_value_length, + const char* const expected_value, + const size_t expected_value_size, + const size_t expected_count) +{ + char buf[9] = {0, 0, 0, 0, 0, 0, 0, 0, 0}; + ExessBlob blob = {sizeof(buf), buf}; + + ExessResult r = exess_read_hex(&blob, string); + assert(r.status == expected_status); + assert(r.count == expected_count); + assert(r.status || blob.size == expected_value_size); + if (expected_value_length > 0) { + assert(!strncmp(buf, expected_value, expected_value_length)); + assert(blob.size <= exess_hex_decoded_size(strlen(string))); + } +} + +static void +test_lowercase(void) +{ + char buf[6] = {0, 0, 0, 0, 0, 0}; + ExessBlob blob = {sizeof(buf), buf}; + + ExessResult r = exess_read_hex(&blob, "6A6B6C6D6E6F"); + assert(r.status == EXESS_SUCCESS); + assert(r.count == 12); + assert(blob.size == 6); + assert(!strncmp((const char*)blob.data, "jklmno", 6)); + + r = exess_read_hex(&blob, "6a6b6c6d6e6f"); + assert(r.status == EXESS_SUCCESS); + assert(r.count == 12); + assert(blob.size == 6); + assert(!strncmp((const char*)blob.data, "jklmno", 6)); +} + +static void +test_whitespace(void) +{ + check_read("666F6F", EXESS_SUCCESS, 3, "foo", 3, 6); + check_read(" 666F6F", EXESS_SUCCESS, 3, "foo", 3, 7); + check_read("6\f66F6F", EXESS_SUCCESS, 3, "foo", 3, 7); + check_read("66\n6F6F", EXESS_SUCCESS, 3, "foo", 3, 7); + check_read("666\rF6F", EXESS_SUCCESS, 3, "foo", 3, 7); + check_read("666F\t6F", EXESS_SUCCESS, 3, "foo", 3, 7); + check_read(" \f\n\r\t\v666F6F", EXESS_SUCCESS, 3, "foo", 3, 12); + check_read("666F6F \f\n\r\t\v", EXESS_SUCCESS, 3, "foo", 3, 12); +} + +static void +test_syntax_errors(void) +{ + check_read("G6", EXESS_EXPECTED_HEX, 0, NULL, 0, 1); + check_read("g6", EXESS_EXPECTED_HEX, 0, NULL, 0, 1); + check_read("!6", EXESS_EXPECTED_HEX, 0, NULL, 0, 1); + check_read("^6", EXESS_EXPECTED_HEX, 0, NULL, 0, 1); + check_read("6G", EXESS_EXPECTED_HEX, 0, NULL, 0, 2); + check_read("6g", EXESS_EXPECTED_HEX, 0, NULL, 0, 2); + check_read("6!", EXESS_EXPECTED_HEX, 0, NULL, 0, 2); + check_read("6^", EXESS_EXPECTED_HEX, 0, NULL, 0, 2); + check_read("6", EXESS_EXPECTED_HEX, 0, NULL, 0, 1); + check_read("66G6", EXESS_EXPECTED_HEX, 0, NULL, 1, 3); + check_read("66 G6", EXESS_EXPECTED_HEX, 0, NULL, 1, 4); +} + +static void +test_read_overflow(void) +{ + char buf[9] = {0, 0, 0, 0, 0, 0, 0, 0, 0}; + ExessBlob blob0 = {0, buf}; + ExessBlob blob1 = {1, buf}; + ExessBlob blob2 = {2, buf}; + + ExessResult r = exess_read_hex(&blob0, "666F6F"); + assert(r.status == EXESS_NO_SPACE); + assert(r.count == 2); + assert(blob0.size == 0); + + r = exess_read_hex(&blob1, "666F6F"); + assert(r.status == EXESS_NO_SPACE); + assert(r.count == 4); + assert(blob1.size == 1); + + r = exess_read_hex(&blob2, "666F6F"); + assert(r.status == EXESS_NO_SPACE); + assert(r.count == 6); + assert(blob2.size == 2); +} + +static void +test_write_overflow(void) +{ + char buf[7] = {1, 2, 3, 4, 5, 6, 7}; + + assert(exess_write_hex(3, "foo", 0, buf).status == EXESS_NO_SPACE); + assert(exess_write_hex(3, "foo", 1, buf).status == EXESS_NO_SPACE); + assert(exess_write_hex(3, "foo", 2, buf).status == EXESS_NO_SPACE); + assert(exess_write_hex(3, "foo", 3, buf).status == EXESS_NO_SPACE); + assert(exess_write_hex(3, "foo", 4, buf).status == EXESS_NO_SPACE); + assert(exess_write_hex(3, "foo", 5, buf).status == EXESS_NO_SPACE); + assert(exess_write_hex(3, "foo", 6, buf).status == EXESS_NO_SPACE); + assert(exess_write_hex(3, "foo", 7, buf).status == EXESS_SUCCESS); +} + +static void +test_round_trip(void) +{ + for (size_t size = 1; size < 256; ++size) { + // Allocate and generate data + uint8_t* const data = (uint8_t*)malloc(size); + for (size_t i = 0; i < size; ++i) { + data[i] = (uint8_t)((size + i) % 256); + } + + // Allocate buffer for encoding with minimum required size + const size_t str_len = exess_write_hex(size, data, 0, NULL).count; + char* const str = (char*)malloc(str_len + 1); + + // Encode data to string buffer + assert(!exess_write_hex(size, data, str_len + 1, str).status); + assert(strlen(str) == str_len); + assert(str_len % 2 == 0); + + // Allocate buffer for decoded data with the same size as the input + uint8_t* const decoded = (uint8_t*)malloc(size); + ExessBlob decoded_blob = {size, decoded}; + + // Decode and check that data matches the original input + assert(!exess_read_hex(&decoded_blob, str).status); + assert(decoded_blob.size == size); + assert(!memcmp(decoded, data, size)); + + free(decoded); + free(str); + free(data); + } +} + +int +main(void) +{ + test_lowercase(); + test_whitespace(); + test_syntax_errors(); + test_read_overflow(); + test_write_overflow(); + test_round_trip(); + + return 0; +} diff --git a/subprojects/exess/test/test_int.c b/subprojects/exess/test/test_int.c new file mode 100644 index 00000000..d3e43f7c --- /dev/null +++ b/subprojects/exess/test/test_int.c @@ -0,0 +1,129 @@ +/* + Copyright 2011-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#undef NDEBUG + +#include "int_test_data.h" +#include "num_test_utils.h" + +#include "exess/exess.h" + +#include +#include +#include +#include + +static void +check_read(const char* const string, + const ExessStatus expected_status, + const int32_t expected_value, + const size_t expected_count) +{ + int32_t value = 0; + const ExessResult r = exess_read_int(&value, string); + + assert(r.status == expected_status); + assert(r.count == expected_count); + assert(value == expected_value); +} + +static void +test_read_int(void) +{ + // Limits + check_read("-2147483648", EXESS_SUCCESS, INT32_MIN, EXESS_MAX_INT_LENGTH); + check_read("2147483647", EXESS_SUCCESS, INT32_MAX, 10); + + // Out of range + check_read("-2147483649", EXESS_OUT_OF_RANGE, 0, 11); + check_read("2147483648", EXESS_OUT_OF_RANGE, 0, 10); + check_read("10000000000", EXESS_OUT_OF_RANGE, 0, 11); + + // Garbage + check_read("+", EXESS_EXPECTED_DIGIT, 0, 1); +} + +static void +check_write(const int32_t value, + const ExessStatus expected_status, + const size_t buf_size, + const char* const expected_string) +{ + char buf[EXESS_MAX_INT_LENGTH + 1] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; + + assert(buf_size <= sizeof(buf)); + + const ExessResult r = exess_write_int(value, buf_size, buf); + assert(!strcmp(buf, expected_string)); + assert(r.status == expected_status); + assert(r.count == strlen(buf)); + assert(r.status || exess_write_int(value, 0, NULL).count == r.count); +} + +static void +test_write_int(void) +{ + check_write(INT32_MIN, EXESS_SUCCESS, 12, "-2147483648"); + check_write(INT32_MAX, EXESS_SUCCESS, 11, "2147483647"); +} + +static void +test_round_trip(const ExessNumTestOptions opts) +{ + int32_t parsed_value = 0; + char buf[EXESS_MAX_INT_LENGTH + 1] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; + + if (opts.exhaustive) { + fprintf(stderr, "Testing xsd:int exhaustively\n"); + + for (int64_t i = INT32_MIN; i <= INT32_MAX; ++i) { + assert(!exess_write_int((int32_t)i, sizeof(buf), buf).status); + assert(!exess_read_int(&parsed_value, buf).status); + assert(parsed_value == i); + + print_num_test_progress((uint64_t)(i - (int64_t)INT32_MIN), UINT32_MAX); + } + } else { + fprintf(stderr, "Testing xsd:int randomly with seed %u\n", opts.seed); + uint32_t rep = opts.seed; + for (uint64_t i = 0; i < opts.n_tests; ++i) { + rep = lcg32(rep); + + const int32_t value = (int32_t)rep; + + assert(!exess_write_int(value, sizeof(buf), buf).status); + assert(!exess_read_int(&parsed_value, buf).status); + assert(parsed_value == value); + + print_num_test_progress(i, opts.n_tests); + } + } +} + +int +main(int argc, char** argv) +{ + const ExessNumTestOptions opts = parse_num_test_options(argc, argv); + if (opts.error) { + return 1; + } + + test_read_int(); + test_write_int(); + test_round_trip(opts); + + return 0; +} diff --git a/subprojects/exess/test/test_int_math.c b/subprojects/exess/test/test_int_math.c new file mode 100644 index 00000000..8ab3f63f --- /dev/null +++ b/subprojects/exess/test/test_int_math.c @@ -0,0 +1,97 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#undef NDEBUG + +#include "attributes.h" +#include "int_math.h" + +#include +#include + +static void +test_clz32(void) +{ + for (unsigned i = 0; i < 32; ++i) { + assert(exess_clz32(1u << i) == 32u - i - 1u); + } +} + +static void +test_clz64(void) +{ + for (unsigned i = 0; i < 64; ++i) { + assert(exess_clz64(1ull << i) == 64u - i - 1u); + } +} + +static void +test_ilog2(void) +{ + for (unsigned i = 0; i < 64; ++i) { + assert(exess_ilog2(1ull << i) == i); + } +} + +static void +test_ilog10(void) +{ + uint64_t power = 1; + for (unsigned i = 0; i < 20; ++i, power *= 10) { + assert(exess_ilog10(power) == i); + } +} + +static void +test_num_digits(void) +{ + assert(1 == exess_num_digits(0)); + assert(1 == exess_num_digits(1)); + assert(1 == exess_num_digits(9)); + assert(2 == exess_num_digits(10)); + assert(2 == exess_num_digits(99ull)); + assert(3 == exess_num_digits(999ull)); + assert(4 == exess_num_digits(9999ull)); + assert(5 == exess_num_digits(99999ull)); + assert(6 == exess_num_digits(999999ull)); + assert(7 == exess_num_digits(9999999ull)); + assert(8 == exess_num_digits(99999999ull)); + assert(9 == exess_num_digits(999999999ull)); + assert(10 == exess_num_digits(9999999999ull)); + assert(11 == exess_num_digits(99999999999ull)); + assert(12 == exess_num_digits(999999999999ull)); + assert(13 == exess_num_digits(9999999999999ull)); + assert(14 == exess_num_digits(99999999999999ull)); + assert(15 == exess_num_digits(999999999999999ull)); + assert(16 == exess_num_digits(9999999999999999ull)); + assert(17 == exess_num_digits(99999999999999999ull)); + assert(18 == exess_num_digits(999999999999999999ull)); + assert(19 == exess_num_digits(9999999999999999999ull)); + assert(20 == exess_num_digits(18446744073709551615ull)); +} + +EXESS_I_PURE_FUNC +int +main(void) +{ + test_clz32(); + test_clz64(); + test_ilog2(); + test_ilog10(); + test_num_digits(); + + return 0; +} diff --git a/subprojects/exess/test/test_long.c b/subprojects/exess/test/test_long.c new file mode 100644 index 00000000..5631ca9f --- /dev/null +++ b/subprojects/exess/test/test_long.c @@ -0,0 +1,144 @@ +/* + Copyright 2011-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#undef NDEBUG + +#include "exess/exess.h" + +#include +#include +#include +#include + +static void +check_read(const char* const string, + const ExessStatus expected_status, + const int64_t expected_value, + const size_t expected_count) +{ + int64_t value = 0; + + const ExessResult r = exess_read_long(&value, string); + assert(r.status == expected_status); + assert(r.count == expected_count); + assert(value == expected_value); +} + +static void +test_read_long(void) +{ + // No input + check_read("", EXESS_EXPECTED_DIGIT, 0, 0); + check_read(" \f\n\r\t\v", EXESS_EXPECTED_DIGIT, 0, 6); + + // Canonical form + check_read("-1", EXESS_SUCCESS, -1, 2); + check_read("0", EXESS_SUCCESS, 0, 1); + check_read("1", EXESS_SUCCESS, 1, 1); + check_read("1234", EXESS_SUCCESS, 1234, 4); + check_read("-1234", EXESS_SUCCESS, -1234, 5); + + // Non-canonical form + check_read(" \f\n\r\t\v1234 ", EXESS_SUCCESS, 1234, 10); + check_read(" \f\n\r\t\v-1234 ", EXESS_SUCCESS, -1234, 11); + check_read(" \f\n\r\t\v+1234 ", EXESS_SUCCESS, 1234, 11); + check_read(" \f\n\r\t\v01234 ", EXESS_SUCCESS, 1234, 11); + check_read(" \f\n\r\t\v-01234 ", EXESS_SUCCESS, -1234, 12); + check_read("-01", EXESS_SUCCESS, -1, 3); + check_read("-0", EXESS_SUCCESS, 0, 2); + check_read("-00", EXESS_SUCCESS, 0, 3); + check_read("00", EXESS_SUCCESS, 0, 2); + check_read("+0", EXESS_SUCCESS, 0, 2); + check_read("+00", EXESS_SUCCESS, 0, 3); + check_read("+1", EXESS_SUCCESS, 1, 2); + check_read("+01", EXESS_SUCCESS, 1, 3); + check_read("+1234", EXESS_SUCCESS, 1234, 5); + check_read("01234", EXESS_SUCCESS, 1234, 5); + check_read("-01234", EXESS_SUCCESS, -1234, 6); + + // Limits + check_read( + "-9223372036854775808", EXESS_SUCCESS, INT64_MIN, EXESS_MAX_LONG_LENGTH); + check_read("9223372036854775807", EXESS_SUCCESS, INT64_MAX, 19); + + // Out of range + check_read("-9223372036854775809", EXESS_OUT_OF_RANGE, 0, 20); + check_read("9223372036854775808", EXESS_OUT_OF_RANGE, 0, 19); + check_read("12345678901234567890", EXESS_OUT_OF_RANGE, 0, 20); + + // Trailing garbage + check_read("1234extra", EXESS_EXPECTED_END, 1234, 4); + + // Garbage + check_read("+", EXESS_EXPECTED_DIGIT, 0, 1); + check_read("-", EXESS_EXPECTED_DIGIT, 0, 1); + check_read("true", EXESS_EXPECTED_DIGIT, 0, 0); + check_read("false", EXESS_EXPECTED_DIGIT, 0, 0); + check_read("zero", EXESS_EXPECTED_DIGIT, 0, 0); + check_read("NaN", EXESS_EXPECTED_DIGIT, 0, 0); + check_read("INF", EXESS_EXPECTED_DIGIT, 0, 0); + check_read("-INF", EXESS_EXPECTED_DIGIT, 0, 1); +} + +static void +check_write(const int64_t value, + const ExessStatus expected_status, + const size_t buf_size, + const char* const expected_string) +{ + char buf[EXESS_MAX_LONG_LENGTH + 1] = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // + 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, // + 21}; + + assert(buf_size <= sizeof(buf)); + + const ExessResult r = exess_write_long(value, buf_size, buf); + assert(!strcmp(buf, expected_string)); + assert(r.status == expected_status); + assert(r.count == strlen(buf)); + assert(r.status || exess_write_long(value, 0, NULL).count == r.count); +} + +static void +test_write_long(void) +{ + check_write(-1, EXESS_SUCCESS, 3, "-1"); + check_write(0, EXESS_SUCCESS, 2, "0"); + check_write(1, EXESS_SUCCESS, 2, "1"); + check_write(INT64_MIN, EXESS_SUCCESS, 21, "-9223372036854775808"); + check_write(INT64_MAX, EXESS_SUCCESS, 20, "9223372036854775807"); + + check_write(INT64_MIN, EXESS_NO_SPACE, 20, ""); + check_write(INT64_MAX, EXESS_NO_SPACE, 19, ""); + check_write(1234, EXESS_NO_SPACE, 4, ""); + check_write(-1234, EXESS_NO_SPACE, 5, ""); + + // Check that nothing is written when there isn't enough space + char c = 42; + const ExessResult r = exess_write_long(1234, 0, &c); + assert(c == 42); + assert(r.status == EXESS_NO_SPACE); + assert(r.count == 0); +} + +int +main(void) +{ + test_read_long(); + test_write_long(); + return 0; +} diff --git a/subprojects/exess/test/test_short.c b/subprojects/exess/test/test_short.c new file mode 100644 index 00000000..e0f3aa82 --- /dev/null +++ b/subprojects/exess/test/test_short.c @@ -0,0 +1,100 @@ +/* + Copyright 2011-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#undef NDEBUG + +#include "exess/exess.h" + +#include +#include +#include +#include + +static void +check_read(const char* const string, + const ExessStatus expected_status, + const int16_t expected_value, + const size_t expected_count) +{ + int16_t value = 0; + + const ExessResult r = exess_read_short(&value, string); + assert(value == expected_value); + assert(r.status == expected_status); + assert(r.count == expected_count); +} + +static void +test_read_short(void) +{ + // Limits + check_read("-32768", EXESS_SUCCESS, INT16_MIN, EXESS_MAX_SHORT_LENGTH); + check_read("32767", EXESS_SUCCESS, INT16_MAX, 5); + + // Out of range + check_read("-32769", EXESS_OUT_OF_RANGE, 0, 6); + check_read("32768", EXESS_OUT_OF_RANGE, 0, 5); + + // Garbage + check_read("+", EXESS_EXPECTED_DIGIT, 0, 1); +} + +static void +check_write(const int16_t value, + const ExessStatus expected_status, + const size_t buf_size, + const char* const expected_string) +{ + char buf[EXESS_MAX_SHORT_LENGTH + 1] = {1, 2, 3, 4, 5, 6}; + + assert(buf_size <= sizeof(buf)); + + const ExessResult r = exess_write_short(value, buf_size, buf); + assert(!strcmp(buf, expected_string)); + assert(r.status == expected_status); + assert(r.count == strlen(buf)); + assert(r.status || exess_write_short(value, 0, NULL).count == r.count); +} + +static void +test_write_short(void) +{ + check_write(INT16_MIN, EXESS_SUCCESS, 7, "-32768"); + check_write(INT16_MAX, EXESS_SUCCESS, 6, "32767"); +} + +static void +test_round_trip(void) +{ + int16_t value = 0; + char buf[EXESS_MAX_SHORT_LENGTH + 1] = {1, 2, 3, 4, 5, 6}; + + for (int32_t i = INT16_MIN; i <= INT16_MAX; ++i) { + assert(!exess_write_short((int16_t)i, sizeof(buf), buf).status); + assert(!exess_read_short(&value, buf).status); + assert(value == i); + } +} + +int +main(void) +{ + test_read_short(); + test_write_short(); + test_round_trip(); + + return 0; +} diff --git a/subprojects/exess/test/test_strerror.c b/subprojects/exess/test/test_strerror.c new file mode 100644 index 00000000..1e5534ad --- /dev/null +++ b/subprojects/exess/test/test_strerror.c @@ -0,0 +1,35 @@ +/* + Copyright 2011-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#undef NDEBUG + +#include "exess/exess.h" + +#include +#include + +int +main(void) +{ + for (ExessStatus i = EXESS_SUCCESS; i <= EXESS_UNSUPPORTED; + i = (ExessStatus)(i + 1)) { + assert(strlen(exess_strerror(i)) > 0); + } + + assert(!strcmp(exess_strerror((ExessStatus)9999), "Unknown error")); + + return 0; +} diff --git a/subprojects/exess/test/test_time.c b/subprojects/exess/test/test_time.c new file mode 100644 index 00000000..6cf20f6d --- /dev/null +++ b/subprojects/exess/test/test_time.c @@ -0,0 +1,222 @@ +/* + Copyright 2011-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#undef NDEBUG + +#include "float_test_data.h" +#include "int_test_data.h" +#include "time_test_utils.h" + +#include "exess/exess.h" + +#include +#include +#include +#include + +static const ExessTime nozone = {{EXESS_LOCAL}, 0, 0, 0, 0}; +static const ExessTime utc = {{0}, 12, 15, 1, 250000000}; +static const ExessTime zoned = {INIT_ZONE(11, 30), 23, 59, 59, 1000000}; +static const ExessTime high = {INIT_ZONE(11, 30), 24, 0, 0, 0}; +static const ExessTime garbage1 = {INIT_ZONE(11, 30), 0, 0, 0, 1000000000}; +static const ExessTime garbage2 = {INIT_ZONE(11, 30), 0, 0, 60, 0}; +static const ExessTime garbage3 = {INIT_ZONE(11, 30), 0, 60, 0, 0}; +static const ExessTime garbage4 = {INIT_ZONE(11, 30), 24, 0, 0, 1}; +static const ExessTime garbage5 = {INIT_ZONE(11, 30), 24, 0, 1, 0}; +static const ExessTime garbage6 = {INIT_ZONE(11, 30), 24, 1, 0, 0}; +static const ExessTime garbage7 = {INIT_ZONE(11, 30), 25, 0, 0, 0}; + +static void +check_read(const char* const string, + const ExessStatus expected_status, + const uint8_t expected_hour, + const uint8_t expected_minute, + const uint8_t expected_second, + const uint32_t expected_nanosecond, + const int8_t expected_tz_hour, + const int8_t expected_tz_minute, + const bool expected_tz_is_present, + const size_t expected_count) +{ + ExessTime value = {{0}, 0, 0, 0, 0}; + + const ExessResult r = exess_read_time(&value, string); + assert(r.status == expected_status); + assert(r.count == expected_count); + assert(value.hour == expected_hour); + assert(value.minute == expected_minute); + assert(value.second == expected_second); + assert(value.nanosecond == expected_nanosecond); + assert((!expected_tz_is_present && value.zone.quarter_hours == EXESS_LOCAL) || + value.zone.quarter_hours == + 4 * expected_tz_hour + expected_tz_minute / 15); +} + +static void +test_read_time(void) +{ + // No value + check_read("", EXESS_EXPECTED_DIGIT, 0, 0, 0, 0, 0, 0, false, 0); + check_read(" \f\n\r\t\v", EXESS_EXPECTED_DIGIT, 0, 0, 0, 0, 0, 0, false, 6); + + // Good values + check_read("13:20:00", EXESS_SUCCESS, 13, 20, 0, 0, 0, 0, false, 8); + check_read( + "13:20:30.5555", EXESS_SUCCESS, 13, 20, 30, 555500000, 0, 0, false, 13); + check_read("13:20:00-05:00", EXESS_SUCCESS, 13, 20, 0, 0, -5, 0, true, 14); + check_read("13:20:00Z", EXESS_SUCCESS, 13, 20, 0, 0, 0, 0, true, 9); + check_read("00:00:00", EXESS_SUCCESS, 0, 0, 0, 0, 0, 0, false, 8); + check_read("24:00:00", EXESS_SUCCESS, 24, 0, 0, 0, 0, 0, false, 8); + check_read("21:32:52", EXESS_SUCCESS, 21, 32, 52, 0, 0, 0, false, 8); + check_read("21:32:52+02:00", EXESS_SUCCESS, 21, 32, 52, 0, 2, 0, true, 14); + check_read("19:32:52Z", EXESS_SUCCESS, 19, 32, 52, 0, 0, 0, true, 9); + check_read("19:32:52+00:00", EXESS_SUCCESS, 19, 32, 52, 0, 0, 0, true, 14); + check_read( + "21:32:52.12679", EXESS_SUCCESS, 21, 32, 52, 126790000, 0, 0, false, 14); + + // Longest possible string + check_read("24:59:59.000000001-14:00", + EXESS_SUCCESS, + 24, + 59, + 59, + 1, + -14, + 0, + true, + EXESS_MAX_TIME_LENGTH); + + // Non-canonical form + check_read( + " \f\n\r\t\v13:20:00 ", EXESS_SUCCESS, 13, 20, 0, 0, 0, 0, false, 14); + + // Trailing garbage + check_read("13:20:00junk", EXESS_EXPECTED_SIGN, 13, 20, 0, 0, 0, 0, false, 8); + check_read("13:20:00Zjunk", EXESS_EXPECTED_END, 13, 20, 0, 0, 0, 0, true, 9); + + // Garbage + check_read("13.20.00", EXESS_EXPECTED_COLON, 13, 0, 0, 0, 0, 0, false, 2); + check_read("13:20:", EXESS_EXPECTED_DIGIT, 13, 20, 0, 0, 0, 0, false, 6); + check_read("5:20:00", EXESS_EXPECTED_DIGIT, 5, 0, 0, 0, 0, 0, false, 1); + check_read("13:20", EXESS_EXPECTED_COLON, 13, 20, 0, 0, 0, 0, false, 5); + check_read("13:20.5:00", EXESS_EXPECTED_COLON, 13, 20, 0, 0, 0, 0, false, 5); + check_read("13:65:00", EXESS_OUT_OF_RANGE, 13, 65, 0, 0, 0, 0, false, 5); + check_read("21:32", EXESS_EXPECTED_COLON, 21, 32, 0, 0, 0, 0, false, 5); + check_read("25:25:10", EXESS_OUT_OF_RANGE, 25, 0, 0, 0, 0, 0, false, 2); + check_read("-10:00:00", EXESS_EXPECTED_DIGIT, 0, 0, 0, 0, 0, 0, false, 0); + check_read("1:20:10", EXESS_EXPECTED_DIGIT, 1, 0, 0, 0, 0, 0, false, 1); + check_read("13:20:00A", EXESS_EXPECTED_SIGN, 13, 20, 0, 0, 0, 0, false, 8); +} + +static void +check_write(const ExessTime value, + const ExessStatus expected_status, + const size_t buf_size, + const char* const expected_string) +{ + char buf[EXESS_MAX_TIME_LENGTH + 1] = {1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, + 19, 20, 21, 22, 23, 24, 25}; + + assert(buf_size <= sizeof(buf)); + + const ExessResult r = exess_write_time(value, buf_size, buf); + assert(r.status == expected_status); + assert(r.count == strlen(buf)); + assert(!strcmp(buf, expected_string)); + assert(r.status || exess_write_time(value, 0, NULL).count == r.count); +} + +static void +test_write_time(void) +{ + check_write(nozone, EXESS_SUCCESS, 9, "00:00:00"); + check_write(utc, EXESS_SUCCESS, 13, "12:15:01.25Z"); + check_write(zoned, EXESS_SUCCESS, 19, "23:59:59.001+11:30"); + check_write(high, EXESS_SUCCESS, 15, "24:00:00+11:30"); + + check_write(garbage1, EXESS_BAD_VALUE, 19, ""); + check_write(garbage2, EXESS_BAD_VALUE, 19, ""); + check_write(garbage3, EXESS_BAD_VALUE, 19, ""); + check_write(garbage4, EXESS_BAD_VALUE, 19, ""); + check_write(garbage5, EXESS_BAD_VALUE, 19, ""); + check_write(garbage6, EXESS_BAD_VALUE, 19, ""); + check_write(garbage7, EXESS_BAD_VALUE, 19, ""); + + check_write(nozone, EXESS_NO_SPACE, 8, ""); + check_write(utc, EXESS_NO_SPACE, 12, ""); + check_write(zoned, EXESS_NO_SPACE, 18, ""); + check_write(zoned, EXESS_NO_SPACE, 12, ""); + check_write(high, EXESS_NO_SPACE, 14, ""); + + // Check that nothing is written when there isn't enough space + char c = 42; + const ExessResult r = exess_write_time(nozone, 0, &c); + assert(c == 42); + assert(r.status == EXESS_NO_SPACE); + assert(r.count == 0); +} + +static void +check_round_trip(const ExessTime value) +{ + ExessTime parsed_value = {{0}, 0, 0, 0, 0}; + char buf[EXESS_MAX_TIME_LENGTH + 1] = {0}; + + assert(!exess_write_time(value, sizeof(buf), buf).status); + assert(!exess_read_time(&parsed_value, buf).status); + assert(parsed_value.hour == value.hour); + assert(parsed_value.minute == value.minute); + assert(double_matches(parsed_value.second, value.second)); + assert(parsed_value.zone.quarter_hours == value.zone.quarter_hours); +} + +static void +test_round_trip(void) +{ + uint32_t rng = 0; + for (uint8_t h = 0; h < 24; ++h) { + for (uint8_t m = 0; m < 60; ++m) { + rng = lcg32(rng); + + const uint32_t ns = rng % 1000000000u; + + rng = lcg32(rng); + + const uint8_t s = (uint8_t)(rng % 60u); + const ExessTime no_zone = {{EXESS_LOCAL}, h, m, s, ns}; + const ExessTime lowest_zone = {INIT_ZONE(-14, 0), h, m, s, ns}; + const ExessTime highest_zone = {INIT_ZONE(14, 0), h, m, s, ns}; + + check_round_trip(no_zone); + check_round_trip(lowest_zone); + check_round_trip(highest_zone); + + const ExessTime value = {random_timezone(&rng), h, m, s, ns}; + check_round_trip(value); + } + } +} + +int +main(void) +{ + test_read_time(); + test_write_time(); + test_round_trip(); + + return 0; +} diff --git a/subprojects/exess/test/test_timezone.c b/subprojects/exess/test/test_timezone.c new file mode 100644 index 00000000..8ccfb12b --- /dev/null +++ b/subprojects/exess/test/test_timezone.c @@ -0,0 +1,178 @@ +/* + Copyright 2011-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#undef NDEBUG + +#include "time_test_utils.h" + +#include "exess/exess.h" + +#include +#include +#include +#include + +#include + +static const ExessTimezone missing = {EXESS_LOCAL}; +static const ExessTimezone utc = INIT_ZONE(0, 0); +static const ExessTimezone plus = INIT_ZONE(11, 30); +static const ExessTimezone minus = INIT_ZONE(-11, -30); +static const ExessTimezone slight = INIT_ZONE(0, 30); +static const ExessTimezone lowest = INIT_ZONE(-14, 0); +static const ExessTimezone highest = INIT_ZONE(14, 0); +static const ExessTimezone garbage1 = INIT_ZONE(-14, -15); +static const ExessTimezone garbage2 = INIT_ZONE(14, 15); +static const ExessTimezone garbage3 = INIT_ZONE(-15, 0); +static const ExessTimezone garbage4 = INIT_ZONE(15, 0); + +static void +check_read(const char* const string, + const ExessStatus expected_status, + const int8_t expected_hour, + const int8_t expected_minute, + const bool expected_is_present, + const size_t expected_count) +{ + // The timezone interface is not public, so we test it via time + char time_string[] = "12:00:00XXXXXX"; + strncpy(time_string + 8, string, sizeof(time_string) - 9); + + ExessTime value = {{0}, 0, 0, 0, 0}; + const ExessResult r = exess_read_time(&value, time_string); + + assert(r.status == expected_status); + assert(r.count == 8 + expected_count); + assert((!expected_is_present && value.zone.quarter_hours == EXESS_LOCAL) || + value.zone.quarter_hours == 4 * expected_hour + expected_minute / 15); +} + +static void +test_read_timezone(void) +{ + // Basic values + check_read("Z", EXESS_SUCCESS, 0, 0, true, 1); + check_read("-05:00", EXESS_SUCCESS, -5, 0, true, 6); + check_read("+02:00", EXESS_SUCCESS, 2, 0, true, 6); + check_read("+00:00", EXESS_SUCCESS, 0, 0, true, 6); + check_read("-00:00", EXESS_SUCCESS, 0, 0, true, 6); + + // Limits + check_read("-14:00", EXESS_SUCCESS, -14, 0, true, 6); + check_read("+14:00", EXESS_SUCCESS, 14, 0, true, 6); + check_read("-13:45", EXESS_SUCCESS, -13, -45, true, 6); + check_read("+13:45", EXESS_SUCCESS, 13, 45, true, 6); + + // Out of range + check_read("-14:15", EXESS_OUT_OF_RANGE, 0, 0, false, 6); + check_read("+14:15", EXESS_OUT_OF_RANGE, 0, 0, false, 6); + check_read("-15:00", EXESS_OUT_OF_RANGE, 0, 0, false, 3); + check_read("+15:00", EXESS_OUT_OF_RANGE, 0, 0, false, 3); + check_read("-13:60", EXESS_OUT_OF_RANGE, 0, 0, false, 6); + check_read("+13:60", EXESS_OUT_OF_RANGE, 0, 0, false, 6); + + // Garbage + check_read("+05:01", EXESS_UNSUPPORTED, 0, 0, false, 6); + check_read("05:00", EXESS_EXPECTED_SIGN, 0, 0, false, 0); + check_read("+5:00", EXESS_EXPECTED_DIGIT, 0, 0, false, 2); + check_read("+5:0", EXESS_EXPECTED_DIGIT, 0, 0, false, 2); + check_read("+5:", EXESS_EXPECTED_DIGIT, 0, 0, false, 2); + check_read("+:0", EXESS_EXPECTED_DIGIT, 0, 0, false, 1); + check_read("+A5:00", EXESS_EXPECTED_DIGIT, 0, 0, false, 1); + check_read("+0A:00", EXESS_EXPECTED_DIGIT, 0, 0, false, 2); + check_read("+05A00", EXESS_EXPECTED_COLON, 0, 0, false, 3); + check_read("+05:A0", EXESS_EXPECTED_DIGIT, 0, 0, false, 4); + check_read("+05:0A", EXESS_EXPECTED_DIGIT, 0, 0, false, 5); +} + +static void +check_write(const ExessTimezone value, + const ExessStatus expected_status, + const size_t buf_size, + const char* const expected_string) +{ + // The timezone interface is not public, so we test it via time + char buf[EXESS_MAX_TIME_LENGTH + 1] = {1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, + 19, 20, 21, 22, 23, 24, 25}; + + assert(buf_size <= sizeof(buf) - 8); + + const ExessTime time = {value, 12, 0, 0, 0}; + const ExessResult r = exess_write_time(time, 8 + buf_size, buf); + assert(r.status == expected_status); + assert(r.count == strlen(buf)); + assert((!buf[0] && !expected_string[0]) || !strcmp(buf + 8, expected_string)); + assert(r.status || exess_write_time(time, 0, NULL).count == r.count); +} + +static void +test_write_timezone(void) +{ + check_write(missing, EXESS_SUCCESS, 1, ""); + check_write(utc, EXESS_SUCCESS, 2, "Z"); + check_write(plus, EXESS_SUCCESS, 7, "+11:30"); + check_write(minus, EXESS_SUCCESS, 7, "-11:30"); + check_write(slight, EXESS_SUCCESS, 7, "+00:30"); + check_write(lowest, EXESS_SUCCESS, 7, "-14:00"); + check_write(highest, EXESS_SUCCESS, 7, "+14:00"); + + check_write(garbage1, EXESS_BAD_VALUE, 7, ""); + check_write(garbage2, EXESS_BAD_VALUE, 7, ""); + check_write(garbage3, EXESS_BAD_VALUE, 7, ""); + check_write(garbage4, EXESS_BAD_VALUE, 7, ""); + + check_write(utc, EXESS_NO_SPACE, 1, ""); + check_write(plus, EXESS_NO_SPACE, 6, ""); +} + +static void +check_round_trip(const ExessTimezone value) +{ + ExessTime parsed_time = {{0}, 0, 0, 0, 0}; + char buf[EXESS_MAX_TIME_LENGTH + 1] = {42}; + + const ExessTime time = {value, 12, 0, 0, 0}; + + assert(!exess_write_time(time, sizeof(buf), buf).status); + assert(!exess_read_time(&parsed_time, buf).status); + assert(!memcmp(&parsed_time.zone, &value, sizeof(ExessTimezone))); +} + +static void +test_round_trip(void) +{ + check_round_trip(lowest); + check_round_trip(highest); + + for (int8_t h = -13; h < 13; ++h) { + for (int8_t q = 0; q < 4; ++q) { + const ExessTimezone value = {(int8_t)(4 * h + q)}; + + check_round_trip(value); + } + } +} + +int +main(void) +{ + test_read_timezone(); + test_write_timezone(); + test_round_trip(); + + return 0; +} diff --git a/subprojects/exess/test/test_ubyte.c b/subprojects/exess/test/test_ubyte.c new file mode 100644 index 00000000..7129c85d --- /dev/null +++ b/subprojects/exess/test/test_ubyte.c @@ -0,0 +1,100 @@ +/* + Copyright 2011-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#undef NDEBUG + +#include "exess/exess.h" + +#include +#include +#include +#include + +static void +check_read(const char* const string, + const ExessStatus expected_status, + const uint8_t expected_value, + const size_t expected_count) +{ + uint8_t value = 0; + + const ExessResult r = exess_read_ubyte(&value, string); + assert(r.status == expected_status); + assert(r.count == expected_count); + assert(value == expected_value); +} + +static void +test_read_ubyte(void) +{ + // Limits + check_read("0", EXESS_SUCCESS, 0, 1); + check_read("255", EXESS_SUCCESS, UINT8_MAX, EXESS_MAX_UBYTE_LENGTH); + + // Out of range + check_read("256", EXESS_OUT_OF_RANGE, 0, 3); + + // Garbage + check_read("-1", EXESS_EXPECTED_DIGIT, 0, 0); + check_read("+", EXESS_EXPECTED_DIGIT, 0, 0); +} + +static void +check_write(const uint8_t value, + const ExessStatus expected_status, + const size_t buf_size, + const char* const expected_string) +{ + char buf[EXESS_MAX_UBYTE_LENGTH + 1] = {1, 2, 3, 4}; + + assert(buf_size <= sizeof(buf)); + + const ExessResult r = exess_write_ubyte(value, buf_size, buf); + assert(r.status == expected_status); + assert(r.count == strlen(buf)); + assert(!strcmp(buf, expected_string)); + assert(r.status || exess_write_ubyte(value, 0, NULL).count == r.count); +} + +static void +test_write_ubyte(void) +{ + check_write(0, EXESS_SUCCESS, 2, "0"); + check_write(UINT8_MAX, EXESS_SUCCESS, 4, "255"); +} + +static void +test_round_trip(void) +{ + uint8_t value = 0; + char buf[EXESS_MAX_UBYTE_LENGTH + 1] = {1, 2, 3, 4}; + + for (uint16_t i = 0; i <= UINT8_MAX; ++i) { + assert(!exess_write_ubyte((uint8_t)i, sizeof(buf), buf).status); + assert(!exess_read_ubyte(&value, buf).status); + assert(value == i); + } +} + +int +main(void) +{ + test_read_ubyte(); + test_write_ubyte(); + test_round_trip(); + + return 0; +} diff --git a/subprojects/exess/test/test_uint.c b/subprojects/exess/test/test_uint.c new file mode 100644 index 00000000..5889fc20 --- /dev/null +++ b/subprojects/exess/test/test_uint.c @@ -0,0 +1,128 @@ +/* + Copyright 2011-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#undef NDEBUG + +#include "int_test_data.h" +#include "num_test_utils.h" + +#include "exess/exess.h" + +#include +#include +#include +#include + +static void +check_read(const char* const string, + const ExessStatus expected_status, + const uint32_t expected_value, + const size_t expected_count) +{ + uint32_t value = 0; + const ExessResult r = exess_read_uint(&value, string); + + assert(r.status == expected_status); + assert(r.count == expected_count); + assert(value == expected_value); +} + +static void +test_read_uint(void) +{ + // Limits + check_read("0", EXESS_SUCCESS, 0, 1); + check_read("4294967295", EXESS_SUCCESS, UINT32_MAX, EXESS_MAX_UINT_LENGTH); + + // Out of range + check_read("4294967296", EXESS_OUT_OF_RANGE, 0, 10); + + // Garbage + check_read("-1", EXESS_EXPECTED_DIGIT, 0, 0); + check_read("+", EXESS_EXPECTED_DIGIT, 0, 0); +} + +static void +check_write(const uint32_t value, + const ExessStatus expected_status, + const size_t buf_size, + const char* const expected_string) +{ + char buf[EXESS_MAX_UINT_LENGTH + 1] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; + + assert(buf_size <= sizeof(buf)); + + const ExessResult r = exess_write_uint(value, buf_size, buf); + assert(r.status == expected_status); + assert(r.count == strlen(buf)); + assert(!strcmp(buf, expected_string)); + assert(r.status || exess_write_uint(value, 0, NULL).count == r.count); +} + +static void +test_write_uint(void) +{ + check_write(0u, EXESS_SUCCESS, 2, "0"); + check_write(UINT32_MAX, EXESS_SUCCESS, 11, "4294967295"); +} + +static void +test_round_trip(const ExessNumTestOptions opts) +{ + uint32_t parsed_value = 0; + char buf[EXESS_MAX_UINT_LENGTH + 1] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; + + if (opts.exhaustive) { + fprintf(stderr, "Testing xsd:unsignedInt exhaustively\n"); + + for (uint64_t i = 0u; i <= UINT32_MAX; ++i) { + assert(!exess_write_uint((uint32_t)i, sizeof(buf), buf).status); + assert(!exess_read_uint(&parsed_value, buf).status); + assert(parsed_value == i); + + print_num_test_progress(i, UINT32_MAX); + } + } else { + fprintf( + stderr, "Testing xsd:unsignedInt randomly with seed %u\n", opts.seed); + + uint32_t value = opts.seed; + for (uint64_t i = 0; i < opts.n_tests; ++i) { + value = lcg32(value); + + assert(!exess_write_uint(value, sizeof(buf), buf).status); + assert(!exess_read_uint(&parsed_value, buf).status); + assert(parsed_value == value); + + print_num_test_progress(i, opts.n_tests); + } + } +} + +int +main(int argc, char** argv) +{ + const ExessNumTestOptions opts = parse_num_test_options(argc, argv); + if (opts.error) { + return 1; + } + + test_read_uint(); + test_write_uint(); + test_round_trip(opts); + + return 0; +} diff --git a/subprojects/exess/test/test_ulong.c b/subprojects/exess/test/test_ulong.c new file mode 100644 index 00000000..ba8ba05f --- /dev/null +++ b/subprojects/exess/test/test_ulong.c @@ -0,0 +1,126 @@ +/* + Copyright 2011-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#undef NDEBUG + +#include "exess/exess.h" + +#include +#include +#include +#include + +static void +check_read(const char* const string, + const ExessStatus expected_status, + const uint64_t expected_value, + const size_t expected_count) +{ + uint64_t value = 0; + const ExessResult r = exess_read_ulong(&value, string); + + assert(r.status == expected_status); + assert(r.count == expected_count); + assert(value == expected_value); +} + +static void +test_read_ulong(void) +{ + // No input + check_read("", EXESS_EXPECTED_DIGIT, 0, 0); + check_read(" \f\n\r\t\v", EXESS_EXPECTED_DIGIT, 0, 6); + + // Canonical form + check_read("0", EXESS_SUCCESS, 0, 1); + check_read("1234", EXESS_SUCCESS, 1234, 4); + + // Non-canonical form + check_read(" \f\n\r\t\v1234 ", EXESS_SUCCESS, 1234, 10); + check_read(" \f\n\r\t\v01234 ", EXESS_SUCCESS, 1234, 11); + check_read("01234", EXESS_SUCCESS, 1234, 5); + check_read("00", EXESS_SUCCESS, 0, 2); + + // Limits + check_read("0", EXESS_SUCCESS, 0, 1); + check_read( + "18446744073709551615", EXESS_SUCCESS, UINT64_MAX, EXESS_MAX_ULONG_LENGTH); + + // Out of range + check_read("18446744073709551616", EXESS_OUT_OF_RANGE, 0, 19); + + // Trailing garbage + check_read("1234extra", EXESS_EXPECTED_END, 1234, 4); + + // Garbage + check_read(" \f\n\r\t\v+1234 ", EXESS_EXPECTED_DIGIT, 0, 6); + check_read("+1234", EXESS_EXPECTED_DIGIT, 0, 0); + check_read("+0", EXESS_EXPECTED_DIGIT, 0, 0); + check_read("+", EXESS_EXPECTED_DIGIT, 0, 0); + check_read("-", EXESS_EXPECTED_DIGIT, 0, 0); + check_read("true", EXESS_EXPECTED_DIGIT, 0, 0); + check_read("false", EXESS_EXPECTED_DIGIT, 0, 0); + check_read("zero", EXESS_EXPECTED_DIGIT, 0, 0); + check_read("NaN", EXESS_EXPECTED_DIGIT, 0, 0); + check_read("INF", EXESS_EXPECTED_DIGIT, 0, 0); + check_read("-INF", EXESS_EXPECTED_DIGIT, 0, 0); +} + +static void +check_write(const uint64_t value, + const ExessStatus expected_status, + const size_t buf_size, + const char* const expected_string) +{ + char buf[EXESS_MAX_ULONG_LENGTH + 1] = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // + 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, // + 21}; + + assert(buf_size <= sizeof(buf)); + + const ExessResult r = exess_write_ulong(value, buf_size, buf); + + assert(r.status == expected_status); + assert(r.count == strlen(buf)); + assert(!strcmp(buf, expected_string)); + assert(r.status || exess_write_ulong(value, 0, NULL).count == r.count); +} + +static void +test_write_ulong(void) +{ + check_write(0u, EXESS_SUCCESS, 2, "0"); + check_write(1u, EXESS_SUCCESS, 2, "1"); + check_write(UINT64_MAX, EXESS_SUCCESS, 21, "18446744073709551615"); + + check_write(1234u, EXESS_NO_SPACE, 4, ""); + + // Check that nothing is written when there isn't enough space + char c = 42; + const ExessResult r = exess_write_ulong(1234u, 0, &c); + assert(c == 42); + assert(r.status == EXESS_NO_SPACE); + assert(r.count == 0); +} + +int +main(void) +{ + test_read_ulong(); + test_write_ulong(); + return 0; +} diff --git a/subprojects/exess/test/test_ushort.c b/subprojects/exess/test/test_ushort.c new file mode 100644 index 00000000..056e182a --- /dev/null +++ b/subprojects/exess/test/test_ushort.c @@ -0,0 +1,100 @@ +/* + Copyright 2011-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#undef NDEBUG + +#include "exess/exess.h" + +#include +#include +#include +#include + +static void +check_read(const char* const string, + const ExessStatus expected_status, + const uint16_t expected_value, + const size_t expected_count) +{ + uint16_t value = 0; + + const ExessResult r = exess_read_ushort(&value, string); + assert(value == expected_value); + assert(r.status == expected_status); + assert(r.count == expected_count); +} + +static void +test_read_ushort(void) +{ + // Limits + check_read("0", EXESS_SUCCESS, 0, 1); + check_read("65535", EXESS_SUCCESS, UINT16_MAX, EXESS_MAX_USHORT_LENGTH); + + // Out of range + check_read("65536", EXESS_OUT_OF_RANGE, 0, 5); + + // Garbage + check_read("-1", EXESS_EXPECTED_DIGIT, 0, 0); + check_read("+", EXESS_EXPECTED_DIGIT, 0, 0); +} + +static void +check_write(const uint16_t value, + const ExessStatus expected_status, + const size_t buf_size, + const char* const expected_string) +{ + char buf[EXESS_MAX_USHORT_LENGTH + 1] = {1, 2, 3, 4, 5, 6}; + + assert(buf_size <= sizeof(buf)); + + const ExessResult r = exess_write_ushort(value, buf_size, buf); + assert(!strcmp(buf, expected_string)); + assert(r.status == expected_status); + assert(r.count == strlen(buf)); + assert(r.status || exess_write_ushort(value, 0, NULL).count == r.count); +} + +static void +test_write_ushort(void) +{ + check_write(0u, EXESS_SUCCESS, 2, "0"); + check_write(UINT16_MAX, EXESS_SUCCESS, 6, "65535"); +} + +static void +test_round_trip(void) +{ + uint16_t value = 0; + char buf[EXESS_MAX_USHORT_LENGTH + 1] = {1, 2, 3, 4, 5, 6}; + + for (uint32_t i = 0; i <= UINT16_MAX; ++i) { + assert(!exess_write_ushort((uint16_t)i, sizeof(buf), buf).status); + assert(!exess_read_ushort(&value, buf).status); + assert(value == i); + } +} + +int +main(void) +{ + test_read_ushort(); + test_write_ushort(); + test_round_trip(); + + return 0; +} diff --git a/subprojects/exess/test/test_variant.c b/subprojects/exess/test/test_variant.c new file mode 100644 index 00000000..7ef7803d --- /dev/null +++ b/subprojects/exess/test/test_variant.c @@ -0,0 +1,301 @@ +/* + Copyright 2011-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#undef NDEBUG + +#include "exess/exess.h" + +#include +#include +#include +#include + +#define CHECK_POINTEE_EQUALS(p, v) assert((p) && (*(p) == (v))) + +static const ExessDuration duration = {14, + 3 * 24 * 60 * 60 + 4 * 60 * 60 + 5 * 60 + + 6, + 0}; + +static const ExessDateTime datetime = {2001, 2, 3, false, 4, 5, 6, 0}; +static const ExessTime time = {{0}, 1, 2, 3, 0}; +static const ExessDate date = {2001, 2, 3, {0}}; + +static void +check_read(ExessVariant* const variant, + const ExessDatatype datatype, + const char* const string, + const ExessStatus expected_status, + const size_t expected_count) +{ + const ExessResult r = exess_read_variant(variant, datatype, string); + + assert(r.status == expected_status); + assert(r.count == expected_count); + assert(variant->datatype == datatype); +} + +static void +test_read_variant(void) +{ + ExessVariant variant; + + check_read(&variant, EXESS_NOTHING, "42", EXESS_UNSUPPORTED, 0); + + check_read(&variant, EXESS_DECIMAL, "1.2", EXESS_SUCCESS, 3); + CHECK_POINTEE_EQUALS(exess_get_double(&variant), 1.2); + + check_read(&variant, EXESS_DOUBLE, "3.4", EXESS_SUCCESS, 3); + assert(variant.value.as_double == 3.4); + + check_read(&variant, EXESS_FLOAT, "5.6", EXESS_SUCCESS, 3); + assert(variant.value.as_float == 5.6f); + + check_read(&variant, EXESS_BOOLEAN, "true", EXESS_SUCCESS, 4); + assert(variant.value.as_bool); + + check_read(&variant, EXESS_INTEGER, "7", EXESS_SUCCESS, 1); + assert(variant.value.as_long == 7); + + check_read( + &variant, EXESS_NON_POSITIVE_INTEGER, "f", EXESS_EXPECTED_DIGIT, 0); + check_read(&variant, EXESS_NON_POSITIVE_INTEGER, "1", EXESS_OUT_OF_RANGE, 1); + check_read(&variant, EXESS_NON_POSITIVE_INTEGER, "-8", EXESS_SUCCESS, 2); + assert(variant.value.as_long == -8); + + check_read(&variant, EXESS_NEGATIVE_INTEGER, "f", EXESS_EXPECTED_DIGIT, 0); + check_read(&variant, EXESS_NEGATIVE_INTEGER, "1", EXESS_OUT_OF_RANGE, 1); + check_read(&variant, EXESS_NEGATIVE_INTEGER, "-9", EXESS_SUCCESS, 2); + assert(variant.value.as_long == -9); + + check_read(&variant, EXESS_LONG, "10", EXESS_SUCCESS, 2); + assert(variant.value.as_long == 10); + + check_read(&variant, EXESS_INT, "11", EXESS_SUCCESS, 2); + assert(variant.value.as_int == 11); + + check_read(&variant, EXESS_SHORT, "12", EXESS_SUCCESS, 2); + assert(variant.value.as_short == 12); + + check_read(&variant, EXESS_BYTE, "13", EXESS_SUCCESS, 2); + assert(variant.value.as_byte == 13); + + check_read(&variant, EXESS_ULONG, "14", EXESS_SUCCESS, 2); + assert(variant.value.as_long == 14); + + check_read(&variant, EXESS_UINT, "15", EXESS_SUCCESS, 2); + assert(variant.value.as_int == 15); + + check_read(&variant, EXESS_USHORT, "16", EXESS_SUCCESS, 2); + assert(variant.value.as_short == 16); + + check_read(&variant, EXESS_UBYTE, "17", EXESS_SUCCESS, 2); + assert(variant.value.as_byte == 17); + + check_read(&variant, EXESS_POSITIVE_INTEGER, "-1", EXESS_EXPECTED_DIGIT, 0); + check_read(&variant, EXESS_POSITIVE_INTEGER, "0", EXESS_OUT_OF_RANGE, 1); + check_read(&variant, EXESS_POSITIVE_INTEGER, "18", EXESS_SUCCESS, 2); + assert(variant.value.as_long == 18); + + check_read(&variant, EXESS_DATE, "2001-01-02", EXESS_SUCCESS, 10); + assert(variant.value.as_date.year == 2001); + assert(variant.value.as_date.month == 1); + assert(variant.value.as_date.day == 2); + + check_read(&variant, EXESS_TIME, "12:15:01.25", EXESS_SUCCESS, 11); + assert(variant.value.as_time.hour == 12); + assert(variant.value.as_time.minute == 15); + assert(variant.value.as_time.second == 1); + assert(variant.value.as_time.nanosecond == 250000000); + + char blob_data[] = {0, 0, 0}; + + variant.datatype = EXESS_HEX; + variant.value.as_blob.size = sizeof(blob_data); + variant.value.as_blob.data = blob_data; + check_read(&variant, EXESS_HEX, "666F6F", EXESS_SUCCESS, 6); + assert(!strncmp(blob_data, "foo", sizeof(blob_data))); + + variant.datatype = EXESS_BASE64; + variant.value.as_blob.size = sizeof(blob_data); + variant.value.as_blob.data = blob_data; + check_read(&variant, EXESS_BASE64, "Zm9v", EXESS_SUCCESS, 4); + assert(!strncmp(blob_data, "foo", sizeof(blob_data))); +} + +static void +test_variant_string_length(void) +{ + const ExessVariant variant = {EXESS_DECIMAL, {.as_double = 12.3456}}; + + assert(exess_write_variant(variant, 0, NULL).count == 7); +} + +static void +check_write(const ExessVariant value, + const ExessStatus expected_status, + const size_t buf_size, + const char* const expected_string) +{ + char buf[328] = {42}; + + assert(buf_size <= sizeof(buf)); + + const ExessResult r = exess_write_variant(value, buf_size, buf); + assert(r.status == expected_status); + if (buf_size > 0) { + assert(r.count == strlen(buf)); + assert(!strcmp(buf, expected_string)); + } +} + +static void +test_write_variant(void) +{ + char blob_data[] = {'f', 'o', 'o'}; + const ExessBlob blob = {sizeof(blob_data), blob_data}; + + const ExessVariant a_nothing = exess_make_nothing(EXESS_SUCCESS); + const ExessVariant a_bool = exess_make_boolean(true); + const ExessVariant a_decimal = exess_make_decimal(1.2); + const ExessVariant a_double = exess_make_double(3.4); + const ExessVariant a_float = exess_make_float(5.6f); + const ExessVariant a_long = exess_make_long(7); + const ExessVariant a_int = exess_make_int(8); + const ExessVariant a_short = exess_make_short(9); + const ExessVariant a_byte = exess_make_byte(10); + const ExessVariant a_ulong = exess_make_ulong(11); + const ExessVariant a_uint = exess_make_uint(12); + const ExessVariant a_ushort = exess_make_ushort(13); + const ExessVariant a_ubyte = exess_make_ubyte(14); + const ExessVariant a_duration = exess_make_duration(duration); + const ExessVariant a_datetime = exess_make_datetime(datetime); + const ExessVariant a_time = exess_make_time(time); + const ExessVariant a_date = exess_make_date(date); + const ExessVariant a_hex = exess_make_hex(blob); + const ExessVariant a_base64 = exess_make_base64(blob); + + check_write(a_nothing, EXESS_BAD_VALUE, 0, ""); + check_write(a_nothing, EXESS_BAD_VALUE, 1, ""); + check_write(a_decimal, EXESS_SUCCESS, 4, "1.2"); + check_write(a_double, EXESS_SUCCESS, 6, "3.4E0"); + check_write(a_float, EXESS_SUCCESS, 12, "5.5999999E0"); + check_write(a_bool, EXESS_SUCCESS, 5, "true"); + check_write(a_long, EXESS_SUCCESS, 2, "7"); + check_write(a_int, EXESS_SUCCESS, 2, "8"); + check_write(a_short, EXESS_SUCCESS, 2, "9"); + check_write(a_byte, EXESS_SUCCESS, 3, "10"); + check_write(a_ulong, EXESS_SUCCESS, 3, "11"); + check_write(a_uint, EXESS_SUCCESS, 3, "12"); + check_write(a_ushort, EXESS_SUCCESS, 3, "13"); + check_write(a_ubyte, EXESS_SUCCESS, 3, "14"); + check_write(a_duration, EXESS_SUCCESS, 15, "P1Y2M3DT4H5M6S"); + check_write(a_datetime, EXESS_SUCCESS, 40, "2001-02-03T04:05:06"); + check_write(a_time, EXESS_SUCCESS, 40, "01:02:03Z"); + check_write(a_date, EXESS_SUCCESS, 40, "2001-02-03Z"); + check_write(a_hex, EXESS_SUCCESS, 7, "666F6F"); + check_write(a_base64, EXESS_SUCCESS, 5, "Zm9v"); + + const ExessBlob null_blob = {0, NULL}; + + const ExessVariant null_hex = exess_make_hex(null_blob); + check_write(null_hex, EXESS_BAD_VALUE, 99, ""); + + const ExessVariant null_base64 = exess_make_base64(null_blob); + check_write(null_base64, EXESS_BAD_VALUE, 99, ""); +} + +static void +test_make_get(void) +{ + char blob_data[] = {'f', 'o', 'o'}; + const ExessBlob blob = {sizeof(blob_data), blob_data}; + + const ExessVariant a_nothing = exess_make_nothing(EXESS_NO_SPACE); + const ExessVariant a_bool = exess_make_boolean(true); + const ExessVariant a_decimal = exess_make_decimal(1.2); + const ExessVariant a_double = exess_make_double(3.4); + const ExessVariant a_float = exess_make_float(5.6f); + const ExessVariant a_long = exess_make_long(7); + const ExessVariant a_int = exess_make_int(8); + const ExessVariant a_short = exess_make_short(9); + const ExessVariant a_byte = exess_make_byte(10); + const ExessVariant a_ulong = exess_make_ulong(11); + const ExessVariant a_uint = exess_make_uint(12); + const ExessVariant a_ushort = exess_make_ushort(13); + const ExessVariant a_ubyte = exess_make_ubyte(14); + const ExessVariant a_duration = exess_make_duration(duration); + const ExessVariant a_datetime = exess_make_datetime(datetime); + const ExessVariant a_time = exess_make_time(time); + const ExessVariant a_date = exess_make_date(date); + const ExessVariant a_hex = exess_make_hex(blob); + const ExessVariant a_base64 = exess_make_base64(blob); + + // Different types as status + assert(exess_get_status(&a_nothing) == EXESS_NO_SPACE); + assert(exess_get_status(&a_bool) == EXESS_SUCCESS); + + // Basic successful get + CHECK_POINTEE_EQUALS(exess_get_boolean(&a_bool), true); + CHECK_POINTEE_EQUALS(exess_get_double(&a_decimal), 1.2); + CHECK_POINTEE_EQUALS(exess_get_double(&a_double), 3.4); + CHECK_POINTEE_EQUALS(exess_get_float(&a_float), 5.6f); + CHECK_POINTEE_EQUALS(exess_get_long(&a_long), 7); + CHECK_POINTEE_EQUALS(exess_get_int(&a_int), 8); + CHECK_POINTEE_EQUALS(exess_get_short(&a_short), 9); + CHECK_POINTEE_EQUALS(exess_get_byte(&a_byte), 10); + CHECK_POINTEE_EQUALS(exess_get_ulong(&a_ulong), 11u); + CHECK_POINTEE_EQUALS(exess_get_uint(&a_uint), 12u); + CHECK_POINTEE_EQUALS(exess_get_ushort(&a_ushort), 13u); + CHECK_POINTEE_EQUALS(exess_get_ubyte(&a_ubyte), 14u); + assert(!memcmp(exess_get_duration(&a_duration), &duration, sizeof(duration))); + assert(!memcmp(exess_get_datetime(&a_datetime), &datetime, sizeof(datetime))); + assert(!memcmp(exess_get_time(&a_time), &time, sizeof(time))); + assert(!memcmp(exess_get_date(&a_date), &date, sizeof(date))); + assert(exess_get_blob(&a_hex)->size == sizeof(blob_data)); + assert(exess_get_blob(&a_hex)->data == blob_data); + assert(exess_get_blob(&a_base64)->size == sizeof(blob_data)); + assert(exess_get_blob(&a_base64)->data == blob_data); + + // Unsuccessful get + assert(!exess_get_boolean(&a_int)); + assert(!exess_get_double(&a_int)); + assert(!exess_get_float(&a_int)); + assert(!exess_get_long(&a_bool)); + assert(!exess_get_int(&a_bool)); + assert(!exess_get_short(&a_int)); + assert(!exess_get_byte(&a_int)); + assert(!exess_get_ulong(&a_int)); + assert(!exess_get_uint(&a_int)); + assert(!exess_get_ushort(&a_int)); + assert(!exess_get_ubyte(&a_int)); + assert(!exess_get_duration(&a_int)); + assert(!exess_get_datetime(&a_int)); + assert(!exess_get_time(&a_int)); + assert(!exess_get_date(&a_int)); + assert(!exess_get_blob(&a_int)); +} + +int +main(void) +{ + test_read_variant(); + test_variant_string_length(); + test_write_variant(); + test_make_get(); + + return 0; +} diff --git a/subprojects/exess/test/time_test_utils.h b/subprojects/exess/test/time_test_utils.h new file mode 100644 index 00000000..00993207 --- /dev/null +++ b/subprojects/exess/test/time_test_utils.h @@ -0,0 +1,52 @@ +/* + Copyright 2011-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "int_test_data.h" + +#include "exess/exess.h" + +#include + +#define INIT_ZONE(hour, minute) \ + { \ + 4 * (hour) + (minute) / 15 \ + } + +static inline ExessTimezone +random_timezone(uint32_t* rng) +{ + *rng = lcg32(*rng); + + const int8_t hour = (int8_t)((*rng % 27) - 13); + + *rng = lcg32(*rng); + + const int8_t minute = (int8_t)((hour < 0 ? -1 : 1) * (int32_t)(*rng % 60)); + + const ExessTimezone zone = {(int8_t)(4 * hour + minute / 15)}; + return zone; +} + +static inline bool +timezone_matches(const ExessTimezone zone, + const int8_t expected_hour, + const int8_t expected_minute, + const bool expected_is_present) + +{ + return (!expected_is_present && zone.quarter_hours == EXESS_LOCAL) || + zone.quarter_hours == 4 * expected_hour + expected_minute / 15; +} -- cgit v1.2.1