[libgdata/libgdata-0-7] tests: Add an XML tree comparison function for the test suite
- From: Philip Withnall <pwithnall src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [libgdata/libgdata-0-7] tests: Add an XML tree comparison function for the test suite
- Date: Sat, 11 Dec 2010 21:52:09 +0000 (UTC)
commit 7de1ab4b5536a62634637c11f975c1168655d5d9
Author: Philip Withnall <philip tecnocode co uk>
Date: Sat Dec 11 11:53:55 2010 +0000
tests: Add an XML tree comparison function for the test suite
Due to a recent change in GLib master which affects the ordering of entries
in hash tables (g_str_hash()'s implementation has been changed), many of the
XML comparisons in libgdata's test suite have been broken. A tidy solution is
to introduce an XML-aware comparison function, rather than just using a
simple string comparison. This commit does that.
This commit only adds the comparison function; a further one will port all
the test suites to use it.
gdata/tests/common.c | 180 +++++++++++++++++++++++++++++++++++++++++++++++++
gdata/tests/common.h | 9 +++
gdata/tests/general.c | 172 ++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 361 insertions(+), 0 deletions(-)
---
diff --git a/gdata/tests/common.c b/gdata/tests/common.c
index 37d3e7d..3659680 100644
--- a/gdata/tests/common.c
+++ b/gdata/tests/common.c
@@ -21,6 +21,8 @@
#include <glib-object.h>
#include <stdio.h>
#include <string.h>
+#include <libxml/parser.h>
+#include <libxml/xmlsave.h>
#include "common.h"
@@ -305,3 +307,181 @@ gdata_test_batch_operation_deletion (GDataBatchOperation *operation, GDataEntry
return op_id;
}
+
+static gboolean
+compare_xml_namespaces (xmlNs *ns1, xmlNs *ns2)
+{
+ if (ns1 == ns2)
+ return TRUE;
+
+ /* Compare various simple properties */
+ if (ns1->type != ns2->type ||
+ xmlStrcmp (ns1->href, ns2->href) != 0 ||
+ xmlStrcmp (ns1->prefix, ns2->prefix) != 0 ||
+ ns1->context != ns2->context) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean compare_xml_nodes (xmlNode *node1, xmlNode *node2);
+
+static gboolean
+compare_xml_node_lists (xmlNode *list1, xmlNode *list2)
+{
+ GHashTable *table;
+ xmlNode *child;
+
+ /* Compare their child elements. We iterate through the first linked list and, for each child node, iterate through the second linked list
+ * comparing it against each node there. We keep a hashed set of nodes in the second linked list which have already been visited and compared
+ * successfully, both for speed and to guarantee that one element in the second linked list doesn't match more than one in the first linked
+ * list. We take this approach because we can't modify the second linked list in place to remove matched nodes.
+ * Finally, we iterate through the second node list and check that all its elements are in the hash table (i.e. they've all been visited
+ * exactly once).
+ * This approach is O(n^2) in the number of nodes in the linked lists, but since we should be dealing with fairly narrow XML trees this should
+ * be OK. */
+ table = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+ for (child = list1; child != NULL; child = child->next) {
+ xmlNode *other_child;
+ gboolean matched = FALSE;
+
+ for (other_child = list2; other_child != NULL; other_child = other_child->next) {
+ if (g_hash_table_lookup (table, other_child) != NULL)
+ continue;
+
+ if (compare_xml_nodes (child, other_child) == TRUE) {
+ g_hash_table_insert (table, other_child, other_child);
+ matched = TRUE;
+ break;
+ }
+ }
+
+ if (matched == FALSE) {
+ g_hash_table_destroy (table);
+ return FALSE;
+ }
+ }
+
+ for (child = list2; child != NULL; child = child->next) {
+ if (g_hash_table_lookup (table, child) == NULL) {
+ g_hash_table_destroy (table);
+ return FALSE;
+ }
+ }
+
+ g_hash_table_destroy (table);
+
+ return TRUE;
+}
+
+static gboolean
+compare_xml_nodes (xmlNode *node1, xmlNode *node2)
+{
+ GHashTable *table;
+ xmlAttr *attr1, *attr2;
+ xmlNs *ns;
+
+ if (node1 == node2)
+ return TRUE;
+
+ /* Compare various simple properties */
+ if (node1->type != node2->type ||
+ xmlStrcmp (node1->name, node2->name) != 0 ||
+ compare_xml_namespaces (node1->ns, node2->ns) == FALSE ||
+ xmlStrcmp (node1->content, node2->content) != 0) {
+ return FALSE;
+ }
+
+ /* Compare their attributes. This is done in document order, which isn't strictly correct, since XML specifically does not apply an ordering
+ * over attributes. However, it suffices for our needs. */
+ for (attr1 = node1->properties, attr2 = node2->properties; attr1 != NULL && attr2 != NULL; attr1 = attr1->next, attr2 = attr2->next) {
+ /* Compare various simple properties */
+ if (attr1->type != attr2->type ||
+ xmlStrcmp (attr1->name, attr2->name) != 0 ||
+ attr1->ns != attr2->ns ||
+ attr1->atype != attr2->atype) {
+ return FALSE;
+ }
+
+ /* Compare their child nodes (values represented as text and entity nodes) */
+ if (compare_xml_node_lists (attr1->children, attr2->children) == FALSE)
+ return FALSE;
+ }
+
+ /* Stragglers? */
+ if (attr1 != NULL || attr2 != NULL)
+ return FALSE;
+
+ /* Compare their namespace definitions regardless of order. Do this by inserting all the definitions from node1 into a hash table, then running
+ * through the definitions in node2 and ensuring they exist in the hash table, removing each one from the table as we go. Check there aren't
+ * any left in the hash table afterwards. */
+ table = g_hash_table_new (g_str_hash, g_str_equal);
+
+ for (ns = node1->nsDef; ns != NULL; ns = ns->next) {
+ /* Prefixes should be unique, but I trust libxml about as far as I can throw it. */
+ if (g_hash_table_lookup (table, ns->prefix ? ns->prefix : (gpointer) "") != NULL) {
+ g_hash_table_destroy (table);
+ return FALSE;
+ }
+
+ g_hash_table_insert (table, ns->prefix ? (gpointer) ns->prefix : (gpointer) "", ns);
+ }
+
+ for (ns = node2->nsDef; ns != NULL; ns = ns->next) {
+ xmlNs *original_ns = g_hash_table_lookup (table, ns->prefix ? ns->prefix : (gpointer) "");
+
+ if (original_ns == NULL ||
+ compare_xml_namespaces (original_ns, ns) == FALSE) {
+ g_hash_table_destroy (table);
+ return FALSE;
+ }
+
+ g_hash_table_remove (table, ns->prefix ? ns->prefix : (gpointer) "");
+ }
+
+ if (g_hash_table_size (table) != 0) {
+ g_hash_table_destroy (table);
+ return FALSE;
+ }
+
+ g_hash_table_destroy (table);
+
+ /* Compare their child nodes */
+ if (compare_xml_node_lists (node1->children, node2->children) == FALSE)
+ return FALSE;
+
+ /* Success! */
+ return TRUE;
+}
+
+gboolean
+gdata_test_compare_xml (GDataParsable *parsable, const gchar *expected_xml, gboolean print_error)
+{
+ gboolean success;
+ gchar *parsable_xml;
+ xmlDoc *parsable_doc, *expected_doc;
+
+ /* Get an XML string for the GDataParsable */
+ parsable_xml = gdata_parsable_get_xml (parsable);
+
+ /* Parse both the XML strings */
+ parsable_doc = xmlReadMemory (parsable_xml, strlen (parsable_xml), "/dev/null", NULL, 0);
+ expected_doc = xmlReadMemory (expected_xml, strlen (expected_xml), "/dev/null", NULL, 0);
+
+ g_assert (parsable_doc != NULL && expected_doc != NULL);
+
+ /* Recursively compare the two XML trees */
+ success = compare_xml_nodes (xmlDocGetRootElement (parsable_doc), xmlDocGetRootElement (expected_doc));
+ if (success == FALSE && print_error == TRUE) {
+ /* The comparison has failed, so print out the two XML strings for ease of debugging */
+ g_message ("\n\nParsable: %s\n\nExpected: %s\n\n", parsable_xml, expected_xml);
+ }
+
+ xmlFreeDoc (expected_doc);
+ xmlFreeDoc (parsable_doc);
+ g_free (parsable_xml);
+
+ return success;
+}
diff --git a/gdata/tests/common.h b/gdata/tests/common.h
index 4c9492d..e8fe566 100644
--- a/gdata/tests/common.h
+++ b/gdata/tests/common.h
@@ -40,6 +40,15 @@ guint gdata_test_batch_operation_insertion (GDataBatchOperation *operation, GDat
guint gdata_test_batch_operation_update (GDataBatchOperation *operation, GDataEntry *entry, GDataEntry **updated_entry, GError **error);
guint gdata_test_batch_operation_deletion (GDataBatchOperation *operation, GDataEntry *entry, GError **error);
+gboolean gdata_test_compare_xml (GDataParsable *parsable, const gchar *expected_xml, gboolean print_error);
+
+/* Convenience macro */
+#define gdata_test_assert_xml(Parsable, XML) \
+ G_STMT_START { \
+ gboolean _test_success = gdata_test_compare_xml (GDATA_PARSABLE (Parsable), XML, TRUE); \
+ g_assert (_test_success == TRUE); \
+ } G_STMT_END
+
G_END_DECLS
#endif /* !GDATA_TEST_COMMON_H */
diff --git a/gdata/tests/general.c b/gdata/tests/general.c
index 7990e47..d527886 100644
--- a/gdata/tests/general.c
+++ b/gdata/tests/general.c
@@ -24,6 +24,176 @@
#include "common.h"
static void
+test_xml_comparison (void)
+{
+ GDataAccessRule *rule;
+
+ /* Since we've written the XML comparison function used across all the test suites, it's necessary to test that it actually works before we
+ * blindly assert that its results are correct. */
+ rule = gdata_access_rule_new ("an-id");
+ gdata_access_rule_set_role (rule, GDATA_ACCESS_ROLE_NONE);
+ gdata_access_rule_set_scope (rule, GDATA_ACCESS_SCOPE_USER, "foo example com");
+
+ /* Check a valid comparison */
+ gdata_test_assert_xml (rule,
+ "<?xml version='1.0' encoding='UTF-8'?>"
+ "<entry xmlns='http://www.w3.org/2005/Atom' "
+ "xmlns:gd='http://schemas.google.com/g/2005' "
+ "xmlns:gAcl='http://schemas.google.com/acl/2007'>"
+ "<title type='text'>none</title>"
+ "<id>an-id</id>"
+ "<category term='http://schemas.google.com/acl/2007#accessRule' scheme='http://schemas.google.com/g/2005#kind'/>"
+ "<gAcl:role value='none'/>"
+ "<gAcl:scope type='user' value='foo example com'/>"
+ "</entry>");
+
+ /* Check a valid comparison with namespaces swapped */
+ gdata_test_assert_xml (rule,
+ "<?xml version='1.0' encoding='UTF-8'?>"
+ "<entry xmlns='http://www.w3.org/2005/Atom' "
+ "xmlns:gAcl='http://schemas.google.com/acl/2007' "
+ "xmlns:gd='http://schemas.google.com/g/2005'>"
+ "<title type='text'>none</title>"
+ "<id>an-id</id>"
+ "<category term='http://schemas.google.com/acl/2007#accessRule' scheme='http://schemas.google.com/g/2005#kind'/>"
+ "<gAcl:role value='none'/>"
+ "<gAcl:scope type='user' value='foo example com'/>"
+ "</entry>");
+
+ /* Check a valid comparison with elements swapped */
+ gdata_test_assert_xml (rule,
+ "<?xml version='1.0' encoding='UTF-8'?>"
+ "<entry xmlns='http://www.w3.org/2005/Atom' "
+ "xmlns:gd='http://schemas.google.com/g/2005' "
+ "xmlns:gAcl='http://schemas.google.com/acl/2007'>"
+ "<id>an-id</id>"
+ "<title type='text'>none</title>"
+ "<category term='http://schemas.google.com/acl/2007#accessRule' scheme='http://schemas.google.com/g/2005#kind'/>"
+ "<gAcl:role value='none'/>"
+ "<gAcl:scope type='user' value='foo example com'/>"
+ "</entry>");
+
+ /* Missing namespace (still valid XML, just not what's expected) */
+ g_assert (gdata_test_compare_xml (GDATA_PARSABLE (rule),
+ "<?xml version='1.0' encoding='UTF-8'?>"
+ "<entry xmlns='http://www.w3.org/2005/Atom' "
+ "xmlns:gAcl='http://schemas.google.com/acl/2007'>"
+ "<title type='text'>none</title>"
+ "<id>an-id</id>"
+ "<category term='http://schemas.google.com/acl/2007#accessRule' scheme='http://schemas.google.com/g/2005#kind'/>"
+ "<gAcl:role value='none'/>"
+ "<gAcl:scope type='user' value='foo example com'/>"
+ "</entry>", FALSE) == FALSE);
+
+ /* Extra namespace */
+ g_assert (gdata_test_compare_xml (GDATA_PARSABLE (rule),
+ "<?xml version='1.0' encoding='UTF-8'?>"
+ "<entry xmlns='http://www.w3.org/2005/Atom' "
+ "xmlns:gd='http://schemas.google.com/g/2005' "
+ "xmlns:gAcl='http://schemas.google.com/acl/2007' "
+ "xmlns:foo='http://foo.com/'>"
+ "<title type='text'>none</title>"
+ "<id>an-id</id>"
+ "<category term='http://schemas.google.com/acl/2007#accessRule' scheme='http://schemas.google.com/g/2005#kind'/>"
+ "<gAcl:role value='none'/>"
+ "<gAcl:scope type='user' value='foo example com'/>"
+ "</entry>", FALSE) == FALSE);
+
+ /* Missing element */
+ g_assert (gdata_test_compare_xml (GDATA_PARSABLE (rule),
+ "<?xml version='1.0' encoding='UTF-8'?>"
+ "<entry xmlns='http://www.w3.org/2005/Atom' "
+ "xmlns:gd='http://schemas.google.com/g/2005' "
+ "xmlns:gAcl='http://schemas.google.com/acl/2007'>"
+ "<id>an-id</id>"
+ "<category term='http://schemas.google.com/acl/2007#accessRule' scheme='http://schemas.google.com/g/2005#kind'/>"
+ "<gAcl:role value='none'/>"
+ "<gAcl:scope type='user' value='foo example com'/>"
+ "</entry>", FALSE) == FALSE);
+
+ /* Extra element */
+ g_assert (gdata_test_compare_xml (GDATA_PARSABLE (rule),
+ "<?xml version='1.0' encoding='UTF-8'?>"
+ "<entry xmlns='http://www.w3.org/2005/Atom' "
+ "xmlns:gd='http://schemas.google.com/g/2005' "
+ "xmlns:gAcl='http://schemas.google.com/acl/2007'>"
+ "<title type='text'>none</title>"
+ "<foo>bar</foo>"
+ "<id>an-id</id>"
+ "<category term='http://schemas.google.com/acl/2007#accessRule' scheme='http://schemas.google.com/g/2005#kind'/>"
+ "<gAcl:role value='none'/>"
+ "<gAcl:scope type='user' value='foo example com'/>"
+ "</entry>", FALSE) == FALSE);
+
+ /* Incorrect namespace on element */
+ g_assert (gdata_test_compare_xml (GDATA_PARSABLE (rule),
+ "<?xml version='1.0' encoding='UTF-8'?>"
+ "<entry xmlns='http://www.w3.org/2005/Atom' "
+ "xmlns:gd='http://schemas.google.com/g/2005' "
+ "xmlns:gAcl='http://schemas.google.com/acl/2007'>"
+ "<gAcl:title type='text'>none</gAcl:title>"
+ "<id>an-id</id>"
+ "<category term='http://schemas.google.com/acl/2007#accessRule' scheme='http://schemas.google.com/g/2005#kind'/>"
+ "<gAcl:role value='none'/>"
+ "<gAcl:scope type='user' value='foo example com'/>"
+ "</entry>", FALSE) == FALSE);
+
+ /* Mis-valued content */
+ g_assert (gdata_test_compare_xml (GDATA_PARSABLE (rule),
+ "<?xml version='1.0' encoding='UTF-8'?>"
+ "<entry xmlns='http://www.w3.org/2005/Atom' "
+ "xmlns:gd='http://schemas.google.com/g/2005' "
+ "xmlns:gAcl='http://schemas.google.com/acl/2007'>"
+ "<title type='text'>none</title>"
+ "<id>an-other-id</id>"
+ "<category term='http://schemas.google.com/acl/2007#accessRule' scheme='http://schemas.google.com/g/2005#kind'/>"
+ "<gAcl:role value='none'/>"
+ "<gAcl:scope type='user' value='foo example com'/>"
+ "</entry>", FALSE) == FALSE);
+
+ /* Missing attribute */
+ g_assert (gdata_test_compare_xml (GDATA_PARSABLE (rule),
+ "<?xml version='1.0' encoding='UTF-8'?>"
+ "<entry xmlns='http://www.w3.org/2005/Atom' "
+ "xmlns:gd='http://schemas.google.com/g/2005' "
+ "xmlns:gAcl='http://schemas.google.com/acl/2007'>"
+ "<title type='text'>none</title>"
+ "<id>an-id</id>"
+ "<category term='http://schemas.google.com/acl/2007#accessRule' scheme='http://schemas.google.com/g/2005#kind'/>"
+ "<gAcl:role/>"
+ "<gAcl:scope type='user' value='foo example com'/>"
+ "</entry>", FALSE) == FALSE);
+
+ /* Extra attribute */
+ g_assert (gdata_test_compare_xml (GDATA_PARSABLE (rule),
+ "<?xml version='1.0' encoding='UTF-8'?>"
+ "<entry xmlns='http://www.w3.org/2005/Atom' "
+ "xmlns:gd='http://schemas.google.com/g/2005' "
+ "xmlns:gAcl='http://schemas.google.com/acl/2007'>"
+ "<title type='text'>none</title>"
+ "<id>an-id</id>"
+ "<category term='http://schemas.google.com/acl/2007#accessRule' scheme='http://schemas.google.com/g/2005#kind'/>"
+ "<gAcl:role value='none' other-value='foo'/>"
+ "<gAcl:scope type='user' value='foo example com'/>"
+ "</entry>", FALSE) == FALSE);
+
+ /* Mis-valued attribute */
+ g_assert (gdata_test_compare_xml (GDATA_PARSABLE (rule),
+ "<?xml version='1.0' encoding='UTF-8'?>"
+ "<entry xmlns='http://www.w3.org/2005/Atom' "
+ "xmlns:gd='http://schemas.google.com/g/2005' "
+ "xmlns:gAcl='http://schemas.google.com/acl/2007'>"
+ "<title type='text'>none</title>"
+ "<id>an-id</id>"
+ "<category term='http://schemas.google.com/acl/2007#accessRule' scheme='http://schemas.google.com/g/2005#kind'/>"
+ "<gAcl:role value='other'/>"
+ "<gAcl:scope type='user' value='foo example com'/>"
+ "</entry>", FALSE) == FALSE);
+
+ g_object_unref (rule);
+}
+
+static void
test_entry_get_xml (void)
{
gint64 updated, published, updated2, published2, updated3, published3;
@@ -3254,6 +3424,8 @@ main (int argc, char *argv[])
{
gdata_test_init (argc, argv);
+ g_test_add_func ("/tests/xml_comparison", test_xml_comparison);
+
g_test_add_func ("/service/network_error", test_service_network_error);
g_test_add_func ("/service/locale", test_service_locale);
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]