[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Xen-devel] [PATCH v2 1/4] util: Introduce virJSONStringCompare for JSON doc comparisons



Comparing JSON docs using strcmp is simple, but is not flexible
as it is sensitive to whitespace used in the doc generation.
When comparing objects it may also be desirable to treat the
existance of keys in the actual object but not expected object
as non-fatal. Introduce a virJSONStringCompare function which
takes two strings representing expected and actual JSON docs
and then does a DOM comparison.

Signed-off-by: Daniel P. Berrange <berrange@xxxxxxxxxx>
---
 src/libvirt_private.syms |   1 +
 src/util/virjson.c       | 185 +++++++++++++++++++++++++++++++++++++++++++++++
 src/util/virjson.h       |  22 ++++++
 3 files changed, 208 insertions(+)

diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index 4c1f61c..5f2b9d9 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -1406,6 +1406,7 @@ virISCSIScanTargets;
 
 
 # util/virjson.h
+virJSONStringCompare;
 virJSONValueArrayAppend;
 virJSONValueArrayGet;
 virJSONValueArraySize;
diff --git a/src/util/virjson.c b/src/util/virjson.c
index 35a8252..0a6d1be 100644
--- a/src/util/virjson.c
+++ b/src/util/virjson.c
@@ -47,6 +47,11 @@
 
 VIR_LOG_INIT("util.json");
 
+VIR_ENUM_DECL(virJSONType)
+VIR_ENUM_IMPL(virJSONType, VIR_JSON_TYPE_LAST,
+              "object", "array", "string",
+              "number", "boolean", "null")
+
 typedef struct _virJSONParserState virJSONParserState;
 typedef virJSONParserState *virJSONParserStatePtr;
 struct _virJSONParserState {
@@ -90,6 +95,7 @@ void virJSONValueFree(virJSONValuePtr value)
         break;
     case VIR_JSON_TYPE_BOOLEAN:
     case VIR_JSON_TYPE_NULL:
+    case VIR_JSON_TYPE_LAST:
         break;
     }
 
@@ -1152,3 +1158,182 @@ char *virJSONValueToString(virJSONValuePtr object 
ATTRIBUTE_UNUSED,
     return NULL;
 }
 #endif
+
+
+static bool
+virJSONValueCompare(virJSONValuePtr expect,
+                    virJSONValuePtr actual,
+                    const char *context,
+                    unsigned int flags)
+{
+    size_t i, j;
+    if (expect->type != actual->type) {
+        if (expect->type == VIR_JSON_TYPE_NULL &&
+            (flags & VIR_JSON_COMPARE_IGNORE_EXPECTED_NULL))
+            return true;
+
+        const char *expectType = virJSONTypeTypeToString(expect->type);
+        const char *actualType = virJSONTypeTypeToString(actual->type);
+
+        virReportError(VIR_ERR_INTERNAL_ERROR,
+                       _("Expected value type '%s' but got value type '%s' at 
'%s'"),
+                       expectType, actualType, context);
+        return false;
+    }
+
+    switch (expect->type) {
+    case VIR_JSON_TYPE_OBJECT:
+        for (i = 0; i < expect->data.object.npairs; i++) {
+            bool found = false;
+            char *childcontext;
+            for (j = 0; j < actual->data.object.npairs; j++) {
+                if (STREQ(expect->data.object.pairs[i].key,
+                          actual->data.object.pairs[j].key)) {
+                    found = true;
+                    break;
+                }
+            }
+            if (!found) {
+                virReportError(VIR_ERR_INTERNAL_ERROR,
+                               _("Expected object key '%s' not found in actual 
object at '%s'"),
+                               expect->data.object.pairs[i].key, context);
+                return false;
+            }
+
+            if (virAsprintf(&childcontext, "%s%s%s",
+                            context,
+                            STREQ(context, "/") ? "" : "/",
+                            expect->data.object.pairs[i].key) < 0)
+                return false;
+
+            if (!virJSONValueCompare(expect->data.object.pairs[i].value,
+                                     actual->data.object.pairs[j].value,
+                                     childcontext,
+                                     flags)) {
+                VIR_FREE(childcontext);
+                return false;
+            }
+            VIR_FREE(childcontext);
+        }
+        if (!(flags & VIR_JSON_COMPARE_IGNORE_EXTRA_OBJECT_KEYS)) {
+            for (i = 0; i < actual->data.object.npairs; i++) {
+                bool found = false;
+                char *childcontext;
+                for (j = 0; j < expect->data.object.npairs; j++) {
+                    if (STREQ(actual->data.object.pairs[i].key,
+                              expect->data.object.pairs[j].key)) {
+                        found = true;
+                        break;
+                    }
+                }
+                if (!found) {
+                    virReportError(VIR_ERR_INTERNAL_ERROR,
+                                   _("Actual object key '%s' not found in 
expected object at '%s'"),
+                                   actual->data.object.pairs[i].key, context);
+                    return false;
+                }
+
+                if (virAsprintf(&childcontext, "%s%s%s",
+                                STREQ(context, "/") ? "" : "/",
+                                context, actual->data.object.pairs[i].key) < 0)
+                    return false;
+
+                if (!virJSONValueCompare(actual->data.object.pairs[i].value,
+                                         expect->data.object.pairs[j].value,
+                                         childcontext,
+                                         flags)) {
+                    VIR_FREE(childcontext);
+                    return false;
+                }
+                VIR_FREE(childcontext);
+            }
+        }
+        break;
+    case VIR_JSON_TYPE_ARRAY:
+        if (expect->data.array.nvalues !=
+            actual->data.array.nvalues) {
+            virReportError(VIR_ERR_INTERNAL_ERROR,
+                           _("Expected array size '%zu' but got size '%zu' at 
'%s'"),
+                           expect->data.array.nvalues,
+                           actual->data.array.nvalues,
+                           context);
+            return false;
+        }
+
+        for (i = 0; i < expect->data.array.nvalues; i++) {
+            char *childcontext;
+            if (virAsprintf(&childcontext, "%s%s[%zu]",
+                            STREQ(context, "/") ? "" : "/",
+                            context, i) < 0)
+                return false;
+
+            if (!virJSONValueCompare(expect->data.array.values[i],
+                                     actual->data.array.values[i],
+                                     childcontext,
+                                     flags)) {
+                VIR_FREE(childcontext);
+                return false;
+            }
+            VIR_FREE(childcontext);
+        }
+        break;
+    case VIR_JSON_TYPE_STRING:
+        if (STRNEQ(expect->data.string,
+                   actual->data.string)) {
+            virReportError(VIR_ERR_INTERNAL_ERROR,
+                           _("Expected string value '%s' but got '%s' at 
'%s'"),
+                           expect->data.string, actual->data.string, context);
+            return false;
+        }
+        break;
+    case VIR_JSON_TYPE_NUMBER:
+        if (STRNEQ(expect->data.number,
+                   actual->data.number)) {
+            virReportError(VIR_ERR_INTERNAL_ERROR,
+                           _("Expected number value '%s' but got '%s' at 
'%s'"),
+                           expect->data.number, actual->data.number, context);
+            return false;
+        }
+        break;
+    case VIR_JSON_TYPE_BOOLEAN:
+        if (expect->data.boolean !=
+            actual->data.boolean) {
+            virReportError(VIR_ERR_INTERNAL_ERROR,
+                           _("Expected bool value '%d' but got '%d' at '%s'"),
+                           expect->data.boolean, actual->data.boolean, 
context);
+            return false;
+        }
+        break;
+    case VIR_JSON_TYPE_NULL:
+        /* trivially equal */
+        break;
+
+    case VIR_JSON_TYPE_LAST:
+        /* nothing */
+        break;
+    }
+
+    return true;
+}
+
+
+bool virJSONStringCompare(const char *expect,
+                          const char *actual,
+                          unsigned int flags)
+{
+    virJSONValuePtr expectVal = NULL;
+    virJSONValuePtr actualVal = NULL;
+    int ret = false;
+
+    if (!(expectVal = virJSONValueFromString(expect)))
+        goto cleanup;
+    if (!(actualVal = virJSONValueFromString(actual)))
+        goto cleanup;
+
+    ret = virJSONValueCompare(expectVal, actualVal, "/", flags);
+
+ cleanup:
+    virJSONValueFree(expectVal);
+    virJSONValueFree(actualVal);
+    return ret;
+}
diff --git a/src/util/virjson.h b/src/util/virjson.h
index db11396..b1f96d3 100644
--- a/src/util/virjson.h
+++ b/src/util/virjson.h
@@ -34,6 +34,8 @@ typedef enum {
     VIR_JSON_TYPE_NUMBER,
     VIR_JSON_TYPE_BOOLEAN,
     VIR_JSON_TYPE_NULL,
+
+    VIR_JSON_TYPE_LAST,
 } virJSONType;
 
 typedef struct _virJSONValue virJSONValue;
@@ -139,4 +141,24 @@ virJSONValuePtr virJSONValueFromString(const char 
*jsonstring);
 char *virJSONValueToString(virJSONValuePtr object,
                            bool pretty);
 
+typedef enum {
+    /*
+     * When comparing a pair of Objects, if the 'actual' object
+     * has keys that are not present in the 'expected' object,
+     * the existance of these extra keys will be non-fatal.
+     */
+    VIR_JSON_COMPARE_IGNORE_EXTRA_OBJECT_KEYS = (1 << 0),
+
+    /*
+     * when comparing two values, if their types are different,
+     * and the 'expected' value type is 'null', then this will
+     * be considered non-fatal.
+     */
+    VIR_JSON_COMPARE_IGNORE_EXPECTED_NULL = (1 << 1),
+} virJSONCompareFlags;
+
+bool virJSONStringCompare(const char *expect,
+                          const char *actual,
+                          unsigned int flags);
+
 #endif /* __VIR_JSON_H_ */
-- 
1.9.3


_______________________________________________
Xen-devel mailing list
Xen-devel@xxxxxxxxxxxxx
http://lists.xen.org/xen-devel


 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.