qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
From: Anthony Liguori <aliguori@us.ibm.com>
To: qemu-devel@nongnu.org
Cc: Anthony Liguori <aliguori@us.ibm.com>
Subject: [Qemu-devel] [PATCH 05/11] Add unit test for json parser
Date: Sat, 17 Oct 2009 08:36:05 -0500	[thread overview]
Message-ID: <1255786571-3528-6-git-send-email-aliguori@us.ibm.com> (raw)
In-Reply-To: <1255786571-3528-1-git-send-email-aliguori@us.ibm.com>

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

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"
-- 
1.6.2.5

  parent reply	other threads:[~2009-10-17 13:36 UTC|newest]

Thread overview: 28+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2009-10-17 13:36 [Qemu-devel] [PATCH 0/11] json parser (v2) Anthony Liguori
2009-10-17 13:36 ` [Qemu-devel] [PATCH 01/11] Add append method to qstring and empty constructor Anthony Liguori
2009-10-18 21:36   ` Luiz Capitulino
2009-10-23 19:33     ` Jamie Lokier
2009-10-17 13:36 ` [Qemu-devel] [PATCH 02/11] Add support for qfloat Anthony Liguori
2009-10-18 22:21   ` Luiz Capitulino
2009-10-19 14:18     ` Anthony Liguori
2009-10-22  8:49       ` Amit Shah
2009-10-22 14:01         ` Anthony Liguori
2009-10-22 14:05           ` Amit Shah
2009-10-23 19:25           ` Jamie Lokier
2009-10-23 19:36             ` Daniel P. Berrange
2009-10-17 13:36 ` [Qemu-devel] [PATCH 03/11] Add a test case " Anthony Liguori
2009-10-17 14:00   ` Edgar E. Iglesias
2009-10-17 16:21     ` Anthony Liguori
2009-10-17 13:36 ` [Qemu-devel] [PATCH 04/11] Add json->qobject parser Anthony Liguori
2009-10-23 17:45   ` Luiz Capitulino
2009-10-17 13:36 ` Anthony Liguori [this message]
2009-10-17 13:36 ` [Qemu-devel] [PATCH 06/11] qobject: add QBool type Anthony Liguori
2009-10-18 21:50   ` Luiz Capitulino
2009-10-19 14:17     ` Anthony Liguori
2009-10-19 14:21       ` Luiz Capitulino
2009-10-17 13:36 ` [Qemu-devel] [PATCH 07/11] qjson: Use QBool for true/false keywords Anthony Liguori
2009-10-17 13:36 ` [Qemu-devel] [PATCH 08/11] qjson: add %i for parsing bools Anthony Liguori
2009-10-17 13:36 ` [Qemu-devel] [PATCH 09/11] qjson: add unit test for varargs bool parsing Anthony Liguori
2009-10-17 13:36 ` [Qemu-devel] [PATCH 10/11] qjson: add vararg format for embedded qobjects Anthony Liguori
2009-10-17 13:36 ` [Qemu-devel] [PATCH 11/11] qjson: add unit test to check %p format Anthony Liguori
2009-10-18 21:34 ` [Qemu-devel] [PATCH 0/11] json parser (v2) Luiz Capitulino

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1255786571-3528-6-git-send-email-aliguori@us.ibm.com \
    --to=aliguori@us.ibm.com \
    --cc=qemu-devel@nongnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).