2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2011,2012,2013, by the GROMACS development team, led by
5 * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6 * and including many others, as listed in the AUTHORS file in the
7 * top-level source directory and at http://www.gromacs.org.
9 * GROMACS is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public License
11 * as published by the Free Software Foundation; either version 2.1
12 * of the License, or (at your option) any later version.
14 * GROMACS is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with GROMACS; if not, see
21 * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 * If you want to redistribute modifications to GROMACS, please
25 * consider that scientific software is very special. Version
26 * control is crucial - bugs must be traceable. We will be happy to
27 * consider code for inclusion in the official distribution, but
28 * derived work must not be called official GROMACS. Details are found
29 * in the README & COPYING files - if they are missing, get the
30 * official version at http://www.gromacs.org.
32 * To help us fund GROMACS development, we humbly ask that you cite
33 * the research papers on the package. Check out http://www.gromacs.org.
37 * Implements classes and functions from refdata.h.
39 * \author Teemu Murtola <teemu.murtola@gmail.com>
40 * \ingroup module_testutils
49 #include <gtest/gtest.h>
50 #include <libxml/parser.h>
51 #include <libxml/xmlmemory.h>
53 #include "gromacs/options/basicoptions.h"
54 #include "gromacs/options/options.h"
55 #include "gromacs/utility/exceptions.h"
56 #include "gromacs/utility/gmxassert.h"
57 #include "gromacs/utility/path.h"
58 #include "gromacs/utility/stringutil.h"
59 #include "testutils/testexceptions.h"
60 #include "testutils/testfilemanager.h"
66 * Global test environment for freeing up libxml2 internal buffers.
68 class TestReferenceDataEnvironment : public ::testing::Environment
71 //! Frees internal buffers allocated by libxml2.
72 virtual void TearDown()
78 //! Global reference data mode set with gmx::test::setReferenceDataMode().
79 // TODO: Make this a real enum; requires solving a TODO in StringOption.
80 int g_referenceDataMode = gmx::test::erefdataCompare;
89 ReferenceDataMode getReferenceDataMode()
91 return static_cast<ReferenceDataMode>(g_referenceDataMode);
94 void setReferenceDataMode(ReferenceDataMode mode)
96 g_referenceDataMode = mode;
99 std::string getReferenceDataPath()
101 return TestFileManager::getInputFilePath("refdata");
104 void initReferenceData(Options *options)
106 // Needs to correspond to the enum order in refdata.h.
107 const char *const refDataEnum[] = { "check", "create", "update" };
109 StringOption("ref-data").enumValue(refDataEnum)
111 .storeEnumIndex(&g_referenceDataMode)
112 .description("Operation mode for tests that use reference data"));
113 ::testing::AddGlobalTestEnvironment(new TestReferenceDataEnvironment);
116 /********************************************************************
117 * TestReferenceData::Impl
121 * Private implementation class for TestReferenceData.
123 * \ingroup module_testutils
125 class TestReferenceData::Impl
128 //! String constant for output XML version string.
129 static const xmlChar * const cXmlVersion;
130 //! String constant for XML stylesheet processing instruction name.
131 static const xmlChar * const cXmlStyleSheetNodeName;
132 //! String constant for XML stylesheet reference.
133 static const xmlChar * const cXmlStyleSheetContent;
134 //! String constant for naming the root XML element.
135 static const xmlChar * const cRootNodeName;
137 //! Initializes a checker in the given mode.
138 explicit Impl(ReferenceDataMode mode);
141 //! Full path of the reference data file.
142 std::string fullFilename_;
144 * XML document for the reference data.
146 * May be NULL if there was an I/O error in initialization.
150 * Whether the reference data is being written (true) or compared
155 * Whether any reference checkers have been created for this data.
160 const xmlChar * const TestReferenceData::Impl::cXmlVersion =
161 (const xmlChar *)"1.0";
162 const xmlChar * const TestReferenceData::Impl::cXmlStyleSheetNodeName =
163 (const xmlChar *)"xml-stylesheet";
164 const xmlChar * const TestReferenceData::Impl::cXmlStyleSheetContent =
165 (const xmlChar *)"type=\"text/xsl\" href=\"referencedata.xsl\"";
166 const xmlChar * const TestReferenceData::Impl::cRootNodeName =
167 (const xmlChar *)"ReferenceData";
170 TestReferenceData::Impl::Impl(ReferenceDataMode mode)
171 : refDoc_(NULL), bWrite_(false), bInUse_(false)
173 std::string dirname = getReferenceDataPath();
174 std::string filename = TestFileManager::getTestSpecificFileName(".xml");
175 fullFilename_ = Path::join(dirname, filename);
178 if (mode != erefdataUpdateAll)
180 FILE *fp = std::fopen(fullFilename_.c_str(), "r");
186 else if (mode == erefdataCompare)
194 // TODO: Error checking
195 refDoc_ = xmlNewDoc(cXmlVersion);
196 xmlNodePtr rootNode = xmlNewDocNode(refDoc_, NULL, cRootNodeName, NULL);
197 xmlDocSetRootElement(refDoc_, rootNode);
198 xmlNodePtr xslNode = xmlNewDocPI(refDoc_, cXmlStyleSheetNodeName,
199 cXmlStyleSheetContent);
200 xmlAddPrevSibling(rootNode, xslNode);
204 refDoc_ = xmlParseFile(fullFilename_.c_str());
207 GMX_THROW(TestException("Reference data not parsed successfully: " + fullFilename_));
209 xmlNodePtr rootNode = xmlDocGetRootElement(refDoc_);
210 if (rootNode == NULL)
213 GMX_THROW(TestException("Reference data is empty: " + fullFilename_));
215 if (xmlStrcmp(rootNode->name, cRootNodeName) != 0)
218 GMX_THROW(TestException("Invalid root node type in " + fullFilename_));
224 TestReferenceData::Impl::~Impl()
226 if (bWrite_ && bInUse_ && refDoc_ != NULL)
228 std::string dirname = getReferenceDataPath();
229 if (!Directory::exists(dirname))
231 if (Directory::create(dirname) != 0)
233 ADD_FAILURE() << "Creation of reference data directory failed for " << dirname;
236 if (xmlSaveFormatFile(fullFilename_.c_str(), refDoc_, 1) == -1)
238 ADD_FAILURE() << "Saving reference data failed for " + fullFilename_;
248 /********************************************************************
249 * TestReferenceChecker::Impl
253 * Private implementation class for TestReferenceChecker.
255 * \ingroup module_testutils
257 class TestReferenceChecker::Impl
260 //! String constant for naming XML elements for boolean values.
261 static const xmlChar * const cBooleanNodeName;
262 //! String constant for naming XML elements for string values.
263 static const xmlChar * const cStringNodeName;
264 //! String constant for naming XML elements for integer values.
265 static const xmlChar * const cIntegerNodeName;
266 //! String constant for naming XML elements for floating-point values.
267 static const xmlChar * const cRealNodeName;
268 //! String constant for naming XML attribute for value identifiers.
269 static const xmlChar * const cIdAttrName;
270 //! String constant for naming compounds for vectors.
271 static const char * const cVectorType;
272 //! String constant for naming compounds for sequences.
273 static const char * const cSequenceType;
274 //! String constant for value identifier for sequence length.
275 static const char * const cSequenceLengthName;
277 //! Creates a checker that does nothing.
278 explicit Impl(bool bWrite);
279 //! Creates a checker with a given root node.
280 Impl(const std::string &path, xmlNodePtr rootNode, bool bWrite);
282 //! Returns a string for SCOPED_TRACE() for checking element \p id.
283 std::string traceString(const char *id) const;
284 //! Returns the path of this checker with \p id appended.
285 std::string appendPath(const char *id) const;
288 * Finds a reference data node.
290 * \param[in] name Type of node to find (can be NULL, in which case
291 * any type is matched).
292 * \param[in] id Unique identifier of the node (can be NULL, in
293 * which case the next node without an id is matched).
294 * \returns Matching node, or NULL if no matching node found.
296 * Searches for a node in the reference data that matches the given
297 * \p name and \p id. Searching starts from the node that follows the
298 * previously matched node (relevant for performance, and if there are
299 * duplicate ids or nodes without ids). Note that the match pointer is
300 * not updated by this method.
302 xmlNodePtr findNode(const xmlChar *name, const char *id) const;
304 * Finds/creates a reference data node to match against.
306 * \param[in] name Type of node to find.
307 * \param[in] id Unique identifier of the node (can be NULL, in
308 * which case the next node without an id is matched).
309 * \param[out] bFound Whether the node was found (false if the node was
310 * created in write mode).
311 * \returns Matching node, or NULL if no matching node found
312 * (NULL is never returned in write mode).
313 * \throws TestException if node creation fails in write mode.
315 * Finds a node using findNode() and updates the match pointer is a
316 * match is found. If a match is not found, the method returns NULL in
317 * read mode and creates a new node in write mode. If the creation
318 * fails in write mode, throws.
320 xmlNodePtr findOrCreateNode(const xmlChar *name, const char *id,
323 * Helper method for checking a reference data value.
325 * \param[in] name Type of node to find.
326 * \param[in] id Unique identifier of the node (can be NULL, in
327 * which case the next node without an id is matched).
328 * \param[in] value String value of the value to be compared.
329 * \param[out] bFound true if a matchin value was found.
330 * \returns String value for the reference value.
331 * \throws TestException if node creation fails in write mode.
333 * Performs common tasks in checking a reference value:
334 * finding/creating the correct XML node and reading/writing its string
335 * value. Caller is responsible for converting the value to and from
336 * string where necessary and performing the actual comparison.
338 * In read mode, if a value is not found, adds a Google Test failure
339 * and returns an empty string. If the reference value is found,
340 * returns it (\p value is not used in this case).
342 * In write mode, creates the node if it is not found, sets its value
343 * as \p value and returns \p value.
345 std::string processItem(const xmlChar *name, const char *id,
346 const char *value, bool *bFound);
347 //! Convenience wrapper that takes a std::string.
348 std::string processItem(const xmlChar *name, const char *id,
349 const std::string &value, bool *bFound);
351 * Whether the checker should ignore all validation calls.
353 * This is used to ignore any calls within compounds for which
354 * reference data could not be found, such that only one error is
355 * issued for the missing compound, instead of every individual value.
357 bool shouldIgnore() const;
360 * Human-readable path to the root node of this checker.
362 * For the root checker, this will be "/", and for each compound, the
363 * id of the compound is added. Used for reporting comparison
368 * Current node under which reference data is searched.
370 * Points to either the root of TestReferenceData::Impl::refDoc_, or to
373 * Can be NULL, in which case this checker does nothing (doesn't even
374 * report errors, see shouldIgnore()).
376 xmlNodePtr currNode_;
378 * Points to a child of \a currNode_ that was last found.
380 * On initialization, is initialized to NULL. After every check, is
381 * updated to point to the node that was used for the check.
382 * Subsequent checks start the search for the matching node on this
385 * Is NULL if \a currNode_ contains no children or if no checks have
387 * Otherwise, always points to a direct child of \a currNode_.
389 xmlNodePtr prevFoundNode_;
391 * Whether the reference data is being written (true) or compared
396 * Current number of unnamed elements in a sequence.
398 * It is the index of the next added unnamed element.
403 const xmlChar * const TestReferenceChecker::Impl::cBooleanNodeName =
404 (const xmlChar *)"Bool";
405 const xmlChar * const TestReferenceChecker::Impl::cStringNodeName =
406 (const xmlChar *)"String";
407 const xmlChar * const TestReferenceChecker::Impl::cIntegerNodeName =
408 (const xmlChar *)"Int";
409 const xmlChar * const TestReferenceChecker::Impl::cRealNodeName =
410 (const xmlChar *)"Real";
411 const xmlChar * const TestReferenceChecker::Impl::cIdAttrName =
412 (const xmlChar *)"Name";
413 const char * const TestReferenceChecker::Impl::cVectorType =
415 const char * const TestReferenceChecker::Impl::cSequenceType =
417 const char * const TestReferenceChecker::Impl::cSequenceLengthName =
421 TestReferenceChecker::Impl::Impl(bool bWrite)
422 : currNode_(NULL), prevFoundNode_(NULL), bWrite_(bWrite), seqIndex_(0)
427 TestReferenceChecker::Impl::Impl(const std::string &path, xmlNodePtr rootNode,
429 : path_(path + "/"), currNode_(rootNode), prevFoundNode_(NULL),
430 bWrite_(bWrite), seqIndex_(0)
436 TestReferenceChecker::Impl::traceString(const char *id) const
438 return "Checking '" + appendPath(id) + "'";
443 TestReferenceChecker::Impl::appendPath(const char *id) const
445 std::string printId = (id != NULL) ? id : formatString("[%d]", seqIndex_);
446 return path_ + printId;
451 TestReferenceChecker::Impl::findNode(const xmlChar *name, const char *id) const
453 if (currNode_ == NULL || currNode_->children == NULL)
457 const xmlChar *xmlId = reinterpret_cast<const xmlChar *>(id);
458 xmlNodePtr node = prevFoundNode_;
464 xmlChar *refId = xmlGetProp(node, cIdAttrName);
467 if (name == NULL || xmlStrcmp(node->name, name) == 0)
485 node = currNode_->children;
490 if (name == NULL || xmlStrcmp(node->name, name) == 0)
492 xmlChar *refId = xmlGetProp(node, cIdAttrName);
493 if (xmlId == NULL && refId == NULL)
499 if (xmlId != NULL && xmlStrcmp(refId, xmlId) == 0)
508 if (bWrap && node == NULL)
510 node = currNode_->children;
513 while (node != NULL && node != prevFoundNode_);
519 TestReferenceChecker::Impl::findOrCreateNode(const xmlChar *name,
524 xmlNodePtr node = findNode(name, id);
528 prevFoundNode_ = node;
534 node = xmlNewTextChild(currNode_, NULL, name, NULL);
535 if (node != NULL && id != NULL)
537 const xmlChar *xmlId = reinterpret_cast<const xmlChar *>(id);
538 xmlAttrPtr prop = xmlNewProp(node, cIdAttrName, xmlId);
547 GMX_THROW(TestException("XML node creation failed"));
549 prevFoundNode_ = node;
553 ADD_FAILURE() << "Reference data item not found";
556 seqIndex_ = (id == NULL) ? seqIndex_+1 : 0;
563 TestReferenceChecker::Impl::processItem(const xmlChar *name, const char *id,
564 const char *value, bool *bFound)
566 xmlNodePtr node = findOrCreateNode(name, id, bFound);
569 return std::string();
571 if (bWrite_ && !*bFound)
573 xmlNodeAddContent(node, reinterpret_cast<const xmlChar *>(value));
575 return std::string(value);
579 xmlChar *refXmlValue = xmlNodeGetContent(node);
580 std::string refValue(reinterpret_cast<const char *>(refXmlValue));
581 xmlFree(refXmlValue);
588 TestReferenceChecker::Impl::processItem(const xmlChar *name, const char *id,
589 const std::string &value, bool *bFound)
591 return processItem(name, id, value.c_str(), bFound);
596 TestReferenceChecker::Impl::shouldIgnore() const
598 return currNode_ == NULL;
602 /********************************************************************
606 TestReferenceData::TestReferenceData()
607 : impl_(new Impl(getReferenceDataMode()))
612 TestReferenceData::TestReferenceData(ReferenceDataMode mode)
613 : impl_(new Impl(mode))
618 TestReferenceData::~TestReferenceData()
623 bool TestReferenceData::isWriteMode() const
625 return impl_->bWrite_;
629 TestReferenceChecker TestReferenceData::rootChecker()
631 if (!isWriteMode() && !impl_->bInUse_ && impl_->refDoc_ == NULL)
633 ADD_FAILURE() << "Reference data file not found: "
634 << impl_->fullFilename_;
636 impl_->bInUse_ = true;
637 if (impl_->refDoc_ == NULL)
639 return TestReferenceChecker(new TestReferenceChecker::Impl(isWriteMode()));
641 xmlNodePtr rootNode = xmlDocGetRootElement(impl_->refDoc_);
642 return TestReferenceChecker(
643 new TestReferenceChecker::Impl("", rootNode, isWriteMode()));
647 /********************************************************************
648 * TestReferenceChecker
651 TestReferenceChecker::TestReferenceChecker(Impl *impl)
657 TestReferenceChecker::TestReferenceChecker(const TestReferenceChecker &other)
658 : impl_(new Impl(*other.impl_))
663 TestReferenceChecker &
664 TestReferenceChecker::operator=(const TestReferenceChecker &other)
666 impl_.reset(new Impl(*other.impl_));
671 TestReferenceChecker::~TestReferenceChecker()
676 bool TestReferenceChecker::isWriteMode() const
678 return impl_->bWrite_;
682 bool TestReferenceChecker::checkPresent(bool bPresent, const char *id)
684 if (isWriteMode() || impl_->shouldIgnore())
688 xmlNodePtr node = impl_->findNode(NULL, id);
689 bool bFound = (node != NULL);
690 if (bFound != bPresent)
692 ADD_FAILURE() << "Mismatch while checking reference data item '"
693 << impl_->appendPath(id) << "'\n"
694 << "Expected: " << (bPresent ? "it is present.\n" : "it is absent.\n")
695 << " Actual: " << (bFound ? "it is present." : "it is absent.");
697 if (bFound && bPresent)
699 impl_->prevFoundNode_ = node;
706 TestReferenceChecker TestReferenceChecker::checkCompound(const char *type, const char *id)
708 SCOPED_TRACE(impl_->traceString(id));
709 if (impl_->shouldIgnore())
711 return TestReferenceChecker(new Impl(isWriteMode()));
713 const xmlChar *xmlNodeName = reinterpret_cast<const xmlChar *>(type);
715 xmlNodePtr newNode = impl_->findOrCreateNode(xmlNodeName, id, &bFound);
718 return TestReferenceChecker(new Impl(isWriteMode()));
720 return TestReferenceChecker(
721 new Impl(impl_->appendPath(id), newNode, isWriteMode()));
725 void TestReferenceChecker::checkBoolean(bool value, const char *id)
727 if (impl_->shouldIgnore())
731 SCOPED_TRACE(impl_->traceString(id));
733 const char *strValue = value ? "true" : "false";
734 std::string refStrValue =
735 impl_->processItem(Impl::cBooleanNodeName, id, strValue, &bFound);
738 EXPECT_EQ(refStrValue, strValue);
743 void TestReferenceChecker::checkString(const char *value, const char *id)
745 if (impl_->shouldIgnore())
749 SCOPED_TRACE(impl_->traceString(id));
751 std::string refStrValue =
752 impl_->processItem(Impl::cStringNodeName, id, value, &bFound);
755 EXPECT_EQ(refStrValue, value);
760 void TestReferenceChecker::checkString(const std::string &value, const char *id)
762 checkString(value.c_str(), id);
766 void TestReferenceChecker::checkStringBlock(const std::string &value,
769 if (impl_->shouldIgnore())
773 SCOPED_TRACE(impl_->traceString(id));
775 xmlNodePtr node = impl_->findOrCreateNode(Impl::cStringNodeName, id, &bFound);
780 // An extra newline is written in the beginning to make lines align
781 // in the output xml (otherwise, the first line would be off by the length
782 // of the starting CDATA tag).
783 if (isWriteMode() && !bFound)
785 std::string adjustedValue = "\n" + value;
786 const xmlChar *xmlValue
787 = reinterpret_cast<const xmlChar *>(adjustedValue.c_str());
788 // TODO: Figure out if \r and \r\n can be handled without them changing
789 // to \n in the roundtrip
791 = xmlNewCDataBlock(node->doc, xmlValue,
792 static_cast<int>(adjustedValue.length()));
793 xmlAddChild(node, cdata);
797 xmlNodePtr cdata = node->children;
798 while (cdata != NULL && cdata->type != XML_CDATA_SECTION_NODE)
804 ADD_FAILURE() << "Invalid string block element";
807 xmlChar *refXmlValue = xmlNodeGetContent(cdata);
808 if (refXmlValue[0] != '\n')
810 ADD_FAILURE() << "Invalid string block element";
811 xmlFree(refXmlValue);
814 std::string refValue(reinterpret_cast<const char *>(refXmlValue + 1));
815 xmlFree(refXmlValue);
816 EXPECT_EQ(refValue, value);
821 void TestReferenceChecker::checkInteger(int value, const char *id)
823 if (impl_->shouldIgnore())
827 SCOPED_TRACE(impl_->traceString(id));
829 std::string strValue = formatString("%d", value);
830 std::string refStrValue =
831 impl_->processItem(Impl::cIntegerNodeName, id, strValue, &bFound);
834 EXPECT_EQ(refStrValue, strValue);
839 void TestReferenceChecker::checkDouble(double value, const char *id)
841 if (impl_->shouldIgnore())
845 SCOPED_TRACE(impl_->traceString(id));
847 std::string strValue = formatString("%f", value);
848 std::string refStrValue =
849 impl_->processItem(Impl::cRealNodeName, id, strValue, &bFound);
853 double refValue = std::strtod(refStrValue.c_str(), &endptr);
854 EXPECT_EQ('\0', *endptr);
855 EXPECT_NEAR(refValue, value, 0.0001);
860 void TestReferenceChecker::checkFloat(float value, const char *id)
862 checkDouble(value, id);
866 void TestReferenceChecker::checkReal(float value, const char *id)
868 checkDouble(value, id);
872 void TestReferenceChecker::checkReal(double value, const char *id)
874 checkDouble(value, id);
878 void TestReferenceChecker::checkVector(const int value[3], const char *id)
880 TestReferenceChecker compound(checkCompound(Impl::cVectorType, id));
881 compound.checkInteger(value[0], "X");
882 compound.checkInteger(value[1], "Y");
883 compound.checkInteger(value[2], "Z");
887 void TestReferenceChecker::checkVector(const float value[3], const char *id)
889 TestReferenceChecker compound(checkCompound(Impl::cVectorType, id));
890 compound.checkReal(value[0], "X");
891 compound.checkReal(value[1], "Y");
892 compound.checkReal(value[2], "Z");
896 void TestReferenceChecker::checkVector(const double value[3], const char *id)
898 TestReferenceChecker compound(checkCompound(Impl::cVectorType, id));
899 compound.checkReal(value[0], "X");
900 compound.checkReal(value[1], "Y");
901 compound.checkReal(value[2], "Z");
906 TestReferenceChecker::checkSequenceCompound(const char *id, size_t length)
908 TestReferenceChecker compound(checkCompound(Impl::cSequenceType, id));
909 compound.checkInteger(static_cast<int>(length), Impl::cSequenceLengthName);