summaryrefslogtreecommitdiffstats
path: root/test/source/diff/diff.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'test/source/diff/diff.cxx')
-rw-r--r--test/source/diff/diff.cxx417
1 files changed, 417 insertions, 0 deletions
diff --git a/test/source/diff/diff.cxx b/test/source/diff/diff.cxx
new file mode 100644
index 000000000..e0ba939d4
--- /dev/null
+++ b/test/source/diff/diff.cxx
@@ -0,0 +1,417 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#define USE_CPPUNIT 1
+
+#include <test/xmldiff.hxx>
+
+#include <libxml/xpath.h>
+#include <libxml/parser.h>
+
+#include <set>
+#include <sstream>
+#include <cassert>
+#include <vector>
+
+#if USE_CPPUNIT
+#include <cppunit/TestAssert.h>
+#endif
+
+#include <rtl/math.hxx>
+
+namespace {
+
+struct tolerance
+{
+ ~tolerance()
+ {
+ xmlFree(elementName);
+ xmlFree(attribName);
+ }
+
+ tolerance()
+ : elementName(nullptr)
+ , attribName(nullptr)
+ , relative(false)
+ , value(0.0)
+ {
+ }
+
+ tolerance(const tolerance& tol)
+ {
+ elementName = xmlStrdup(tol.elementName);
+ attribName = xmlStrdup(tol.attribName);
+ relative = tol.relative;
+ value = tol.value;
+ }
+
+ xmlChar* elementName;
+ xmlChar* attribName;
+ bool relative;
+ double value;
+ bool operator<(const tolerance& rTol) const
+ {
+ int cmp = xmlStrcmp(elementName, rTol.elementName);
+ if(cmp == 0)
+ {
+ cmp = xmlStrcmp(attribName, rTol.attribName);
+ }
+
+ return cmp < 0;
+ }
+};
+
+class XMLDiff
+{
+public:
+ XMLDiff(const char* pFileName, const char* pContent, int size, const char* pToleranceFileName);
+ ~XMLDiff();
+
+ bool compare();
+private:
+ typedef std::set<tolerance> ToleranceContainer;
+
+ void loadToleranceFile(xmlDocPtr xmlTolerance);
+ bool compareAttributes(xmlNodePtr node1, xmlNodePtr node2);
+ bool compareElements(xmlNode* node1, xmlNode* node2);
+
+ /// Error message for cppunit that prints out when expected and found are not equal.
+ void cppunitAssertEqual(const xmlChar *expected, const xmlChar *found);
+
+ /// Error message for cppunit that prints out when expected and found are not equal - for doubles.
+ void cppunitAssertEqualDouble(const xmlNodePtr node, const xmlAttrPtr attr, double expected, double found, double delta);
+
+ ToleranceContainer toleranceContainer;
+ xmlDocPtr xmlFile1;
+ xmlDocPtr xmlFile2;
+ std::string fileName;
+};
+
+}
+
+XMLDiff::XMLDiff( const char* pFileName, const char* pContent, int size, const char* pToleranceFile)
+ : xmlFile1(xmlParseFile(pFileName))
+ , xmlFile2(xmlParseMemory(pContent, size))
+ , fileName(pFileName)
+{
+ if(pToleranceFile)
+ {
+ xmlDocPtr xmlToleranceFile = xmlParseFile(pToleranceFile);
+ loadToleranceFile(xmlToleranceFile);
+ xmlFreeDoc(xmlToleranceFile);
+ }
+}
+
+XMLDiff::~XMLDiff()
+{
+ xmlFreeDoc(xmlFile1);
+ xmlFreeDoc(xmlFile2);
+}
+
+namespace {
+
+void readAttributesForTolerance(xmlNodePtr node, tolerance& tol)
+{
+ xmlChar* elementName = xmlGetProp(node, BAD_CAST("elementName"));
+ tol.elementName = elementName;
+
+ xmlChar* attribName = xmlGetProp(node, BAD_CAST("attribName"));
+ tol.attribName = attribName;
+
+ xmlChar* value = xmlGetProp(node, BAD_CAST("value"));
+ double val = xmlXPathCastStringToNumber(value);
+ xmlFree(value);
+ tol.value = val;
+
+ xmlChar* relative = xmlGetProp(node, BAD_CAST("relative"));
+ bool rel = false;
+ if(xmlStrEqual(relative, BAD_CAST("true")))
+ rel = true;
+ xmlFree(relative);
+ tol.relative = rel;
+}
+
+}
+
+void XMLDiff::loadToleranceFile(xmlDocPtr xmlToleranceFile)
+{
+ xmlNodePtr root = xmlDocGetRootElement(xmlToleranceFile);
+#if USE_CPPUNIT
+ CPPUNIT_ASSERT_MESSAGE("did not find correct tolerance file", xmlStrEqual( root->name, BAD_CAST("tolerances") ));
+#else
+ if(!xmlStrEqual( root->name, BAD_CAST("tolerances") ))
+ {
+ assert(false);
+ return;
+ }
+#endif
+ xmlNodePtr child = nullptr;
+ for (child = root->children; child != nullptr; child = child->next)
+ {
+ // assume a valid xml file
+ if(child->type != XML_ELEMENT_NODE)
+ continue;
+
+ assert(xmlStrEqual(child->name, BAD_CAST("tolerance")));
+
+ tolerance tol;
+ readAttributesForTolerance(child, tol);
+ toleranceContainer.insert(tol);
+ }
+}
+
+bool XMLDiff::compare()
+{
+ xmlNode* root1 = xmlDocGetRootElement(xmlFile1);
+ xmlNode* root2 = xmlDocGetRootElement(xmlFile2);
+
+#if USE_CPPUNIT
+ CPPUNIT_ASSERT(root1);
+ CPPUNIT_ASSERT(root2);
+ cppunitAssertEqual(root1->name, root2->name);
+#else
+ if (!root1 || !root2)
+ return false;
+ if(!xmlStrEqual(root1->name, root2->name))
+ return false;
+#endif
+ return compareElements(root1, root2);
+}
+
+namespace {
+
+bool checkForEmptyChildren(xmlNodePtr node)
+{
+ if(!node)
+ return true;
+
+ for(; node != nullptr; node = node->next)
+ {
+ if (node->type == XML_ELEMENT_NODE)
+ return false;
+ }
+ return true;
+}
+
+}
+
+bool XMLDiff::compareElements(xmlNode* node1, xmlNode* node2)
+{
+#if USE_CPPUNIT
+ cppunitAssertEqual(node1->name, node2->name);
+#else
+ if (!xmlStrEqual( node1->name, node2->name ))
+ return false;
+#endif
+
+ //compare attributes
+ bool sameAttribs = compareAttributes(node1, node2);
+#if USE_CPPUNIT
+ CPPUNIT_ASSERT(sameAttribs);
+#else
+ if (!sameAttribs)
+ return false;
+#endif
+
+ // compare children
+ xmlNode* child2 = nullptr;
+ xmlNode* child1 = nullptr;
+ for(child1 = node1->children, child2 = node2->children; child1 != nullptr && child2 != nullptr; child1 = child1->next, child2 = child2->next)
+ {
+ if (child1->type == XML_ELEMENT_NODE)
+ {
+ bool bCompare = compareElements(child1, child2);
+ if(!bCompare)
+ {
+ return false;
+ }
+ }
+ }
+
+#if USE_CPPUNIT
+ CPPUNIT_ASSERT(checkForEmptyChildren(child1));
+ CPPUNIT_ASSERT(checkForEmptyChildren(child2));
+#else
+ if(!checkForEmptyChildren(child1) || !checkForEmptyChildren(child2))
+ return false;
+#endif
+
+ return true;
+}
+
+void XMLDiff::cppunitAssertEqual(const xmlChar *expected, const xmlChar *found)
+{
+#if USE_CPPUNIT
+ std::stringstream stringStream;
+ stringStream << "Reference: " << fileName << "\n- Expected: " << reinterpret_cast<const char*>(expected) << "\n- Found: " << reinterpret_cast<const char*>(found);
+
+ CPPUNIT_ASSERT_MESSAGE(stringStream.str(), xmlStrEqual(expected, found));
+#endif
+}
+
+void XMLDiff::cppunitAssertEqualDouble(const xmlNodePtr node, const xmlAttrPtr attr, double expected, double found, double delta)
+{
+#if USE_CPPUNIT
+ xmlChar * path = xmlGetNodePath(node);
+ std::stringstream stringStream;
+ stringStream << "Reference: " << fileName << "\n- Node: " << reinterpret_cast<const char*>(path) << "\n- Attr: " << reinterpret_cast<const char*>(attr->name);
+ xmlFree(path);
+
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(stringStream.str(), expected, found, delta);
+#endif
+}
+
+namespace {
+
+bool compareValuesWithTolerance(double val1, double val2, double tolerance, bool relative)
+{
+ if(relative)
+ {
+ return (val1/tolerance) <= val2 && val2 <= (val1*tolerance);
+ }
+ else
+ {
+ return (val1 - tolerance) <= val2 && val2 <= (val1 + tolerance);
+ }
+}
+
+}
+
+bool XMLDiff::compareAttributes(xmlNodePtr node1, xmlNodePtr node2)
+{
+ xmlAttrPtr attr1 = nullptr;
+ xmlAttrPtr attr2 = nullptr;
+ for(attr1 = node1->properties, attr2 = node2->properties; attr1 != nullptr && attr2 != nullptr; attr1 = attr1->next, attr2 = attr2->next)
+ {
+#if USE_CPPUNIT
+ cppunitAssertEqual(attr1->name, attr2->name);
+#else
+ if (!xmlStrEqual( attr1->name, attr2->name ))
+ return false;
+#endif
+
+ xmlChar* val1 = xmlGetProp(node1, attr1->name);
+ xmlChar* val2 = xmlGetProp(node2, attr2->name);
+
+ double dVal1 = xmlXPathCastStringToNumber(val1);
+ double dVal2 = xmlXPathCastStringToNumber(val2);
+
+ if(!std::isnan(dVal1) || !std::isnan(dVal2))
+ {
+ //compare by value and respect tolerance
+ tolerance tol;
+ tol.elementName = xmlStrdup(node1->name);
+ tol.attribName = xmlStrdup(attr1->name);
+ ToleranceContainer::iterator itr = toleranceContainer.find( tol );
+ bool useTolerance = false;
+ if (itr != toleranceContainer.end())
+ {
+ useTolerance = true;
+ }
+
+ if (useTolerance)
+ {
+ bool valInTolerance = compareValuesWithTolerance(dVal1, dVal2, itr->value, itr->relative);
+#if USE_CPPUNIT
+ std::stringstream stringStream("Expected Value: ");
+ stringStream << dVal1 << "; Found Value: " << dVal2 << "; Tolerance: " << itr->value;
+ stringStream << "; Relative: " << itr->relative;
+ CPPUNIT_ASSERT_MESSAGE(stringStream.str(), valInTolerance);
+#else
+ if (!valInTolerance)
+ return false;
+#endif
+ }
+ else
+ {
+#if USE_CPPUNIT
+ cppunitAssertEqualDouble(node1, attr1, dVal1, dVal2, 1e-08);
+#else
+ if (dVal1 != dVal2)
+ return false;
+#endif
+ }
+ }
+ else
+ {
+
+#if USE_CPPUNIT
+ cppunitAssertEqual(val1, val2);
+#else
+ if(!xmlStrEqual( val1, val2 ))
+ return false;
+#endif
+ }
+
+ xmlFree(val1);
+ xmlFree(val2);
+ }
+
+ // unequal number of attributes
+#ifdef CPPUNIT_ASSERT
+ if (attr1 || attr2)
+ {
+ std::stringstream failStream;
+ failStream << "Unequal number of attributes in ";
+ // print chain from document root
+ std::vector<std::string> parents;
+ auto n = node1;
+ while (n)
+ {
+ if (n->name)
+ parents.push_back(std::string(reinterpret_cast<const char *>(n->name)));
+ n = n->parent;
+ }
+ bool first = true;
+ for (auto it = parents.rbegin(); it != parents.rend(); ++it)
+ {
+ if (!first)
+ failStream << "->";
+ first = false;
+ failStream << *it;
+ }
+ failStream << " Attr1: ";
+ attr1 = node1->properties;
+ while (attr1 != nullptr)
+ {
+ xmlChar* val1 = xmlGetProp(node1, attr1->name);
+ failStream << BAD_CAST(attr1->name) << "=" << BAD_CAST(val1) << ", ";
+ xmlFree(val1);
+ attr1 = attr1->next;
+ }
+
+ failStream << " Attr2: ";
+ attr2 = node2->properties;
+ while (attr2 != nullptr)
+ {
+ xmlChar* val2 = xmlGetProp(node2, attr2->name);
+ failStream << BAD_CAST(attr2->name) << "=" << BAD_CAST(val2) << ", ";
+ xmlFree(val2);
+ attr2 = attr2->next;
+ }
+ CPPUNIT_ASSERT_MESSAGE(failStream.str(), false);
+ }
+#else
+ if (attr1 || attr2)
+ return false;
+#endif
+
+ return true;
+}
+
+
+bool
+doXMLDiff(char const*const pFileName, char const*const pContent, int const size,
+ char const*const pToleranceFileName)
+{
+ XMLDiff aDiff(pFileName, pContent, size, pToleranceFileName);
+ return aDiff.compare();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */