3 * This source code is part of
7 * GROningen MAchine for Chemical Simulations
9 * Written by David van der Spoel, Erik Lindahl, Berk Hess, and others.
10 * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
11 * Copyright (c) 2001-2009, The GROMACS development team,
12 * check out http://www.gromacs.org for more information.
14 * This program is free software; you can redistribute it and/or
15 * modify it under the terms of the GNU General Public License
16 * as published by the Free Software Foundation; either version 2
17 * of the License, or (at your option) any later version.
19 * If you want to redistribute modifications, please consider that
20 * scientific software is very special. Version control is crucial -
21 * bugs must be traceable. We will be happy to consider code for
22 * inclusion in the official distribution, but derived work must not
23 * be called official GROMACS. Details are found in the README & COPYING
24 * files - if they are missing, get the official version at www.gromacs.org.
26 * To help us fund GROMACS development, we humbly ask that you cite
27 * the papers on the package - you can find them in the top README file.
29 * For more info, check our website at http://www.gromacs.org
33 * Implements classes and functions from refdata.h.
35 * \author Teemu Murtola <teemu.murtola@cbr.su.se>
36 * \ingroup module_testutils
47 #include <gtest/gtest.h>
48 #include <libxml/parser.h>
49 #include <libxml/xmlmemory.h>
51 #include "gromacs/utility/exceptions.h"
52 #include "gromacs/utility/gmxassert.h"
53 #include "gromacs/utility/path.h"
54 #include "gromacs/utility/stringutil.h"
55 #include "testutils/testexceptions.h"
56 #include "testutils/testfilemanager.h"
62 * Global test environment for freeing up libxml2 internal buffers.
64 class TestReferenceDataEnvironment : public ::testing::Environment
67 //! Frees internal buffers allocated by libxml2.
68 virtual void TearDown()
74 //! Global reference data mode set with gmx::test::setReferenceDataMode().
75 gmx::test::ReferenceDataMode g_referenceDataMode = gmx::test::erefdataCompare;
84 ReferenceDataMode getReferenceDataMode()
86 return g_referenceDataMode;
89 void setReferenceDataMode(ReferenceDataMode mode)
91 g_referenceDataMode = mode;
94 std::string getReferenceDataPath()
96 return TestFileManager::getInputFilePath("refdata");
99 void initReferenceData(int *argc, char **argv)
103 for (i = newi = 1; i < *argc; ++i, ++newi)
105 argv[newi] = argv[i];
106 if (!std::strcmp(argv[i], "--create-ref-data"))
108 setReferenceDataMode(erefdataCreateMissing);
111 else if (!std::strcmp(argv[i], "--update-ref-data"))
113 setReferenceDataMode(erefdataUpdateAll);
120 ::testing::AddGlobalTestEnvironment(new TestReferenceDataEnvironment);
122 catch (const std::bad_alloc &)
124 std::fprintf(stderr, "Out of memory\n");
129 /********************************************************************
130 * TestReferenceData::Impl
134 * Private implementation class for TestReferenceData.
136 * \ingroup module_testutils
138 class TestReferenceData::Impl
141 //! String constant for output XML version string.
142 static const xmlChar * const cXmlVersion;
143 //! String constant for XML stylesheet processing instruction name.
144 static const xmlChar * const cXmlStyleSheetNodeName;
145 //! String constant for XML stylesheet reference.
146 static const xmlChar * const cXmlStyleSheetContent;
147 //! String constant for naming the root XML element.
148 static const xmlChar * const cRootNodeName;
150 //! Initializes a checker in the given mode.
151 explicit Impl(ReferenceDataMode mode);
154 //! Full path of the reference data file.
155 std::string fullFilename_;
157 * XML document for the reference data.
159 * May be NULL if there was an I/O error in initialization.
163 * Whether the reference data is being written (true) or compared
168 * Whether any reference checkers have been created for this data.
173 const xmlChar * const TestReferenceData::Impl::cXmlVersion =
174 (const xmlChar *)"1.0";
175 const xmlChar * const TestReferenceData::Impl::cXmlStyleSheetNodeName =
176 (const xmlChar *)"xml-stylesheet";
177 const xmlChar * const TestReferenceData::Impl::cXmlStyleSheetContent =
178 (const xmlChar *)"type=\"text/xsl\" href=\"referencedata.xsl\"";
179 const xmlChar * const TestReferenceData::Impl::cRootNodeName =
180 (const xmlChar *)"ReferenceData";
183 TestReferenceData::Impl::Impl(ReferenceDataMode mode)
184 : refDoc_(NULL), bWrite_(false), bInUse_(false)
186 std::string dirname = getReferenceDataPath();
187 std::string filename = TestFileManager::getTestSpecificFileName(".xml");
188 fullFilename_ = Path::join(dirname, filename);
191 if (mode != erefdataUpdateAll)
193 FILE *fp = std::fopen(fullFilename_.c_str(), "r");
199 else if (mode == erefdataCompare)
207 // TODO: Error checking
208 refDoc_ = xmlNewDoc(cXmlVersion);
209 xmlNodePtr rootNode = xmlNewDocNode(refDoc_, NULL, cRootNodeName, NULL);
210 xmlDocSetRootElement(refDoc_, rootNode);
211 xmlNodePtr xslNode = xmlNewDocPI(refDoc_, cXmlStyleSheetNodeName,
212 cXmlStyleSheetContent);
213 xmlAddPrevSibling(rootNode, xslNode);
217 refDoc_ = xmlParseFile(fullFilename_.c_str());
220 GMX_THROW(TestException("Reference data not parsed successfully: " + fullFilename_));
222 xmlNodePtr rootNode = xmlDocGetRootElement(refDoc_);
223 if (rootNode == NULL)
226 GMX_THROW(TestException("Reference data is empty: " + fullFilename_));
228 if (xmlStrcmp(rootNode->name, cRootNodeName) != 0)
231 GMX_THROW(TestException("Invalid root node type in " + fullFilename_));
237 TestReferenceData::Impl::~Impl()
239 if (bWrite_ && bInUse_ && refDoc_ != NULL)
241 std::string dirname = getReferenceDataPath();
242 if (!Directory::exists(dirname))
244 if (Directory::create(dirname) != 0)
246 ADD_FAILURE() << "Creation of reference data directory failed for " << dirname;
249 if (xmlSaveFormatFile(fullFilename_.c_str(), refDoc_, 1) == -1)
251 ADD_FAILURE() << "Saving reference data failed for " + fullFilename_;
261 /********************************************************************
262 * TestReferenceChecker::Impl
266 * Private implementation class for TestReferenceChecker.
268 * \ingroup module_testutils
270 class TestReferenceChecker::Impl
273 //! String constant for naming XML elements for boolean values.
274 static const xmlChar * const cBooleanNodeName;
275 //! String constant for naming XML elements for string values.
276 static const xmlChar * const cStringNodeName;
277 //! String constant for naming XML elements for integer values.
278 static const xmlChar * const cIntegerNodeName;
279 //! String constant for naming XML elements for floating-point values.
280 static const xmlChar * const cRealNodeName;
281 //! String constant for naming XML attribute for value identifiers.
282 static const xmlChar * const cIdAttrName;
283 //! String constant for naming compounds for vectors.
284 static const char * const cVectorType;
285 //! String constant for naming compounds for sequences.
286 static const char * const cSequenceType;
287 //! String constant for value identifier for sequence length.
288 static const char * const cSequenceLengthName;
290 //! Creates a checker that does nothing.
291 explicit Impl(bool bWrite);
292 //! Creates a checker with a given root node.
293 Impl(const std::string &path, xmlNodePtr rootNode, bool bWrite);
295 //! Returns a string for SCOPED_TRACE() for checking element \p id.
296 std::string traceString(const char *id) const;
297 //! Returns the path of this checker with \p id appended.
298 std::string appendPath(const char *id) const;
301 * Finds a reference data node.
303 * \param[in] name Type of node to find (can be NULL, in which case
304 * any type is matched).
305 * \param[in] id Unique identifier of the node (can be NULL, in
306 * which case the next node without an id is matched).
307 * \returns Matching node, or NULL if no matching node found.
309 * Searches for a node in the reference data that matches the given
310 * \p name and \p id. Searching starts from the node that follows the
311 * previously matched node (relevant for performance, and if there are
312 * duplicate ids or nodes without ids). Note that the match pointer is
313 * not updated by this method.
315 xmlNodePtr findNode(const xmlChar *name, const char *id) const;
317 * Finds/creates a reference data node to match against.
319 * \param[in] name Type of node to find.
320 * \param[in] id Unique identifier of the node (can be NULL, in
321 * which case the next node without an id is matched).
322 * \returns Matching node, or NULL if no matching node found
323 * (NULL is never returned in write mode).
324 * \throws TestException if node creation fails in write mode.
326 * Finds a node using findNode() and updates the match pointer is a
327 * match is found. If a match is not found, the method returns NULL in
328 * read mode and creates a new node in write mode. If the creation
329 * fails in write mode, throws.
331 xmlNodePtr findOrCreateNode(const xmlChar *name, const char *id);
333 * Helper method for checking a reference data value.
335 * \param[in] name Type of node to find.
336 * \param[in] id Unique identifier of the node (can be NULL, in
337 * which case the next node without an id is matched).
338 * \param[in] value String value of the value to be compared.
339 * \param[out] bFound true if a matchin value was found.
340 * \returns String value for the reference value.
341 * \throws TestException if node creation fails in write mode.
343 * Performs common tasks in checking a reference value:
344 * finding/creating the correct XML node and reading/writing its string
345 * value. Caller is responsible for converting the value to and from
346 * string where necessary and performing the actual comparison.
348 * In read mode, if a value is not found, adds a Google Test failure
349 * and returns an empty string. If the reference value is found,
350 * returns it (\p value is not used in this case).
352 * In write mode, creates the node if it is not found, sets its value
353 * as \p value and returns \p value.
355 std::string processItem(const xmlChar *name, const char *id,
356 const char *value, bool *bFound);
357 //! Convenience wrapper that takes a std::string.
358 std::string processItem(const xmlChar *name, const char *id,
359 const std::string &value, bool *bFound);
361 * Whether the checker should ignore all validation calls.
363 * This is used to ignore any calls within compounds for which
364 * reference data could not be found, such that only one error is
365 * issued for the missing compound, instead of every individual value.
367 bool shouldIgnore() const;
370 * Human-readable path to the root node of this checker.
372 * For the root checker, this will be "/", and for each compound, the
373 * id of the compound is added. Used for reporting comparison
378 * Current node under which reference data is searched.
380 * Points to either the root of TestReferenceData::Impl::refDoc_, or to
383 * Can be NULL, in which case this checker does nothing (doesn't even
384 * report errors, see shouldIgnore()).
386 xmlNodePtr currNode_;
388 * Points to a child of \a currNode_ where the next search should start.
390 * On initialization, points to the first child of \a currNode_. After
391 * every check, is updated to point to the node following the one
392 * found, with possible wrapping.
394 * Is NULL if and only if \a currNode_ contains no children.
395 * Otherwise, always points to a direct child of \a currNode_.
397 xmlNodePtr nextSearchNode_;
399 * Whether the reference data is being written (true) or compared
404 * Current number of unnamed elements in a sequence.
406 * It is the index of the next added unnamed element.
411 const xmlChar * const TestReferenceChecker::Impl::cBooleanNodeName =
412 (const xmlChar *)"Bool";
413 const xmlChar * const TestReferenceChecker::Impl::cStringNodeName =
414 (const xmlChar *)"String";
415 const xmlChar * const TestReferenceChecker::Impl::cIntegerNodeName =
416 (const xmlChar *)"Int";
417 const xmlChar * const TestReferenceChecker::Impl::cRealNodeName =
418 (const xmlChar *)"Real";
419 const xmlChar * const TestReferenceChecker::Impl::cIdAttrName =
420 (const xmlChar *)"Name";
421 const char * const TestReferenceChecker::Impl::cVectorType =
423 const char * const TestReferenceChecker::Impl::cSequenceType =
425 const char * const TestReferenceChecker::Impl::cSequenceLengthName =
429 TestReferenceChecker::Impl::Impl(bool bWrite)
430 : currNode_(NULL), nextSearchNode_(NULL), bWrite_(bWrite), seqIndex_(0)
435 TestReferenceChecker::Impl::Impl(const std::string &path, xmlNodePtr rootNode,
437 : path_(path + "/"), currNode_(rootNode),
438 nextSearchNode_(rootNode->xmlChildrenNode),
439 bWrite_(bWrite), seqIndex_(0)
445 TestReferenceChecker::Impl::traceString(const char *id) const
447 return "Checking '" + appendPath(id) + "'";
452 TestReferenceChecker::Impl::appendPath(const char *id) const
454 std::string printId = (id != NULL) ? id : formatString("[%d]", seqIndex_);
455 return path_ + printId;
460 TestReferenceChecker::Impl::findNode(const xmlChar *name, const char *id) const
462 const xmlChar *xmlId = reinterpret_cast<const xmlChar *>(id);
463 xmlNodePtr node = nextSearchNode_;
470 if (name == NULL || xmlStrcmp(node->name, name) == 0)
472 xmlChar *refId = xmlGetProp(node, cIdAttrName);
473 if (xmlId == NULL && refId == NULL)
479 if (xmlId != NULL && xmlStrcmp(refId, xmlId) == 0)
488 if (node == NULL && nextSearchNode_ != currNode_->xmlChildrenNode)
490 node = currNode_->xmlChildrenNode;
493 while (node != NULL && node != nextSearchNode_);
499 TestReferenceChecker::Impl::findOrCreateNode(const xmlChar *name, const char *id)
501 xmlNodePtr node = findNode(name, id);
506 node = xmlNewTextChild(currNode_, NULL, name, NULL);
507 if (node != NULL && id != NULL)
509 const xmlChar *xmlId = reinterpret_cast<const xmlChar *>(id);
510 xmlAttrPtr prop = xmlNewProp(node, cIdAttrName, xmlId);
519 GMX_THROW(TestException("XML node creation failed"));
529 nextSearchNode_ = node->next;
530 if (nextSearchNode_ == NULL)
532 nextSearchNode_ = currNode_->xmlChildrenNode;
537 GMX_RELEASE_ASSERT(!bWrite_, "Node creation failed without exception");
538 ADD_FAILURE() << "Reference data item not found";
540 seqIndex_ = (id == NULL) ? seqIndex_+1 : 0;
547 TestReferenceChecker::Impl::processItem(const xmlChar *name, const char *id,
548 const char *value, bool *bFound)
551 xmlNodePtr node = findOrCreateNode(name, id);
554 return std::string();
559 xmlNodeAddContent(node, reinterpret_cast<const xmlChar *>(value));
560 return std::string(value);
564 xmlChar *refXmlValue = xmlNodeGetContent(node);
565 std::string refValue(reinterpret_cast<const char *>(refXmlValue));
566 xmlFree(refXmlValue);
573 TestReferenceChecker::Impl::processItem(const xmlChar *name, const char *id,
574 const std::string &value, bool *bFound)
576 return processItem(name, id, value.c_str(), bFound);
581 TestReferenceChecker::Impl::shouldIgnore() const
583 return currNode_ == NULL;
587 /********************************************************************
591 TestReferenceData::TestReferenceData()
592 : impl_(new Impl(getReferenceDataMode()))
597 TestReferenceData::TestReferenceData(ReferenceDataMode mode)
598 : impl_(new Impl(mode))
603 TestReferenceData::~TestReferenceData()
608 bool TestReferenceData::isWriteMode() const
610 return impl_->bWrite_;
614 TestReferenceChecker TestReferenceData::rootChecker()
616 if (!isWriteMode() && !impl_->bInUse_ && impl_->refDoc_ == NULL)
618 ADD_FAILURE() << "Reference data file not found: "
619 << impl_->fullFilename_;
621 impl_->bInUse_ = true;
622 if (impl_->refDoc_ == NULL)
624 return TestReferenceChecker(new TestReferenceChecker::Impl(isWriteMode()));
626 xmlNodePtr rootNode = xmlDocGetRootElement(impl_->refDoc_);
627 return TestReferenceChecker(
628 new TestReferenceChecker::Impl("", rootNode, isWriteMode()));
632 /********************************************************************
633 * TestReferenceChecker
636 TestReferenceChecker::TestReferenceChecker(Impl *impl)
642 TestReferenceChecker::TestReferenceChecker(const TestReferenceChecker &other)
643 : impl_(new Impl(*other.impl_))
648 TestReferenceChecker &
649 TestReferenceChecker::operator=(const TestReferenceChecker &other)
651 impl_.reset(new Impl(*other.impl_));
656 TestReferenceChecker::~TestReferenceChecker()
661 bool TestReferenceChecker::isWriteMode() const
663 return impl_->bWrite_;
667 bool TestReferenceChecker::checkPresent(bool bPresent, const char *id)
673 xmlNodePtr node = impl_->findNode(NULL, id);
674 bool bFound = (node != NULL);
675 if (bFound != bPresent)
677 ADD_FAILURE() << "Mismatch while checking reference data item'"
678 << impl_->appendPath(id) << "'\n"
679 << "Expected: " << (bPresent ? "it is present.\n" : "it is absent.\n")
680 << " Actual: " << (bFound ? "it is present." : "it is absent.");
682 if (bFound && bPresent)
684 impl_->nextSearchNode_ = node;
691 TestReferenceChecker TestReferenceChecker::checkCompound(const char *type, const char *id)
693 SCOPED_TRACE(impl_->traceString(id));
694 if (impl_->shouldIgnore())
696 return TestReferenceChecker(new Impl(isWriteMode()));
698 const xmlChar *xmlNodeName = reinterpret_cast<const xmlChar *>(type);
699 xmlNodePtr newNode = impl_->findOrCreateNode(xmlNodeName, id);
702 return TestReferenceChecker(new Impl(isWriteMode()));
704 return TestReferenceChecker(
705 new Impl(impl_->appendPath(id), newNode, isWriteMode()));
709 void TestReferenceChecker::checkBoolean(bool value, const char *id)
711 if (impl_->shouldIgnore())
715 SCOPED_TRACE(impl_->traceString(id));
717 const char *strValue = value ? "true" : "false";
718 std::string refStrValue =
719 impl_->processItem(Impl::cBooleanNodeName, id, strValue, &bFound);
722 EXPECT_EQ(refStrValue, strValue);
727 void TestReferenceChecker::checkString(const char *value, const char *id)
729 if (impl_->shouldIgnore())
733 SCOPED_TRACE(impl_->traceString(id));
735 std::string refStrValue =
736 impl_->processItem(Impl::cStringNodeName, id, value, &bFound);
739 EXPECT_EQ(refStrValue, value);
744 void TestReferenceChecker::checkString(const std::string &value, const char *id)
746 checkString(value.c_str(), id);
750 void TestReferenceChecker::checkStringBlock(const std::string &value,
753 if (impl_->shouldIgnore())
757 SCOPED_TRACE(impl_->traceString(id));
758 xmlNodePtr node = impl_->findOrCreateNode(Impl::cStringNodeName, id);
763 // An extra newline is written in the beginning to make lines align
764 // in the output xml (otherwise, the first line would be off by the length
765 // of the starting CDATA tag).
768 std::string adjustedValue = "\n" + value;
769 const xmlChar *xmlValue
770 = reinterpret_cast<const xmlChar *>(adjustedValue.c_str());
771 // TODO: Figure out if \r and \r\n can be handled without them changing
772 // to \n in the roundtrip
774 = xmlNewCDataBlock(node->doc, xmlValue,
775 static_cast<int>(adjustedValue.length()));
776 xmlAddChild(node, cdata);
780 xmlNodePtr cdata = node->children;
781 while (cdata != NULL && cdata->type != XML_CDATA_SECTION_NODE)
787 ADD_FAILURE() << "Invalid string block element";
790 xmlChar *refXmlValue = xmlNodeGetContent(cdata);
791 if (refXmlValue[0] != '\n')
793 ADD_FAILURE() << "Invalid string block element";
794 xmlFree(refXmlValue);
797 std::string refValue(reinterpret_cast<const char *>(refXmlValue + 1));
798 xmlFree(refXmlValue);
799 EXPECT_EQ(refValue, value);
804 void TestReferenceChecker::checkInteger(int value, const char *id)
806 if (impl_->shouldIgnore())
810 SCOPED_TRACE(impl_->traceString(id));
812 std::string strValue = formatString("%d", value);
813 std::string refStrValue =
814 impl_->processItem(Impl::cIntegerNodeName, id, strValue, &bFound);
817 EXPECT_EQ(refStrValue, strValue);
822 void TestReferenceChecker::checkDouble(double value, const char *id)
824 if (impl_->shouldIgnore())
828 SCOPED_TRACE(impl_->traceString(id));
830 std::string strValue = formatString("%f", value);
831 std::string refStrValue =
832 impl_->processItem(Impl::cRealNodeName, id, strValue, &bFound);
836 double refValue = std::strtod(refStrValue.c_str(), &endptr);
837 EXPECT_EQ('\0', *endptr);
838 EXPECT_NEAR(refValue, value, 0.0001);
843 void TestReferenceChecker::checkFloat(float value, const char *id)
845 checkDouble(value, id);
849 void TestReferenceChecker::checkReal(float value, const char *id)
851 checkDouble(value, id);
855 void TestReferenceChecker::checkReal(double value, const char *id)
857 checkDouble(value, id);
861 void TestReferenceChecker::checkVector(const int value[3], const char *id)
863 TestReferenceChecker compound(checkCompound(Impl::cVectorType, id));
864 compound.checkInteger(value[0], "X");
865 compound.checkInteger(value[1], "Y");
866 compound.checkInteger(value[2], "Z");
870 void TestReferenceChecker::checkVector(const float value[3], const char *id)
872 TestReferenceChecker compound(checkCompound(Impl::cVectorType, id));
873 compound.checkReal(value[0], "X");
874 compound.checkReal(value[1], "Y");
875 compound.checkReal(value[2], "Z");
879 void TestReferenceChecker::checkVector(const double value[3], const char *id)
881 TestReferenceChecker compound(checkCompound(Impl::cVectorType, id));
882 compound.checkReal(value[0], "X");
883 compound.checkReal(value[1], "Y");
884 compound.checkReal(value[2], "Z");
889 TestReferenceChecker::checkSequenceCompound(const char *id, size_t length)
891 TestReferenceChecker compound(checkCompound(Impl::cSequenceType, id));
892 compound.checkInteger(static_cast<int>(length), Impl::cSequenceLengthName);