Add JSON mutation functions and tests

This commit is contained in:
Aaron Culliney 2016-03-05 13:43:09 -08:00
parent 01b25527fe
commit a87092bbd8
3 changed files with 656 additions and 61 deletions

View File

@ -15,7 +15,23 @@
#define JSON_LENGTH 16
#define DEFAULT_NUMTOK 16
#define MAX_INDENT 16
#define STACK_BUFSIZ 256
#define _JSMN_NONSTRING (JSMN_OBJECT)
#define QUOTE "\""
#define QUOTE_LEN (sizeof(QUOTE)-1)
#define COLON ":"
#define COLON_LEN (sizeof(COLON)-1)
#define COMMA ","
#define COMMA_LEN (sizeof(COMMA)-1)
#define DEBUG_JSON 1
#if DEBUG_JSON
# define JSON_LOG(...) LOG(__VA_ARGS__)
#else
# define JSON_LOG(...)
#endif
static bool _json_write(const char *jsonString, size_t buflen, int fd) {
ssize_t idx = 0;
@ -32,6 +48,35 @@ static bool _json_write(const char *jsonString, size_t buflen, int fd) {
return idx == buflen;
}
static char *_json_copyAndQuote(const char *str, size_t *newLen) {
char *qCopy = NULL;
do {
if (!str) {
break;
}
size_t len = strlen(str);
qCopy = MALLOC(QUOTE_LEN + len + QUOTE_LEN + 1);
if (!qCopy) {
break;
}
*newLen = QUOTE_LEN + len + QUOTE_LEN;
char *p = qCopy;
memcpy(p, QUOTE, QUOTE_LEN);
p += QUOTE_LEN;
memcpy(p, str, len);
p += len;
memcpy(p, QUOTE, QUOTE_LEN);
p += QUOTE_LEN;
*p = '\0';
} while (0);
return qCopy;
}
// recursive
static bool _json_prettyPrint(JSON_s *parsedData, int start, int end, const unsigned int indent, int fd) {
@ -167,7 +212,7 @@ static int _json_createFromString(const char *jsonString, INOUT JSON_ref *jsonRe
break;
}
JSON_s *parsedData = MALLOC(sizeof(*parsedData));
JSON_s *parsedData = CALLOC(sizeof(*parsedData), 1);
if (!parsedData) {
break;
}
@ -176,14 +221,13 @@ static int _json_createFromString(const char *jsonString, INOUT JSON_ref *jsonRe
if (!parsedData) {
break;
}
parsedData->jsonString = STRDUP(jsonString);
parsedData->jsonTokens = NULL;
unsigned int numTokens = DEFAULT_NUMTOK;
do {
if (!parsedData->jsonTokens) {
parsedData->jsonTokens = CALLOC(numTokens, sizeof(jsmntok_t));
if (!parsedData->jsonTokens) {
if (UNLIKELY(!parsedData->jsonTokens)) {
ERRLOG("WHOA3 : %s", strerror(errno));
break;
}
@ -191,7 +235,7 @@ static int _json_createFromString(const char *jsonString, INOUT JSON_ref *jsonRe
//LOG("reallocating json tokens ...");
numTokens <<= 1;
jsmntok_t *newTokens = REALLOC(parsedData->jsonTokens, numTokens * sizeof(jsmntok_t));
if (!newTokens) {
if (UNLIKELY(!newTokens)) {
ERRLOG("WHOA4 : %s", strerror(errno));
break;
}
@ -199,7 +243,7 @@ static int _json_createFromString(const char *jsonString, INOUT JSON_ref *jsonRe
parsedData->jsonTokens = newTokens;
}
jsmn_init(&parser);
errCount = jsmn_parse(&parser, parsedData->jsonString, jsonLen, parsedData->jsonTokens, numTokens-(numTokens/2));
errCount = jsmn_parse(&parser, jsonString, jsonLen, parsedData->jsonTokens, numTokens-(numTokens/2));
} while (errCount == JSMN_ERROR_NOMEM);
if (errCount == JSMN_ERROR_NOMEM) {
@ -225,6 +269,7 @@ static int _json_createFromString(const char *jsonString, INOUT JSON_ref *jsonRe
break;
}
parsedData->jsonString = STRDUP(jsonString);
parsedData->numTokens = errCount;
parsedData->jsonLen = jsonLen;
@ -254,7 +299,7 @@ int json_createFromFD(int fd, INOUT JSON_ref *jsonRef) {
jsonLen = JSON_LENGTH*2;
jsonString = MALLOC(jsonLen);
if (jsonString == NULL) {
if (UNLIKELY(jsonString == NULL)) {
ERRLOG("WHOA : %s", strerror(errno));
break;
}
@ -272,7 +317,7 @@ int json_createFromFD(int fd, INOUT JSON_ref *jsonRef) {
//LOG("reallocating json string ...");
jsonLen <<= 1;
char *newString = REALLOC(jsonString, jsonLen);
if (!newString) {
if (UNLIKELY(!newString)) {
ERRLOG("WHOA2 : %s", strerror(errno));
bytesRead = -1;
break;
@ -333,19 +378,15 @@ int json_createFromString(const char *jsonString, INOUT JSON_ref *jsonRef) {
return _json_createFromString(jsonString, jsonRef, strlen(jsonString));
}
static bool _json_mapGetStringValue(const JSON_s *map, const char *key, INOUT char **val, INOUT int *len) {
static bool _json_mapGetStringValue(const JSON_s *map, const char *key, INOUT int *index) {
bool foundMatch = false;
do {
if (!val) {
break;
}
if (!len) {
if (!index) {
break;
}
*val = NULL;
*len = -1;
*index = -1;
int tokCount = map->numTokens;
if (tokCount < 0) {
@ -383,11 +424,7 @@ static bool _json_mapGetStringValue(const JSON_s *map, const char *key, INOUT ch
if (size == keySize) {
foundMatch = (strncmp(key, &map->jsonString[start], size) == 0);
if (foundMatch) {
start = valTok.start;
end = valTok.end;
assert(end >= start && "bug");
*len = end - start;
*val = &map->jsonString[start];
*index = idx;
break;
}
}
@ -399,13 +436,49 @@ static bool _json_mapGetStringValue(const JSON_s *map, const char *key, INOUT ch
return foundMatch;
}
int json_mapCopyJSON(const JSON_ref jsonRef, const char *key, INOUT JSON_ref *val) {
JSON_s *map = (JSON_s *)jsonRef;
int idx = 0;
jsmnerr_t errCount = JSMN_ERROR_NOMEM;
do {
bool foundMatch = _json_mapGetStringValue(map, key, &idx);
if (!foundMatch) {
break;
}
jsmntok_t tok = map->jsonTokens[idx];
assert(tok.end >= tok.start && "bug");
int len = tok.end - tok.start;
if (len<=0) {
break;
}
char *str = STRNDUP(&map->jsonString[tok.start], len);
if (!str) {
break;
}
errCount = json_createFromString(str, val);
FREE(str);
} while (0);
return errCount;
}
bool json_mapCopyStringValue(const JSON_ref jsonRef, const char *key, INOUT char **val) {
JSON_s *map = (JSON_s *)jsonRef;
int len = 0;
bool foundMatch = _json_mapGetStringValue(map, key, val, &len);
int idx = 0;
bool foundMatch = _json_mapGetStringValue(map, key, &idx);
if (foundMatch) {
*val = len>0 ? STRNDUP(*val, len) : STRDUP("");
jsmntok_t tok = map->jsonTokens[idx];
assert(tok.end >= tok.start && "bug");
int len = tok.end - tok.start;
*val = len>0 ? STRNDUP(&map->jsonString[tok.start], len) : STRDUP("");
}
return foundMatch;
}
@ -418,10 +491,13 @@ bool json_mapParseLongValue(const JSON_ref jsonRef, const char *key, INOUT long
break;
}
int len = 0;
char *str = NULL;
foundMatch = _json_mapGetStringValue(map, key, &str, &len);
int idx = 0;
foundMatch = _json_mapGetStringValue(map, key, &idx);
if (foundMatch) {
jsmntok_t tok = map->jsonTokens[idx];
assert(tok.end >= tok.start && "bug");
int len = tok.end - tok.start;
char *str = &map->jsonString[tok.start];
char ch = str[len];
str[len] = '\0';
*val = strtol(str, NULL, base);
@ -432,6 +508,32 @@ bool json_mapParseLongValue(const JSON_ref jsonRef, const char *key, INOUT long
return foundMatch;
}
bool json_mapParseBoolValue(const JSON_ref jsonRef, const char *key, INOUT bool *val) {
JSON_s *map = (JSON_s *)jsonRef;
bool foundMatch = false;
do {
if (!val) {
break;
}
int idx = 0;
foundMatch = _json_mapGetStringValue(map, key, &idx);
if (foundMatch) {
jsmntok_t tok = map->jsonTokens[idx];
assert(tok.end >= tok.start && "bug");
int len = tok.end - tok.start;
char *str = &map->jsonString[tok.start];
char ch = str[len];
str[len] = '\0';
*val = (strncasecmp("false", str, sizeof("false")) != 0);
str[len] = ch;
}
} while (0);
return foundMatch;
}
bool json_mapParseFloatValue(const JSON_ref jsonRef, const char *key, INOUT float *val) {
JSON_s *map = (JSON_s *)jsonRef;
bool foundMatch = false;
@ -441,10 +543,13 @@ bool json_mapParseFloatValue(const JSON_ref jsonRef, const char *key, INOUT floa
break;
}
int len = 0;
char *str = NULL;
foundMatch = _json_mapGetStringValue(map, key, &str, &len);
int idx = 0;
foundMatch = _json_mapGetStringValue(map, key, &idx);
if (foundMatch) {
jsmntok_t tok = map->jsonTokens[idx];
assert(tok.end >= tok.start && "bug");
int len = tok.end - tok.start;
char *str = &map->jsonString[tok.start];
char ch = str[len];
str[len] = '\0';
*val = strtof(str, NULL);
@ -455,6 +560,200 @@ bool json_mapParseFloatValue(const JSON_ref jsonRef, const char *key, INOUT floa
return foundMatch;
}
// 2016/03/02 : HACK NOTE TODO FIXME : we do nasty string splicing because I haven't settled on a C library to use for
// collections. It would be nice to be able to use something like CoreFoundation (CFArray, CFDictionary, etc), but
// don't believe the APSL license is GPL-compatible. (Should investigate whether Swift OpenSource drop includes
// low-level C libs with a compatible FOSS license ;-)
static bool _json_mapSetValue(const JSON_ref jsonRef, const char *key, const char *val, jsmntype_t valType) {
JSON_s *map = (JSON_s *)jsonRef;
bool didSetValue = false;
char *newVal = NULL;
do {
if (!map) {
break;
}
if (!key) {
break;
}
if (!val) {
break;
}
if (map->jsonTokens[0].type != JSMN_OBJECT) {
ERRLOG("Map JSON : object not a map!");
break;
}
size_t spliceBegin = 0;
size_t spliceEnd = 0;
size_t valLen = strlen(val);
int idx = 0;
bool foundMatch = _json_mapGetStringValue(map, key, &idx);
if (foundMatch) {
jsmntok_t tok = map->jsonTokens[idx];
assert(tok.end >= tok.start && "bug");
spliceBegin = tok.start;
spliceEnd = tok.end;
if (tok.type == JSMN_STRING) {
if (valType == JSMN_STRING) {
// string -> string
assert(map->jsonString[spliceBegin-1] == '"');
assert(map->jsonString[spliceEnd] == '"');
} else {
// string -> non-string (strip previous quotes)
--spliceBegin;
++spliceEnd;
assert(map->jsonString[spliceBegin] == '"');
assert(map->jsonString[spliceEnd-1] == '"');
}
} else {
if (valType == JSMN_STRING) {
// non-string -> string
newVal = _json_copyAndQuote(val, &valLen);
if (!newVal) {
break;
}
val = newVal;
}
}
} else {
jsmntok_t tok = map->jsonTokens[0];
size_t keyLen = strlen(key);
size_t quoLen = (valType == JSMN_STRING) ? QUOTE_LEN : 0;
size_t comLen = tok.size ? COMMA_LEN : 0;
int keyValLen = QUOTE_LEN + keyLen + QUOTE_LEN + COLON_LEN + quoLen + valLen + quoLen + comLen;
newVal = MALLOC(keyValLen+1);
if (!newVal) {
break;
}
char *p = newVal;
memcpy(p, QUOTE, QUOTE_LEN);
p += QUOTE_LEN;
memcpy(p, key, keyLen);
p += keyLen;
memcpy(p, QUOTE, QUOTE_LEN);
p += QUOTE_LEN;
memcpy(p, COLON, COLON_LEN);
p += COLON_LEN;
if (quoLen) {
memcpy(p, QUOTE, quoLen);
p += quoLen;
}
memcpy(p, val, valLen);
p += valLen;
if (quoLen) {
memcpy(p, QUOTE, quoLen);
p += quoLen;
}
if (comLen) {
memcpy(p, COMMA, comLen);
p += comLen;
}
newVal[keyValLen] = '\0';
spliceBegin = tok.start+1; // insert at beginning
spliceEnd = spliceBegin;
val = newVal;
valLen = keyValLen;
}
assert(spliceBegin <= spliceEnd);
assert(spliceBegin > 0); // must always begin with '{'
assert(spliceEnd < map->jsonLen); // must always close with '}'
int prefixLen = spliceBegin;
int suffixLen = (map->jsonLen - spliceEnd);
int newLen = prefixLen+valLen+suffixLen;
char *jsonString = MALLOC(newLen + 1);
if (!jsonString) {
break;
}
memcpy(jsonString, &map->jsonString[0], prefixLen);
memcpy(jsonString+prefixLen, val, valLen);
memcpy(jsonString+prefixLen+valLen, &map->jsonString[spliceEnd], suffixLen);
jsonString[newLen] = '\0';
JSON_ref newRef = NULL;
int errCount = json_createFromString(jsonString, &newRef);
if (errCount < 0) {
ERRLOG("Cannot set new JSON value err : %d", errCount);
} else {
JSON_s *newMap = (JSON_s *)newRef;
FREE(map->jsonString);
FREE(map->jsonTokens);
*map = *newMap;
newMap->jsonString = NULL;
newMap->jsonTokens = NULL;
json_destroy(&newRef);
didSetValue = true;
}
FREE(jsonString);
} while (0);
if (newVal) {
FREE(newVal);
}
return didSetValue;
}
bool json_mapSetStringValue(const JSON_ref jsonRef, const char *key, const char *val) {
return _json_mapSetValue(jsonRef, key, val, JSMN_STRING);
}
bool json_mapSetRawStringValue(const JSON_ref jsonRef, const char *key, const char *val) {
return _json_mapSetValue(jsonRef, key, val, _JSMN_NONSTRING);
}
bool json_mapSetJSONValue(const JSON_ref jsonRef, const char *key, const JSON_ref jsonSubRef) {
bool didSetValue = false;
do {
if (!jsonRef) {
break;
}
didSetValue = _json_mapSetValue(jsonRef, key, ((JSON_s *)jsonSubRef)->jsonString, _JSMN_NONSTRING);
} while (0);
return didSetValue;
}
bool json_mapSetLongValue(const JSON_ref jsonRef, const char *key, long val) {
char buf[STACK_BUFSIZ];
buf[0] = '\0';
snprintf(buf, STACK_BUFSIZ, "%ld", val);
return _json_mapSetValue(jsonRef, key, buf, JSMN_PRIMITIVE);
}
bool json_mapSetBoolValue(const JSON_ref jsonRef, const char *key, bool val) {
char buf[STACK_BUFSIZ];
buf[0] = '\0';
snprintf(buf, STACK_BUFSIZ, "%s", val ? "true" : "false");
return _json_mapSetValue(jsonRef, key, buf, JSMN_PRIMITIVE);
}
bool json_mapSetFloatValue(const JSON_ref jsonRef, const char *key, float val) {
char buf[STACK_BUFSIZ];
buf[0] = '\0';
snprintf(buf, STACK_BUFSIZ, "%f", val);
return _json_mapSetValue(jsonRef, key, buf, JSMN_PRIMITIVE);
}
bool json_serialize(JSON_ref jsonRef, int fd, bool pretty) {
JSON_s *parsedData = (JSON_s *)jsonRef;
if (pretty) {
@ -470,8 +769,15 @@ void json_destroy(JSON_ref *jsonRef) {
}
JSON_s *parsedData = (JSON_s *)*jsonRef;
if (!parsedData) {
return;
}
FREE(parsedData->jsonString);
FREE(parsedData->jsonTokens);
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wincompatible-pointer-types-discards-qualifiers"
FREE(*jsonRef);
#pragma clang diagnostic pop
}

View File

@ -18,6 +18,9 @@
// opaque type
typedef const struct JSON_s *JSON_ref;
// ----------------------------------------------------------------------------
// constructors
// parses string into tokens. returns positive token count or negative jsmnerr_t error code.
int json_createFromString(const char *jsonString, INOUT JSON_ref *jsonRef);
@ -30,15 +33,39 @@ int json_createFromFD(int fd, INOUT JSON_ref *jsonRef);
// ----------------------------------------------------------------------------
// map functions
// get JSON_ref value for key in map JSON, returns true upon success and allocated JSON_ref
int json_mapCopyJSON(const JSON_ref map, const char *key, INOUT JSON_ref *val);
// get string value for key in map JSON, returns true upon success and strdup()'d value in *val
bool json_mapCopyStringValue(const JSON_ref map, const char *key, INOUT char **val);
// get long value for key in map JSON, returns true upon success
bool json_mapParseLongValue(const JSON_ref map, const char *key, INOUT long *val, const long base);
// get bool value for key in map JSON, returns true upon success
bool json_mapParseBoolValue(const JSON_ref map, const char *key, INOUT bool *val);
// get float value for key in map JSON, returns true upon success
bool json_mapParseFloatValue(const JSON_ref map, const char *key, INOUT float *val);
// set string value for key in map JSON, returns true upon success
bool json_mapSetStringValue(const JSON_ref map, const char *key, const char *val);
// set raw string (does not add quotes) value for key in map JSON, returns true upon success
bool json_mapSetRawStringValue(const JSON_ref map, const char *key, const char *val);
// set JSON_ref value for key in map JSON, returns true upon success
bool json_mapSetJSONValue(const JSON_ref map, const char *key, const JSON_ref val);
// set long value for key in map JSON, returns true upon success
bool json_mapSetLongValue(const JSON_ref map, const char *key, long val);
// set bool value for key in map JSON, returns true upon success
bool json_mapSetBoolValue(const JSON_ref map, const char *key, bool val);
// set float value for key in map JSON, returns true upon success
bool json_mapSetFloatValue(const JSON_ref map, const char *key, float val);
// ----------------------------------------------------------------------------
// array functions
@ -48,10 +75,12 @@ bool json_mapParseFloatValue(const JSON_ref map, const char *key, INOUT float *v
// ----------------------------------------------------------------------------
// serialization
// serialize to file descriptor
bool json_serialize(JSON_ref json, int fd, bool pretty);
// ----------------------------------------------------------------------------
// destroys internal allocated memory (if any)
// destructor
void json_destroy(JSON_ref *jsonRef);
#endif

View File

@ -24,33 +24,34 @@ static void testprefs_teardown(void *unused) {
static const char *get_default_preferences(void) {
return
"{\n"
" \"cpu\" : {\n"
" \"speed\" : 1.0,\n"
" \"altspeed\" : 4.0\n"
" },\n"
" \"disk\" : {\n"
" \"diskPath\" : \"/usr/local/games/apple2/disks\"\n"
" },\n"
" \"video\" : {\n"
" \"color\" : \"interpolated\"\n"
" },\n"
" \"speaker\" : {\n"
" \"volume\" : 5\n"
" },\n"
" \"joystick\" : {\n"
" \"variant\" : \"keypad\",\n"
" \"pcJoystickParms\" : \"128 128 255 1 255 1\",\n"
" \"kpJoystickParms\" : \"8 1\"\n"
" },\n"
" \"keyboard\" : {\n"
" \"caps\" : true\n"
" }\n"
"}\n"
"{"
" \"audio\" : {"
" \"speakerVolume\" : 4,"
" \"mbVolume\" : 2"
" },"
" \"interface\" : {"
" \"diskPath\" : \"/usr/local/games/apple2/disks\""
" },"
" \"joystick\" : {"
" \"variant\" : \"keypad\","
" \"pcJoystickParms\" : \"128 128 255 1 255 1\","
" \"kpJoystickParms\" : \"8 1\""
" },"
" \"keyboard\" : {"
" \"caps\" : true"
" }",
" \"video\" : {"
" \"color\" : \"interpolated\""
" },"
" \"vm\" : {"
" \"speed\" : 1.0,"
" \"altSpeed\" : 4.0"
" }"
"}"
;
}
static const char *get_sample_json_1(void) {
static const char *get_sample_json_0(void) {
return
" { "
" \"key0\" : \"a value zero\", "
@ -170,7 +171,7 @@ TEST test_json_map_0(JSON_ref parsedData) {
TEST test_json_map_1() {
const char *testMapStr0 = get_sample_json_1();
const char *testMapStr0 = get_sample_json_0();
JSON_ref parsedData = NULL;
int tokCount = json_createFromString(testMapStr0, &parsedData);
@ -184,7 +185,7 @@ TEST test_json_map_1() {
TEST test_json_serialization() {
const char *testMapStr0 = get_sample_json_1();
const char *testMapStr0 = get_sample_json_0();
JSON_ref parsedData = NULL;
int tokCount = json_createFromString(testMapStr0, &parsedData);
@ -198,7 +199,8 @@ TEST test_json_serialization() {
json_serialize(parsedData, fd, /*pretty:*/false);
json_destroy(&parsedData);
lseek(fd, 0, SEEK_SET);
json_createFromFD(fd, &parsedData);
tokCount = json_createFromFD(fd, &parsedData);
ASSERT(tokCount > 0);
test_json_map_0(parsedData);
@ -210,7 +212,7 @@ TEST test_json_serialization() {
TEST test_json_serialization_pretty() {
const char *testMapStr0 = get_sample_json_1();
const char *testMapStr0 = get_sample_json_0();
JSON_ref parsedData = NULL;
int tokCount = json_createFromString(testMapStr0, &parsedData);
@ -224,7 +226,8 @@ TEST test_json_serialization_pretty() {
json_serialize(parsedData, fd, /*pretty:*/true);
json_destroy(&parsedData);
lseek(fd, 0, SEEK_SET);
json_createFromFD(fd, &parsedData);
tokCount = json_createFromFD(fd, &parsedData);
ASSERT(tokCount > 0);
do {
long lVal;
@ -334,11 +337,259 @@ TEST test_json_serialization_pretty() {
PASS();
}
TEST test_json_invalid_bareKey() {
JSON_ref parsedData = NULL;
jsmnerr_t errCount = (jsmnerr_t)json_createFromString("{ aBareKey : \"aNonBareVal\" }", &parsedData);
ASSERT(errCount == JSMN_ERROR_INVAL);
json_destroy(&parsedData);
PASS();
}
TEST test_json_invalid_bareVal() {
JSON_ref parsedData = NULL;
jsmnerr_t errCount = (jsmnerr_t)json_createFromString("{ \"aNonBareKey\" : aBareVal }", &parsedData);
ASSERT(errCount == JSMN_ERROR_INVAL);
json_destroy(&parsedData);
PASS();
}
TEST test_json_map_invalid_danglingComma() {
JSON_ref parsedData = NULL;
jsmnerr_t errCount = (jsmnerr_t)json_createFromString("{ \"aNonBareKey\" : \"aNonBareVal\", }", &parsedData);
ASSERT(errCount == JSMN_ERROR_INVAL);
json_destroy(&parsedData);
PASS();
}
TEST test_json_map_invalid_danglingKey() {
JSON_ref parsedData = NULL;
jsmnerr_t errCount = (jsmnerr_t)json_createFromString("{ \"aNonBareKey\" : \"aNonBareVal\", \"aNoneBareButDanglingKey\" }", &parsedData);
ASSERT(errCount == JSMN_ERROR_INVAL);
json_destroy(&parsedData);
PASS();
}
TEST test_json_map_mutation_1() {
JSON_ref parsedData = NULL;
int tokCount = json_createFromString("{}", &parsedData);
ASSERT(tokCount == 1);
bool ok = false;
char *val = NULL;
long lVal = 0;
float fVal = 0.f;
ok = json_mapSetStringValue(parsedData, "key0", "val0");
ASSERT(ok);
ok = json_mapCopyStringValue(parsedData, "key0", &val);
ASSERT(ok);
ASSERT(strcmp(val, "val0") == 0);
FREE(val);
ok = json_mapSetStringValue(parsedData, "key1", "val1");
ASSERT(ok);
ok = json_mapCopyStringValue(parsedData, "key1", &val);
ASSERT(ok);
ASSERT(strcmp(val, "val1") == 0);
FREE(val);
ok = json_mapSetLongValue(parsedData, "longKey0", 42);
ASSERT(ok);
ok = json_mapCopyStringValue(parsedData, "longKey0", &val);
ASSERT(ok);
ASSERT(strcmp(val, "42") == 0);
FREE(val);
ok = json_mapParseLongValue(parsedData, "longKey0", &lVal, 10);
ASSERT(ok);
ASSERT(lVal == 42);
ok = json_mapSetStringValue(parsedData, "key0", "");
ASSERT(ok);
ok = json_mapCopyStringValue(parsedData, "key0", &val);
ASSERT(ok);
ASSERT(strlen(val) == 0);
FREE(val);
ok = json_mapSetStringValue(parsedData, "key1", "hello world");
ASSERT(ok);
ok = json_mapCopyStringValue(parsedData, "key1", &val);
ASSERT(ok);
ASSERT(strcmp(val, "hello world") == 0);
FREE(val);
ok = json_mapSetFloatValue(parsedData, "floatKey0", 0.25);
ASSERT(ok);
ok = json_mapParseFloatValue(parsedData, "floatKey0", &fVal);
ASSERT(ok);
ASSERT(fVal == 0.25);
ok = json_mapSetFloatValue(parsedData, "floatKey0", -0.5);
ASSERT(ok);
ok = json_mapParseFloatValue(parsedData, "floatKey0", &fVal);
ASSERT(ok);
ASSERT(fVal == -0.5);
// test sub maps ...
do {
JSON_ref parsedSubData = NULL;
tokCount = json_createFromString("{}", &parsedSubData);
ASSERT(tokCount == 1);
ok = json_mapSetStringValue(parsedSubData, "foo", "bar");
ASSERT(ok);
do {
JSON_ref parsedSubSubData = NULL;
tokCount = json_createFromString("{}", &parsedSubSubData);
ASSERT(tokCount == 1);
ok = json_mapSetStringValue(parsedSubSubData, "subFoo", "subBar");
ASSERT(ok);
ok = json_mapSetJSONValue(parsedSubData, "subKey0", parsedSubSubData);
ASSERT(ok);
json_destroy(&parsedSubSubData);
} while (0);
ok = json_mapSetJSONValue(parsedData, "key2", parsedSubData);
ASSERT(ok);
json_destroy(&parsedSubData);
} while (0);
ok = json_mapCopyStringValue(parsedData, "key2", &val);
ASSERT(ok);
ASSERT(strcmp(val, "{\"subKey0\":{\"subFoo\":\"subBar\"},\"foo\":\"bar\"}") == 0); // HACK : testing whitespace implementation details
FREE(val);
do {
JSON_ref parsedSubData = NULL;
ok = json_mapCopyJSON(parsedData, "key2", &parsedSubData);
ASSERT(ok);
ASSERT(parsedSubData);
ok = json_mapCopyStringValue(parsedSubData, "foo", &val);
ASSERT(ok);
ASSERT(strcmp(val, "bar") == 0);
FREE(val);
ok = json_mapCopyStringValue(parsedSubData, "subKey0", &val);
ASSERT(ok);
ASSERT(strcmp(val, "{\"subFoo\":\"subBar\"}") == 0); // HACK : testing whitespace implementation details
FREE(val);
do {
JSON_ref parsedSubSubData = NULL;
ok = json_mapCopyJSON(parsedSubData, "subKey0", &parsedSubSubData);
ASSERT(ok);
ASSERT(parsedSubSubData);
ok = json_mapCopyStringValue(parsedSubSubData, "subFoo", &val);
ASSERT(ok);
ASSERT(strcmp(val, "subBar") == 0);
FREE(val);
json_destroy(&parsedSubSubData);
} while (0);
json_destroy(&parsedSubData);
} while (0);
// setting invalid JSON does not clobber existing data ...
ok = json_mapSetRawStringValue(parsedData, "key2", "{ aBareKey : \"this is invalid\" }");
ASSERT(!ok);
ok = json_mapCopyStringValue(parsedData, "key2", &val);
ASSERT(ok);
ASSERT(strcmp(val, "{\"subKey0\":{\"subFoo\":\"subBar\"},\"foo\":\"bar\"}") == 0); // HACK : testing whitespace implementation details
FREE(val);
ok = json_mapSetRawStringValue(parsedData, "key2", "{ \"aNotBareKey\" : \"this should now be valid\" }");
ASSERT(ok);
ok = json_mapCopyStringValue(parsedData, "key2", &val);
ASSERT(ok);
ASSERT(strcmp(val, "{ \"aNotBareKey\" : \"this should now be valid\" }") == 0); // HACK : testing whitespace implementation details
FREE(val);
json_destroy(&parsedData);
PASS();
}
#if 0
TEST test_prefs_loadString_1() {
const char *prefsJSON = get_default_preferences();
bool loaded = prefs_loadString(prefsJSON);
ASSERT(loaded);
bool ok = prefs_loadString(prefsJSON);
ASSERT(ok);
char *val = NULL;
bool bVal = false;
long lVal = 0;
bool fVal = 0.f;
ok = prefs_parseLongValue(PREF_DOMAIN_AUDIO, "speakerVolume", &lVal);
ASSERT(ok);
ASSERT(lVal == 4);
ok = prefs_parseLongValue(PREF_DOMAIN_AUDIO, "mbVolume", &lVal);
ASSERT(ok);
ASSERT(lVal == 2);
ok = prefs_copyStringValue(PREF_DOMAIN_INTERFACE, "diskPath", &val);
ASSERT(ok);
ASSERT(strcmp(val, "/usr/local/games/apple2/disks") == 0);
FREE(val);
ok = prefs_copyStringValue(PREF_DOMAIN_JOYSTICK, "variant", &val);
ASSERT(ok);
ASSERT(strcmp(val, "keypad") == 0);
FREE(val);
ok = prefs_copyStringValue(PREF_DOMAIN_JOYSTICK, "pcJoystickParms", &val);
ASSERT(ok);
ASSERT(strcmp(val, "128 128 255 1 255 1") == 0);
FREE(val);
ok = prefs_copyStringValue(PREF_DOMAIN_JOYSTICK, "kpJoystickParms", &val);
ASSERT(ok);
ASSERT(strcmp(val, "8 1") == 0);
FREE(val);
ok = prefs_parseBoolValue(PREF_DOMAIN_KEYBOARD, "caps", &bVal);
ASSERT(ok);
ASSERT(bVal == true);
ok = prefs_copyStringValue(PREF_DOMAIN_VIDEO, "color", &val);
ASSERT(ok);
ASSERT(strcmp(val, "interpolated") == 0);
FREE(val);
ok = prefs_parseFloatValue(PREF_DOMAIN_VM, "speed", &fVal);
ASSERT(ok);
ASSERT(fVal == 1.f);
ok = prefs_parseFloatValue(PREF_DOMAIN_VM, "altSpeed", &fVal);
ASSERT(ok);
ASSERT(fVal == 4.f);
PASS();
}
#endif
@ -348,7 +599,7 @@ TEST test_prefs_loadString_1() {
GREATEST_SUITE(test_suite_prefs) {
GREATEST_SET_SETUP_CB(testprefs_setup, NULL);
GREATEST_SET_TEARDOWN_CB(testprefs_teardown, NULL);
GREATEST_SET_BREAKPOINT_CB(test_breakpoint, NULL);
//GREATEST_SET_BREAKPOINT_CB(test_breakpoint, NULL);
// TESTS --------------------------
test_thread_running = true;
@ -358,6 +609,15 @@ GREATEST_SUITE(test_suite_prefs) {
RUN_TESTp(test_json_serialization);
RUN_TESTp(test_json_serialization_pretty);
RUN_TESTp(test_json_invalid_bareKey);
RUN_TESTp(test_json_invalid_bareVal);
RUN_TESTp(test_json_map_invalid_danglingComma);
RUN_TESTp(test_json_map_invalid_danglingKey);
RUN_TESTp(test_json_map_mutation_1);
//RUN_TESTp(test_prefs_loadString_1);
// --------------------------------
pthread_mutex_unlock(&interface_mutex);
}