Blame SOURCES/satyr-0.13-ureport-auth-support.patch

f499a8
From 3e11b3c67bc31662810f72571311d7d7580e3f0a Mon Sep 17 00:00:00 2001
f499a8
From: Jakub Filak <jfilak@redhat.com>
f499a8
Date: Mon, 18 Aug 2014 20:39:34 +0200
f499a8
Subject: [SATYR PATCH 6/6] Add authentication data support in uReport
f499a8
f499a8
Support authentication data in uReport
f499a8
f499a8
We need this for 'machine id' element which should be present only if
f499a8
users wishes to it.
f499a8
f499a8
The authentication entries are string based, key value pairs (JSON
f499a8
string elements).
f499a8
f499a8
The entries are enclosed in an envelope object called 'auth'.
f499a8
f499a8
Example:
f499a8
f499a8
"auth" : {   "machineid" : "deadbeaf"
f499a8
         }
f499a8
f499a8
Fixes #171
f499a8
f499a8
Signed-off-by: Jakub Filak <jfilak@redhat.com>
f499a8
Signed-off-by: Martin Milata <mmilata@redhat.com>
f499a8
f499a8
mmilata: s/custom/auth/g, python bindings
f499a8
f499a8
tests: add a test for uReport auth data
f499a8
f499a8
Related to #171
f499a8
f499a8
Signed-off-by: Jakub Filak <jfilak@redhat.com>
f499a8
Signed-off-by: Martin Milata <mmilata@redhat.com>
f499a8
f499a8
mmilata: python test
f499a8
f499a8
ureport: fix indentation of 'auth' key in JSON
f499a8
f499a8
Fixes a bug in commit 189f7adbba9c38bd434c30e7d0c07b546c373312
f499a8
f499a8
Related to #171
f499a8
f499a8
Signed-off-by: Jakub Filak <jfilak@redhat.com>
f499a8
---
f499a8
 include/report.h                |  17 ++++++
f499a8
 lib/internal_utils.h            |  12 +++++
f499a8
 lib/json.c                      |  26 ++++++++++
f499a8
 lib/report.c                    |  82 +++++++++++++++++++++++++++++
f499a8
 python/py_report.c              |  30 +++++++++++
f499a8
 python/py_report.h              |   2 +
f499a8
 tests/json_files/ureport-1-auth | 112 ++++++++++++++++++++++++++++++++++++++++
f499a8
 tests/python/report.py          |   6 +++
f499a8
 tests/report.at                 |  81 +++++++++++++++++++++++++++++
f499a8
 9 files changed, 368 insertions(+)
f499a8
 create mode 100644 tests/json_files/ureport-1-auth
f499a8
f499a8
diff --git a/include/report.h b/include/report.h
f499a8
index 424b730..025d8f8 100644
f499a8
--- a/include/report.h
f499a8
+++ b/include/report.h
f499a8
@@ -31,6 +31,13 @@ extern "C" {
f499a8
 struct sr_json_value;
f499a8
 struct sr_stacktrace;
f499a8
 
f499a8
+struct sr_report_custom_entry
f499a8
+{
f499a8
+    char *key;
f499a8
+    char *value;
f499a8
+    struct sr_report_custom_entry *next;
f499a8
+};
f499a8
+
f499a8
 struct sr_report
f499a8
 {
f499a8
     uint32_t report_version;
f499a8
@@ -49,6 +56,8 @@ struct sr_report
f499a8
     struct sr_rpm_package *rpm_packages;
f499a8
 
f499a8
     struct sr_stacktrace *stacktrace;
f499a8
+
f499a8
+    struct sr_report_custom_entry *auth_entries;
f499a8
 };
f499a8
 
f499a8
 struct sr_report *
f499a8
@@ -60,6 +69,14 @@ sr_report_init(struct sr_report *report);
f499a8
 void
f499a8
 sr_report_free(struct sr_report *report);
f499a8
 
f499a8
+
f499a8
+/* @brief Adds a new entry to 'auth' object
f499a8
+ *
f499a8
+ * The implementation is LIFO. The resulting list is in reversed.
f499a8
+ */
f499a8
+void
f499a8
+sr_report_add_auth(struct sr_report *report, const char *key, const char *value);
f499a8
+
f499a8
 char *
f499a8
 sr_report_to_json(struct sr_report *report);
f499a8
 
f499a8
diff --git a/lib/internal_utils.h b/lib/internal_utils.h
f499a8
index 20f65de..3a10520 100644
f499a8
--- a/lib/internal_utils.h
f499a8
+++ b/lib/internal_utils.h
f499a8
@@ -130,4 +130,16 @@ json_read_bool(struct sr_json_value *object, const char *key_name, bool *dest,
f499a8
         abort();                                                                      \
f499a8
     }
f499a8
 
f499a8
+/* returns the number of object's children */
f499a8
+unsigned
f499a8
+json_object_children_count(struct sr_json_value *object);
f499a8
+
f499a8
+/* returns the child_noth child and passes its name in child_name arg */
f499a8
+struct sr_json_value *
f499a8
+json_object_get_child(struct sr_json_value *object, unsigned child_no, const char **child_name);
f499a8
+
f499a8
+/* returns string's value */
f499a8
+const char *
f499a8
+json_string_get_value(struct sr_json_value *object);
f499a8
+
f499a8
 #endif
f499a8
diff --git a/lib/json.c b/lib/json.c
f499a8
index 35b7e6b..d777973 100644
f499a8
--- a/lib/json.c
f499a8
+++ b/lib/json.c
f499a8
@@ -787,6 +787,32 @@ json_element(struct sr_json_value *object, const char *key_name)
f499a8
     return NULL;
f499a8
 }
f499a8
 
f499a8
+unsigned
f499a8
+json_object_children_count(struct sr_json_value *object)
f499a8
+{
f499a8
+    assert(object->type == SR_JSON_OBJECT);
f499a8
+
f499a8
+    return object->u.object.length;
f499a8
+}
f499a8
+
f499a8
+struct sr_json_value *
f499a8
+json_object_get_child(struct sr_json_value *object, unsigned child_no, const char **child_name)
f499a8
+{
f499a8
+    assert(object->type == SR_JSON_OBJECT);
f499a8
+    assert(child_no < object->u.object.length);
f499a8
+
f499a8
+    *child_name = object->u.object.values[child_no].name;
f499a8
+    return object->u.object.values[child_no].value;
f499a8
+}
f499a8
+
f499a8
+const char *
f499a8
+json_string_get_value(struct sr_json_value *object)
f499a8
+{
f499a8
+    assert(object->type == SR_JSON_STRING);
f499a8
+
f499a8
+    return object->u.string.ptr;
f499a8
+}
f499a8
+
f499a8
 #define DEFINE_JSON_READ(name, c_type, json_type, json_member, conversion)                       \
f499a8
     bool                                                                                         \
f499a8
     name(struct sr_json_value *object, const char *key_name, c_type *dest, char **error_message) \
f499a8
diff --git a/lib/report.c b/lib/report.c
f499a8
index a6ecae4..411df64 100644
f499a8
--- a/lib/report.c
f499a8
+++ b/lib/report.c
f499a8
@@ -66,6 +66,7 @@ sr_report_init(struct sr_report *report)
f499a8
     report->component_name = NULL;
f499a8
     report->rpm_packages = NULL;
f499a8
     report->stacktrace = NULL;
f499a8
+    report->auth_entries = NULL;
f499a8
 }
f499a8
 
f499a8
 void
f499a8
@@ -75,9 +76,36 @@ sr_report_free(struct sr_report *report)
f499a8
     sr_operating_system_free(report->operating_system);
f499a8
     sr_rpm_package_free(report->rpm_packages, true);
f499a8
     sr_stacktrace_free(report->stacktrace);
f499a8
+
f499a8
+    struct sr_report_custom_entry *iter = report->auth_entries;
f499a8
+    while (iter)
f499a8
+    {
f499a8
+        struct sr_report_custom_entry *tmp = iter->next;
f499a8
+
f499a8
+        free(iter->value);
f499a8
+        free(iter->key);
f499a8
+        free(iter);
f499a8
+
f499a8
+        iter = tmp;
f499a8
+    }
f499a8
+
f499a8
     free(report);
f499a8
 }
f499a8
 
f499a8
+void
f499a8
+sr_report_add_auth(struct sr_report *report, const char *key, const char *value)
f499a8
+{
f499a8
+    struct sr_report_custom_entry *new_entry = sr_malloc(sizeof(*new_entry));
f499a8
+    new_entry->key = sr_strdup(key);
f499a8
+    new_entry->value = sr_strdup(value);
f499a8
+
f499a8
+    /* prepend the new value
f499a8
+     * it is much faster and easier
f499a8
+     * we can do it because we do not to preserve the order(?) */
f499a8
+    new_entry->next = report->auth_entries;
f499a8
+    report->auth_entries = new_entry;
f499a8
+}
f499a8
+
f499a8
 /* The object has to be non-empty, i.e. contain at least one key-value pair. */
f499a8
 static void
f499a8
 dismantle_object(char *obj)
f499a8
@@ -221,6 +249,34 @@ sr_report_to_json(struct sr_report *report)
f499a8
         free(rpms_str_indented);
f499a8
     }
f499a8
 
f499a8
+    /* Custom entries.
f499a8
+     *    "auth" : {   "foo": "blah"
f499a8
+     *             ,   "one": "two"
f499a8
+     *             }
f499a8
+     */
f499a8
+    struct sr_report_custom_entry *iter = report->auth_entries;
f499a8
+    if (iter)
f499a8
+    {
f499a8
+        sr_strbuf_append_strf(strbuf, ",   \"auth\": {   ");
f499a8
+        sr_json_append_escaped(strbuf, iter->key);
f499a8
+        sr_strbuf_append_str(strbuf, ": ");
f499a8
+        sr_json_append_escaped(strbuf, iter->value);
f499a8
+        sr_strbuf_append_str(strbuf, "\n");
f499a8
+
f499a8
+        /* the first entry is prefix with '{', see lines above */
f499a8
+        iter = iter->next;
f499a8
+        while (iter)
f499a8
+        {
f499a8
+            sr_strbuf_append_strf(strbuf, "            ,   ");
f499a8
+            sr_json_append_escaped(strbuf, iter->key);
f499a8
+            sr_strbuf_append_str(strbuf, ": ");
f499a8
+            sr_json_append_escaped(strbuf, iter->value);
f499a8
+            sr_strbuf_append_str(strbuf, "\n");
f499a8
+            iter = iter->next;
f499a8
+        }
f499a8
+        sr_strbuf_append_str(strbuf, "            } ");
f499a8
+    }
f499a8
+
f499a8
     sr_strbuf_append_str(strbuf, "}");
f499a8
     return sr_strbuf_free_nobuf(strbuf);
f499a8
 }
f499a8
@@ -336,6 +392,32 @@ sr_report_from_json(struct sr_json_value *root, char **error_message)
f499a8
 
f499a8
     }
f499a8
 
f499a8
+    /* Authentication entries. */
f499a8
+    struct sr_json_value *extra = json_element(root, "auth");
f499a8
+    if (extra)
f499a8
+    {
f499a8
+        if (!JSON_CHECK_TYPE(extra, SR_JSON_OBJECT, "auth"))
f499a8
+            goto fail;
f499a8
+
f499a8
+        const unsigned children = json_object_children_count(extra);
f499a8
+
f499a8
+        /* from the last children down to the first for easier testing :)
f499a8
+         * keep it as it is as long as sr_report_add_auth() does LIFO */
f499a8
+        for (unsigned i = 1; i <= children; ++i)
f499a8
+        {
f499a8
+            const char *child_name = NULL;
f499a8
+            struct sr_json_value *child_object = json_object_get_child(extra,
f499a8
+                                                                       children - i,
f499a8
+                                                                       &child_name);
f499a8
+
f499a8
+            if (!JSON_CHECK_TYPE(child_object, SR_JSON_STRING, child_name))
f499a8
+                continue;
f499a8
+
f499a8
+            const char *child_value = json_string_get_value(child_object);
f499a8
+            sr_report_add_auth(report, child_name, child_value);
f499a8
+        }
f499a8
+    }
f499a8
+
f499a8
     return report;
f499a8
 
f499a8
 fail:
f499a8
diff --git a/python/py_report.c b/python/py_report.c
f499a8
index d314027..8aa35dd 100644
f499a8
--- a/python/py_report.c
f499a8
+++ b/python/py_report.c
f499a8
@@ -42,6 +42,8 @@
f499a8
 #define to_json_doc "Usage: report.to_json()\n\n" \
f499a8
                     "Returns: string - the report serialized as JSON"
f499a8
 
f499a8
+#define auth_doc "Dictinary of key/value pairs used for authentication"
f499a8
+
f499a8
 /* See python/py_common.h and python/py_gdb_frame.c for generic getters/setters documentation. */
f499a8
 #define GSOFF_PY_STRUCT sr_py_report
f499a8
 #define GSOFF_PY_MEMBER report
f499a8
@@ -64,6 +66,7 @@ report_getset[] =
f499a8
     SR_ATTRIBUTE_STRING(component_name,   "Name of the software component this report pertains to (string)"       ),
f499a8
     { (char*)"report_version", sr_py_report_get_version, sr_py_setter_readonly, (char*)"Version of the report (int)", NULL },
f499a8
     { (char*)"report_type", sr_py_report_get_type, sr_py_report_set_type, (char*)"Report type (string)", NULL },
f499a8
+    { (char*)"auth",        sr_py_report_get_auth, sr_py_report_set_auth, (char*)auth_doc, NULL               },
f499a8
     { NULL },
f499a8
 };
f499a8
 
f499a8
@@ -492,3 +495,30 @@ sr_py_report_to_json(PyObject *self, PyObject *args)
f499a8
     free(json);
f499a8
     return result;
f499a8
 }
f499a8
+
f499a8
+PyObject *
f499a8
+sr_py_report_get_auth(PyObject *self, void *data)
f499a8
+{
f499a8
+    struct sr_report *report = ((struct sr_py_report *)self)->report;
f499a8
+    struct sr_report_custom_entry *ae = report->auth_entries;
f499a8
+
f499a8
+    PyObject *auth = PyDict_New();
f499a8
+    for (ae = report->auth_entries; ae; ae = ae->next)
f499a8
+    {
f499a8
+        PyObject *val = PyString_FromString(ae->value);
f499a8
+        if (!val)
f499a8
+            return NULL;
f499a8
+
f499a8
+        if (PyDict_SetItemString(auth, ae->key, val) == -1)
f499a8
+            return NULL;
f499a8
+    }
f499a8
+
f499a8
+    return auth;
f499a8
+}
f499a8
+
f499a8
+int
f499a8
+sr_py_report_set_auth(PyObject *self, PyObject *rhs, void *data)
f499a8
+{
f499a8
+    PyErr_SetString(PyExc_NotImplementedError, "Setting auth data is not implemented.");
f499a8
+    return -1;
f499a8
+}
f499a8
diff --git a/python/py_report.h b/python/py_report.h
f499a8
index a451dbc..88ec30f 100644
f499a8
--- a/python/py_report.h
f499a8
+++ b/python/py_report.h
f499a8
@@ -65,6 +65,8 @@ PyObject *sr_py_report_str(PyObject *self);
f499a8
 PyObject *sr_py_report_get_version(PyObject *self, void *data);
f499a8
 PyObject *sr_py_report_get_type(PyObject *self, void *data);
f499a8
 int sr_py_report_set_type(PyObject *self, PyObject *rhs, void *data);
f499a8
+PyObject *sr_py_report_get_auth(PyObject *self, void *data);
f499a8
+int sr_py_report_set_auth(PyObject *self, PyObject *rhs, void *data);
f499a8
 
f499a8
 /**
f499a8
  * Methods.
f499a8
diff --git a/tests/json_files/ureport-1-auth b/tests/json_files/ureport-1-auth
f499a8
new file mode 100644
f499a8
index 0000000..a8db2df
f499a8
--- /dev/null
f499a8
+++ b/tests/json_files/ureport-1-auth
f499a8
@@ -0,0 +1,112 @@
f499a8
+{
f499a8
+  "ureport_version": 2,
f499a8
+
f499a8
+  "reason": "Program /usr/bin/sleep was terminated by signal 11",
f499a8
+
f499a8
+  "os": {
f499a8
+    "name": "fedora",
f499a8
+    "version": "18",
f499a8
+    "architecture": "x86_64"
f499a8
+  },
f499a8
+
f499a8
+  "problem": {
f499a8
+    "type": "core",
f499a8
+
f499a8
+    "executable": "/usr/bin/sleep",
f499a8
+
f499a8
+    "signal": 11,
f499a8
+
f499a8
+    "component": "coreutils",
f499a8
+
f499a8
+    "user": {
f499a8
+      "local": true,
f499a8
+      "root": false
f499a8
+    },
f499a8
+
f499a8
+    "stacktrace": [
f499a8
+      {
f499a8
+        "crash_thread": true,
f499a8
+
f499a8
+        "frames": [
f499a8
+          {
f499a8
+            "build_id": "5f6632d75fd027f5b7b410787f3f06c6bf73eee6",
f499a8
+            "build_id_offset": 767024,
f499a8
+            "file_name": "/lib64/libc.so.6",
f499a8
+            "address": 251315074096,
f499a8
+            "fingerprint": "6c1eb9626919a2a5f6a4fc4c2edc9b21b33b7354",
f499a8
+            "function_name": "__nanosleep"
f499a8
+          },
f499a8
+          {
f499a8
+            "build_id": "cd379d3bb5d07c96d491712e41c34bcd06b2ce32",
f499a8
+            "build_id_offset": 16567,
f499a8
+            "file_name": "/usr/bin/sleep",
f499a8
+            "address": 4210871,
f499a8
+            "fingerprint": "d24433b82a2c751fc580f47154823e0bed641a54",
f499a8
+            "function_name": "close_stdout"
f499a8
+          },
f499a8
+          {
f499a8
+            "build_id": "cd379d3bb5d07c96d491712e41c34bcd06b2ce32",
f499a8
+            "build_id_offset": 16202,
f499a8
+            "file_name": "/usr/bin/sleep",
f499a8
+            "address": 4210506,
f499a8
+            "fingerprint": "562719fb960d1c4dbf30c04b3cff37c82acc3d2d",
f499a8
+            "function_name": "close_stdout"
f499a8
+          },
f499a8
+          {
f499a8
+            "build_id": "cd379d3bb5d07c96d491712e41c34bcd06b2ce32",
f499a8
+            "build_id_offset": 6404,
f499a8
+            "fingerprint": "2e8fb95adafe21d035b9bcb9993810fecf4be657",
f499a8
+            "file_name": "/usr/bin/sleep",
f499a8
+            "address": 4200708
f499a8
+          },
f499a8
+          {
f499a8
+            "build_id": "5f6632d75fd027f5b7b410787f3f06c6bf73eee6",
f499a8
+            "build_id_offset": 137733,
f499a8
+            "file_name": "/lib64/libc.so.6",
f499a8
+            "address": 251314444805,
f499a8
+            "fingerprint": "075acda5d3230e115cf7c88597eaba416bdaa6bb",
f499a8
+            "function_name": "__libc_start_main"
f499a8
+          }
f499a8
+        ]
f499a8
+      }
f499a8
+    ]
f499a8
+  },
f499a8
+
f499a8
+  "packages": [
f499a8
+    {
f499a8
+      "name": "coreutils",
f499a8
+      "epoch": 0,
f499a8
+      "version": "8.17",
f499a8
+      "architecture": "x86_64",
f499a8
+      "package_role": "affected",
f499a8
+      "release": "8.fc18",
f499a8
+      "install_time": 1371464601
f499a8
+    },
f499a8
+    {
f499a8
+      "name": "glibc",
f499a8
+      "epoch": 0,
f499a8
+      "version": "2.16",
f499a8
+      "architecture": "x86_64",
f499a8
+      "release": "31.fc18",
f499a8
+      "install_time": 1371464176
f499a8
+    },
f499a8
+    {
f499a8
+      "name": "glibc-common",
f499a8
+      "epoch": 0,
f499a8
+      "version": "2.16",
f499a8
+      "architecture": "x86_64",
f499a8
+      "release": "31.fc18",
f499a8
+      "install_time": 1371464184
f499a8
+    }
f499a8
+  ],
f499a8
+
f499a8
+  "reporter": {
f499a8
+    "version": "0.3",
f499a8
+    "name": "satyr"
f499a8
+  },
f499a8
+
f499a8
+  "auth": {
f499a8
+    "hostname": "localhost",
f499a8
+    "machine_id": "0000"
f499a8
+  }
f499a8
+}
f499a8
diff --git a/tests/python/report.py b/tests/python/report.py
f499a8
index 83fa76b..5f731b5 100755
f499a8
--- a/tests/python/report.py
f499a8
+++ b/tests/python/report.py
f499a8
@@ -91,6 +91,12 @@ class TestReport(BindingsTestCase):
f499a8
     def test_hash(self):
f499a8
         self.assertHashable(self.report)
f499a8
 
f499a8
+    def test_auth(self):
f499a8
+        self.assertFalse(self.report.auth)
f499a8
+        self.assertRaises(NotImplementedError, self.report.__setattr__, 'auth', {'hostname': 'darkstar'})
f499a8
+
f499a8
+        report_with_auth = satyr.Report(load_input_contents('../json_files/ureport-1-auth'))
f499a8
+        self.assertEqual(report_with_auth.auth, {'hostname': 'localhost', 'machine_id': '0000'})
f499a8
 
f499a8
 if __name__ == '__main__':
f499a8
     unittest.main()
f499a8
diff --git a/tests/report.at b/tests/report.at
f499a8
index 8736137..1e773d2 100644
f499a8
--- a/tests/report.at
f499a8
+++ b/tests/report.at
f499a8
@@ -57,3 +57,84 @@ int main(void)
f499a8
   return 0;
f499a8
 }
f499a8
 ]])
f499a8
+
f499a8
+## ------------------ ##
f499a8
+## sr_report_add_auth ##
f499a8
+## ------------------ ##
f499a8
+
f499a8
+AT_TESTFUN([sr_report_add_auth],
f499a8
+[[
f499a8
+#include <assert.h>
f499a8
+#include <stdio.h>
f499a8
+#include <stdlib.h>
f499a8
+#include <string.h>
f499a8
+#include "report.h"
f499a8
+
f499a8
+void check_struct(struct sr_report *report, const char **expected)
f499a8
+{
f499a8
+    const char **exp_iter = expected;
f499a8
+    struct sr_report_custom_entry *cust_iter = report->auth_entries;
f499a8
+
f499a8
+    while(cust_iter && *exp_iter)
f499a8
+    {
f499a8
+        fprintf(stdout, "Expected('%s':'%s') vs. Current('%s':'%s')\n",
f499a8
+            exp_iter[0], exp_iter[1], cust_iter->key, cust_iter->value);
f499a8
+
f499a8
+        assert(strcmp(cust_iter->key, exp_iter[0]) == 0 &&
f499a8
+               strcmp(cust_iter->value, exp_iter[1]) == 0);
f499a8
+
f499a8
+        cust_iter = cust_iter->next;
f499a8
+        exp_iter += 2;
f499a8
+    }
f499a8
+
f499a8
+    assert(cust_iter == NULL);
f499a8
+    assert(*exp_iter == NULL);
f499a8
+}
f499a8
+
f499a8
+void check_json(const char *json, const char **expected)
f499a8
+{
f499a8
+    const char **exp_iter = expected;
f499a8
+    while (*exp_iter)
f499a8
+    {
f499a8
+        char *entry = NULL;
f499a8
+        asprintf(&entry, "\"%s\": \"%s\"", exp_iter[0], exp_iter[1]);
f499a8
+
f499a8
+        fprintf(stdout, "Checking: '%s'\n", entry);
f499a8
+
f499a8
+        if (strstr(json, entry) == NULL)
f499a8
+        {
f499a8
+            fprintf(stderr, "JSON:\n%s\n", json);
f499a8
+            abort();
f499a8
+        }
f499a8
+
f499a8
+        exp_iter += 2;
f499a8
+    }
f499a8
+}
f499a8
+
f499a8
+
f499a8
+int main(void)
f499a8
+{
f499a8
+  struct sr_report *report = sr_report_new();
f499a8
+
f499a8
+  sr_report_add_auth(report, "foo", "blah");
f499a8
+  sr_report_add_auth(report, "abrt", "awesome");
f499a8
+  sr_report_add_auth(report, "satyr", "wonderful");
f499a8
+
f499a8
+  const char *expected[] = { "satyr", "wonderful", "abrt", "awesome", "foo", "blah", NULL };
f499a8
+
f499a8
+  check_struct(report, expected);
f499a8
+
f499a8
+  sr_report_to_json(report);
f499a8
+
f499a8
+  char *json = sr_report_to_json(report);
f499a8
+
f499a8
+  check_json(json, expected);
f499a8
+
f499a8
+  char *error = NULL;
f499a8
+  struct sr_report *copy = sr_report_from_json_text(json, &error);
f499a8
+
f499a8
+  check_struct(copy, expected);
f499a8
+
f499a8
+  return 0;
f499a8
+}
f499a8
+]])
f499a8
-- 
f499a8
1.9.3
f499a8