Files
odfstudio/test/source/diff/diff.cxx
T
Armin Le Grand (collabora) 609d3192d3 SmartArt: Replace PointNameMap & optimize some stuff
Replaced PointNameMap in DiagramModel, too unsafe with
needed StateUpdates. Simplified some stuff dependent on
that. More stable now with changes. Embedding of
DiagramModelID back to ObjectName, that's just better.

Re-activated locked state for SdrObject members of
Diagrams for now due to UnitTests, but see comment
there.

Had to add an exceptional case to
XMLDiff::compareAttributes due to usage of GuidString,
see comment there. DiagramNodes do save their
GuidString in the name attribute of XShape. That
ID can change at every usage, thus when extracting
it *cannot* be compared to something saved/checked
in in a given state.

Change-Id: I112425d1efde58cfde8792cf2ee8c0c688d517e4
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/199502
Reviewed-by: Armin Le Grand <Armin.Le.Grand@me.com>
Tested-by: Jenkins
2026-02-17 17:57:59 +01:00

471 lines
13 KiB
C++

/* -*- 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 <cmath>
#include <vector>
#if USE_CPPUNIT
#include <cppunit/TestAssert.h>
#endif
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 xmlNodePtr node, const xmlAttrPtr attr, const xmlChar *expected, const xmlChar *found);
void cppunitAssertEqual(const xmlNodePtr node, 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(nullptr, 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(nullptr, 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 xmlNodePtr node, const xmlAttrPtr attr, const xmlChar *expected, const xmlChar *found)
{
#if USE_CPPUNIT
xmlChar * path = xmlGetNodePath(node);
std::stringstream stringStream;
stringStream
<< "Reference: " << fileName
<< "\n- Node: " << (path ? reinterpret_cast<const char*>(path) : "null")
<< "\n- Attr: " << (attr ? reinterpret_cast<const char*>(attr->name) : "null")
<< "\n- Expected: " << reinterpret_cast<const char*>(expected)
<< "\n- Found: " << reinterpret_cast<const char*>(found);
xmlFree(path);
CPPUNIT_ASSERT_MESSAGE(stringStream.str(), xmlStrEqual(expected, found));
#endif
}
void XMLDiff::cppunitAssertEqual(const xmlNodePtr node, const xmlChar *expected, const xmlChar *found)
{
#if USE_CPPUNIT
xmlChar * path = xmlGetNodePath(node);
std::stringstream stringStream;
stringStream
<< "Reference: " << fileName
<< "\n- Parent Node: " << (path ? reinterpret_cast<const char*>(path) : "null")
<< "\n- Expected Child Node: " << reinterpret_cast<const char*>(expected)
<< "\n- Found Child Node: " << reinterpret_cast<const char*>(found);
xmlFree(path);
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)
{
CPPUNIT_ASSERT(node1);
CPPUNIT_ASSERT(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(node1, 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
{
bool bIgnoreGUIDStrings(false);
if(!xmlStrEqual( val1, val2 ))
{
// there might be GuidString's used, these may get created
// at every export or internal usage, so these cannot be
// compared to a saved pattern. To identify, use their
// format. An example is:
// "{662825F2-FD0A-477C-81F6-D00196403932}"
// We can use here their specific length of 38, that
// should be good for now. If needed this may be extended
// to check for
// - curly braces at start/end
// - minus ('-') at positions 9, 14, 19 and 24
// - upper-case hex alphabet (0..9, A..F) for the characters
// also see ObjectNameIsDiagramModelID() which is similar
const int len1(xmlStrlen(val1));
const int len2(xmlStrlen(val2));
if (len1 == len2 && 38 == len1)
bIgnoreGUIDStrings = true;
}
if (!bIgnoreGUIDStrings)
{
#if USE_CPPUNIT
cppunitAssertEqual(node1, attr1, 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: */