LibreOffice Module test (master)  1
diff.cxx
Go to the documentation of this file.
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  */
9 
10 #define USE_CPPUNIT 1
11 
12 #include <test/xmldiff.hxx>
13 
14 #include <libxml/xpath.h>
15 #include <libxml/parser.h>
16 
17 #include <set>
18 #include <sstream>
19 #include <cassert>
20 #include <vector>
21 
22 #if USE_CPPUNIT
23 #include <cppunit/TestAssert.h>
24 #endif
25 
26 #include <rtl/math.hxx>
27 
28 namespace {
29 
30 struct tolerance
31 {
32  ~tolerance()
33  {
34  xmlFree(elementName);
35  xmlFree(attribName);
36  }
37 
38  tolerance()
39  : elementName(nullptr)
40  , attribName(nullptr)
41  , relative(false)
42  , value(0.0)
43  {
44  }
45 
46  tolerance(const tolerance& tol)
47  {
48  elementName = xmlStrdup(tol.elementName);
49  attribName = xmlStrdup(tol.attribName);
50  relative = tol.relative;
51  value = tol.value;
52  }
53 
54  xmlChar* elementName;
55  xmlChar* attribName;
56  bool relative;
57  double value;
58  bool operator<(const tolerance& rTol) const
59  {
60  int cmp = xmlStrcmp(elementName, rTol.elementName);
61  if(cmp == 0)
62  {
63  cmp = xmlStrcmp(attribName, rTol.attribName);
64  }
65 
66  return cmp < 0;
67  }
68 };
69 
70 class XMLDiff
71 {
72 public:
73  XMLDiff(const char* pFileName, const char* pContent, int size, const char* pToleranceFileName);
74  ~XMLDiff();
75 
76  bool compare();
77 private:
78  typedef std::set<tolerance> ToleranceContainer;
79 
80  void loadToleranceFile(xmlDocPtr xmlTolerance);
81  bool compareAttributes(xmlNodePtr node1, xmlNodePtr node2);
82  bool compareElements(xmlNode* node1, xmlNode* node2);
83 
85  void cppunitAssertEqual(const xmlChar *expected, const xmlChar *found);
86 
88  void cppunitAssertEqualDouble(const xmlNodePtr node, const xmlAttrPtr attr, double expected, double found, double delta);
89 
90  ToleranceContainer toleranceContainer;
91  xmlDocPtr xmlFile1;
92  xmlDocPtr xmlFile2;
93  std::string fileName;
94 };
95 
96 }
97 
98 XMLDiff::XMLDiff( const char* pFileName, const char* pContent, int size, const char* pToleranceFile)
99  : xmlFile1(xmlParseFile(pFileName))
100  , xmlFile2(xmlParseMemory(pContent, size))
101  , fileName(pFileName)
102 {
103  if(pToleranceFile)
104  {
105  xmlDocPtr xmlToleranceFile = xmlParseFile(pToleranceFile);
106  loadToleranceFile(xmlToleranceFile);
107  xmlFreeDoc(xmlToleranceFile);
108  }
109 }
110 
111 XMLDiff::~XMLDiff()
112 {
113  xmlFreeDoc(xmlFile1);
114  xmlFreeDoc(xmlFile2);
115 }
116 
117 namespace {
118 
119 void readAttributesForTolerance(xmlNodePtr node, tolerance& tol)
120 {
121  xmlChar* elementName = xmlGetProp(node, BAD_CAST("elementName"));
122  tol.elementName = elementName;
123 
124  xmlChar* attribName = xmlGetProp(node, BAD_CAST("attribName"));
125  tol.attribName = attribName;
126 
127  xmlChar* value = xmlGetProp(node, BAD_CAST("value"));
128  double val = xmlXPathCastStringToNumber(value);
129  xmlFree(value);
130  tol.value = val;
131 
132  xmlChar* relative = xmlGetProp(node, BAD_CAST("relative"));
133  bool rel = false;
134  if(xmlStrEqual(relative, BAD_CAST("true")))
135  rel = true;
136  xmlFree(relative);
137  tol.relative = rel;
138 }
139 
140 }
141 
142 void XMLDiff::loadToleranceFile(xmlDocPtr xmlToleranceFile)
143 {
144  xmlNodePtr root = xmlDocGetRootElement(xmlToleranceFile);
145 #if USE_CPPUNIT
146  CPPUNIT_ASSERT_MESSAGE("did not find correct tolerance file", xmlStrEqual( root->name, BAD_CAST("tolerances") ));
147 #else
148  if(!xmlStrEqual( root->name, BAD_CAST("tolerances") ))
149  {
150  assert(false);
151  return;
152  }
153 #endif
154  xmlNodePtr child = nullptr;
155  for (child = root->children; child != nullptr; child = child->next)
156  {
157  // assume a valid xml file
158  if(child->type != XML_ELEMENT_NODE)
159  continue;
160 
161  assert(xmlStrEqual(child->name, BAD_CAST("tolerance")));
162 
163  tolerance tol;
164  readAttributesForTolerance(child, tol);
165  toleranceContainer.insert(tol);
166  }
167 }
168 
169 bool XMLDiff::compare()
170 {
171  xmlNode* root1 = xmlDocGetRootElement(xmlFile1);
172  xmlNode* root2 = xmlDocGetRootElement(xmlFile2);
173 
174 #if USE_CPPUNIT
175  CPPUNIT_ASSERT(root1);
176  CPPUNIT_ASSERT(root2);
177  cppunitAssertEqual(root1->name, root2->name);
178 #else
179  if (!root1 || !root2)
180  return false;
181  if(!xmlStrEqual(root1->name, root2->name))
182  return false;
183 #endif
184  return compareElements(root1, root2);
185 }
186 
187 namespace {
188 
189 bool checkForEmptyChildren(xmlNodePtr node)
190 {
191  if(!node)
192  return true;
193 
194  for(; node != nullptr; node = node->next)
195  {
196  if (node->type == XML_ELEMENT_NODE)
197  return false;
198  }
199  return true;
200 }
201 
202 }
203 
204 bool XMLDiff::compareElements(xmlNode* node1, xmlNode* node2)
205 {
206 #if USE_CPPUNIT
207  cppunitAssertEqual(node1->name, node2->name);
208 #else
209  if (!xmlStrEqual( node1->name, node2->name ))
210  return false;
211 #endif
212 
213  //compare attributes
214  bool sameAttribs = compareAttributes(node1, node2);
215 #if USE_CPPUNIT
216  CPPUNIT_ASSERT(sameAttribs);
217 #else
218  if (!sameAttribs)
219  return false;
220 #endif
221 
222  // compare children
223  xmlNode* child2 = nullptr;
224  xmlNode* child1 = nullptr;
225  for(child1 = node1->children, child2 = node2->children; child1 != nullptr && child2 != nullptr; child1 = child1->next, child2 = child2->next)
226  {
227  if (child1->type == XML_ELEMENT_NODE)
228  {
229  bool bCompare = compareElements(child1, child2);
230  if(!bCompare)
231  {
232  return false;
233  }
234  }
235  }
236 
237 #if USE_CPPUNIT
238  CPPUNIT_ASSERT(checkForEmptyChildren(child1));
239  CPPUNIT_ASSERT(checkForEmptyChildren(child2));
240 #else
241  if(!checkForEmptyChildren(child1) || !checkForEmptyChildren(child2))
242  return false;
243 #endif
244 
245  return true;
246 }
247 
248 void XMLDiff::cppunitAssertEqual(const xmlChar *expected, const xmlChar *found)
249 {
250 #if USE_CPPUNIT
251  std::stringstream stringStream;
252  stringStream << "Reference: " << fileName << "\n- Expected: " << reinterpret_cast<const char*>(expected) << "\n- Found: " << reinterpret_cast<const char*>(found);
253 
254  CPPUNIT_ASSERT_MESSAGE(stringStream.str(), xmlStrEqual(expected, found));
255 #endif
256 }
257 
258 void XMLDiff::cppunitAssertEqualDouble(const xmlNodePtr node, const xmlAttrPtr attr, double expected, double found, double delta)
259 {
260 #if USE_CPPUNIT
261  xmlChar * path = xmlGetNodePath(node);
262  std::stringstream stringStream;
263  stringStream << "Reference: " << fileName << "\n- Node: " << reinterpret_cast<const char*>(path) << "\n- Attr: " << reinterpret_cast<const char*>(attr->name);
264  xmlFree(path);
265 
266  CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(stringStream.str(), expected, found, delta);
267 #endif
268 }
269 
270 namespace {
271 
272 bool compareValuesWithTolerance(double val1, double val2, double tolerance, bool relative)
273 {
274  if(relative)
275  {
276  return (val1/tolerance) <= val2 && val2 <= (val1*tolerance);
277  }
278  else
279  {
280  return (val1 - tolerance) <= val2 && val2 <= (val1 + tolerance);
281  }
282 }
283 
284 }
285 
286 bool XMLDiff::compareAttributes(xmlNodePtr node1, xmlNodePtr node2)
287 {
288  xmlAttrPtr attr1 = nullptr;
289  xmlAttrPtr attr2 = nullptr;
290  for(attr1 = node1->properties, attr2 = node2->properties; attr1 != nullptr && attr2 != nullptr; attr1 = attr1->next, attr2 = attr2->next)
291  {
292 #if USE_CPPUNIT
293  cppunitAssertEqual(attr1->name, attr2->name);
294 #else
295  if (!xmlStrEqual( attr1->name, attr2->name ))
296  return false;
297 #endif
298 
299  xmlChar* val1 = xmlGetProp(node1, attr1->name);
300  xmlChar* val2 = xmlGetProp(node2, attr2->name);
301 
302  double dVal1 = xmlXPathCastStringToNumber(val1);
303  double dVal2 = xmlXPathCastStringToNumber(val2);
304 
305  if(!std::isnan(dVal1) || !std::isnan(dVal2))
306  {
307  //compare by value and respect tolerance
308  tolerance tol;
309  tol.elementName = xmlStrdup(node1->name);
310  tol.attribName = xmlStrdup(attr1->name);
311  ToleranceContainer::iterator itr = toleranceContainer.find( tol );
312  bool useTolerance = false;
313  if (itr != toleranceContainer.end())
314  {
315  useTolerance = true;
316  }
317 
318  if (useTolerance)
319  {
320  bool valInTolerance = compareValuesWithTolerance(dVal1, dVal2, itr->value, itr->relative);
321 #if USE_CPPUNIT
322  std::stringstream stringStream("Expected Value: ");
323  stringStream << dVal1 << "; Found Value: " << dVal2 << "; Tolerance: " << itr->value;
324  stringStream << "; Relative: " << itr->relative;
325  CPPUNIT_ASSERT_MESSAGE(stringStream.str(), valInTolerance);
326 #else
327  if (!valInTolerance)
328  return false;
329 #endif
330  }
331  else
332  {
333 #if USE_CPPUNIT
334  cppunitAssertEqualDouble(node1, attr1, dVal1, dVal2, 1e-08);
335 #else
336  if (dVal1 != dVal2)
337  return false;
338 #endif
339  }
340  }
341  else
342  {
343 
344 #if USE_CPPUNIT
345  cppunitAssertEqual(val1, val2);
346 #else
347  if(!xmlStrEqual( val1, val2 ))
348  return false;
349 #endif
350  }
351 
352  xmlFree(val1);
353  xmlFree(val2);
354  }
355 
356  // unequal number of attributes
357 #ifdef CPPUNIT_ASSERT
358  if (attr1 || attr2)
359  {
360  std::stringstream failStream;
361  failStream << "Unequal number of attributes in ";
362  // print chain from document root
363  std::vector<std::string> parents;
364  auto n = node1;
365  while (n)
366  {
367  if (n->name)
368  parents.push_back(std::string(reinterpret_cast<const char *>(n->name)));
369  n = n->parent;
370  }
371  bool first = true;
372  for (auto it = parents.rbegin(); it != parents.rend(); ++it)
373  {
374  if (!first)
375  failStream << "->";
376  first = false;
377  failStream << *it;
378  }
379  failStream << " Attr1: ";
380  attr1 = node1->properties;
381  while (attr1 != nullptr)
382  {
383  xmlChar* val1 = xmlGetProp(node1, attr1->name);
384  failStream << BAD_CAST(attr1->name) << "=" << BAD_CAST(val1) << ", ";
385  xmlFree(val1);
386  attr1 = attr1->next;
387  }
388 
389  failStream << " Attr2: ";
390  attr2 = node2->properties;
391  while (attr2 != nullptr)
392  {
393  xmlChar* val2 = xmlGetProp(node2, attr2->name);
394  failStream << BAD_CAST(attr2->name) << "=" << BAD_CAST(val2) << ", ";
395  xmlFree(val2);
396  attr2 = attr2->next;
397  }
398  CPPUNIT_ASSERT_MESSAGE(failStream.str(), false);
399  }
400 #else
401  if (attr1 || attr2)
402  return false;
403 #endif
404 
405  return true;
406 }
407 
408 
409 bool
410 doXMLDiff(char const*const pFileName, char const*const pContent, int const size,
411  char const*const pToleranceFileName)
412 {
413  XMLDiff aDiff(pFileName, pContent, size, pToleranceFileName);
414  return aDiff.compare();
415 }
416 
417 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
sal_Int64 n
bool operator<(const wwFont &r1, const wwFont &r2)
size
Any value
constexpr OUStringLiteral first
bool doXMLDiff(char const *const pFileName, char const *const pContent, int const size, char const *const pToleranceFileName)
Definition: diff.cxx:410
SbxDecimal::CmpResult compare(SAL_UNUSED_PARAMETER const SbxDecimal &, SAL_UNUSED_PARAMETER const SbxDecimal &)