/** * @file json_helpers.c * @brief Unit tests for the typed JSON accessors. * * These exercise the accessors directly rather than through sprite, character, * or tilemap loading, so the error paths are reachable without building a * malformed asset for every case. */ #include #include #include #include #include #include #include #include #include #include "testutil.h" /** @brief Fixture document shared by every test in this file. */ static json_t *fixture = NULL; /** @brief Load tests/assets/snippets/test_json_helpers.json into @ref fixture. */ static akerr_ErrorContext *load_fixture(void) { PREPARE_ERROR(e); json_error_t jsonerr; akgl_String *pathstr = NULL; ATTEMPT { CATCH(e, akgl_heap_next_string(&pathstr)); snprintf( (char *)&pathstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets/snippets/test_json_helpers.json"); fixture = json_load_file((char *)&pathstr->data, 0, &jsonerr); FAIL_ZERO_BREAK(e, fixture, AKERR_IO, "Unable to load the JSON fixture: %s", (char *)jsonerr.text); } CLEANUP { IGNORE(akgl_heap_release_string(pathstr)); } PROCESS(e) { } FINISH(e, true); SUCCEED_RETURN(e); } /** * @brief Every accessor must refuse a NULL key or destination, not dereference it. * * Only the two string accessors did. The other eight validated the container * and then wrote through @p dest unconditionally, so which arguments a caller * could safely get wrong depended on which typed accessor they happened to * call -- and the ones that crashed were the ones used most. */ /** * @brief An exhausted string pool must report AKGL_ERR_HEAP, not crash. * * Both string accessors ended their ATTEMPT block with `FINISH(errctx, false)`, * which swallows the failure instead of passing it up, and then ran * `strncpy(&(*dest)->data, ...)` through the pointer akgl_heap_next_string * never set. So the one condition the pool is designed to report -- it is full, * usually because something is not releasing -- arrived as a segfault inside * strncpy. * * This is what TODO.md Performance item 29 looked like from the outside: not * "the tilemap loader leaks five strings a load", but a crash somewhere else * entirely, fifty levels later. */ akerr_ErrorContext *test_json_string_accessor_reports_pool_exhaustion(void) { PREPARE_ERROR(e); akgl_String *claimed[AKGL_MAX_HEAP_STRING]; akgl_String *dest = NULL; json_t *strings = NULL; int held = 0; int i = 0; memset(&claimed, 0x00, sizeof(claimed)); ATTEMPT { CATCH(e, akgl_get_json_array_value(fixture, "strings", &strings)); // Take every slot the pool has. Whatever else is holding one already // simply means this stops sooner. while ( held < AKGL_MAX_HEAP_STRING ) { akerr_ErrorContext *claim = akgl_heap_next_string(&claimed[held]); if ( claim != NULL ) { claim->handled = true; claim = akerr_release_error(claim); claimed[held] = NULL; break; } held += 1; } TEST_ASSERT(e, held > 0, "could not claim any pool strings"); TEST_ASSERT(e, test_string_pool_used() == AKGL_MAX_HEAP_STRING, "the string pool is not full: %d of %d claimed", test_string_pool_used(), AKGL_MAX_HEAP_STRING); // dest is NULL, so both accessors have to claim -- and cannot. dest = NULL; TEST_EXPECT_STATUS(e, AKGL_ERR_HEAP, akgl_get_json_string_value(fixture, "name", &dest), "reading a string with the pool exhausted"); TEST_ASSERT(e, dest == NULL, "a refused string accessor wrote to its destination"); dest = NULL; TEST_EXPECT_STATUS(e, AKGL_ERR_HEAP, akgl_get_json_array_index_string(strings, 0, &dest), "reading an array string with the pool exhausted"); TEST_ASSERT(e, dest == NULL, "a refused array string accessor wrote to its destination"); } CLEANUP { for ( i = 0; i < AKGL_MAX_HEAP_STRING; i++ ) { if ( claimed[i] != NULL ) { IGNORE(akgl_heap_release_string(claimed[i])); } } } PROCESS(e) { } FINISH(e, true); SUCCEED_RETURN(e); } akerr_ErrorContext *test_json_accessor_null_arguments(void) { PREPARE_ERROR(e); int intval = 0; float floatval = 0; double dblval = 0; bool boolval = false; json_t *jsonval = NULL; akgl_String *strval = NULL; ATTEMPT { TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_get_json_object_value(fixture, NULL, &jsonval), "object accessor with a NULL key"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_get_json_object_value(fixture, "nested", NULL), "object accessor with a NULL destination"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_get_json_boolean_value(fixture, NULL, &boolval), "boolean accessor with a NULL key"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_get_json_boolean_value(fixture, "enabled", NULL), "boolean accessor with a NULL destination"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_get_json_integer_value(fixture, NULL, &intval), "integer accessor with a NULL key"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_get_json_integer_value(fixture, "count", NULL), "integer accessor with a NULL destination"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_get_json_number_value(fixture, NULL, &floatval), "number accessor with a NULL key"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_get_json_number_value(fixture, "ratio", NULL), "number accessor with a NULL destination"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_get_json_double_value(fixture, NULL, &dblval), "double accessor with a NULL key"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_get_json_double_value(fixture, "ratio", NULL), "double accessor with a NULL destination"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_get_json_array_value(fixture, NULL, &jsonval), "array accessor with a NULL key"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_get_json_array_value(fixture, "integers", NULL), "array accessor with a NULL destination"); // The index accessors take no key, but they take a destination. CATCH(e, akgl_get_json_array_value(fixture, "integers", &jsonval)); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_get_json_array_index_integer(jsonval, 0, NULL), "array index integer accessor with a NULL destination"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_get_json_array_index_object(jsonval, 0, NULL), "array index object accessor with a NULL destination"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_get_json_array_index_string(jsonval, 0, NULL), "array index string accessor with a NULL destination"); // Nothing above should have claimed a pool string on the way to // refusing, which is the other half of "refuse rather than proceed". TEST_ASSERT(e, strval == NULL, "a refused accessor wrote to its destination anyway"); } CLEANUP { } PROCESS(e) { } FINISH(e, true); SUCCEED_RETURN(e); } akerr_ErrorContext *test_json_scalar_accessors(void) { PREPARE_ERROR(e); int intval = 0; float floatval = 0; bool boolval = false; json_t *objval = NULL; json_t *arrayval = NULL; ATTEMPT { TEST_EXPECT_OK(e, akgl_get_json_integer_value(fixture, "count", &intval), "read count"); TEST_ASSERT(e, intval == 42, "count read as %d, expected 42", intval); TEST_EXPECT_OK(e, akgl_get_json_integer_value(fixture, "negative", &intval), "read negative"); TEST_ASSERT(e, intval == -17, "negative read as %d, expected -17", intval); TEST_EXPECT_OK(e, akgl_get_json_number_value(fixture, "ratio", &floatval), "read ratio"); TEST_ASSERT_FEQ(e, floatval, 2.5f, "ratio read as %f, expected 2.5", floatval); // A JSON integer is a number, so the float accessor accepts it too. TEST_EXPECT_OK(e, akgl_get_json_number_value(fixture, "count", &floatval), "read count as a number"); TEST_ASSERT_FEQ(e, floatval, 42.0f, "count read as number %f, expected 42", floatval); TEST_EXPECT_OK(e, akgl_get_json_boolean_value(fixture, "enabled", &boolval), "read enabled"); TEST_ASSERT(e, boolval == true, "enabled read as false"); TEST_EXPECT_OK(e, akgl_get_json_boolean_value(fixture, "disabled", &boolval), "read disabled"); TEST_ASSERT(e, boolval == false, "disabled read as true"); TEST_EXPECT_OK(e, akgl_get_json_object_value(fixture, "nested", &objval), "read nested"); TEST_ASSERT(e, objval != NULL, "nested object came back NULL"); TEST_EXPECT_OK(e, akgl_get_json_integer_value(objval, "innercount", &intval), "read nested innercount"); TEST_ASSERT(e, intval == 7, "nested innercount read as %d, expected 7", intval); TEST_EXPECT_OK(e, akgl_get_json_array_value(fixture, "integers", &arrayval), "read integers array"); TEST_ASSERT(e, json_array_size(arrayval) == 3, "integers array had %d entries, expected 3", (int)json_array_size(arrayval)); } CLEANUP { } PROCESS(e) { } FINISH(e, true); SUCCEED_RETURN(e); } akerr_ErrorContext *test_json_double_value(void) { PREPARE_ERROR(e); double dblval = 0; ATTEMPT { TEST_EXPECT_OK(e, akgl_get_json_double_value(fixture, "ratio", &dblval), "read ratio as a double"); TEST_ASSERT(e, dblval > 2.4999 && dblval < 2.5001, "ratio read as double %f, expected 2.5", dblval); // Integers satisfy json_is_number, so the double accessor takes them. TEST_EXPECT_OK(e, akgl_get_json_double_value(fixture, "count", &dblval), "read count as a double"); TEST_ASSERT(e, dblval > 41.999 && dblval < 42.001, "count read as double %f, expected 42", dblval); TEST_EXPECT_STATUS(e, AKERR_KEY, akgl_get_json_double_value(fixture, "absent", &dblval), "double accessor on a missing key"); TEST_EXPECT_STATUS(e, AKERR_TYPE, akgl_get_json_double_value(fixture, "name", &dblval), "double accessor on a string value"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_get_json_double_value(NULL, "ratio", &dblval), "double accessor on a NULL object"); } CLEANUP { } PROCESS(e) { } FINISH(e, true); SUCCEED_RETURN(e); } akerr_ErrorContext *test_json_string_accessor(void) { PREPARE_ERROR(e); akgl_String *allocated = NULL; akgl_String *reused = NULL; ATTEMPT { // A NULL destination makes the accessor claim a heap string. TEST_EXPECT_OK(e, akgl_get_json_string_value(fixture, "name", &allocated), "read name into a NULL destination"); TEST_ASSERT(e, allocated != NULL, "the accessor did not allocate a destination string"); TEST_ASSERT(e, strcmp((char *)&allocated->data, "json helper fixture") == 0, "name read as \"%s\"", (char *)&allocated->data); // A caller-supplied destination is written in place, not replaced. CATCH(e, akgl_heap_next_string(&reused)); CATCH(e, akgl_string_initialize(reused, "placeholder")); { akgl_String *before = reused; TEST_EXPECT_OK(e, akgl_get_json_string_value(fixture, "name", &reused), "read name into a preallocated destination"); TEST_ASSERT(e, reused == before, "the accessor replaced a caller-supplied destination pointer"); } TEST_ASSERT(e, strcmp((char *)&reused->data, "json helper fixture") == 0, "in-place read produced \"%s\"", (char *)&reused->data); TEST_EXPECT_STATUS(e, AKERR_KEY, akgl_get_json_string_value(fixture, "absent", &reused), "string accessor on a missing key"); TEST_EXPECT_STATUS(e, AKERR_TYPE, akgl_get_json_string_value(fixture, "count", &reused), "string accessor on an integer value"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_get_json_string_value(NULL, "name", &reused), "string accessor on a NULL object"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_get_json_string_value(fixture, NULL, &reused), "string accessor with a NULL key"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_get_json_string_value(fixture, "name", NULL), "string accessor with a NULL destination"); } CLEANUP { IGNORE(akgl_heap_release_string(allocated)); IGNORE(akgl_heap_release_string(reused)); } PROCESS(e) { } FINISH(e, true); SUCCEED_RETURN(e); } akerr_ErrorContext *test_json_array_index_accessors(void) { PREPARE_ERROR(e); json_t *integers = NULL; json_t *strings = NULL; json_t *objects = NULL; json_t *mixed = NULL; json_t *element = NULL; akgl_String *strval = NULL; int intval = 0; ATTEMPT { CATCH(e, akgl_get_json_array_value(fixture, "integers", &integers)); CATCH(e, akgl_get_json_array_value(fixture, "strings", &strings)); CATCH(e, akgl_get_json_array_value(fixture, "objects", &objects)); CATCH(e, akgl_get_json_array_value(fixture, "mixed", &mixed)); TEST_EXPECT_OK(e, akgl_get_json_array_index_integer(integers, 0, &intval), "integers[0]"); TEST_ASSERT(e, intval == 10, "integers[0] read as %d, expected 10", intval); TEST_EXPECT_OK(e, akgl_get_json_array_index_integer(integers, 2, &intval), "integers[2]"); TEST_ASSERT(e, intval == 30, "integers[2] read as %d, expected 30", intval); TEST_EXPECT_OK(e, akgl_get_json_array_index_string(strings, 1, &strval), "strings[1]"); TEST_ASSERT(e, strcmp((char *)&strval->data, "beta") == 0, "strings[1] read as \"%s\", expected \"beta\"", (char *)&strval->data); TEST_EXPECT_OK(e, akgl_get_json_array_index_object(objects, 1, &element), "objects[1]"); TEST_EXPECT_OK(e, akgl_get_json_integer_value(element, "id", &intval), "objects[1].id"); TEST_ASSERT(e, intval == 2, "objects[1].id read as %d, expected 2", intval); // One past the end, and far past the end, are both out of bounds. TEST_EXPECT_STATUS(e, AKERR_OUTOFBOUNDS, akgl_get_json_array_index_integer(integers, 3, &intval), "integers[3] is one past the end"); TEST_EXPECT_STATUS(e, AKERR_OUTOFBOUNDS, akgl_get_json_array_index_integer(integers, 99, &intval), "integers[99] is far past the end"); TEST_EXPECT_STATUS(e, AKERR_OUTOFBOUNDS, akgl_get_json_array_index_object(objects, 5, &element), "objects[5] is past the end"); TEST_EXPECT_STATUS(e, AKERR_OUTOFBOUNDS, akgl_get_json_array_index_string(strings, 5, &strval), "strings[5] is past the end"); // The mixed array holds one of each type, so every accessor can be shown // to reject the entries that are not its own. TEST_EXPECT_STATUS(e, AKERR_TYPE, akgl_get_json_array_index_integer(mixed, 1, &intval), "integer accessor on a string element"); TEST_EXPECT_STATUS(e, AKERR_TYPE, akgl_get_json_array_index_string(mixed, 0, &strval), "string accessor on an integer element"); TEST_EXPECT_STATUS(e, AKERR_TYPE, akgl_get_json_array_index_object(mixed, 0, &element), "object accessor on an integer element"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_get_json_array_index_integer(NULL, 0, &intval), "integer index accessor on a NULL array"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_get_json_array_index_object(NULL, 0, &element), "object index accessor on a NULL array"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_get_json_array_index_string(NULL, 0, &strval), "string index accessor on a NULL array"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_get_json_array_index_string(strings, 0, NULL), "string index accessor with a NULL destination"); } CLEANUP { IGNORE(akgl_heap_release_string(strval)); } PROCESS(e) { } FINISH(e, true); SUCCEED_RETURN(e); } akerr_ErrorContext *test_json_type_and_key_errors(void) { PREPARE_ERROR(e); int intval = 0; float floatval = 0; bool boolval = false; json_t *objval = NULL; json_t *arrayval = NULL; ATTEMPT { // Missing keys. TEST_EXPECT_STATUS(e, AKERR_KEY, akgl_get_json_integer_value(fixture, "absent", &intval), "integer accessor on a missing key"); TEST_EXPECT_STATUS(e, AKERR_KEY, akgl_get_json_number_value(fixture, "absent", &floatval), "number accessor on a missing key"); TEST_EXPECT_STATUS(e, AKERR_KEY, akgl_get_json_boolean_value(fixture, "absent", &boolval), "boolean accessor on a missing key"); TEST_EXPECT_STATUS(e, AKERR_KEY, akgl_get_json_object_value(fixture, "absent", &objval), "object accessor on a missing key"); TEST_EXPECT_STATUS(e, AKERR_KEY, akgl_get_json_array_value(fixture, "absent", &arrayval), "array accessor on a missing key"); // Wrong types. TEST_EXPECT_STATUS(e, AKERR_TYPE, akgl_get_json_integer_value(fixture, "name", &intval), "integer accessor on a string"); TEST_EXPECT_STATUS(e, AKERR_TYPE, akgl_get_json_integer_value(fixture, "ratio", &intval), "integer accessor on a real"); TEST_EXPECT_STATUS(e, AKERR_TYPE, akgl_get_json_number_value(fixture, "name", &floatval), "number accessor on a string"); TEST_EXPECT_STATUS(e, AKERR_TYPE, akgl_get_json_boolean_value(fixture, "count", &boolval), "boolean accessor on an integer"); TEST_EXPECT_STATUS(e, AKERR_TYPE, akgl_get_json_object_value(fixture, "integers", &objval), "object accessor on an array"); TEST_EXPECT_STATUS(e, AKERR_TYPE, akgl_get_json_array_value(fixture, "nested", &arrayval), "array accessor on an object"); // NULL containers. TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_get_json_integer_value(NULL, "count", &intval), "integer accessor on a NULL object"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_get_json_number_value(NULL, "ratio", &floatval), "number accessor on a NULL object"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_get_json_boolean_value(NULL, "enabled", &boolval), "boolean accessor on a NULL object"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_get_json_object_value(NULL, "nested", &objval), "object accessor on a NULL object"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_get_json_array_value(NULL, "integers", &arrayval), "array accessor on a NULL object"); } CLEANUP { } PROCESS(e) { } FINISH(e, true); SUCCEED_RETURN(e); } akerr_ErrorContext *test_json_with_default(void) { PREPARE_ERROR(e); int dest = 0; int defval = 99; akerr_ErrorContext *keyerr = NULL; akerr_ErrorContext *typeerr = NULL; akerr_ErrorContext *defaulted = NULL; json_t *shortarray = NULL; json_t *junkobj = NULL; int junk = 0; ATTEMPT { // A NULL error means the read succeeded, so the default is not applied. dest = 1; TEST_EXPECT_OK(e, akgl_get_json_with_default(NULL, (void *)&defval, (void *)&dest, sizeof(int)), "with_default on a NULL error"); TEST_ASSERT(e, dest == 1, "with_default overwrote a successful read (dest is now %d)", dest); // An AKERR_KEY failure substitutes the default. dest = 1; keyerr = akgl_get_json_integer_value(fixture, "absent", &junk); TEST_ASSERT(e, keyerr != NULL, "the missing-key read unexpectedly succeeded"); TEST_EXPECT_OK(e, akgl_get_json_with_default(keyerr, (void *)&defval, (void *)&dest, sizeof(int)), "with_default on an AKERR_KEY error"); TEST_ASSERT(e, dest == 99, "with_default did not apply the default (dest is %d)", dest); keyerr = NULL; // An unrelated failure is not swallowed, so the caller still sees it. dest = 1; typeerr = akgl_get_json_integer_value(fixture, "name", &junk); TEST_ASSERT(e, typeerr != NULL, "the wrong-type read unexpectedly succeeded"); TEST_EXPECT_STATUS(e, AKERR_TYPE, akgl_get_json_with_default(typeerr, (void *)&defval, (void *)&dest, sizeof(int)), "with_default must propagate an unrelated error"); TEST_ASSERT(e, dest == 1, "with_default applied the default for an unrelated error"); typeerr = NULL; // A short array. This is the one that did not work: the three array // index accessors report AKERR_OUTOFBOUNDS, which with_default never // handled, so "this element is optional" worked for an object member // and silently did not for an array element. Nothing in tree passed an // array accessor's error here, which is why it went unnoticed. dest = 1; CATCH(e, akgl_get_json_array_value(fixture, "integers", &shortarray)); keyerr = akgl_get_json_array_index_integer(shortarray, 99, &junk); TEST_ASSERT(e, keyerr != NULL, "reading past the end of an array unexpectedly succeeded"); TEST_ASSERT(e, keyerr->status == AKERR_OUTOFBOUNDS, "a short array reported %d, expected AKERR_OUTOFBOUNDS", keyerr->status); // Not TEST_EXPECT_OK here. That macro releases whatever context the // statement returns, and akgl_get_json_with_default returns *the context // it was given* when it does not handle it -- so on the failing path the // CLEANUP block below would release the same context a second time, and // a double-released context corrupts the failure rather than reporting // it. Ownership passes to with_default either way: it releases through // FINISH when it handles the status, and hands the context back when it // does not. defaulted = akgl_get_json_with_default(keyerr, (void *)&defval, (void *)&dest, sizeof(int)); keyerr = NULL; if ( defaulted != NULL ) { defaulted->handled = true; defaulted = akerr_release_error(defaulted); FAIL_BREAK(e, AKGL_ERR_BEHAVIOR, "with_default propagated AKERR_OUTOFBOUNDS instead of applying the default"); } TEST_ASSERT(e, dest == 99, "with_default did not apply the default for a short array (dest is %d)", dest); // The same through the object index accessor, so the fix is pinned to // the status rather than to one call site. dest = 1; keyerr = akgl_get_json_array_index_object(shortarray, 99, &junkobj); TEST_ASSERT(e, keyerr != NULL, "reading an object past the end unexpectedly succeeded"); defaulted = akgl_get_json_with_default(keyerr, (void *)&defval, (void *)&dest, sizeof(int)); keyerr = NULL; if ( defaulted != NULL ) { defaulted->handled = true; defaulted = akerr_release_error(defaulted); FAIL_BREAK(e, AKGL_ERR_BEHAVIOR, "with_default propagated a short object array instead of applying the default"); } TEST_ASSERT(e, dest == 99, "with_default did not apply the default (dest is %d)", dest); // NULL arguments alongside a real error are a contract violation. keyerr = akgl_get_json_integer_value(fixture, "absent", &junk); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_get_json_with_default(keyerr, NULL, (void *)&dest, sizeof(int)), "with_default with a NULL default value"); keyerr = NULL; keyerr = akgl_get_json_integer_value(fixture, "absent", &junk); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_get_json_with_default(keyerr, (void *)&defval, NULL, sizeof(int)), "with_default with a NULL destination"); keyerr = NULL; } CLEANUP { if ( keyerr != NULL ) { keyerr->handled = true; keyerr = akerr_release_error(keyerr); } if ( typeerr != NULL ) { typeerr->handled = true; typeerr = akerr_release_error(typeerr); } } PROCESS(e) { } FINISH(e, true); SUCCEED_RETURN(e); } int main(void) { PREPARE_ERROR(errctx); SDL_SetHint(SDL_HINT_VIDEO_DRIVER, "dummy"); SDL_SetHint(SDL_HINT_AUDIO_DRIVER, "dummy"); ATTEMPT { CATCH(errctx, akgl_error_init()); TEST_TRAP_UNHANDLED_ERRORS(); CATCH(errctx, akgl_heap_init()); CATCH(errctx, akgl_registry_init()); CATCH(errctx, load_fixture()); CATCH(errctx, test_json_accessor_null_arguments()); CATCH(errctx, test_json_scalar_accessors()); CATCH(errctx, test_json_double_value()); CATCH(errctx, test_json_string_accessor()); CATCH(errctx, test_json_array_index_accessors()); CATCH(errctx, test_json_type_and_key_errors()); CATCH(errctx, test_json_with_default()); CATCH(errctx, test_json_string_accessor_reports_pool_exhaustion()); } CLEANUP { if ( fixture != NULL ) { json_decref(fixture); } } PROCESS(errctx) { } FINISH_NORETURN(errctx); }