Add some more tests, prepping for asset testing
This commit is contained in:
@@ -184,6 +184,7 @@ errorret_t assetUpdate(void) {
|
||||
loading->entry->state == ASSET_ENTRY_STATE_ERROR,
|
||||
"Loader did not set entry state to error on failed load."
|
||||
);
|
||||
errorCatch(errorPrint(ret));
|
||||
}
|
||||
|
||||
threadMutexUnlock(&loading->mutex);
|
||||
|
||||
@@ -303,16 +303,24 @@ errorret_t assetLocaleLineSkipBlanks(
|
||||
while(!reader->eof) {
|
||||
// Skip blank lines
|
||||
if(lineBuffer[0] == '\0') {
|
||||
errorChain(assetFileLineReaderNext(reader));
|
||||
errorret_t r = assetFileLineReaderNext(reader);
|
||||
if(errorIsNotOk(r)) {
|
||||
errorCatch(r);
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip comment lines
|
||||
if(lineBuffer[0] == '#') {
|
||||
errorChain(assetFileLineReaderNext(reader));
|
||||
errorret_t r = assetFileLineReaderNext(reader);
|
||||
if(errorIsNotOk(r)) {
|
||||
errorCatch(r);
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// Is line only spaces?
|
||||
size_t lineLength = strlen((char_t *)lineBuffer);
|
||||
size_t i;
|
||||
@@ -325,7 +333,11 @@ errorret_t assetLocaleLineSkipBlanks(
|
||||
}
|
||||
|
||||
if(onlySpaces) {
|
||||
errorChain(assetFileLineReaderNext(reader));
|
||||
errorret_t r = assetFileLineReaderNext(reader);
|
||||
if(errorIsNotOk(r)) {
|
||||
errorCatch(r);
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
@@ -361,10 +373,18 @@ errorret_t assetLocaleLineUnbuffer(
|
||||
|
||||
// Now start buffering lines out
|
||||
while(!reader->eof) {
|
||||
errorChain(assetFileLineReaderNext(reader));
|
||||
errorret_t r = assetFileLineReaderNext(reader);
|
||||
if(errorIsNotOk(r)) {
|
||||
errorCatch(r);
|
||||
break;
|
||||
}
|
||||
|
||||
// Skip blank lines
|
||||
errorChain(assetLocaleLineSkipBlanks(reader, lineBuffer));
|
||||
r = assetLocaleLineSkipBlanks(reader, lineBuffer);
|
||||
if(errorIsNotOk(r)) {
|
||||
errorCatch(r);
|
||||
break;
|
||||
}
|
||||
|
||||
// Skip starting spaces
|
||||
char_t *ptr = (char_t *)lineBuffer;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
@@ -11,11 +11,22 @@
|
||||
typedef struct assetloading_s assetloading_t;
|
||||
typedef struct assetentry_s assetentry_t;
|
||||
|
||||
/** Input passed to the locale loader — currently unused. */
|
||||
typedef struct { void *nothing; } assetlocaleloaderinput_t;
|
||||
|
||||
/** Per-slot scratch data used while the locale file is loading. */
|
||||
typedef struct { void *nothing; } assetlocaleloaderloading_t;
|
||||
|
||||
/** Maximum number of distinct plural forms a locale file may declare. */
|
||||
#define ASSET_LOCALE_FILE_PLURAL_FORM_COUNT 6
|
||||
|
||||
/**
|
||||
* Comparison operator used in a plural-form expression.
|
||||
*
|
||||
* Each condition in the plural-form header is evaluated as
|
||||
* `n <op> value`
|
||||
* where `n` is the runtime plural count.
|
||||
*/
|
||||
typedef enum {
|
||||
ASSET_LOCALE_PLURAL_OP_EQUAL,
|
||||
ASSET_LOCALE_PLURAL_OP_NOT_EQUAL,
|
||||
@@ -25,13 +36,24 @@ typedef enum {
|
||||
ASSET_LOCALE_PLURAL_OP_GREATER_EQUAL
|
||||
} assetlocalepluraloperation_t;
|
||||
|
||||
/**
|
||||
* Discriminator tag for a locale string argument.
|
||||
* @see assetlocalearg_t
|
||||
*/
|
||||
typedef enum {
|
||||
ASSET_LOCALE_ARG_STRING,
|
||||
ASSET_LOCALE_ARG_INT,
|
||||
ASSET_LOCALE_ARG_FLOAT
|
||||
} assetlocaleargtype_t;
|
||||
|
||||
/**
|
||||
* A single typed argument for locale string substitution.
|
||||
*
|
||||
* Used with @ref assetLocaleGetStringWithArgs to fill `%s`, `%d`, or `%f`
|
||||
* placeholders inside a translated string.
|
||||
*/
|
||||
typedef struct {
|
||||
/** Which union member is active. */
|
||||
assetlocaleargtype_t type;
|
||||
union {
|
||||
const char_t *stringValue;
|
||||
@@ -40,20 +62,72 @@ typedef struct {
|
||||
};
|
||||
} assetlocalearg_t;
|
||||
|
||||
/**
|
||||
* Runtime state for an open locale file.
|
||||
*
|
||||
* Loaded once by @ref assetLocaleLoaderSync and kept alive for the lifetime
|
||||
* of the asset entry. The plural-form fields are populated from the PO header
|
||||
* by @ref assetLocaleParseHeader.
|
||||
*
|
||||
* Plural evaluation works as a linear scan over `pluralStateCount - 1`
|
||||
* conditions; if none match, `pluralDefaultIndex` is returned.
|
||||
*/
|
||||
typedef struct {
|
||||
/** Underlying file handle used to rewind and re-read the PO data. */
|
||||
assetfile_t file;
|
||||
|
||||
/** Comparison operator for each conditional plural clause. */
|
||||
assetlocalepluraloperation_t pluralOps[ASSET_LOCALE_FILE_PLURAL_FORM_COUNT];
|
||||
|
||||
/** Right-hand value for each conditional plural clause. */
|
||||
int32_t pluralValues[ASSET_LOCALE_FILE_PLURAL_FORM_COUNT];
|
||||
|
||||
/** Form index returned when the corresponding condition is true. */
|
||||
int32_t pluralIndices[ASSET_LOCALE_FILE_PLURAL_FORM_COUNT];
|
||||
|
||||
/** Total number of plural forms declared by `nplurals=`. */
|
||||
uint8_t pluralStateCount;
|
||||
|
||||
/** Form index used when no conditional clause matches. */
|
||||
uint8_t pluralDefaultIndex;
|
||||
} assetlocalefile_t;
|
||||
|
||||
/** Convenience alias — the loaded output type of a locale asset entry. */
|
||||
typedef assetlocalefile_t assetlocaleoutput_t;
|
||||
|
||||
/**
|
||||
* Synchronous loader callback. Opens and validates the locale file, reads the
|
||||
* PO header, and parses plural-form rules. Sets entry state to
|
||||
* `ASSET_ENTRY_STATE_LOADED` on success or `ASSET_ENTRY_STATE_ERROR` on
|
||||
* failure.
|
||||
*
|
||||
* @param loading The loading slot for this asset entry.
|
||||
* @return OK on success, error otherwise.
|
||||
*/
|
||||
errorret_t assetLocaleLoaderSync(assetloading_t *loading);
|
||||
|
||||
/**
|
||||
* Dispose callback. Closes the open file handle and zeros the locale data.
|
||||
*
|
||||
* @param entry The asset entry to dispose.
|
||||
* @return OK on success, error otherwise.
|
||||
*/
|
||||
errorret_t assetLocaleDispose(assetentry_t *entry);
|
||||
|
||||
/**
|
||||
* Parses the `Plural-Forms:` line from a PO header string and populates the
|
||||
* plural-form fields of `localeFile`. If no `Plural-Forms:` key is present
|
||||
* the function returns OK without modifying the struct.
|
||||
*
|
||||
* Supports the `nplurals=N; plural=(<expr>);` syntax where `<expr>` is a
|
||||
* chain of `n <op> value ? index : ...` ternary conditions ending with a
|
||||
* fallback index.
|
||||
*
|
||||
* @param localeFile Struct whose plural fields will be filled in.
|
||||
* @param headerBuffer The raw PO header string (msgstr of msgid "").
|
||||
* @param headerBufferSize Size of `headerBuffer` in bytes.
|
||||
* @return OK on success, error if the plural expression is malformed.
|
||||
*/
|
||||
errorret_t assetLocaleParseHeader(
|
||||
assetlocalefile_t *localeFile,
|
||||
char_t *headerBuffer,
|
||||
@@ -61,11 +135,29 @@ errorret_t assetLocaleParseHeader(
|
||||
);
|
||||
|
||||
/**
|
||||
* Skips blank lines and comment lines in the line reader.
|
||||
*
|
||||
* @param reader Line reader to read from.
|
||||
* @param lineBuffer Buffer to use for reading lines.
|
||||
* @return Any error that occurs during skipping.
|
||||
* Evaluates which plural form index to use for a given count.
|
||||
*
|
||||
* Walks the conditions stored in `file` in order; returns the index for the
|
||||
* first condition that matches `pluralCount`. Falls back to
|
||||
* `file->pluralDefaultIndex` if none match.
|
||||
*
|
||||
* @param file Locale file with parsed plural rules.
|
||||
* @param pluralCount The runtime count (e.g. number of items).
|
||||
* @return Zero-based plural form index.
|
||||
*/
|
||||
uint8_t assetLocaleEvaluatePlural(
|
||||
assetlocalefile_t *file,
|
||||
const int32_t pluralCount
|
||||
);
|
||||
|
||||
/**
|
||||
* Advances the line reader past blank lines, comment lines (starting with
|
||||
* `#`), and lines containing only spaces. Stops at the first content line or
|
||||
* end of file.
|
||||
*
|
||||
* @param reader Line reader positioned at the current line.
|
||||
* @param lineBuffer Output buffer that the reader writes each line into.
|
||||
* @return OK on success, error if reading fails.
|
||||
*/
|
||||
errorret_t assetLocaleLineSkipBlanks(
|
||||
assetfilelinereader_t *reader,
|
||||
@@ -73,16 +165,19 @@ errorret_t assetLocaleLineSkipBlanks(
|
||||
);
|
||||
|
||||
/**
|
||||
* Unbuffers a potentially multi-line quoted string from the line reader.
|
||||
*
|
||||
* This will read lines until it finds a line that starts with a quote, then
|
||||
* read until the closing quote.
|
||||
*
|
||||
* @param reader Line reader to read from.
|
||||
* @param lineBuffer Buffer to use for reading lines.
|
||||
* @param stringBuffer Buffer to write the unbuffered string to.
|
||||
* @param stringBufferSize Size of the string buffer.
|
||||
* @return Any error that occurs during unbuffering.
|
||||
* Reads a PO quoted string value from the current line and any immediately
|
||||
* following continuation lines that begin with `"`.
|
||||
*
|
||||
* Handles standard C escape sequences (`\n`, `\t`, `\\`, `\"`).
|
||||
*
|
||||
* @param reader Line reader positioned at the line containing the opening
|
||||
* quote (e.g. `msgstr "..."`).
|
||||
* @param lineBuffer Buffer the reader fills on each @ref assetFileLineReaderNext
|
||||
* call; also used to detect continuation lines.
|
||||
* @param stringBuffer Destination for the unescaped string content.
|
||||
* @param stringBufferSize Capacity of `stringBuffer` in bytes.
|
||||
* @return OK on success, error if a quote or escape sequence is malformed or
|
||||
* the buffer would overflow.
|
||||
*/
|
||||
errorret_t assetLocaleLineUnbuffer(
|
||||
assetfilelinereader_t *reader,
|
||||
@@ -92,14 +187,19 @@ errorret_t assetLocaleLineUnbuffer(
|
||||
);
|
||||
|
||||
/**
|
||||
* Test function for locale asset loading.
|
||||
*
|
||||
* @param file Asset file to test loading from.
|
||||
* @param messageId The message ID to retrieve.
|
||||
* @param pluralCount Count for formulating the plural variant.
|
||||
* @param stringBuffer Buffer to write the retrieved string to.
|
||||
* @param stringBufferSize Size of the string buffer.
|
||||
* @return Any error that occurs during testing.
|
||||
* Looks up a translated string by message ID from the open locale file.
|
||||
*
|
||||
* Rewinds the file and scans from the beginning on every call. For plural
|
||||
* entries (`msgid_plural`) the `pluralCount` is evaluated against the loaded
|
||||
* plural rules to select the correct `msgstr[N]` form.
|
||||
*
|
||||
* @param file Locale file to search. Must be open.
|
||||
* @param messageId PO message ID to find (`""` retrieves the header entry).
|
||||
* @param pluralCount Count used to select the plural form (ignored for
|
||||
* singular entries).
|
||||
* @param stringBuffer Buffer to receive the translated string.
|
||||
* @param stringBufferSize Capacity of `stringBuffer` in bytes.
|
||||
* @return OK on success, error if the message ID is not found or I/O fails.
|
||||
*/
|
||||
errorret_t assetLocaleGetString(
|
||||
assetlocalefile_t *file,
|
||||
@@ -110,15 +210,21 @@ errorret_t assetLocaleGetString(
|
||||
);
|
||||
|
||||
/**
|
||||
* Test function for locale asset loading with a variable argument list.
|
||||
*
|
||||
* @param file Asset file to test loading from.
|
||||
* @param messageId The message ID to retrieve.
|
||||
* @param pluralCount Count for formulating the plural variant.
|
||||
* @param buffer Buffer to write the retrieved string to.
|
||||
* @param bufferSize Size of the buffer.
|
||||
* @param ... Additional arguments for formatting the string.
|
||||
* @return Any error that occurs during testing.
|
||||
* Looks up a translated string and formats it with a `printf`-style variadic
|
||||
* argument list.
|
||||
*
|
||||
* Retrieves the raw format string via @ref assetLocaleGetString then applies
|
||||
* `vsnprintf` with the provided arguments.
|
||||
*
|
||||
* @param file Locale file to search. Must be open.
|
||||
* @param messageId PO message ID to find.
|
||||
* @param pluralCount Count used to select the plural form.
|
||||
* @param buffer Buffer to receive the formatted string.
|
||||
* @param bufferSize Capacity of `buffer` in bytes.
|
||||
* @param ... Format arguments matching the specifiers in the translated
|
||||
* string.
|
||||
* @return OK on success, error if the message is not found or formatting
|
||||
* fails.
|
||||
*/
|
||||
errorret_t assetLocaleGetStringWithVA(
|
||||
assetlocalefile_t *file,
|
||||
@@ -130,16 +236,22 @@ errorret_t assetLocaleGetStringWithVA(
|
||||
);
|
||||
|
||||
/**
|
||||
* Test function for locale asset loading with a list of arguments.
|
||||
*
|
||||
* @param file Asset file to test loading from.
|
||||
* @param messageId The message ID to retrieve.
|
||||
* @param pluralCount Count for formulating the plural variant.
|
||||
* @param buffer Buffer to write the retrieved string to.
|
||||
* @param bufferSize Size of the buffer.
|
||||
* @param args List of arguments for formatting the string.
|
||||
* @param argCount Number of arguments in the list.
|
||||
* @return Any error that occurs during testing.
|
||||
* Looks up a translated string and substitutes typed arguments into its
|
||||
* `%s`, `%d`, and `%f` placeholders.
|
||||
*
|
||||
* Unlike @ref assetLocaleGetStringWithVA this variant accepts an explicit
|
||||
* array of @ref assetlocalearg_t values, which is safer to use from
|
||||
* generated or scripted call sites.
|
||||
*
|
||||
* @param file Locale file to search. Must be open.
|
||||
* @param messageId PO message ID to find.
|
||||
* @param pluralCount Count used to select the plural form.
|
||||
* @param buffer Buffer to receive the formatted string.
|
||||
* @param bufferSize Capacity of `buffer` in bytes.
|
||||
* @param args Array of typed arguments; may be NULL when `argCount` is 0.
|
||||
* @param argCount Number of elements in `args`.
|
||||
* @return OK on success, error if the message is not found, an argument type
|
||||
* mismatches the specifier, or the buffer would overflow.
|
||||
*/
|
||||
errorret_t assetLocaleGetStringWithArgs(
|
||||
assetlocalefile_t *file,
|
||||
@@ -149,4 +261,4 @@ errorret_t assetLocaleGetStringWithArgs(
|
||||
const size_t bufferSize,
|
||||
const assetlocalearg_t *args,
|
||||
const size_t argCount
|
||||
);
|
||||
);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
add_subdirectory(assert)
|
||||
add_subdirectory(asset)
|
||||
add_subdirectory(error)
|
||||
add_subdirectory(thread)
|
||||
add_subdirectory(display)
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
include(dusktest)
|
||||
|
||||
dusktest(test_assetlocale.c)
|
||||
dusktest(test_asset.c)
|
||||
@@ -0,0 +1,242 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "dusktest.h"
|
||||
#include "asset/asset.h"
|
||||
#include "asset/loader/assetloader.h"
|
||||
#include "asset/loader/assetentry.h"
|
||||
#include "util/memory.h"
|
||||
#include "util/string.h"
|
||||
|
||||
// ============================================================
|
||||
// Stub loader callbacks
|
||||
// ============================================================
|
||||
|
||||
static errorret_t stub_load_success(assetloading_t *loading) {
|
||||
loading->entry->state = ASSET_ENTRY_STATE_LOADED;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
static errorret_t stub_load_fail(assetloading_t *loading) {
|
||||
loading->entry->state = ASSET_ENTRY_STATE_ERROR;
|
||||
errorThrow("Stub loader failed");
|
||||
}
|
||||
|
||||
static errorret_t stub_dispose(assetentry_t *entry) {
|
||||
errorOk();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Per-test setup / teardown
|
||||
// ============================================================
|
||||
|
||||
static assetloadercallbacks_t saved_callbacks[ASSET_LOADER_TYPE_COUNT];
|
||||
|
||||
static int asset_setup(void **state) {
|
||||
// Save real callbacks so we can restore them in teardown.
|
||||
memoryCopy(saved_callbacks, ASSET_LOADER_CALLBACKS, sizeof(saved_callbacks));
|
||||
|
||||
// Manually init ASSET — no thread, no ZIP.
|
||||
memoryZero(&ASSET, sizeof(ASSET));
|
||||
for(size_t i = 0; i < ASSET_LOADING_COUNT_MAX; i++) {
|
||||
threadMutexInit(&ASSET.loading[i].mutex);
|
||||
}
|
||||
|
||||
// Replace all loader callbacks with stubs.
|
||||
for(int i = 0; i < ASSET_LOADER_TYPE_COUNT; i++) {
|
||||
ASSET_LOADER_CALLBACKS[i].loadSync = stub_load_success;
|
||||
ASSET_LOADER_CALLBACKS[i].dispose = stub_dispose;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int asset_teardown(void **state) {
|
||||
// Dispose any entries that tests left behind.
|
||||
for(int i = 0; i < ASSET_ENTRY_COUNT_MAX; i++) {
|
||||
if(ASSET.entries[i].type != ASSET_LOADER_TYPE_NULL) {
|
||||
errorret_t ret = assetEntryDispose(&ASSET.entries[i]);
|
||||
if(errorIsNotOk(ret)) errorCatch(ret);
|
||||
}
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < ASSET_LOADING_COUNT_MAX; i++) {
|
||||
threadMutexDispose(&ASSET.loading[i].mutex);
|
||||
}
|
||||
|
||||
// Restore real callbacks before zeroing state.
|
||||
memoryCopy(ASSET_LOADER_CALLBACKS, saved_callbacks, sizeof(saved_callbacks));
|
||||
memoryZero(&ASSET, sizeof(ASSET));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Helper: find which loading slot owns a given entry
|
||||
// ============================================================
|
||||
|
||||
static bool_t loading_slot_has_entry(const assetentry_t *entry) {
|
||||
for(size_t i = 0; i < ASSET_LOADING_COUNT_MAX; i++) {
|
||||
if(ASSET.loading[i].entry == entry) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// assetGetEntry tests
|
||||
// ============================================================
|
||||
|
||||
static void test_getEntry_creates_new(void **state) {
|
||||
assetentry_t *entry = assetGetEntry("test.locale", ASSET_LOADER_TYPE_LOCALE, NULL);
|
||||
|
||||
assert_non_null(entry);
|
||||
assert_int_equal(entry->state, ASSET_ENTRY_STATE_NOT_STARTED);
|
||||
assert_int_equal(entry->type, ASSET_LOADER_TYPE_LOCALE);
|
||||
assert_true(stringEquals(entry->name, "test.locale"));
|
||||
|
||||
assert_int_equal(memoryGetAllocatedCount(), 0);
|
||||
}
|
||||
|
||||
static void test_getEntry_dedup(void **state) {
|
||||
assetentry_t *a = assetGetEntry("test.locale", ASSET_LOADER_TYPE_LOCALE, NULL);
|
||||
assetentry_t *b = assetGetEntry("test.locale", ASSET_LOADER_TYPE_LOCALE, NULL);
|
||||
|
||||
assert_ptr_equal(a, b);
|
||||
assert_int_equal(memoryGetAllocatedCount(), 0);
|
||||
}
|
||||
|
||||
static void test_getEntry_distinct_names(void **state) {
|
||||
assetentry_t *a = assetGetEntry("a.locale", ASSET_LOADER_TYPE_LOCALE, NULL);
|
||||
assetentry_t *b = assetGetEntry("b.locale", ASSET_LOADER_TYPE_LOCALE, NULL);
|
||||
|
||||
assert_ptr_not_equal(a, b);
|
||||
assert_int_equal(memoryGetAllocatedCount(), 0);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// assetUpdate — state machine tests
|
||||
// ============================================================
|
||||
|
||||
static void test_update_entry_reaches_loaded(void **state) {
|
||||
assetentry_t *entry = assetGetEntry("test.locale", ASSET_LOADER_TYPE_LOCALE, NULL);
|
||||
assert_int_equal(entry->state, ASSET_ENTRY_STATE_NOT_STARTED);
|
||||
|
||||
errorret_t ret = assetUpdate();
|
||||
assert_true(errorIsOk(ret));
|
||||
assert_int_equal(entry->state, ASSET_ENTRY_STATE_LOADED);
|
||||
|
||||
assert_int_equal(memoryGetAllocatedCount(), 0);
|
||||
}
|
||||
|
||||
static void test_update_slot_occupied_after_first_update(void **state) {
|
||||
assetentry_t *entry = assetGetEntry("test.locale", ASSET_LOADER_TYPE_LOCALE, NULL);
|
||||
|
||||
assetUpdate();
|
||||
assert_int_equal(entry->state, ASSET_ENTRY_STATE_LOADED);
|
||||
|
||||
// Slot not cleared until the second update processes the LOADED case.
|
||||
assert_true(loading_slot_has_entry(entry));
|
||||
|
||||
assert_int_equal(memoryGetAllocatedCount(), 0);
|
||||
}
|
||||
|
||||
static void test_update_slot_cleared_after_second_update(void **state) {
|
||||
assetentry_t *entry = assetGetEntry("test.locale", ASSET_LOADER_TYPE_LOCALE, NULL);
|
||||
|
||||
assetUpdate();
|
||||
assetUpdate();
|
||||
|
||||
assert_false(loading_slot_has_entry(entry));
|
||||
assert_int_equal(entry->state, ASSET_ENTRY_STATE_LOADED);
|
||||
|
||||
assert_int_equal(memoryGetAllocatedCount(), 0);
|
||||
}
|
||||
|
||||
static void test_update_four_slots_fill_independently(void **state) {
|
||||
// ASSET_LOADING_COUNT_MAX concurrent entries should all load in one pass.
|
||||
assetentry_t *entries[ASSET_LOADING_COUNT_MAX];
|
||||
for(int i = 0; i < ASSET_LOADING_COUNT_MAX; i++) {
|
||||
char_t name[ASSET_FILE_NAME_MAX];
|
||||
snprintf(name, sizeof(name), "asset%d.locale", i);
|
||||
entries[i] = assetGetEntry(name, ASSET_LOADER_TYPE_LOCALE, NULL);
|
||||
}
|
||||
|
||||
errorret_t ret = assetUpdate();
|
||||
assert_true(errorIsOk(ret));
|
||||
|
||||
for(int i = 0; i < ASSET_LOADING_COUNT_MAX; i++) {
|
||||
assert_int_equal(entries[i]->state, ASSET_ENTRY_STATE_LOADED);
|
||||
}
|
||||
|
||||
assert_int_equal(memoryGetAllocatedCount(), 0);
|
||||
}
|
||||
|
||||
static void test_update_error_state(void **state) {
|
||||
ASSET_LOADER_CALLBACKS[ASSET_LOADER_TYPE_LOCALE].loadSync = stub_load_fail;
|
||||
|
||||
assetentry_t *entry = assetGetEntry("fail.locale", ASSET_LOADER_TYPE_LOCALE, NULL);
|
||||
|
||||
// First update: dispatches and calls the failing stub.
|
||||
// assetUpdate itself returns OK here; the error from loadSync is caught internally.
|
||||
errorret_t ret = assetUpdate();
|
||||
assert_true(errorIsOk(ret));
|
||||
assert_int_equal(entry->state, ASSET_ENTRY_STATE_ERROR);
|
||||
|
||||
// Second update: sees ERROR state and propagates it.
|
||||
ret = assetUpdate();
|
||||
assert_true(errorIsNotOk(ret));
|
||||
errorCatch(ret);
|
||||
|
||||
assert_int_equal(memoryGetAllocatedCount(), 0);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// assetRequireLoaded tests
|
||||
// ============================================================
|
||||
|
||||
static void test_requireLoaded_already_loaded(void **state) {
|
||||
assetentry_t *entry = assetGetEntry("test.locale", ASSET_LOADER_TYPE_LOCALE, NULL);
|
||||
assetUpdate();
|
||||
assert_int_equal(entry->state, ASSET_ENTRY_STATE_LOADED);
|
||||
|
||||
// Should return immediately without calling assetUpdate again.
|
||||
errorret_t ret = assetRequireLoaded(entry);
|
||||
assert_true(errorIsOk(ret));
|
||||
|
||||
assert_int_equal(memoryGetAllocatedCount(), 0);
|
||||
}
|
||||
|
||||
static void test_requireLoaded_spins_to_loaded(void **state) {
|
||||
assetentry_t *entry = assetGetEntry("test.locale", ASSET_LOADER_TYPE_LOCALE, NULL);
|
||||
assert_int_equal(entry->state, ASSET_ENTRY_STATE_NOT_STARTED);
|
||||
|
||||
// requireLoaded calls assetUpdate internally until LOADED.
|
||||
errorret_t ret = assetRequireLoaded(entry);
|
||||
assert_true(errorIsOk(ret));
|
||||
assert_int_equal(entry->state, ASSET_ENTRY_STATE_LOADED);
|
||||
|
||||
assert_int_equal(memoryGetAllocatedCount(), 0);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// main
|
||||
// ============================================================
|
||||
|
||||
int main(void) {
|
||||
const struct CMUnitTest tests[] = {
|
||||
cmocka_unit_test_setup_teardown(test_getEntry_creates_new, asset_setup, asset_teardown),
|
||||
cmocka_unit_test_setup_teardown(test_getEntry_dedup, asset_setup, asset_teardown),
|
||||
cmocka_unit_test_setup_teardown(test_getEntry_distinct_names, asset_setup, asset_teardown),
|
||||
cmocka_unit_test_setup_teardown(test_update_entry_reaches_loaded, asset_setup, asset_teardown),
|
||||
cmocka_unit_test_setup_teardown(test_update_slot_occupied_after_first_update, asset_setup, asset_teardown),
|
||||
cmocka_unit_test_setup_teardown(test_update_slot_cleared_after_second_update, asset_setup, asset_teardown),
|
||||
cmocka_unit_test_setup_teardown(test_update_four_slots_fill_independently, asset_setup, asset_teardown),
|
||||
cmocka_unit_test_setup_teardown(test_update_error_state, asset_setup, asset_teardown),
|
||||
cmocka_unit_test_setup_teardown(test_requireLoaded_already_loaded, asset_setup, asset_teardown),
|
||||
cmocka_unit_test_setup_teardown(test_requireLoaded_spins_to_loaded, asset_setup, asset_teardown),
|
||||
};
|
||||
return cmocka_run_group_tests(tests, NULL, NULL);
|
||||
}
|
||||
@@ -0,0 +1,423 @@
|
||||
/**
|
||||
* 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 <zip.h>
|
||||
|
||||
// ============================================================
|
||||
// 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;
|
||||
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!");
|
||||
assert_int_equal(memoryGetAllocatedCount(), 0);
|
||||
}
|
||||
|
||||
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(), 0);
|
||||
}
|
||||
|
||||
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(), 0);
|
||||
}
|
||||
|
||||
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 rewinds the file and re-reads from scratch.
|
||||
ret = assetLocaleGetString(&g_locale, "greeting", 0, b, sizeof(b));
|
||||
assert_true(errorIsOk(ret));
|
||||
|
||||
assert_string_equal(a, b);
|
||||
assert_int_equal(memoryGetAllocatedCount(), 0);
|
||||
}
|
||||
|
||||
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(), 0);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 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(), 0);
|
||||
}
|
||||
|
||||
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(), 0);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// main
|
||||
// ============================================================
|
||||
|
||||
int main(void) {
|
||||
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),
|
||||
};
|
||||
return cmocka_run_group_tests(tests, NULL, NULL);
|
||||
}
|
||||
Reference in New Issue
Block a user