xen-devel.lists.xenproject.org archive mirror
 help / color / mirror / Atom feed
From: "Daniel P. Berrange" <berrange@redhat.com>
To: libvir-list@redhat.com
Cc: xen-devel@lists.xensource.com, Ian Campbell <Ian.Campbell@citrix.com>
Subject: [libvirt] [PATCH v2 1/4] util: Introduce virJSONStringCompare for JSON doc comparisons
Date: Tue,  3 Jun 2014 12:02:50 +0100	[thread overview]
Message-ID: <1401793373-26680-2-git-send-email-berrange@redhat.com> (raw)
In-Reply-To: <1401793373-26680-1-git-send-email-berrange@redhat.com>

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@redhat.com>
---
 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

  reply	other threads:[~2014-06-03 11:02 UTC|newest]

Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2014-06-03 11:02 [PATCH v2 0/4] Testing libvirt XML -> libxl_domain_config conversion Daniel P. Berrange
2014-06-03 11:02 ` Daniel P. Berrange [this message]
2014-06-03 21:18   ` [libvirt] [PATCH v2 1/4] util: Introduce virJSONStringCompare for JSON doc comparisons Jim Fehlig
2014-06-03 11:02 ` [PATCH v2 2/4] util: Allow port allocator to skip bind() check Daniel P. Berrange
2014-06-03 21:25   ` [libvirt] " Jim Fehlig
2014-06-03 11:02 ` [PATCH v2 3/4] tests: Add more test suite mock helpers Daniel P. Berrange
2014-06-03 21:30   ` [libvirt] " Jim Fehlig
2014-06-03 11:02 ` [PATCH v2 4/4] libxl: Add a test suite for libxl option generator Daniel P. Berrange
2014-06-03 21:45   ` [libvirt] " Jim Fehlig
2014-06-16 23:11     ` Jim Fehlig
2014-06-17  8:52       ` Ian Campbell
2014-06-17 18:40         ` Jim Fehlig
2014-06-18  8:33           ` Ian Campbell
2014-06-18  9:07           ` Daniel P. Berrange

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=1401793373-26680-2-git-send-email-berrange@redhat.com \
    --to=berrange@redhat.com \
    --cc=Ian.Campbell@citrix.com \
    --cc=libvir-list@redhat.com \
    --cc=xen-devel@lists.xensource.com \
    /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).