From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mailman by lists.gnu.org with tmda-scanned (Exim 4.43) id 1Mz9SN-0005oy-Sx for qemu-devel@nongnu.org; Sat, 17 Oct 2009 09:36:28 -0400 Received: from exim by lists.gnu.org with spam-scanned (Exim 4.43) id 1Mz9SG-0005kW-Fn for qemu-devel@nongnu.org; Sat, 17 Oct 2009 09:36:24 -0400 Received: from [199.232.76.173] (port=58963 helo=monty-python.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1Mz9SF-0005kQ-UX for qemu-devel@nongnu.org; Sat, 17 Oct 2009 09:36:20 -0400 Received: from e35.co.us.ibm.com ([32.97.110.153]:55257) by monty-python.gnu.org with esmtps (TLS-1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.60) (envelope-from ) id 1Mz9SF-0000OQ-84 for qemu-devel@nongnu.org; Sat, 17 Oct 2009 09:36:19 -0400 Received: from d03relay04.boulder.ibm.com (d03relay04.boulder.ibm.com [9.17.195.106]) by e35.co.us.ibm.com (8.14.3/8.13.1) with ESMTP id n9HDPNxr032315 for ; Sat, 17 Oct 2009 07:25:23 -0600 Received: from d03av04.boulder.ibm.com (d03av04.boulder.ibm.com [9.17.195.170]) by d03relay04.boulder.ibm.com (8.13.8/8.13.8/NCO v10.0) with ESMTP id n9HDaDEK174338 for ; Sat, 17 Oct 2009 07:36:13 -0600 Received: from d03av04.boulder.ibm.com (loopback [127.0.0.1]) by d03av04.boulder.ibm.com (8.12.11.20060308/8.13.3) with ESMTP id n9HDaDha021539 for ; Sat, 17 Oct 2009 07:36:13 -0600 From: Anthony Liguori Date: Sat, 17 Oct 2009 08:36:05 -0500 Message-Id: <1255786571-3528-6-git-send-email-aliguori@us.ibm.com> In-Reply-To: <1255786571-3528-1-git-send-email-aliguori@us.ibm.com> References: <1255786571-3528-1-git-send-email-aliguori@us.ibm.com> Subject: [Qemu-devel] [PATCH 05/11] Add unit test for json parser List-Id: qemu-devel.nongnu.org List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: qemu-devel@nongnu.org Cc: Anthony Liguori I've attempted to make this a very thorough unit test for the json parser. It covers every rule and tries to test the corner cases of each rule. There's some interesting things in this test case like a literal qobject type. It may be worth extract that into common code. Signed-off-by: Anthony Liguori --- Makefile | 2 +- check-qjson.c | 586 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ configure | 2 +- 3 files changed, 588 insertions(+), 2 deletions(-) create mode 100644 check-qjson.c diff --git a/Makefile b/Makefile index 1a99f66..8b80150 100644 --- a/Makefile +++ b/Makefile @@ -217,7 +217,7 @@ check-qstring: check-qstring.o qstring.o qemu-malloc.o check-qdict: check-qdict.o qdict.o qint.o qstring.o qemu-malloc.o check-qlist: check-qlist.o qlist.o qint.o qemu-malloc.o check-qfloat: check-qfloat.o qfloat.o qemu-malloc.o - +check-qjson: check-qjson.o qjson.o qstring.o qint.o qdict.o qlist.o qfloat.o qemu-malloc.o clean: # avoid old build problems by removing potentially incorrect old files diff --git a/check-qjson.c b/check-qjson.c new file mode 100644 index 0000000..0f661b9 --- /dev/null +++ b/check-qjson.c @@ -0,0 +1,586 @@ +/* + * Copyright IBM, Corp. 2009 + * + * Authors: + * Anthony Liguori + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ +#include + +#include "qstring.h" +#include "qint.h" +#include "qdict.h" +#include "qlist.h" +#include "qfloat.h" +#include "qjson.h" + +#include "qemu-common.h" + +START_TEST(escaped_string) +{ + int i; + struct { + const char *encoded; + const char *decoded; + } test_cases[] = { + { "\"\\\"\"", "\"" }, + { "\"hello world \\\"embedded string\\\"\"", + "hello world \"embedded string\"" }, + { "\"hello world\\nwith new line\"", "hello world\nwith new line" }, + { "\"single byte utf-8 \\u0020\"", "single byte utf-8 " }, + { "\"double byte utf-8 \\u00A2\"", "double byte utf-8 \xc2\xa2" }, + { "\"triple byte utf-8 \\u20AC\"", "triple byte utf-8 \xe2\x82\xac" }, + {} + }; + + for (i = 0; test_cases[i].encoded; i++) { + QObject *obj; + QString *str; + size_t length = 0; + + obj = qobject_from_json(test_cases[i].encoded, &length); + + fail_unless(obj != NULL); + fail_unless(qobject_type(obj) == QTYPE_QSTRING); + fail_unless(length == strlen(test_cases[i].encoded)); + + str = qobject_to_qstring(obj); + fail_unless(strcmp(qstring_get_str(str), test_cases[i].decoded) == 0); + + QDECREF(str); + } +} +END_TEST + +START_TEST(simple_string) +{ + int i; + struct { + const char *encoded; + const char *decoded; + } test_cases[] = { + { "\"hello world\"", "hello world" }, + { "\"the quick brown fox jumped over the fence\"", + "the quick brown fox jumped over the fence" }, + {} + }; + + for (i = 0; test_cases[i].encoded; i++) { + QObject *obj; + QString *str; + size_t length = 0; + + obj = qobject_from_json(test_cases[i].encoded, &length); + + fail_unless(obj != NULL); + fail_unless(qobject_type(obj) == QTYPE_QSTRING); + fail_unless(length == strlen(test_cases[i].encoded)); + + str = qobject_to_qstring(obj); + fail_unless(strcmp(qstring_get_str(str), test_cases[i].decoded) == 0); + + QDECREF(str); + } +} +END_TEST + +START_TEST(single_quote_string) +{ + int i; + struct { + const char *encoded; + const char *decoded; + } test_cases[] = { + { "'hello world'", "hello world" }, + { "'the quick brown fox \\' jumped over the fence'", + "the quick brown fox ' jumped over the fence" }, + {} + }; + + for (i = 0; test_cases[i].encoded; i++) { + QObject *obj; + QString *str; + size_t length = 0; + + obj = qobject_from_json(test_cases[i].encoded, &length); + + fail_unless(obj != NULL); + fail_unless(qobject_type(obj) == QTYPE_QSTRING); + fail_unless(length == strlen(test_cases[i].encoded)); + + str = qobject_to_qstring(obj); + fail_unless(strcmp(qstring_get_str(str), test_cases[i].decoded) == 0); + + QDECREF(str); + } +} +END_TEST + +START_TEST(vararg_string) +{ + int i; + struct { + const char *decoded; + } test_cases[] = { + { "hello world" }, + { "the quick brown fox jumped over the fence" }, + {} + }; + + for (i = 0; test_cases[i].decoded; i++) { + QObject *obj; + QString *str; + size_t length = 0; + + obj = qobject_from_jsonf("%s", &length, test_cases[i].decoded); + + fail_unless(obj != NULL); + fail_unless(qobject_type(obj) == QTYPE_QSTRING); + fail_unless(length == 2); + + str = qobject_to_qstring(obj); + fail_unless(strcmp(qstring_get_str(str), test_cases[i].decoded) == 0); + + QDECREF(str); + } +} +END_TEST + +START_TEST(simple_number) +{ + int i; + struct { + const char *encoded; + int64_t decoded; + } test_cases[] = { + { "0", 0 }, + { "1234", 1234 }, + { "1", 1 }, + { "-32", -32 }, + { "-0", 0 }, + { }, + }; + + for (i = 0; test_cases[i].encoded; i++) { + QObject *obj; + QInt *qint; + size_t length = 0; + + obj = qobject_from_json(test_cases[i].encoded, &length); + fail_unless(obj != NULL); + fail_unless(qobject_type(obj) == QTYPE_QINT); + fail_unless(length == strlen(test_cases[i].encoded)); + + qint = qobject_to_qint(obj); + fail_unless(qint_get_int(qint) == test_cases[i].decoded); + + QDECREF(qint); + } +} +END_TEST + +START_TEST(float_number) +{ + int i; + struct { + const char *encoded; + double decoded; + } test_cases[] = { + { "32.43", 32.43 }, + { "0.222", 0.222 }, + { "-32.12313", -32.12313 }, + { "-32.20e-10", -32.20e-10 }, + { }, + }; + + for (i = 0; test_cases[i].encoded; i++) { + QObject *obj; + QFloat *qfloat; + size_t length = 0; + + obj = qobject_from_json(test_cases[i].encoded, &length); + fail_unless(obj != NULL); + fail_unless(qobject_type(obj) == QTYPE_QFLOAT); + fail_unless(length == strlen(test_cases[i].encoded)); + + qfloat = qobject_to_qfloat(obj); + fail_unless(qfloat_get_double(qfloat) == test_cases[i].decoded); + + QDECREF(qfloat); + } +} +END_TEST + +START_TEST(vararg_number) +{ + QObject *obj; + QInt *qint; + QFloat *qfloat; + int value = 0x2342; + int64_t value64 = 0x2342342343LL; + double valuef = 2.323423423; + size_t length = 0; + + obj = qobject_from_jsonf("%d", &length, value); + fail_unless(obj != NULL); + fail_unless(qobject_type(obj) == QTYPE_QINT); + fail_unless(length == 2); + + qint = qobject_to_qint(obj); + fail_unless(qint_get_int(qint) == value); + + QDECREF(qint); + + obj = qobject_from_jsonf("%" PRId64, &length, value64); + fail_unless(obj != NULL); + fail_unless(qobject_type(obj) == QTYPE_QINT); + fail_unless(length == 1 + strlen(PRId64)); + + qint = qobject_to_qint(obj); + fail_unless(qint_get_int(qint) == value64); + + QDECREF(qint); + + obj = qobject_from_jsonf("%f", &length, valuef); + fail_unless(obj != NULL); + fail_unless(qobject_type(obj) == QTYPE_QFLOAT); + fail_unless(length == 2); + + qfloat = qobject_to_qfloat(obj); + fail_unless(qfloat_get_double(qfloat) == valuef); + + QDECREF(qfloat); +} +END_TEST + +START_TEST(keyword_literal) +{ + QObject *obj; + QInt *qint; + size_t length = 0; + + obj = qobject_from_json("true", &length); + fail_unless(obj != NULL); + fail_unless(qobject_type(obj) == QTYPE_QINT); + fail_unless(length == 4); + + qint = qobject_to_qint(obj); + fail_unless(qint_get_int(qint) != 0); + + QDECREF(qint); + + obj = qobject_from_json("false", &length); + fail_unless(obj != NULL); + fail_unless(qobject_type(obj) == QTYPE_QINT); + fail_unless(length == 5); + + qint = qobject_to_qint(obj); + fail_unless(qint_get_int(qint) == 0); + + QDECREF(qint); +} +END_TEST + +typedef struct LiteralQDictEntry LiteralQDictEntry; +typedef struct LiteralQObject LiteralQObject; + +struct LiteralQObject +{ + int type; + union { + int64_t qint; + const char *qstr; + LiteralQDictEntry *qdict; + LiteralQObject *qlist; + } value; +}; + +struct LiteralQDictEntry +{ + const char *key; + LiteralQObject value; +}; + +#define QLIT_QINT(val) (LiteralQObject){.type = QTYPE_QINT, .value.qint = (val)} +#define QLIT_QSTR(val) (LiteralQObject){.type = QTYPE_QSTRING, .value.qstr = (val)} +#define QLIT_QDICT(val) (LiteralQObject){.type = QTYPE_QDICT, .value.qdict = (val)} +#define QLIT_QLIST(val) (LiteralQObject){.type = QTYPE_QLIST, .value.qlist = (val)} + +typedef struct QListCompareHelper +{ + int index; + LiteralQObject *objs; + int result; +} QListCompareHelper; + +static int compare_litqobj_to_qobj(LiteralQObject *lhs, QObject *rhs); + +static void compare_helper(QObject *obj, void *opaque) +{ + QListCompareHelper *helper = opaque; + + if (helper->result == 0) { + return; + } + + if (helper->objs[helper->index].type == QTYPE_NONE) { + helper->result = 0; + return; + } + + helper->result = compare_litqobj_to_qobj(&helper->objs[helper->index++], obj); +} + +static int compare_litqobj_to_qobj(LiteralQObject *lhs, QObject *rhs) +{ + if (lhs->type != qobject_type(rhs)) { + return 0; + } + + switch (lhs->type) { + case QTYPE_QINT: + return lhs->value.qint == qint_get_int(qobject_to_qint(rhs)); + case QTYPE_QSTRING: + return (strcmp(lhs->value.qstr, qstring_get_str(qobject_to_qstring(rhs))) == 0); + case QTYPE_QDICT: { + int i; + + for (i = 0; lhs->value.qdict[i].key; i++) { + QObject *obj = qdict_get(qobject_to_qdict(rhs), lhs->value.qdict[i].key); + + if (!compare_litqobj_to_qobj(&lhs->value.qdict[i].value, obj)) { + return 0; + } + } + + return 1; + } + case QTYPE_QLIST: { + QListCompareHelper helper; + + helper.index = 0; + helper.objs = lhs->value.qlist; + helper.result = 1; + + qlist_iter(qobject_to_qlist(rhs), compare_helper, &helper); + + return helper.result; + } + default: + break; + } + + return 0; +} + +START_TEST(simple_dict) +{ + int i; + struct { + const char *encoded; + LiteralQObject decoded; + } test_cases[] = { + { + .encoded = "{\"foo\":42,\"bar\":\"hello world\"}", + .decoded = QLIT_QDICT(((LiteralQDictEntry[]){ + { "foo", QLIT_QINT(42) }, + { "bar", QLIT_QSTR("hello world") }, + { } + })), + }, { + .encoded = "{}", + .decoded = QLIT_QDICT(((LiteralQDictEntry[]){ + { } + })), + }, { + .encoded = "{\"foo\":43}", + .decoded = QLIT_QDICT(((LiteralQDictEntry[]){ + { "foo", QLIT_QINT(43) }, + { } + })), + }, + { } + }; + + for (i = 0; test_cases[i].encoded; i++) { + QObject *obj; + size_t length = 0; + + obj = qobject_from_json(test_cases[i].encoded, &length); + fail_unless(obj != NULL); + fail_unless(qobject_type(obj) == QTYPE_QDICT); + fail_unless(length == strlen(test_cases[i].encoded)); + + fail_unless(compare_litqobj_to_qobj(&test_cases[i].decoded, obj) == 1); + + qobject_decref(obj); + } +} +END_TEST + +START_TEST(simple_list) +{ + int i; + struct { + const char *encoded; + LiteralQObject decoded; + } test_cases[] = { + { + .encoded = "[43,42]", + .decoded = QLIT_QLIST(((LiteralQObject[]){ + QLIT_QINT(43), + QLIT_QINT(42), + { } + })), + }, + { + .encoded = "[43]", + .decoded = QLIT_QLIST(((LiteralQObject[]){ + QLIT_QINT(43), + { } + })), + }, + { + .encoded = "[]", + .decoded = QLIT_QLIST(((LiteralQObject[]){ + { } + })), + }, + { } + }; + + for (i = 0; test_cases[i].encoded; i++) { + QObject *obj; + size_t length = 0; + + obj = qobject_from_json(test_cases[i].encoded, &length); + fail_unless(obj != NULL); + fail_unless(qobject_type(obj) == QTYPE_QLIST); + fail_unless(length == strlen(test_cases[i].encoded)); + + fail_unless(compare_litqobj_to_qobj(&test_cases[i].decoded, obj) == 1); + + qobject_decref(obj); + } +} +END_TEST + +START_TEST(simple_whitespace) +{ + int i; + struct { + const char *encoded; + LiteralQObject decoded; + } test_cases[] = { + { + .encoded = " [ 43 , 42 ]", + .decoded = QLIT_QLIST(((LiteralQObject[]){ + QLIT_QINT(43), + QLIT_QINT(42), + { } + })), + }, + { + .encoded = " [ 43 , { 'h' : 'b' }, [ ], 42 ]", + .decoded = QLIT_QLIST(((LiteralQObject[]){ + QLIT_QINT(43), + QLIT_QDICT(((LiteralQDictEntry[]){ + { "h", QLIT_QSTR("b") }, + { }})), + QLIT_QLIST(((LiteralQObject[]){ + { }})), + QLIT_QINT(42), + { } + })), + }, + { + .encoded = " [ 43 , { 'h' : 'b' , 'a' : 32 }, [ ], 42 ]", + .decoded = QLIT_QLIST(((LiteralQObject[]){ + QLIT_QINT(43), + QLIT_QDICT(((LiteralQDictEntry[]){ + { "h", QLIT_QSTR("b") }, + { "a", QLIT_QINT(32) }, + { }})), + QLIT_QLIST(((LiteralQObject[]){ + { }})), + QLIT_QINT(42), + { } + })), + }, + { } + }; + + for (i = 0; test_cases[i].encoded; i++) { + QObject *obj; + size_t length = 0; + + obj = qobject_from_json(test_cases[i].encoded, &length); + fail_unless(obj != NULL); + fail_unless(qobject_type(obj) == QTYPE_QLIST); + fail_unless(length == strlen(test_cases[i].encoded)); + + fail_unless(compare_litqobj_to_qobj(&test_cases[i].decoded, obj) == 1); + + qobject_decref(obj); + } +} +END_TEST + +static Suite *qjson_suite(void) +{ + Suite *suite; + TCase *string_literals, *number_literals, *keyword_literals; + TCase *dicts, *lists, *whitespace; + + string_literals = tcase_create("String Literals"); + tcase_add_test(string_literals, simple_string); + tcase_add_test(string_literals, escaped_string); + tcase_add_test(string_literals, single_quote_string); + tcase_add_test(string_literals, vararg_string); + + number_literals = tcase_create("Number Literals"); + tcase_add_test(number_literals, simple_number); + tcase_add_test(number_literals, float_number); + tcase_add_test(number_literals, vararg_number); + + keyword_literals = tcase_create("Keywords"); + tcase_add_test(keyword_literals, keyword_literal); + + dicts = tcase_create("Objects"); + tcase_add_test(dicts, simple_dict); + + lists = tcase_create("Lists"); + tcase_add_test(lists, simple_list); + + whitespace = tcase_create("Whitespace"); + tcase_add_test(whitespace, simple_whitespace); + + suite = suite_create("QJSON test-suite"); + suite_add_tcase(suite, string_literals); + suite_add_tcase(suite, number_literals); + suite_add_tcase(suite, keyword_literals); + suite_add_tcase(suite, dicts); + suite_add_tcase(suite, lists); + suite_add_tcase(suite, whitespace); + + return suite; +} + +int main(void) +{ + int nf; + Suite *s; + SRunner *sr; + + s = qjson_suite(); + sr = srunner_create(s); + + srunner_run_all(sr, CK_NORMAL); + nf = srunner_ntests_failed(sr); + srunner_free(sr); + + return (nf == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/configure b/configure index 3e6f980..24caaac 100755 --- a/configure +++ b/configure @@ -2024,7 +2024,7 @@ if test `expr "$target_list" : ".*softmmu.*"` != 0 ; then if [ "$linux" = "yes" ] ; then tools="qemu-nbd\$(EXESUF) qemu-io\$(EXESUF) $tools" if [ "$check_utests" = "yes" ]; then - tools="check-qint check-qstring check-qdict check-qlist check-qfloat $tools" + tools="check-qint check-qstring check-qdict check-qlist check-qfloat check-qjson $tools" fi elif test "$mingw32" = "yes" ; then tools="qemu-io\$(EXESUF) $tools" -- 1.6.2.5