diff options
Diffstat (limited to 'tests/test_json.c')
-rw-r--r-- | tests/test_json.c | 623 |
1 files changed, 623 insertions, 0 deletions
diff --git a/tests/test_json.c b/tests/test_json.c new file mode 100644 index 00000000..01f36d3a --- /dev/null +++ b/tests/test_json.c @@ -0,0 +1,623 @@ +/* json.c - unit test for JSON parsing into fixed-extent structures + * + * This file is Copyright (c) 2010 by the GPSD project + * SPDX-License-Identifier: BSD-2-clause + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stddef.h> +#include <getopt.h> + +#include "../gpsd.h" +#include "../gps_json.h" +#include "../revision.h" + +/* GPSD is built with JSON_MINIMAL. Any !JSON_MINIMAL tests, + * like 18, 19 and 20 will thus fail. + * So this define removes them, they never execute. + */ +#define JSON_MINIMAL + +static int debug = 0; +static int current_test = 0; + +static void assert_case(int status) +{ + if (status != 0) { + (void)fprintf(stderr, "case %d FAILED\n", current_test); + (void)fprintf(stderr, "status %d (%s).\n", + status, json_error_string(status)); + exit(EXIT_FAILURE); + } +} + +static void assert_string(char *attr, char *fld, char *val) +{ + if (strcmp(fld, val)) { + (void)fprintf(stderr, "case %d FAILED\n", current_test); + (void)fprintf(stderr, + "'%s' string attribute eval failed, value = %s.\n", + attr, fld); + exit(EXIT_FAILURE); + } +} + +static void assert_integer(char *attr, int fld, int val) +{ + if (fld != val) { + (void)fprintf(stderr, "case %d FAILED\n", current_test); + (void)fprintf(stderr, + "'%s' integer attribute eval failed, value = %d.\n", + attr, fld); + exit(EXIT_FAILURE); + } +} + +static void assert_uinteger(char *attr, unsigned int fld, unsigned int val) +{ + if (fld != val) { + (void)fprintf(stderr, "case %d FAILED\n", current_test); + (void)fprintf(stderr, + "'%s' integer attribute eval failed, value = %u.\n", + attr, fld); + exit(EXIT_FAILURE); + } +} + +static void assert_boolean(char *attr, bool fld, bool val) +{ + if (fld != val) { + (void)fprintf(stderr, "case %d FAILED\n", current_test); + (void)fprintf(stderr, + "'%s' boolean attribute eval failed, value = %s.\n", + attr, fld ? "true" : "false"); + exit(EXIT_FAILURE); + } +} + +static void assert_other(char *desc, int val, int val1) +{ + if (val != val1) { + (void)fprintf(stderr, "case %d FAILED\n", current_test); + (void)fprintf(stderr, "'%s' was %d, s/b %d\n", desc, val, val1); + exit(EXIT_FAILURE); + } +} + +/* + * Floating point comparisons are iffy, but at least if any of these fail + * the output will make it clear whether it was a precision issue + */ +static void assert_real(char *attr, double fld, double val) +{ + if (fld != val) { + (void)fprintf(stderr, "case %d FAILED\n", current_test); + (void)fprintf(stderr, + "'%s' real attribute eval failed, value = %f.\n", attr, + fld); + exit(EXIT_FAILURE); + } +} + + +static struct gps_data_t gpsdata; + +/* Case 1: TPV report */ + +/* *INDENT-OFF* */ +static const char json_str1[] = "{\"class\":\"TPV\",\ + \"device\":\"GPS#1\", \ + \"time\":\"2005-06-19T08:12:41.89Z\",\"lon\":46.498203637,\"lat\":7.568074350,\ + \"alt\":1327.780,\"epx\":21.000,\"epy\":23.000,\"epv\":124.484,\"mode\":3}"; + +/* + * Case 2: SKY report + * + * The fields of the last satellite entry are arranged in the reverse order + * of the structure fields, in order to test for field overflow. + */ + +static const char *json_str2 = "{\"class\":\"SKY\",\ + \"time\":\"2005-06-19T12:12:42.03Z\", \ + \"satellites\":[\ + {\"PRN\":10,\"el\":45,\"az\":196,\"ss\":34,\"used\":true},\ + {\"PRN\":29,\"el\":67,\"az\":310,\"ss\":40,\"used\":true},\ + {\"PRN\":28,\"el\":59,\"az\":108,\"ss\":42,\"used\":true},\ + {\"PRN\":26,\"el\":51,\"az\":304,\"ss\":43,\"used\":true},\ + {\"PRN\":8,\"el\":44,\"az\":58,\"ss\":41,\"used\":true},\ + {\"PRN\":27,\"el\":16,\"az\":66,\"ss\":39,\"used\":true},\ + {\"az\":301,\"el\":10,\"PRN\":21,\"used\":false,\"ss\":0}]}"; + +/* Case 3: String list syntax */ + +static const char *json_str3 = "[\"foo\",\"bar\",\"baz\"]"; + +static char *stringptrs[3]; +static char stringstore[256]; +static int stringcount; + +static const struct json_array_t json_array_3 = { + .element_type = t_string, + .arr.strings.ptrs = stringptrs, + .arr.strings.store = stringstore, + .arr.strings.storelen = sizeof(stringstore), + .count = &stringcount, + .maxlen = sizeof(stringptrs)/sizeof(stringptrs[0]), +}; + +/* Case 4: test defaulting of unspecified attributes */ + +static const char *json_str4 = "{\"flag1\":true,\"flag2\":false}"; + +static bool flag1, flag2; +static double dftreal; +static int dftinteger; +static unsigned int dftuinteger; + +static const struct json_attr_t json_attrs_4[] = { + {"dftint", t_integer, .addr.integer = &dftinteger, .dflt.integer = -5}, + {"dftuint", t_integer, .addr.uinteger = &dftuinteger, .dflt.uinteger = 10}, + {"dftreal", t_real, .addr.real = &dftreal, .dflt.real = 23.17}, + {"flag1", t_boolean, .addr.boolean = &flag1,}, + {"flag2", t_boolean, .addr.boolean = &flag2,}, + {NULL}, +}; + +/* Case 5: test DEVICE parsing */ + +static const char *json_str5 = "{\"class\":\"DEVICE\",\ + \"path\":\"/dev/ttyUSB0\",\ + \"flags\":5,\ + \"driver\":\"Foonly\",\"subtype\":\"Foonly Frob\"\ + }"; + +/* Case 6: test parsing of subobject list into array of structures */ + +static const char *json_str6 = "{\"parts\":[\ + {\"name\":\"Urgle\", \"flag\":true, \"count\":3},\ + {\"name\":\"Burgle\",\"flag\":false,\"count\":1},\ + {\"name\":\"Witter\",\"flag\":true, \"count\":4},\ + {\"name\":\"Thud\", \"flag\":false,\"count\":1}]}"; + +struct dumbstruct_t { + char name[64]; + bool flag; + int count; +}; +static struct dumbstruct_t dumbstruck[5]; +static int dumbcount; + +static const struct json_attr_t json_attrs_6_subtype[] = { + {"name", t_string, .addr.offset = offsetof(struct dumbstruct_t, name), + .len = 64}, + {"flag", t_boolean, .addr.offset = offsetof(struct dumbstruct_t, flag),}, + {"count", t_integer, .addr.offset = offsetof(struct dumbstruct_t, count),}, + {NULL}, +}; + +static const struct json_attr_t json_attrs_6[] = { + {"parts", t_array, .addr.array.element_type = t_structobject, + .addr.array.arr.objects.base = (char*)&dumbstruck, + .addr.array.arr.objects.stride = sizeof(struct dumbstruct_t), + .addr.array.arr.objects.subtype = json_attrs_6_subtype, + .addr.array.count = &dumbcount, + .addr.array.maxlen = sizeof(dumbstruck)/sizeof(dumbstruck[0])}, + {NULL}, +}; + +/* Case 7: test parsing of version response */ + +static const char *json_str7 = "{\"class\":\"VERSION\",\ + \"release\":\"2.40dev\",\"rev\":\"dummy-revision\",\ + \"proto_major\":3,\"proto_minor\":1}"; + +/* Case 8: test parsing arrays of enumerated types */ + +static const char *json_str8 = "{\"fee\":\"FOO\",\"fie\":\"BAR\",\"foe\":\"BAZ\"}"; +static const struct json_enum_t enum_table[] = { + {"BAR", 6}, {"FOO", 3}, {"BAZ", 14}, {NULL} +}; + +static int fee, fie, foe; +static const struct json_attr_t json_attrs_8[] = { + {"fee", t_integer, .addr.integer = &fee, .map=enum_table}, + {"fie", t_integer, .addr.integer = &fie, .map=enum_table}, + {"foe", t_integer, .addr.integer = &foe, .map=enum_table}, + {NULL}, +}; + +/* Case 9: Like case 6 but w/ an empty array */ + +static const char *json_str9 = "{\"parts\":[]}"; + +/* Case 10: test parsing of PPS message */ + +static const char *json_strPPS = "{\"class\":\"PPS\",\"device\":\"GPS#1\"," \ + "\"real_sec\":1428001514, \"real_nsec\":1000000," \ + "\"clock_sec\":1428001513,\"clock_nsec\":999999999," \ + "\"precision\":-20}"; + +/* Case 11: test parsing of TOFF message */ + +static const char *json_strTOFF = "{\"class\":\"TOFF\",\"device\":\"GPS#1\"," \ + "\"real_sec\":1428001514, \"real_nsec\":1000000," \ + "\"clock_sec\":1428001513,\"clock_nsec\":999999999}"; + +/* Case 12: test parsing of OSC message */ + +static const char *json_strOSC = "{\"class\":\"OSC\",\"device\":\"GPS#1\"," \ + "\"running\":true,\"reference\":true,\"disciplined\":false," \ + "\"delta\":67}"; + +/* Case 13: test parsing of ERROR message, and some escape sequences */ + +static char *json_strErr = "{\"class\":\"ERROR\",\"message\":" \ + "\"Hello\b\f\n\r\t\"}"; + +/* Case 14: test parsing of ERROR message and \u escape */ +/* per ECMA-404, \u must be followed by 4 hex digits */ + +static char *json_strErr1 = "{\"class\":\"ERROR\",\"message\":\"0\\u00334\"}"; + +/* Case 15: test buffer overflow of short string destination */ + +static char *json_strOver = "{\"name\":\"\\u0033\\u0034\\u0035\\u0036\"}"; + +char json_short_string_dst[2]; +int json_short_string_cnt = 5; +static const struct json_attr_t json_short_string[] = { + {"name", t_string, + .addr.string = json_short_string_dst, + .len = sizeof(json_short_string_dst)}, + {"count", t_integer, .addr.integer = &json_short_string_cnt}, + {NULL}, +}; + +/* Case 16: test buffer overflow of short string destination */ + +static char json_strOver2[7 * JSON_VAL_MAX]; /* dynamically built */ + + +#ifndef JSON_MINIMAL +/* Case 17: Read array of integers */ + +static const char *json_strInt = "[23,-17,5]"; +static int intstore[4], intcount; + +static const struct json_array_t json_array_Int = { + .element_type = t_integer, + .arr.integers.store = intstore, + .count = &intcount, + .maxlen = sizeof(intstore)/sizeof(intstore[0]), +}; + +/* Case 18: Read array of booleans */ + +static const char *json_strBool = "[true,false,true]"; +static bool boolstore[4]; +static int boolcount; + +static const struct json_array_t json_array_Bool = { + .element_type = t_boolean, + .arr.booleans.store = boolstore, + .count = &boolcount, + .maxlen = sizeof(boolstore)/sizeof(boolstore[0]), +}; + +/* Case 19: Read array of reals */ + +static const char *json_strReal = "[23.1,-17.2,5.3]"; +static double realstore[4]; +static int realcount; + +static const struct json_array_t json_array_Real = { + .element_type = t_real, + .arr.reals.store = realstore, + .count = &realcount, + .maxlen = sizeof(realstore)/sizeof(realstore[0]), +}; +#endif /* JSON_MINIMAL */ + +/* *INDENT-ON* */ + +static void jsontest(int i) +{ + int status = 0; /* libgps_json_unpack() returned status */ + int n; /* generic index */ + + if (0 < debug) { + (void)fprintf(stderr, "Running test #%d.\n", i); + } + current_test = i; + + /* do not keep old data! */ + memset((void *)&gpsdata, 0, sizeof(gpsdata)); + + switch (i) + { + case 1: + status = libgps_json_unpack(json_str1, &gpsdata, NULL); + assert_case(status); + assert_string("device", gpsdata.dev.path, "GPS#1"); + assert_integer("mode", gpsdata.fix.mode, 3); + assert_real("time", gpsdata.fix.time, 1119168761.8900001); + assert_real("lon", gpsdata.fix.longitude, 46.498203637); + assert_real("lat", gpsdata.fix.latitude, 7.568074350); + break; + + case 2: + status = libgps_json_unpack(json_str2, &gpsdata, NULL); + assert_case(status); + assert_integer("used", gpsdata.satellites_used, 6); + assert_integer("PRN[0]", gpsdata.skyview[0].PRN, 10); + assert_integer("el[0]", gpsdata.skyview[0].elevation, 45); + assert_integer("az[0]", gpsdata.skyview[0].azimuth, 196); + assert_real("ss[0]", gpsdata.skyview[0].ss, 34); + assert_boolean("used[0]", gpsdata.skyview[0].used, true); + assert_integer("PRN[6]", gpsdata.skyview[6].PRN, 21); + assert_integer("el[6]", gpsdata.skyview[6].elevation, 10); + assert_integer("az[6]", gpsdata.skyview[6].azimuth, 301); + assert_real("ss[6]", gpsdata.skyview[6].ss, 0); + assert_boolean("used[6]", gpsdata.skyview[6].used, false); + break; + + case 3: + status = json_read_array(json_str3, &json_array_3, NULL); + assert_case(status); + assert_other("stringcount", stringcount, 3); + assert_other("stringptrs[0] == foo", strcmp(stringptrs[0], "foo"), 0); + assert_other("stringptrs[1] == bar", strcmp(stringptrs[1], "bar"), 0); + assert_other("stringptrs[2] == baz", strcmp(stringptrs[2], "baz"), 0); + break; + + case 4: + status = json_read_object(json_str4, json_attrs_4, NULL); + assert_case(status); + assert_integer("dftint", dftinteger, -5); /* did the default work? */ + assert_uinteger("dftuint", dftuinteger, 10); /* did the default work? */ + assert_real("dftreal", dftreal, 23.17); /* did the default work? */ + assert_boolean("flag1", flag1, true); + assert_boolean("flag2", flag2, false); + break; + + case 5: + status = libgps_json_unpack(json_str5, &gpsdata, NULL); + assert_case(status); + assert_string("path", gpsdata.dev.path, "/dev/ttyUSB0"); + assert_integer("flags", gpsdata.dev.flags, 5); + assert_string("driver", gpsdata.dev.driver, "Foonly"); + break; + + case 6: + status = json_read_object(json_str6, json_attrs_6, NULL); + assert_case(status); + assert_integer("dumbcount", dumbcount, 4); + assert_string("dumbstruck[0].name", dumbstruck[0].name, "Urgle"); + assert_string("dumbstruck[1].name", dumbstruck[1].name, "Burgle"); + assert_string("dumbstruck[2].name", dumbstruck[2].name, "Witter"); + assert_string("dumbstruck[3].name", dumbstruck[3].name, "Thud"); + assert_boolean("dumbstruck[0].flag", dumbstruck[0].flag, true); + assert_boolean("dumbstruck[1].flag", dumbstruck[1].flag, false); + assert_boolean("dumbstruck[2].flag", dumbstruck[2].flag, true); + assert_boolean("dumbstruck[3].flag", dumbstruck[3].flag, false); + assert_integer("dumbstruck[0].count", dumbstruck[0].count, 3); + assert_integer("dumbstruck[1].count", dumbstruck[1].count, 1); + assert_integer("dumbstruck[2].count", dumbstruck[2].count, 4); + assert_integer("dumbstruck[3].count", dumbstruck[3].count, 1); + break; + + case 7: + status = libgps_json_unpack(json_str7, &gpsdata, NULL); + assert_case(status); + assert_string("release", gpsdata.version.release, "2.40dev"); + assert_string("rev", gpsdata.version.rev, "dummy-revision"); + assert_integer("proto_major", gpsdata.version.proto_major, 3); + assert_integer("proto_minor", gpsdata.version.proto_minor, 1); + break; + + case 8: + status = json_read_object(json_str8, json_attrs_8, NULL); + assert_case(status); + assert_integer("fee", fee, 3); + assert_integer("fie", fie, 6); + assert_integer("foe", foe, 14); + break; + + case 9: + /* yes, the '6' in the next line is correct */ + status = json_read_object(json_str9, json_attrs_6, NULL); + assert_case(status); + assert_integer("dumbcount", dumbcount, 0); + break; + + case 10: + status = json_pps_read(json_strPPS, &gpsdata, NULL); + assert_case(status); + assert_string("device", gpsdata.dev.path, "GPS#1"); + assert_integer("real_sec", gpsdata.pps.real.tv_sec, 1428001514); + assert_integer("real_nsec", gpsdata.pps.real.tv_nsec, 1000000); + assert_integer("clock_sec", gpsdata.pps.clock.tv_sec, 1428001513); + assert_integer("clock_nsec", gpsdata.pps.clock.tv_nsec, 999999999); + break; + + case 11: + status = json_toff_read(json_strTOFF, &gpsdata, NULL); + assert_case(status); + assert_string("device", gpsdata.dev.path, "GPS#1"); + assert_integer("real_sec", gpsdata.pps.real.tv_sec, 1428001514); + assert_integer("real_nsec", gpsdata.pps.real.tv_nsec, 1000000); + assert_integer("clock_sec", gpsdata.pps.clock.tv_sec, 1428001513); + assert_integer("clock_nsec", gpsdata.pps.clock.tv_nsec, 999999999); + break; + + case 12: + status = json_oscillator_read(json_strOSC, &gpsdata, NULL); + assert_case(status); + assert_string("device", gpsdata.dev.path, "GPS#1"); + assert_boolean("running", gpsdata.osc.running, true); + assert_boolean("reference", gpsdata.osc.reference, true); + assert_boolean("disciplined", gpsdata.osc.disciplined, false); + assert_integer("delta", gpsdata.osc.delta, 67); + break; + + case 13: + if (2 < debug) { + (void)fprintf(stderr, "test string: %s.\n", json_strErr); + } + status = libgps_json_unpack(json_strErr, &gpsdata, NULL); + assert_case(status); + assert_string("message", gpsdata.error, "Hello\b\f\n\r\t"); + break; + + case 14: + if (2 < debug) { + (void)fprintf(stderr, "test string: %s.\n", json_strErr1); + } + status = libgps_json_unpack(json_strErr1, &gpsdata, NULL); + assert_case(status); + assert_string("message", gpsdata.error, "034"); + break; + + case 15: + /* check for string overrun caught */ + if (2 < debug) { + (void)fprintf(stderr, "test string: %s.\n", json_strOver); + } + json_short_string_cnt = 7; + status = json_read_object(json_strOver, json_short_string, NULL); + assert_case(JSON_ERR_STRLONG != status); + assert_string("name", json_short_string_dst, ""); + assert_integer("count", json_short_string_cnt, 0); + break; + + case 16: + /* check for string overrun caught */ + json_strOver2[0] = '\0'; + /* build a LONG test string */ + strlcat(json_strOver2, "{\"name\":\"", sizeof(json_strOver2)); + for (n = 0; n < (2 * JSON_VAL_MAX); n++) { + strlcat(json_strOver2, "\\u0033", sizeof(json_strOver2)); + } + strlcat(json_strOver2, "\"}", sizeof(json_strOver2)); + + if (2 < debug) { + (void)fprintf(stderr, "test string: %s.\n", json_strOver2); + } + json_short_string_cnt = 7; + status = json_read_object(json_strOver2, json_short_string, NULL); + assert_case(JSON_ERR_STRLONG != status); + assert_string("name", json_short_string_dst, ""); + assert_integer("count", json_short_string_cnt, 0); + break; + + case 17: + /* check for a different string overrun caught */ + json_strOver2[0] = '\0'; + /* build a LONG test string */ + strlcat(json_strOver2, "{\"name\":\"", sizeof(json_strOver2)); + for (n = 0; n < (2 * JSON_VAL_MAX); n++) { + strlcat(json_strOver2, "\\A", sizeof(json_strOver2)); + } + strlcat(json_strOver2, "\"}", sizeof(json_strOver2)); + + if (2 < debug) { + (void)fprintf(stderr, "test string: %s.\n", json_strOver2); + } + json_short_string_cnt = 7; + status = json_read_object(json_strOver2, json_short_string, NULL); + assert_case(JSON_ERR_STRLONG != status); + assert_string("name", json_short_string_dst, ""); + assert_integer("count", json_short_string_cnt, 0); + break; + +#ifdef JSON_MINIMAL +#define MAXTEST 17 +#else + case 18: + status = json_read_array(json_strInt, &json_array_Int, NULL); + assert_integer("count", intcount, 3); + assert_integer("intstore[0]", intstore[0], 23); + assert_integer("intstore[1]", intstore[1], -17); + assert_integer("intstore[2]", intstore[2], 5); + assert_integer("intstore[3]", intstore[3], 0); + break; + + case 19: + status = json_read_array(json_strBool, &json_array_Bool, NULL); + assert_integer("count", boolcount, 3); + assert_boolean("boolstore[0]", boolstore[0], true); + assert_boolean("boolstore[1]", boolstore[1], false); + assert_boolean("boolstore[2]", boolstore[2], true); + assert_boolean("boolstore[3]", boolstore[3], false); + break; + + case 20: + status = json_read_array(json_strReal, &json_array_Real, NULL); + assert_integer("count", realcount, 3); + assert_real("realstore[0]", realstore[0], 23.1); + assert_real("realstore[1]", realstore[1], -17.2); + assert_real("realstore[2]", realstore[2], 5.3); + assert_real("realstore[3]", realstore[3], 0); + break; + +#define MAXTEST 20 +#endif /* JSON_MINIMAL */ + + default: + (void)fputs("Unknown test number\n", stderr); + exit(EXIT_FAILURE); + } +} + +int main(int argc UNUSED, char *argv[]UNUSED) +{ + int option; + int individual = 0; + + while ((option = getopt(argc, argv, "D:hn:V?")) != -1) { + switch (option) { +#ifdef CLIENTDEBUG_ENABLE + case 'D': + debug = atoi(optarg); + gps_enable_debug(debug, stdout); + break; +#endif + case 'n': + individual = atoi(optarg); + break; + case '?': + case 'h': + default: + (void)fprintf(stderr, + "usage: %s [-D lvl] [-n tst] [-V]\n" + " -D lvl set debug level\n" + " -n tst run only test tst\n" + " -V Print version and exit\n", + argv[0]); + exit(EXIT_FAILURE); + case 'V': + (void)fprintf(stderr, "%s: %s (revision %s)\n", + argv[0], VERSION, REVISION); + exit(EXIT_SUCCESS); + } + } + + (void)fprintf(stderr, "JSON unit tests\n"); + + if (individual) + jsontest(individual); + else { + int i; + for (i = 1; i <= MAXTEST; i++) { + jsontest(i); + } + } + + (void)fprintf(stderr, "succeeded.\n"); + + exit(EXIT_SUCCESS); +} + +/* end */ |