Patchwork [05/11] Add unit test for json parser

login
register
mail settings
Submitter Anthony Liguori
Date Oct. 17, 2009, 1:36 p.m.
Message ID <1255786571-3528-6-git-send-email-aliguori@us.ibm.com>
Download mbox | patch
Permalink /patch/36309/
State New
Headers show

Comments

Anthony Liguori - Oct. 17, 2009, 1:36 p.m.
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 <aliguori@us.ibm.com>
---
 Makefile      |    2 +-
 check-qjson.c |  586 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 configure     |    2 +-
 3 files changed, 588 insertions(+), 2 deletions(-)
 create mode 100644 check-qjson.c

Patch

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   <aliguori@us.ibm.com>
+ *
+ * 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 <check.h>
+
+#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"