#include #include #include #include #include "support/test.h" #include "base/allocator.h" #include "mcp/json.h" #include "storage/codec.h" #include "sys/encoding.h" #include "sys/system.h" static void test_allocator_boundaries(tiny_test_state_t *state) { void *first; void *resized; size_t baseline = tiny_allocator_outstanding(); TINY_TEST_CASE(state, "allocator overflow failure positions and balance"); TINY_EXPECT(state, tiny_calloc(SIZE_MAX, 2) == NULL); tiny_allocator_fail_after(0); TINY_EXPECT(state, tiny_alloc(1) == NULL); TINY_EXPECT_EQ_U64(state, 1, tiny_allocator_call_count()); tiny_allocator_reset(); first = tiny_alloc(1); TINY_EXPECT(state, first != NULL); tiny_allocator_fail_after(0); resized = tiny_realloc(first, 2); TINY_EXPECT(state, resized == NULL); tiny_allocator_reset(); tiny_free(first); TINY_EXPECT_EQ_U64(state, baseline, tiny_allocator_outstanding()); first = tiny_alloc(1); TINY_EXPECT(state, tiny_realloc(first, 0) == NULL); TINY_EXPECT_EQ_U64(state, baseline, tiny_allocator_outstanding()); tiny_free(NULL); } static void test_encoding_valid(tiny_test_state_t *state) { static const struct vector { const char *utf8; size_t utf8_length; uint16_t utf16[3]; size_t utf16_length; } vectors[] = { { "", 0, { 0 }, 0 }, { "\x7f", 1, { 0x007f }, 1 }, { "\xc2\x80", 2, { 0x0080 }, 1 }, { "\xdf\xbf", 2, { 0x07ff }, 1 }, { "\xe0\xa0\x80", 3, { 0x0800 }, 1 }, { "\xed\x9f\xbf", 3, { 0xd7ff }, 1 }, { "\xee\x80\x80", 3, { 0xe000 }, 1 }, { "\xef\xbf\xbf", 3, { 0xffff }, 1 }, { "\xf0\x90\x80\x80", 4, { 0xd800, 0xdc00 }, 2 }, { "\xf4\x8f\xbf\xbf", 4, { 0xdbff, 0xdfff }, 2 }, }; size_t index; TINY_TEST_CASE(state, "encoding valid scalar boundaries"); for (index = 0; index < sizeof(vectors) / sizeof(vectors[0]); ++index) { uint16_t *wide = NULL; char *roundtrip = NULL; size_t wide_length = SIZE_MAX, roundtrip_length = SIZE_MAX; TINY_EXPECT( state, tiny_utf8_to_utf16(vectors[index].utf8, vectors[index].utf8_length, &wide, &wide_length) == TINY_ENCODING_OK); TINY_EXPECT_EQ_U64(state, vectors[index].utf16_length, wide_length); TINY_EXPECT_BYTES(state, vectors[index].utf16, wide, wide_length * sizeof(*wide)); TINY_EXPECT(state, tiny_utf16_to_utf8(wide, wide_length, &roundtrip, &roundtrip_length) == TINY_ENCODING_OK); TINY_EXPECT_EQ_U64(state, vectors[index].utf8_length, roundtrip_length); TINY_EXPECT_BYTES(state, vectors[index].utf8, roundtrip, roundtrip_length); free(roundtrip); free(wide); } } static void test_encoding_invalid(tiny_test_state_t *state) { static const unsigned char invalid[][4] = { { 0x80, 0, 0, 0 }, { 0xc0, 0x80, 0, 0 }, { 0xe0, 0x80, 0x80, 0 }, { 0xed, 0xa0, 0x80, 0 }, { 0xf0, 0x80, 0x80, 0x80 }, { 0xf4, 0x90, 0x80, 0x80 }, }; static const size_t lengths[] = { 1, 2, 3, 3, 4, 4 }; size_t index; TINY_TEST_CASE(state, "encoding invalid and allocation failure"); for (index = 0; index < sizeof(lengths) / sizeof(lengths[0]); ++index) { uint16_t *output = (uint16_t *)(uintptr_t)1; size_t length = SIZE_MAX; TINY_EXPECT(state, tiny_utf8_to_utf16((const char *)invalid[index], lengths[index], &output, &length) == TINY_ENCODING_INVALID); TINY_EXPECT(state, output == NULL && length == 0); } { uint16_t high[] = { 0xd800 }; uint16_t low[] = { 0xdc00 }; char *output = NULL; size_t length = 0; TINY_EXPECT(state, tiny_utf16_to_utf8(high, 1, &output, &length) == TINY_ENCODING_INVALID); TINY_EXPECT(state, tiny_utf16_to_utf8(low, 1, &output, &length) == TINY_ENCODING_INVALID); } TINY_EXPECT(state, tiny_utf8_to_utf16(NULL, 1, NULL, NULL) == TINY_ENCODING_INVALID); { uint16_t *output = NULL; size_t length = 0; tiny_allocator_fail_after(0); TINY_EXPECT(state, tiny_utf8_to_utf16("a", 1, &output, &length) == TINY_ENCODING_NO_MEMORY); TINY_EXPECT(state, output == NULL && length == 0); tiny_allocator_reset(); } } static void test_codec_alignment(tiny_test_state_t *state) { unsigned char storage[32]; size_t offset; TINY_TEST_CASE(state, "codec every alignment offset"); for (offset = 0; offset < 16; ++offset) { unsigned char *position = storage + offset; memset(storage, 0, sizeof(storage)); tiny_codec_put_u32(position, UINT32_MAX); TINY_EXPECT_EQ_U64(state, UINT32_MAX, tiny_codec_get_u32(position)); tiny_codec_put_u64(position, UINT64_MAX); TINY_EXPECT_EQ_U64(state, UINT64_MAX, tiny_codec_get_u64(position)); } TINY_EXPECT_EQ_U64(state, 0, tiny_codec_crc32(NULL, 1)); TINY_EXPECT_EQ_U64(state, 0, tiny_codec_crc32(NULL, 0)); tiny_codec_put_u32(NULL, 1); tiny_codec_put_u64(NULL, 1); TINY_EXPECT_EQ_U64(state, 0, tiny_codec_get_u32(NULL)); TINY_EXPECT_EQ_U64(state, 0, tiny_codec_get_u64(NULL)); TINY_EXPECT_EQ_U64(state, 0xcbf43926U, tiny_codec_crc32((const unsigned char *)"123456789", 9)); } static void test_json_boundaries(tiny_test_state_t *state) { tiny_json_buffer_t buffer; static const char controls[] = "\b\f\n\r\t\x01"; TINY_TEST_CASE(state, "json growth escaping integer and OOM"); tiny_json_init(&buffer); tiny_json_init(NULL); tiny_json_free(NULL); TINY_EXPECT(state, !tiny_json_raw(NULL, "x")); TINY_EXPECT(state, !tiny_json_raw(&buffer, NULL)); TINY_EXPECT(state, !tiny_json_raw_n(&buffer, NULL, 1)); TINY_EXPECT(state, !tiny_json_string(&buffer, NULL)); TINY_EXPECT(state, tiny_json_raw_n(&buffer, "", 0)); TINY_EXPECT(state, tiny_json_string(&buffer, controls)); TINY_EXPECT(state, strstr(buffer.data, "\\b\\f\\n\\r\\t\\u0001") != NULL); TINY_EXPECT(state, tiny_json_i64(&buffer, LLONG_MIN)); TINY_EXPECT(state, tiny_json_u64(&buffer, ULLONG_MAX)); tiny_json_free(&buffer); tiny_allocator_fail_after(0); tiny_json_init(&buffer); TINY_EXPECT(state, !tiny_json_raw(&buffer, "allocation")); TINY_EXPECT(state, buffer.failed); tiny_json_free(&buffer); tiny_allocator_reset(); } static void test_sys_boundaries(tiny_test_state_t *state) { char *joined; char *directory; char *path; tiny_sys_file_t file; uint32_t actual = UINT32_MAX; uint64_t size = UINT64_MAX; static const unsigned char bytes[] = { 1, 2, 3, 4 }; unsigned char received[sizeof(bytes)] = { 0 }; TINY_TEST_CASE(state, "sys public boundary values"); tiny_sys_test_reset(); TINY_EXPECT(state, tiny_sys_temp_directory_create(NULL) == NULL); TINY_EXPECT(state, tiny_sys_path_join(NULL, "name") == NULL); TINY_EXPECT(state, tiny_sys_path_join("directory", NULL) == NULL); joined = tiny_sys_path_join("", "name"); TINY_EXPECT(state, joined != NULL && strcmp(joined, "name") == 0); free(joined); tiny_sys_file_init(NULL); TINY_EXPECT(state, !tiny_sys_file_valid(NULL)); tiny_sys_file_init(&file); TINY_EXPECT(state, !tiny_sys_file_valid(&file)); TINY_EXPECT(state, tiny_sys_file_open(NULL, "unused", TINY_SYS_FILE_READ) == TINY_SYS_ERROR); TINY_EXPECT(state, tiny_sys_file_open(&file, NULL, TINY_SYS_FILE_READ) == TINY_SYS_ERROR && !tiny_sys_file_valid(&file)); TINY_EXPECT(state, !tiny_sys_file_seek(&file, 0, (tiny_sys_seek_origin_t)-1)); TINY_EXPECT(state, !tiny_sys_file_size(&file, &size)); TINY_EXPECT_EQ_U64(state, 0, size); TINY_EXPECT(state, !tiny_sys_file_read(&file, received, 1, &actual)); TINY_EXPECT_EQ_U64(state, 0, actual); directory = tiny_sys_temp_directory_create("tcm"); TINY_EXPECT(state, directory != NULL); path = directory == NULL ? NULL : tiny_sys_path_join(directory, "fault.bin"); TINY_EXPECT(state, path != NULL); if (path != NULL) { TINY_EXPECT(state, tiny_sys_file_open(&file, path, TINY_SYS_FILE_WRITE_NEW) == TINY_SYS_OK); tiny_sys_test_max_transfer(1); TINY_EXPECT(state, tiny_sys_file_write(&file, bytes, sizeof(bytes), &actual)); TINY_EXPECT_EQ_U64(state, 1, actual); TINY_EXPECT(state, tiny_sys_test_fail_after(TINY_SYS_TEST_FLUSH, 1)); TINY_EXPECT(state, !tiny_sys_file_flush(&file)); tiny_sys_test_reset(); TINY_EXPECT(state, tiny_sys_file_flush(&file)); tiny_sys_file_close(&file); TINY_EXPECT(state, tiny_sys_test_fail_after(TINY_SYS_TEST_OPEN, 1)); TINY_EXPECT(state, tiny_sys_file_open(&file, path, TINY_SYS_FILE_READ) == TINY_SYS_ERROR && !tiny_sys_file_valid(&file)); tiny_sys_test_reset(); TINY_EXPECT(state, tiny_sys_file_open(&file, path, TINY_SYS_FILE_READ) == TINY_SYS_OK); TINY_EXPECT(state, tiny_sys_test_fail_after(TINY_SYS_TEST_READ, 1)); actual = UINT32_MAX; TINY_EXPECT(state, !tiny_sys_file_read(&file, received, sizeof(received), &actual)); TINY_EXPECT_EQ_U64(state, 0, actual); tiny_sys_test_reset(); tiny_sys_file_close(&file); TINY_EXPECT(state, tiny_sys_file_remove(path)); } free(path); if (directory != NULL) { TINY_EXPECT(state, tiny_sys_directory_remove(directory)); } free(directory); tiny_sys_test_reset(); } int main(int argc, char **argv) { tiny_test_state_t state = { "initial", 0, 0 }; const char *group = argc > 1 ? argv[1] : "all"; tiny_allocator_reset(); if (strcmp(group, "all") == 0 || strcmp(group, "allocator") == 0) { test_allocator_boundaries(&state); } if (strcmp(group, "all") == 0 || strcmp(group, "encoding") == 0) { test_encoding_valid(&state); test_encoding_invalid(&state); } if (strcmp(group, "all") == 0 || strcmp(group, "codec") == 0) { test_codec_alignment(&state); } if (strcmp(group, "all") == 0 || strcmp(group, "json") == 0) { test_json_boundaries(&state); } if (strcmp(group, "all") == 0 || strcmp(group, "sys") == 0) { test_sys_boundaries(&state); } TINY_TEST_CASE(&state, "allocator leak balance"); TINY_EXPECT_EQ_U64(&state, 0, tiny_allocator_outstanding()); return tiny_test_finish(&state); }