From c73c25c1c04e83dd6b901dbe1a354640b44d6cdc Mon Sep 17 00:00:00 2001 From: Rintaro Kuroiwa Date: Fri, 11 Apr 2014 18:23:20 -0700 Subject: [PATCH] XML compare function XML compare function for comparing a subset of MPD or the whole MPD. The children must appear in the same order but the order of attributes do not matter. Change existing tests to use XmlEqual() instead of string comparison. Change-Id: Ib7f80f52b5bed5b5f7c2517620c8955261a4b6a2 --- mpd/base/xml/xml_node_unittest.cc | 139 ++++++++++++++++++++-------- mpd/mpd.gyp | 2 + mpd/test/mpd_builder_test_helper.cc | 8 +- mpd/test/xml_compare.cc | 122 ++++++++++++++++++++++++ mpd/test/xml_compare.h | 24 +++++ 5 files changed, 251 insertions(+), 44 deletions(-) create mode 100644 mpd/test/xml_compare.cc create mode 100644 mpd/test/xml_compare.h diff --git a/mpd/base/xml/xml_node_unittest.cc b/mpd/base/xml/xml_node_unittest.cc index 1870fc97cf..c7d5756dcf 100644 --- a/mpd/base/xml/xml_node_unittest.cc +++ b/mpd/base/xml/xml_node_unittest.cc @@ -7,6 +7,7 @@ #include "base/logging.h" #include "base/strings/string_util.h" #include "mpd/base/xml/xml_node.h" +#include "mpd/test/xml_compare.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/libxml/src/include/libxml/tree.h" @@ -27,49 +28,109 @@ void AddAttribute(const std::string& name, attribute->set_value(value); } -std::string GetDocAsFlatString(xmlDocPtr doc) { - static const int kFlatFormat = 0; - int doc_str_size = 0; - xmlChar* doc_str = NULL; - xmlDocDumpFormatMemoryEnc(doc, &doc_str, &doc_str_size, "UTF-8", kFlatFormat); - DCHECK(doc_str); - - std::string output(doc_str, doc_str + doc_str_size); - xmlFree(doc_str); - return output; -} - -// Ownership transfers, IOW this function will release the resource for |node|. -// Returns |node| in string format. -std::string GetStringFormat(ScopedXmlPtr::type node) { +ScopedXmlPtr::type MakeDoc(ScopedXmlPtr::type node) { xml::ScopedXmlPtr::type doc(xmlNewDoc(BAD_CAST "")); - - // Because you cannot easily get the string format of a xmlNodePtr, it gets - // attached to a temporary xml doc. xmlDocSetRootElement(doc.get(), node.release()); - std::string doc_str = GetDocAsFlatString(doc.get()); - // GetDocAsFlatString() adds - // - // to the first line. So this removes the first line. - const size_t first_newline_char_pos = doc_str.find('\n'); - DCHECK_NE(first_newline_char_pos, std::string::npos); - return doc_str.substr(first_newline_char_pos + 1); + return doc.Pass(); } - } // namespace +// Make sure XmlEqual() is functioning correctly. +TEST(MetaTest, XmlEqual) { + static const char kXml1[] = + "\n" + " \n" + " \n" + " \n" + " \n" + ""; + + + // This is same as kXml1 but the attributes are reordered. Note that the + // children are not reordered. + static const char kXml1AttributeReorder[] = + "\n" + " " + " \n" + " \n" + " \n" + ""; + + // is before . + static const char kXml1ChildrenReordered[] = + "\n" + " \n" + " " + " \n" + " \n" + ""; + + // is before . + static const char kXml1RemovedAttributes[] = + "\n" + " " + " \n" + " \n" + " \n" + ""; + + static const char kXml2[] = + "\n" + " \n" + ""; + + // In XML , , and mean the same thing. + static const char kXml2DifferentSyntax[] = + "\n" + " \n" + ""; + + static const char kXml2MoreDifferentSyntax[] = + "\n" + " \n" + ""; + + // Identity. + ASSERT_TRUE(XmlEqual(kXml1, kXml1)); + + // Equivalent. + ASSERT_TRUE(XmlEqual(kXml1, kXml1AttributeReorder)); + ASSERT_TRUE(XmlEqual(kXml2, kXml2DifferentSyntax)); + ASSERT_TRUE(XmlEqual(kXml2, kXml2MoreDifferentSyntax)); + + // Different. + ASSERT_FALSE(XmlEqual(kXml1, kXml2)); + ASSERT_FALSE(XmlEqual(kXml1, kXml1ChildrenReordered)); + ASSERT_FALSE(XmlEqual(kXml1, kXml1RemovedAttributes)); + ASSERT_FALSE(XmlEqual(kXml1AttributeReorder, kXml1ChildrenReordered)); +} + TEST(Representation, AddContentProtectionXml) { static const char kExpectedRepresentaionString[] = - "\n\ - \n\ - \n\ - \n\ - "; + "\n" + " \n" + " \n" + " \n" + ""; MediaInfo media_info; MediaInfo::ContentProtectionXml* content_protection_xml = @@ -89,11 +150,9 @@ TEST(Representation, AddContentProtectionXml) { ASSERT_TRUE( representation.AddContentProtectionElementsFromMediaInfo(media_info)); - std::string representation_xml_string = - GetStringFormat(representation.PassScopedPtr()); - // Compare with expected output (both flattened). - ASSERT_EQ(CollapseWhitespaceASCII(kExpectedRepresentaionString, true), - CollapseWhitespaceASCII(representation_xml_string, true)); + ScopedXmlPtr::type doc(MakeDoc(representation.PassScopedPtr())); + ASSERT_TRUE( + XmlEqual(kExpectedRepresentaionString, doc.get())); } } // namespace xml diff --git a/mpd/mpd.gyp b/mpd/mpd.gyp index f3879361bd..98bbabcb7e 100644 --- a/mpd/mpd.gyp +++ b/mpd/mpd.gyp @@ -71,6 +71,8 @@ 'base/xml/xml_node_unittest.cc', 'test/mpd_builder_test_helper.cc', 'test/mpd_builder_test_helper.h', + 'test/xml_compare.cc', + 'test/xml_compare.h', 'util/mpd_writer_unittest.cc', ], 'dependencies': [ diff --git a/mpd/test/mpd_builder_test_helper.cc b/mpd/test/mpd_builder_test_helper.cc index 8f65cdde31..dc6e9c8070 100644 --- a/mpd/test/mpd_builder_test_helper.cc +++ b/mpd/test/mpd_builder_test_helper.cc @@ -11,6 +11,7 @@ #include "mpd/base/media_info.pb.h" #include "mpd/base/mpd_builder.h" #include "mpd/base/xml/scoped_xml_ptr.h" +#include "mpd/test/xml_compare.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/protobuf/src/google/protobuf/text_format.h" @@ -104,10 +105,9 @@ void ExpectMpdToEqualExpectedOutputFile( << "Failed to read: " << expected_output_file; // Adding extra << here to get a formatted output. - ASSERT_EQ(expected_mpd, mpd_string) << "Expected:" << std::endl - << expected_mpd << std::endl - << "Actual:" << std::endl - << mpd_string; + ASSERT_TRUE(XmlEqual(expected_mpd, mpd_string)) + << "Expected:" << std::endl << expected_mpd << std::endl + << "Actual:" << std::endl << mpd_string; } } // namespace dash_packager diff --git a/mpd/test/xml_compare.cc b/mpd/test/xml_compare.cc new file mode 100644 index 0000000000..fea32cb1d5 --- /dev/null +++ b/mpd/test/xml_compare.cc @@ -0,0 +1,122 @@ +#include "mpd/test/xml_compare.h" + +#include +#include +#include + +#include "base/logging.h" +#include "mpd/base/xml/scoped_xml_ptr.h" +#include "third_party/libxml/src/include/libxml/tree.h" + +namespace dash_packager { + +namespace { +xml::ScopedXmlPtr::type GetDocFromString(const std::string& xml_str) { + xml::ScopedXmlPtr::type schema_as_doc( + xmlReadMemory(xml_str.data(), xml_str.size(), NULL, NULL, 0)); + + return schema_as_doc.Pass(); +} + +// Make a map from attributes of the node. +std::map GetMapOfAttributes(xmlNodePtr node) { + std::map attribute_map; + for (xmlAttr* attribute = node->properties; + attribute && attribute->name && attribute->children; + attribute = attribute->next) { + const char* name = reinterpret_cast(attribute->name); + xml::ScopedXmlPtr::type value( + xmlNodeListGetString(node->doc, attribute->children, 1)); + + attribute_map[name] = reinterpret_cast(value.get()); + DLOG(INFO) << "In node " << reinterpret_cast(node->name) + << " found attribute " << name << " with value " + << reinterpret_cast(value.get()); + } + + return attribute_map; +} + +bool MapsEqual(const std::map& map1, + const std::map& map2) { + return map1.size() == map2.size() && + std::equal(map1.begin(), map1.end(), map2.begin()); +} + +// Return true if the nodes have the same attributes. +bool CompareAttributes(xmlNodePtr node1, xmlNodePtr node2) { + return MapsEqual(GetMapOfAttributes(node1), GetMapOfAttributes(node2)); +} + +// Return true if the name of the nodes match. +bool CompareNames(xmlNodePtr node1, xmlNodePtr node2) { + DLOG(INFO) << "Comparing " << reinterpret_cast(node1->name) + << " and " << reinterpret_cast(node2->name); + return xmlStrcmp(node1->name, node2->name) == 0; +} + +// Recursively check the elements. +// Note that the terminating condition of the recursion is when the children do +// not match (inside the loop). +bool CompareNodes(xmlNodePtr node1, xmlNodePtr node2) { + DCHECK(node1 && node2); + + if (!CompareNames(node1, node2)) { + LOG(ERROR) << "Names of the nodes do not match: " + << reinterpret_cast(node1->name) << " " + << reinterpret_cast(node2->name); + return false; + } + + if (!CompareAttributes(node1, node2)) { + LOG(ERROR) << "Attributes of element " + << reinterpret_cast(node1->name) + << " do not match."; + return false; + } + + xmlNodePtr node1_child = xmlFirstElementChild(node1); + xmlNodePtr node2_child = xmlFirstElementChild(node2); + do { + if (!node1_child || !node2_child) + return node1_child == node2_child; + + DCHECK(node1_child && node2_child); + if (!CompareNodes(node1_child, node2_child)) + return false; + + node1_child = xmlNextElementSibling(node1_child); + node2_child = xmlNextElementSibling(node2_child); + } while (node1_child || node2_child); + + // Both nodes have equivalent descendents. + return true; +} +} // namespace + +bool XmlEqual(const std::string& xml1, const std::string& xml2) { + xml::ScopedXmlPtr::type xml1_doc(GetDocFromString(xml1)); + xml::ScopedXmlPtr::type xml2_doc(GetDocFromString(xml2)); + return XmlEqual(xml1_doc.get(), xml2_doc.get()); +} + +bool XmlEqual(const std::string& xml1, xmlDocPtr xml2) { + xml::ScopedXmlPtr::type xml1_doc(GetDocFromString(xml1)); + return XmlEqual(xml1_doc.get(), xml2); +} + +bool XmlEqual(xmlDocPtr xml1, xmlDocPtr xml2) { + if (!xml1|| !xml2) { + LOG(ERROR) << "xml1 and/or xml2 are not valid XML."; + return false; + } + + xmlNodePtr xml1_root_element = xmlDocGetRootElement(xml1); + xmlNodePtr xml2_root_element = xmlDocGetRootElement(xml2); + if (!xml1_root_element || !xml2_root_element) + return xml1_root_element == xml2_root_element; + + return CompareNodes(xml1_root_element, xml2_root_element); +} + +} // namespace dash_packager diff --git a/mpd/test/xml_compare.h b/mpd/test/xml_compare.h new file mode 100644 index 0000000000..76ea4007aa --- /dev/null +++ b/mpd/test/xml_compare.h @@ -0,0 +1,24 @@ +#ifndef MPD_TEST_XML_COMPARE_H_ +#define MPD_TEST_XML_COMPARE_H_ + +#include + +#include "third_party/libxml/src/include/libxml/tree.h" + +namespace dash_packager { + +/// Checks whether the XMLs are equivalent. An XML schema can specify strict +/// ordering of children and MPD does. These functions are currently tuned to +/// compare subsets of MPD. Therefore this function requires strict ordering of +/// the child elements of an element. Attributes can be in any order since XML +/// cannot enforce ordering of attributes. +/// @param xml1 is compared against @a xml2. +/// @param xml2 is compared against @a xml1. +/// @return true if @a xml1 and @a xml2 are equivalent, false otherwise. +bool XmlEqual(const std::string& xml1, const std::string& xml2); +bool XmlEqual(const std::string& xml1, xmlDocPtr xml2); +bool XmlEqual(xmlDocPtr xml1, xmlDocPtr xml2); + +} // namespace dash_packager + +#endif // MPD_TEST_XML_COMPARE_H_