Patchwork [3/3] Add test suite for json marshalling

login
register
mail settings
Submitter Anthony Liguori
Date Nov. 11, 2009, 7:24 p.m.
Message ID <1257967478-4847-3-git-send-email-aliguori@us.ibm.com>
Download mbox | patch
Permalink /patch/38176/
State New
Headers show

Comments

Anthony Liguori - Nov. 11, 2009, 7:24 p.m.
By reusing the qjson test suite.  After checking that we can demarshal, marshal
again and compared to the expected decoded value.  This doesn't work so well
for floats because they cannot be accurately represented in decimal but we
try our best.

Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
---
 check-qjson.c |   80 +++++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 files changed, 75 insertions(+), 5 deletions(-)
Jamie Lokier - Nov. 13, 2009, 3:14 a.m.
Anthony Liguori wrote:
> After checking that we can demarshal, marshal again and compared to
> the expected decoded value.  This doesn't work so well for floats
> because they cannot be accurately represented in decimal but we try
> our best.

Good sprintf/scanf/strtod implementations do guarantee that what's
printed and then parsed gets back the same floating point value, as
long as you have printed sufficient decimal digits.

I'm not sure if FLT_DIG/DLB_DIG are the right number of digits,
though.  Glibc's documentation of those is confusing and they might
mean something a little different.

-- Jamie
Anthony Liguori - Nov. 13, 2009, 2:05 p.m.
Jamie Lokier wrote:
> Anthony Liguori wrote:
>   
>> After checking that we can demarshal, marshal again and compared to
>> the expected decoded value.  This doesn't work so well for floats
>> because they cannot be accurately represented in decimal but we try
>> our best.
>>     
>
> Good sprintf/scanf/strtod implementations do guarantee that what's
> printed and then parsed gets back the same floating point value, as
> long as you have printed sufficient decimal digits.
>
> I'm not sure if FLT_DIG/DLB_DIG are the right number of digits,
> though.  Glibc's documentation of those is confusing and they might
> mean something a little different.
>   

Eh, I played around quite a bit and the results were disappointing.

$ printf "%f\n" 43.32
43.320000
$ printf "%0.32f\n" 43.32
43.31999999999999999972244424384371

Regards,

Anthony Liguori
> -- Jamie
>
>
>
Jamie Lokier - Nov. 16, 2009, 2:26 a.m.
Anthony Liguori wrote:
> Jamie Lokier wrote:
> >Anthony Liguori wrote:
> >  
> >>After checking that we can demarshal, marshal again and compared to
> >>the expected decoded value.  This doesn't work so well for floats
> >>because they cannot be accurately represented in decimal but we try
> >>our best.
> >>    
> >
> >Good sprintf/scanf/strtod implementations do guarantee that what's
> >printed and then parsed gets back the same floating point value, as
> >long as you have printed sufficient decimal digits.
> >
> >I'm not sure if FLT_DIG/DLB_DIG are the right number of digits,
> >though.  Glibc's documentation of those is confusing and they might
> >mean something a little different.
> >  
> 
> Eh, I played around quite a bit and the results were disappointing.
> 
> $ printf "%f\n" 43.32
> 43.320000
> $ printf "%0.32f\n" 43.32
> 43.31999999999999999972244424384371

True enough.  I'm not sure how to ask for the minimum number of digits
for unambiguous representation.

Point still stands though: Try *parsing* both those outputs, and the
double result is exactly the same in each case.

$ printf '%0.32f\n' $(printf '%0.32f\n' 43.32)
43.31999999999999999972244424384371

So you can do this:

    unambiguous decimal text -> double -> same text

and this:

    double -> unambiguous decimal text -> same double

As long as "unambiguous decimal" was printed with enough digits.


-- Jamie
Markus Armbruster - Nov. 18, 2009, 10:48 a.m.
Jamie Lokier <jamie@shareable.org> writes:

> Anthony Liguori wrote:
>> After checking that we can demarshal, marshal again and compared to
>> the expected decoded value.  This doesn't work so well for floats
>> because they cannot be accurately represented in decimal but we try
>> our best.
>
> Good sprintf/scanf/strtod implementations do guarantee that what's
> printed and then parsed gets back the same floating point value, as
> long as you have printed sufficient decimal digits.
>
> I'm not sure if FLT_DIG/DLB_DIG are the right number of digits,
> though.  Glibc's documentation of those is confusing and they might
> mean something a little different.
>
> -- Jamie

Yes, conversion fp <-> decimal with sufficient precision can be made
exact.  However, it's non-trivial[1], and thus neither IEEE-754 nor ISO
C require it.  IIRC, GNU libc makes it exact.

No, FLT_DIG/DBL_DIG is not sufficient precision.  You need 7 decimal
digits for IEEE single, 17 for IEEE double[2].  FLT_DIG is 6 and DBL_DIG
is 15 on my system.

An alternative to all that ingenuity is C99's hexadecimal format.


[1] Steele & White: How to print floating-point numbers accurately
http://portal.acm.org/citation.cfm?id=93559
Clinger: How to Read Floating Point Numbers Accurately,
http://portal.acm.org/citation.cfm?id=93557
[2] http://docs.sun.com/source/806-3568/ncg_goldberg.html#1251

Patch

diff --git a/check-qjson.c b/check-qjson.c
index f763de6..4b591a5 100644
--- a/check-qjson.c
+++ b/check-qjson.c
@@ -27,12 +27,13 @@  START_TEST(escaped_string)
     struct {
         const char *encoded;
         const char *decoded;
+        int skip;
     } 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  " },
+        { "\"single byte utf-8 \\u0020\"", "single byte utf-8  ", .skip = 1 },
         { "\"double byte utf-8 \\u00A2\"", "double byte utf-8 \xc2\xa2" },
         { "\"triple byte utf-8 \\u20AC\"", "triple byte utf-8 \xe2\x82\xac" },
         {}
@@ -50,6 +51,13 @@  START_TEST(escaped_string)
         str = qobject_to_qstring(obj);
         fail_unless(strcmp(qstring_get_str(str), test_cases[i].decoded) == 0);
 
+        if (test_cases[i].skip == 0) {
+            str = qobject_to_json(obj);
+            fail_unless(strcmp(qstring_get_str(str), test_cases[i].encoded) == 0);
+
+            qobject_decref(obj);
+        }
+
         QDECREF(str);
     }
 }
@@ -80,6 +88,11 @@  START_TEST(simple_string)
         str = qobject_to_qstring(obj);
         fail_unless(strcmp(qstring_get_str(str), test_cases[i].decoded) == 0);
 
+        str = qobject_to_json(obj);
+        fail_unless(strcmp(qstring_get_str(str), test_cases[i].encoded) == 0);
+
+        qobject_decref(obj);
+        
         QDECREF(str);
     }
 }
@@ -149,12 +162,13 @@  START_TEST(simple_number)
     struct {
         const char *encoded;
         int64_t decoded;
+        int skip;
     } test_cases[] = {
         { "0", 0 },
         { "1234", 1234 },
         { "1", 1 },
         { "-32", -32 },
-        { "-0", 0 },
+        { "-0", 0, .skip = 1 },
         { },
     };
 
@@ -168,6 +182,13 @@  START_TEST(simple_number)
 
         qint = qobject_to_qint(obj);
         fail_unless(qint_get_int(qint) == test_cases[i].decoded);
+        if (test_cases[i].skip == 0) {
+            QString *str;
+
+            str = qobject_to_json(obj);
+            fail_unless(strcmp(qstring_get_str(str), test_cases[i].encoded) == 0);
+            QDECREF(str);
+        }
 
         QDECREF(qint);
     }
@@ -180,11 +201,12 @@  START_TEST(float_number)
     struct {
         const char *encoded;
         double decoded;
+        int skip;
     } test_cases[] = {
         { "32.43", 32.43 },
         { "0.222", 0.222 },
         { "-32.12313", -32.12313 },
-        { "-32.20e-10", -32.20e-10 },
+        { "-32.20e-10", -32.20e-10, .skip = 1 },
         { },
     };
 
@@ -199,6 +221,14 @@  START_TEST(float_number)
         qfloat = qobject_to_qfloat(obj);
         fail_unless(qfloat_get_double(qfloat) == test_cases[i].decoded);
 
+        if (test_cases[i].skip == 0) {
+            QString *str;
+
+            str = qobject_to_json(obj);
+            fail_unless(strcmp(qstring_get_str(str), test_cases[i].encoded) == 0);
+            QDECREF(str);
+        }
+
         QDECREF(qfloat);
     }
 }
@@ -246,6 +276,7 @@  START_TEST(keyword_literal)
 {
     QObject *obj;
     QBool *qbool;
+    QString *str;
 
     obj = qobject_from_json("true");
     fail_unless(obj != NULL);
@@ -254,6 +285,10 @@  START_TEST(keyword_literal)
     qbool = qobject_to_qbool(obj);
     fail_unless(qbool_get_int(qbool) != 0);
 
+    str = qobject_to_json(obj);
+    fail_unless(strcmp(qstring_get_str(str), "true") == 0);
+    QDECREF(str);
+
     QDECREF(qbool);
 
     obj = qobject_from_json("false");
@@ -263,6 +298,10 @@  START_TEST(keyword_literal)
     qbool = qobject_to_qbool(obj);
     fail_unless(qbool_get_int(qbool) == 0);
 
+    str = qobject_to_json(obj);
+    fail_unless(strcmp(qstring_get_str(str), "false") == 0);
+    QDECREF(str);
+
     QDECREF(qbool);
 
     obj = qobject_from_jsonf("%i", false);
@@ -385,7 +424,7 @@  START_TEST(simple_dict)
         LiteralQObject decoded;
     } test_cases[] = {
         {
-            .encoded = "{\"foo\":42,\"bar\":\"hello world\"}",
+            .encoded = "{\"foo\": 42, \"bar\": \"hello world\"}",
             .decoded = QLIT_QDICT(((LiteralQDictEntry[]){
                         { "foo", QLIT_QINT(42) },
                         { "bar", QLIT_QSTR("hello world") },
@@ -397,7 +436,7 @@  START_TEST(simple_dict)
                         { }
                     })),
         }, {
-            .encoded = "{\"foo\":43}",
+            .encoded = "{\"foo\": 43}",
             .decoded = QLIT_QDICT(((LiteralQDictEntry[]){
                         { "foo", QLIT_QINT(43) },
                         { }
@@ -408,6 +447,7 @@  START_TEST(simple_dict)
 
     for (i = 0; test_cases[i].encoded; i++) {
         QObject *obj;
+        QString *str;
 
         obj = qobject_from_json(test_cases[i].encoded);
         fail_unless(obj != NULL);
@@ -415,7 +455,16 @@  START_TEST(simple_dict)
 
         fail_unless(compare_litqobj_to_qobj(&test_cases[i].decoded, obj) == 1);
 
+        str = qobject_to_json(obj);
+        qobject_decref(obj);
+
+        obj = qobject_from_json(qstring_get_str(str));
+        fail_unless(obj != NULL);
+        fail_unless(qobject_type(obj) == QTYPE_QDICT);
+
+        fail_unless(compare_litqobj_to_qobj(&test_cases[i].decoded, obj) == 1);
         qobject_decref(obj);
+        QDECREF(str);
     }
 }
 END_TEST
@@ -453,6 +502,7 @@  START_TEST(simple_list)
 
     for (i = 0; test_cases[i].encoded; i++) {
         QObject *obj;
+        QString *str;
 
         obj = qobject_from_json(test_cases[i].encoded);
         fail_unless(obj != NULL);
@@ -460,7 +510,16 @@  START_TEST(simple_list)
 
         fail_unless(compare_litqobj_to_qobj(&test_cases[i].decoded, obj) == 1);
 
+        str = qobject_to_json(obj);
+        qobject_decref(obj);
+
+        obj = qobject_from_json(qstring_get_str(str));
+        fail_unless(obj != NULL);
+        fail_unless(qobject_type(obj) == QTYPE_QLIST);
+
+        fail_unless(compare_litqobj_to_qobj(&test_cases[i].decoded, obj) == 1);
         qobject_decref(obj);
+        QDECREF(str);
     }
 }
 END_TEST
@@ -512,6 +571,7 @@  START_TEST(simple_whitespace)
 
     for (i = 0; test_cases[i].encoded; i++) {
         QObject *obj;
+        QString *str;
 
         obj = qobject_from_json(test_cases[i].encoded);
         fail_unless(obj != NULL);
@@ -519,7 +579,17 @@  START_TEST(simple_whitespace)
 
         fail_unless(compare_litqobj_to_qobj(&test_cases[i].decoded, obj) == 1);
 
+        str = qobject_to_json(obj);
         qobject_decref(obj);
+
+        obj = qobject_from_json(qstring_get_str(str));
+        fail_unless(obj != NULL);
+        fail_unless(qobject_type(obj) == QTYPE_QLIST);
+
+        fail_unless(compare_litqobj_to_qobj(&test_cases[i].decoded, obj) == 1);
+
+        qobject_decref(obj);
+        QDECREF(str);
     }
 }
 END_TEST