/** * Copyright (c) 2026 Dominic Masters * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ #include "dusktest.h" #include "asset/loader/locale/assetlocaleloader.h" #include "asset/asset.h" #include "util/memory.h" #include "util/string.h" #include // ============================================================ // Test locale file (gettext PO format) // ============================================================ static const char_t *LOCALE_EN = "msgid \"\"\n" "msgstr \"\"\n" "\"Plural-Forms: nplurals=2; plural=(n != 1 ? 1 : 0);\\n\"\n" "\n" "msgid \"greeting\"\n" "msgstr \"Hello, World!\"\n" "\n" "msgid \"item\"\n" "msgid_plural \"items\"\n" "msgstr[0] \"one item\"\n" "msgstr[1] \"many items\"\n" "\n" "msgid \"score\"\n" "msgstr \"Score: %d\"\n" "\n" "msgid \"player\"\n" "msgstr \"Player: %s\"\n"; // ============================================================ // In-memory ZIP fixture (shared across all ZIP-based tests) // ============================================================ static zip_t *g_zip = NULL; static assetlocalefile_t g_locale; static int locale_setup(void **state) { zip_error_t err; zip_error_init(&err); // Phase 1: write the zip to a growable buffer source zip_source_t *write_src = zip_source_buffer_create(NULL, 0, 1, &err); if(!write_src) return -1; zip_t *za = zip_open_from_source(write_src, ZIP_TRUNCATE, &err); if(!za) { zip_source_free(write_src); return -1; } size_t flen = strlen(LOCALE_EN); zip_source_t *fs = zip_source_buffer(za, LOCALE_EN, flen, 0); if(zip_file_add(za, "en.locale", fs, ZIP_FL_OVERWRITE) < 0) { zip_close(za); return -1; } // Keep write_src alive after zip_close so we can read the bytes back out zip_source_keep(write_src); if(zip_close(za) != 0) { zip_source_free(write_src); return -1; } // Phase 2: extract the raw zip bytes from the write buffer. // zip_source_stat must be called before zip_source_open on a written source. zip_stat_t zs; memset(&zs, 0, sizeof(zs)); if(zip_source_stat(write_src, &zs) != 0 || !(zs.valid & ZIP_STAT_SIZE)) { zip_source_free(write_src); return -1; } void *zipbuf = malloc((size_t)zs.size); if(!zipbuf) { zip_source_free(write_src); return -1; } if(zip_source_open(write_src) != 0) { free(zipbuf); zip_source_free(write_src); return -1; } zip_source_read(write_src, zipbuf, (zip_uint64_t)zs.size); zip_source_close(write_src); zip_source_free(write_src); // Phase 3: open a fresh read-only archive from the extracted bytes. // The archive takes ownership of the source (and thus zipbuf via freep=1). zip_error_init(&err); zip_source_t *read_src = zip_source_buffer_create( zipbuf, (zip_uint64_t)zs.size, 1, &err ); if(!read_src) { free(zipbuf); return -1; } g_zip = zip_open_from_source(read_src, 0, &err); if(!g_zip) { zip_source_free(read_src); return -1; } ASSET.zip = g_zip; // Init locale file and parse the header memoryZero(&g_locale, sizeof(g_locale)); errorret_t ret = assetFileInit(&g_locale.file, "en.locale", NULL, NULL); if(errorIsNotOk(ret)) { errorCatch(ret); goto fail; } ret = assetFileOpen(&g_locale.file); if(errorIsNotOk(ret)) { errorCatch(ret); goto fail; } char_t header[512]; ret = assetLocaleGetString(&g_locale, "", 0, header, sizeof(header)); if(errorIsNotOk(ret)) { errorCatch(ret); assetFileClose(&g_locale.file); goto fail; } ret = assetLocaleParseHeader(&g_locale, header, sizeof(header)); if(errorIsNotOk(ret)) { errorCatch(ret); assetFileClose(&g_locale.file); goto fail; } return 0; fail: zip_close(g_zip); g_zip = NULL; ASSET.zip = NULL; return -1; } static int locale_teardown(void **state) { if(g_locale.file.zipFile != NULL) { errorret_t ret = assetFileClose(&g_locale.file); if(errorIsNotOk(ret)) errorCatch(ret); } if(g_zip != NULL) { zip_close(g_zip); // also frees the read_src and zipbuf g_zip = NULL; } ASSET.zip = NULL; if(g_locale.cache != NULL) memoryFree(g_locale.cache); memoryZero(&g_locale, sizeof(g_locale)); return 0; } // ============================================================ // assetLocaleParseHeader - pure tests (no ZIP required) // ============================================================ static void test_parseHeader_english(void **state) { assetlocalefile_t locale; memoryZero(&locale, sizeof(locale)); char_t hdr[] = "Plural-Forms: nplurals=2; plural=(n != 1 ? 1 : 0);\n"; errorret_t ret = assetLocaleParseHeader(&locale, hdr, sizeof(hdr)); assert_true(errorIsOk(ret)); assert_int_equal(locale.pluralStateCount, 2); assert_int_equal(locale.pluralDefaultIndex, 0); assert_int_equal(locale.pluralOps[0], ASSET_LOCALE_PLURAL_OP_NOT_EQUAL); assert_int_equal(locale.pluralValues[0], 1); assert_int_equal(locale.pluralIndices[0], 1); assert_int_equal(memoryGetAllocatedCount(), 0); } static void test_parseHeader_singular(void **state) { assetlocalefile_t locale; memoryZero(&locale, sizeof(locale)); char_t hdr[] = "Plural-Forms: nplurals=1; plural=(0);\n"; errorret_t ret = assetLocaleParseHeader(&locale, hdr, sizeof(hdr)); assert_true(errorIsOk(ret)); assert_int_equal(locale.pluralStateCount, 1); assert_int_equal(locale.pluralDefaultIndex, 0); assert_int_equal(memoryGetAllocatedCount(), 0); } static void test_parseHeader_less_than(void **state) { assetlocalefile_t locale; memoryZero(&locale, sizeof(locale)); char_t hdr[] = "Plural-Forms: nplurals=2; plural=(n < 2 ? 0 : 1);\n"; errorret_t ret = assetLocaleParseHeader(&locale, hdr, sizeof(hdr)); assert_true(errorIsOk(ret)); assert_int_equal(locale.pluralStateCount, 2); assert_int_equal(locale.pluralOps[0], ASSET_LOCALE_PLURAL_OP_LESS); assert_int_equal(locale.pluralValues[0], 2); assert_int_equal(locale.pluralIndices[0], 0); assert_int_equal(locale.pluralDefaultIndex, 1); assert_int_equal(memoryGetAllocatedCount(), 0); } static void test_parseHeader_greater_equal(void **state) { assetlocalefile_t locale; memoryZero(&locale, sizeof(locale)); char_t hdr[] = "Plural-Forms: nplurals=2; plural=(n >= 2 ? 1 : 0);\n"; errorret_t ret = assetLocaleParseHeader(&locale, hdr, sizeof(hdr)); assert_true(errorIsOk(ret)); assert_int_equal(locale.pluralStateCount, 2); assert_int_equal(locale.pluralOps[0], ASSET_LOCALE_PLURAL_OP_GREATER_EQUAL); assert_int_equal(locale.pluralValues[0], 2); assert_int_equal(locale.pluralIndices[0], 1); assert_int_equal(locale.pluralDefaultIndex, 0); assert_int_equal(memoryGetAllocatedCount(), 0); } static void test_parseHeader_no_plural_forms(void **state) { assetlocalefile_t locale; memoryZero(&locale, sizeof(locale)); char_t hdr[] = "Content-Type: text/plain; charset=UTF-8\n"; errorret_t ret = assetLocaleParseHeader(&locale, hdr, sizeof(hdr)); assert_true(errorIsOk(ret)); assert_int_equal(locale.pluralStateCount, 0); assert_int_equal(memoryGetAllocatedCount(), 0); } static void test_parseHeader_error_nplurals_zero(void **state) { assetlocalefile_t locale; memoryZero(&locale, sizeof(locale)); char_t hdr[] = "Plural-Forms: nplurals=0; plural=(0);\n"; errorret_t ret = assetLocaleParseHeader(&locale, hdr, sizeof(hdr)); assert_true(errorIsNotOk(ret)); errorCatch(ret); assert_int_equal(memoryGetAllocatedCount(), 0); } static void test_parseHeader_error_nplurals_too_large(void **state) { assetlocalefile_t locale; memoryZero(&locale, sizeof(locale)); char_t hdr[] = "Plural-Forms: nplurals=7; plural=(0);\n"; errorret_t ret = assetLocaleParseHeader(&locale, hdr, sizeof(hdr)); assert_true(errorIsNotOk(ret)); errorCatch(ret); assert_int_equal(memoryGetAllocatedCount(), 0); } static void test_parseHeader_error_missing_nplurals(void **state) { assetlocalefile_t locale; memoryZero(&locale, sizeof(locale)); char_t hdr[] = "Plural-Forms: plural=(0);\n"; errorret_t ret = assetLocaleParseHeader(&locale, hdr, sizeof(hdr)); assert_true(errorIsNotOk(ret)); errorCatch(ret); assert_int_equal(memoryGetAllocatedCount(), 0); } // ============================================================ // assetLocaleEvaluatePlural - pure tests // ============================================================ static void test_evaluatePlural_english_singular(void **state) { assetlocalefile_t locale; memoryZero(&locale, sizeof(locale)); char_t hdr[] = "Plural-Forms: nplurals=2; plural=(n != 1 ? 1 : 0);\n"; assetLocaleParseHeader(&locale, hdr, sizeof(hdr)); assert_int_equal(assetLocaleEvaluatePlural(&locale, 1), 0); assert_int_equal(memoryGetAllocatedCount(), 0); } static void test_evaluatePlural_english_plural(void **state) { assetlocalefile_t locale; memoryZero(&locale, sizeof(locale)); char_t hdr[] = "Plural-Forms: nplurals=2; plural=(n != 1 ? 1 : 0);\n"; assetLocaleParseHeader(&locale, hdr, sizeof(hdr)); assert_int_equal(assetLocaleEvaluatePlural(&locale, 0), 1); assert_int_equal(assetLocaleEvaluatePlural(&locale, 2), 1); assert_int_equal(assetLocaleEvaluatePlural(&locale, 100), 1); assert_int_equal(memoryGetAllocatedCount(), 0); } static void test_evaluatePlural_singular_only(void **state) { assetlocalefile_t locale; memoryZero(&locale, sizeof(locale)); char_t hdr[] = "Plural-Forms: nplurals=1; plural=(0);\n"; assetLocaleParseHeader(&locale, hdr, sizeof(hdr)); assert_int_equal(assetLocaleEvaluatePlural(&locale, 0), 0); assert_int_equal(assetLocaleEvaluatePlural(&locale, 1), 0); assert_int_equal(assetLocaleEvaluatePlural(&locale, 99), 0); assert_int_equal(memoryGetAllocatedCount(), 0); } static void test_evaluatePlural_less_than_boundary(void **state) { assetlocalefile_t locale; memoryZero(&locale, sizeof(locale)); char_t hdr[] = "Plural-Forms: nplurals=2; plural=(n < 2 ? 0 : 1);\n"; assetLocaleParseHeader(&locale, hdr, sizeof(hdr)); assert_int_equal(assetLocaleEvaluatePlural(&locale, 0), 0); assert_int_equal(assetLocaleEvaluatePlural(&locale, 1), 0); assert_int_equal(assetLocaleEvaluatePlural(&locale, 2), 1); assert_int_equal(assetLocaleEvaluatePlural(&locale, 10), 1); assert_int_equal(memoryGetAllocatedCount(), 0); } // ============================================================ // assetLocaleGetString - ZIP-based tests // ============================================================ static void test_getString_simple(void **state) { char_t result[256]; errorret_t ret = assetLocaleGetString(&g_locale, "greeting", 0, result, sizeof(result)); assert_true(errorIsOk(ret)); assert_string_equal(result, "Hello, World!"); // 1, not 0: the locale-string cache is lazily allocated on first use (the // header lookup in locale_setup already triggered it) and lives for the // rest of this file's lifetime - it's a bounded one-time cost, not a leak. assert_int_equal(memoryGetAllocatedCount(), 1); } static void test_getString_plural_singular(void **state) { char_t result[256]; errorret_t ret = assetLocaleGetString(&g_locale, "item", 1, result, sizeof(result)); assert_true(errorIsOk(ret)); assert_string_equal(result, "one item"); assert_int_equal(memoryGetAllocatedCount(), 1);// see test_getString_simple } static void test_getString_plural_many(void **state) { char_t result[256]; errorret_t ret = assetLocaleGetString(&g_locale, "item", 5, result, sizeof(result)); assert_true(errorIsOk(ret)); assert_string_equal(result, "many items"); assert_int_equal(memoryGetAllocatedCount(), 1);// see test_getString_simple } static void test_getString_multiple_calls(void **state) { char_t a[256], b[256]; errorret_t ret = assetLocaleGetString(&g_locale, "greeting", 0, a, sizeof(a)); assert_true(errorIsOk(ret)); // Second call is now served straight from the cache the first call // populated, rather than rewinding the file and re-scanning from scratch. ret = assetLocaleGetString(&g_locale, "greeting", 0, b, sizeof(b)); assert_true(errorIsOk(ret)); assert_string_equal(a, b); assert_int_equal(memoryGetAllocatedCount(), 1);// see test_getString_simple } static void test_getString_missing_id(void **state) { char_t result[256]; errorret_t ret = assetLocaleGetString(&g_locale, "nonexistent", 0, result, sizeof(result)); assert_true(errorIsNotOk(ret)); errorCatch(ret); assert_int_equal(memoryGetAllocatedCount(), 1);// see test_getString_simple } // ============================================================ // assetLocaleCacheFind / assetLocaleCacheInsert - pure tests // ============================================================ // These call the cache directly rather than through assetLocaleGetString - // no open file is ever needed since the cache never touches file->file. static void test_cache_findMiss_onEmptyCache(void **state) { assetlocalefile_t locale; memoryZero(&locale, sizeof(locale)); char_t result[64]; bool_t hit = true; errorret_t ret = assetLocaleCacheFind(&locale, "greeting", 0, result, sizeof(result), &hit); assert_true(errorIsOk(ret)); assert_false(hit); assert_null(locale.cache);// a pure miss must not allocate anything assert_int_equal(memoryGetAllocatedCount(), 0); } static void test_cache_insertThenFind_hits(void **state) { assetlocalefile_t locale; memoryZero(&locale, sizeof(locale)); assetLocaleCacheInsert(&locale, "greeting", 0, "Hello, World!"); assert_non_null(locale.cache); char_t result[64]; bool_t hit = false; errorret_t ret = assetLocaleCacheFind(&locale, "greeting", 0, result, sizeof(result), &hit); assert_true(errorIsOk(ret)); assert_true(hit); assert_string_equal(result, "Hello, World!"); memoryFree(locale.cache); assert_int_equal(memoryGetAllocatedCount(), 0); } static void test_cache_distinguishesByPluralCount(void **state) { assetlocalefile_t locale; memoryZero(&locale, sizeof(locale)); assetLocaleCacheInsert(&locale, "item", 1, "one item"); assetLocaleCacheInsert(&locale, "item", 5, "many items"); char_t result[64]; bool_t hit = false; errorret_t ret = assetLocaleCacheFind(&locale, "item", 1, result, sizeof(result), &hit); assert_true(errorIsOk(ret)); assert_true(hit); assert_string_equal(result, "one item"); ret = assetLocaleCacheFind(&locale, "item", 5, result, sizeof(result), &hit); assert_true(errorIsOk(ret)); assert_true(hit); assert_string_equal(result, "many items"); memoryFree(locale.cache); } static void test_cache_find_missOnDifferentMessageId(void **state) { assetlocalefile_t locale; memoryZero(&locale, sizeof(locale)); assetLocaleCacheInsert(&locale, "greeting", 0, "Hello, World!"); char_t result[64]; bool_t hit = true; errorret_t ret = assetLocaleCacheFind(&locale, "farewell", 0, result, sizeof(result), &hit); assert_true(errorIsOk(ret)); assert_false(hit); memoryFree(locale.cache); } static void test_cache_find_bufferTooSmall_errors(void **state) { assetlocalefile_t locale; memoryZero(&locale, sizeof(locale)); assetLocaleCacheInsert(&locale, "greeting", 0, "Hello, World!"); char_t result[4];// too small for "Hello, World!" bool_t hit = false; errorret_t ret = assetLocaleCacheFind(&locale, "greeting", 0, result, sizeof(result), &hit); assert_true(errorIsNotOk(ret)); errorCatch(ret); memoryFree(locale.cache); } static void test_cache_insert_tooLongMessageId_notCached(void **state) { assetlocalefile_t locale; memoryZero(&locale, sizeof(locale)); char_t longId[ASSET_LOCALE_CACHE_KEY_MAX + 1]; memorySet(longId, 'a', sizeof(longId) - 1); longId[sizeof(longId) - 1] = '\0'; assetLocaleCacheInsert(&locale, longId, 0, "value"); // Nothing should have been cached - the cache itself is never even // allocated, since this was the only insert attempted. assert_null(locale.cache); assert_int_equal(memoryGetAllocatedCount(), 0); } static void test_cache_insert_tooLongValue_notCached(void **state) { assetlocalefile_t locale; memoryZero(&locale, sizeof(locale)); char_t longValue[ASSET_LOCALE_CACHE_VALUE_MAX + 1]; memorySet(longValue, 'a', sizeof(longValue) - 1); longValue[sizeof(longValue) - 1] = '\0'; assetLocaleCacheInsert(&locale, "greeting", 0, longValue); assert_null(locale.cache); assert_int_equal(memoryGetAllocatedCount(), 0); } static void test_cache_insert_evictsLeastRecentlyUsed(void **state) { assetlocalefile_t locale; memoryZero(&locale, sizeof(locale)); // Fill the cache completely, "id0" first (so it's the first to become // least-recently-used) through "id127" last (most-recently-used). char_t key[32], value[32]; for(int32_t i = 0; i < ASSET_LOCALE_CACHE_COUNT; i++) { stringFormat(key, sizeof(key), "id%d", i); stringFormat(value, sizeof(value), "value%d", i); assetLocaleCacheInsert(&locale, key, 0, value); } // One more insert should evict "id0", the least-recently-used entry. assetLocaleCacheInsert(&locale, "newcomer", 0, "new value"); char_t result[32]; bool_t hit = true; errorret_t ret = assetLocaleCacheFind(&locale, "id0", 0, result, sizeof(result), &hit); assert_true(errorIsOk(ret)); assert_false(hit); // But "id1" (and everything after it) should have survived. ret = assetLocaleCacheFind(&locale, "id1", 0, result, sizeof(result), &hit); assert_true(errorIsOk(ret)); assert_true(hit); assert_string_equal(result, "value1"); memoryFree(locale.cache); } static void test_cache_find_movesHitToFront(void **state) { assetlocalefile_t locale; memoryZero(&locale, sizeof(locale)); // Fill the cache, then immediately re-touch "id0" so it becomes // most-recently-used instead of least-recently-used. char_t key[32], value[32]; for(int32_t i = 0; i < ASSET_LOCALE_CACHE_COUNT; i++) { stringFormat(key, sizeof(key), "id%d", i); stringFormat(value, sizeof(value), "value%d", i); assetLocaleCacheInsert(&locale, key, 0, value); } char_t result[32]; bool_t hit = false; errorret_t ret = assetLocaleCacheFind(&locale, "id0", 0, result, sizeof(result), &hit); assert_true(errorIsOk(ret)); assert_true(hit); // Without the touch above, this insert would have evicted "id0" (the // original least-recently-used entry) - it should now evict "id1" instead. assetLocaleCacheInsert(&locale, "newcomer", 0, "new value"); ret = assetLocaleCacheFind(&locale, "id0", 0, result, sizeof(result), &hit); assert_true(errorIsOk(ret)); assert_true(hit); ret = assetLocaleCacheFind(&locale, "id1", 0, result, sizeof(result), &hit); assert_true(errorIsOk(ret)); assert_false(hit); memoryFree(locale.cache); } static void test_cache_nullAsserts(void **state) { assetlocalefile_t locale; memoryZero(&locale, sizeof(locale)); char_t result[64]; bool_t hit; expect_assert_failure( assetLocaleCacheFind(NULL, "greeting", 0, result, sizeof(result), &hit) ); expect_assert_failure( assetLocaleCacheFind(&locale, NULL, 0, result, sizeof(result), &hit) ); expect_assert_failure( assetLocaleCacheFind(&locale, "greeting", 0, result, sizeof(result), NULL) ); expect_assert_failure(assetLocaleCacheInsert(NULL, "greeting", 0, "value")); expect_assert_failure(assetLocaleCacheInsert(&locale, NULL, 0, "value")); expect_assert_failure(assetLocaleCacheInsert(&locale, "greeting", 0, NULL)); } static void test_localeDispose_freesCache(void **state) { zip_error_t err; zip_error_init(&err); zip_source_t *write_src = zip_source_buffer_create(NULL, 0, 1, &err); assert_non_null(write_src); zip_t *za = zip_open_from_source(write_src, ZIP_TRUNCATE, &err); assert_non_null(za); size_t flen = strlen(LOCALE_EN); zip_source_t *fs = zip_source_buffer(za, LOCALE_EN, flen, 0); assert_true(zip_file_add(za, "en.locale", fs, ZIP_FL_OVERWRITE) >= 0); zip_source_keep(write_src); assert_int_equal(zip_close(za), 0); zip_stat_t zs; memset(&zs, 0, sizeof(zs)); assert_int_equal(zip_source_stat(write_src, &zs), 0); void *zipbuf = malloc((size_t)zs.size); assert_non_null(zipbuf); assert_int_equal(zip_source_open(write_src), 0); zip_source_read(write_src, zipbuf, (zip_uint64_t)zs.size); zip_source_close(write_src); zip_source_free(write_src); zip_error_init(&err); zip_source_t *read_src = zip_source_buffer_create(zipbuf, (zip_uint64_t)zs.size, 1, &err); assert_non_null(read_src); zip_t *zip = zip_open_from_source(read_src, 0, &err); assert_non_null(zip); ASSET.zip = zip; assetentry_t entry; memoryZero(&entry, sizeof(entry)); entry.type = ASSET_LOADER_TYPE_LOCALE; errorret_t ret = assetFileInit(&entry.data.locale.file, "en.locale", NULL, NULL); assert_true(errorIsOk(ret)); ret = assetFileOpen(&entry.data.locale.file); assert_true(errorIsOk(ret)); char_t header[512]; ret = assetLocaleGetString(&entry.data.locale, "", 0, header, sizeof(header)); assert_true(errorIsOk(ret)); assert_non_null(entry.data.locale.cache);// the header lookup populated it ret = assetLocaleDispose(&entry); assert_true(errorIsOk(ret)); assert_null(entry.data.locale.cache); assert_int_equal(memoryGetAllocatedCount(), 0); zip_close(zip); ASSET.zip = NULL; } // ============================================================ // assetLocaleGetStringWithArgs - ZIP-based tests // ============================================================ static void test_getStringWithArgs_int(void **state) { assetlocalearg_t args[] = { { .type = ASSET_LOCALE_ARG_INT, .intValue = 42 } }; char_t result[256]; errorret_t ret = assetLocaleGetStringWithArgs( &g_locale, "score", 0, result, sizeof(result), args, 1 ); assert_true(errorIsOk(ret)); assert_string_equal(result, "Score: 42"); assert_int_equal(memoryGetAllocatedCount(), 1);// see test_getString_simple } static void test_getStringWithArgs_string(void **state) { assetlocalearg_t args[] = { { .type = ASSET_LOCALE_ARG_STRING, .stringValue = "Alice" } }; char_t result[256]; errorret_t ret = assetLocaleGetStringWithArgs( &g_locale, "player", 0, result, sizeof(result), args, 1 ); assert_true(errorIsOk(ret)); assert_string_equal(result, "Player: Alice"); assert_int_equal(memoryGetAllocatedCount(), 1);// see test_getString_simple } // ============================================================ // main // ============================================================ int main(void) { assertInit(); const struct CMUnitTest tests[] = { // parseHeader - pure cmocka_unit_test(test_parseHeader_english), cmocka_unit_test(test_parseHeader_singular), cmocka_unit_test(test_parseHeader_less_than), cmocka_unit_test(test_parseHeader_greater_equal), cmocka_unit_test(test_parseHeader_no_plural_forms), cmocka_unit_test(test_parseHeader_error_nplurals_zero), cmocka_unit_test(test_parseHeader_error_nplurals_too_large), cmocka_unit_test(test_parseHeader_error_missing_nplurals), // evaluatePlural - pure cmocka_unit_test(test_evaluatePlural_english_singular), cmocka_unit_test(test_evaluatePlural_english_plural), cmocka_unit_test(test_evaluatePlural_singular_only), cmocka_unit_test(test_evaluatePlural_less_than_boundary), // getString - in-memory ZIP cmocka_unit_test_setup_teardown(test_getString_simple, locale_setup, locale_teardown), cmocka_unit_test_setup_teardown(test_getString_plural_singular, locale_setup, locale_teardown), cmocka_unit_test_setup_teardown(test_getString_plural_many, locale_setup, locale_teardown), cmocka_unit_test_setup_teardown(test_getString_multiple_calls, locale_setup, locale_teardown), cmocka_unit_test_setup_teardown(test_getString_missing_id, locale_setup, locale_teardown), // getStringWithArgs - in-memory ZIP cmocka_unit_test_setup_teardown(test_getStringWithArgs_int, locale_setup, locale_teardown), cmocka_unit_test_setup_teardown(test_getStringWithArgs_string, locale_setup, locale_teardown), // assetLocaleCacheFind / assetLocaleCacheInsert - pure cmocka_unit_test(test_cache_findMiss_onEmptyCache), cmocka_unit_test(test_cache_insertThenFind_hits), cmocka_unit_test(test_cache_distinguishesByPluralCount), cmocka_unit_test(test_cache_find_missOnDifferentMessageId), cmocka_unit_test(test_cache_find_bufferTooSmall_errors), cmocka_unit_test(test_cache_insert_tooLongMessageId_notCached), cmocka_unit_test(test_cache_insert_tooLongValue_notCached), cmocka_unit_test(test_cache_insert_evictsLeastRecentlyUsed), cmocka_unit_test(test_cache_find_movesHitToFront), cmocka_unit_test(test_cache_nullAsserts), // assetLocaleDispose - in-memory ZIP cmocka_unit_test(test_localeDispose_freesCache), }; return cmocka_run_group_tests(tests, NULL, NULL); }