Remove Options::isSet()
[alexxy/gromacs.git] / src / testutils / refdata.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2011,2012,2013,2014,2015, 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.
8  *
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.
13  *
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.
18  *
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.
23  *
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.
31  *
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.
34  */
35 /*! \internal \file
36  * \brief
37  * Implements classes and functions from refdata.h.
38  *
39  * \author Teemu Murtola <teemu.murtola@gmail.com>
40  * \ingroup module_testutils
41  */
42 #include "gmxpre.h"
43
44 #include "refdata.h"
45
46 #include <cstdio>
47 #include <cstdlib>
48
49 #include <limits>
50 #include <string>
51
52 #include <gtest/gtest.h>
53 #include <libxml/parser.h>
54 #include <libxml/xmlmemory.h>
55
56 #include "gromacs/options/basicoptions.h"
57 #include "gromacs/options/ioptionscontainer.h"
58 #include "gromacs/utility/exceptions.h"
59 #include "gromacs/utility/gmxassert.h"
60 #include "gromacs/utility/path.h"
61 #include "gromacs/utility/stringutil.h"
62
63 #include "testutils/testasserts.h"
64 #include "testutils/testexceptions.h"
65 #include "testutils/testfilemanager.h"
66
67 namespace
68 {
69
70 /*! \internal \brief
71  * Global test environment for freeing up libxml2 internal buffers.
72  */
73 class TestReferenceDataEnvironment : public ::testing::Environment
74 {
75     public:
76         //! Frees internal buffers allocated by libxml2.
77         virtual void TearDown()
78         {
79             xmlCleanupParser();
80         }
81 };
82
83 //! Global reference data mode set with gmx::test::setReferenceDataMode().
84 // TODO: Make this a real enum; requires solving a TODO in StringOption.
85 int g_referenceDataMode = gmx::test::erefdataCompare;
86
87 //! Returns the global reference data mode.
88 gmx::test::ReferenceDataMode getReferenceDataMode()
89 {
90     return static_cast<gmx::test::ReferenceDataMode>(g_referenceDataMode);
91 }
92
93 } // namespace
94
95 namespace gmx
96 {
97 namespace test
98 {
99
100 void initReferenceData(IOptionsContainer *options)
101 {
102     // Needs to correspond to the enum order in refdata.h.
103     const char *const refDataEnum[] = { "check", "create", "update" };
104     options->addOption(
105             StringOption("ref-data").enumValue(refDataEnum)
106                 .defaultEnumIndex(0)
107                 .storeEnumIndex(&g_referenceDataMode)
108                 .description("Operation mode for tests that use reference data"));
109     ::testing::AddGlobalTestEnvironment(new TestReferenceDataEnvironment);
110 }
111
112 /********************************************************************
113  * TestReferenceData::Impl
114  */
115
116 /*! \internal \brief
117  * Private implementation class for TestReferenceData.
118  *
119  * \ingroup module_testutils
120  */
121 class TestReferenceData::Impl
122 {
123     public:
124         //! String constant for output XML version string.
125         static const xmlChar * const cXmlVersion;
126         //! String constant for XML stylesheet processing instruction name.
127         static const xmlChar * const cXmlStyleSheetNodeName;
128         //! String constant for XML stylesheet reference.
129         static const xmlChar * const cXmlStyleSheetContent;
130         //! String constant for naming the root XML element.
131         static const xmlChar * const cRootNodeName;
132
133         //! Initializes a checker in the given mode.
134         Impl(ReferenceDataMode mode, bool bSelfTestMode);
135         ~Impl();
136
137         //! Full path of the reference data file.
138         std::string             fullFilename_;
139         /*! \brief
140          * XML document for the reference data.
141          *
142          * May be NULL if there was an I/O error in initialization.
143          */
144         xmlDocPtr               refDoc_;
145         /*! \brief
146          * Whether the reference data is being written (true) or compared
147          * (false).
148          */
149         bool                    bWrite_;
150         //! `true` if self-testing (enables extra failure messages).
151         bool                    bSelfTestMode_;
152         /*! \brief
153          * Whether any reference checkers have been created for this data.
154          */
155         bool                    bInUse_;
156 };
157
158 const xmlChar * const TestReferenceData::Impl::cXmlVersion =
159     (const xmlChar *)"1.0";
160 const xmlChar * const TestReferenceData::Impl::cXmlStyleSheetNodeName =
161     (const xmlChar *)"xml-stylesheet";
162 const xmlChar * const TestReferenceData::Impl::cXmlStyleSheetContent =
163     (const xmlChar *)"type=\"text/xsl\" href=\"referencedata.xsl\"";
164 const xmlChar * const TestReferenceData::Impl::cRootNodeName =
165     (const xmlChar *)"ReferenceData";
166
167
168 TestReferenceData::Impl::Impl(ReferenceDataMode mode, bool bSelfTestMode)
169     : refDoc_(NULL), bWrite_(false), bSelfTestMode_(bSelfTestMode), bInUse_(false)
170 {
171     const std::string dirname =
172         bSelfTestMode
173         ? TestFileManager::getGlobalOutputTempDirectory()
174         : TestFileManager::getInputDataDirectory();
175     const std::string filename = TestFileManager::getTestSpecificFileName(".xml");
176     fullFilename_ = Path::join(dirname, "refdata", filename);
177
178     bWrite_ = true;
179     if (mode != erefdataUpdateAll)
180     {
181         FILE *fp = std::fopen(fullFilename_.c_str(), "r");
182         if (fp != NULL)
183         {
184             bWrite_ = false;
185             fclose(fp);
186         }
187         else if (mode == erefdataCompare)
188         {
189             bWrite_ = false;
190             return;
191         }
192     }
193     if (bWrite_)
194     {
195         // TODO: Error checking
196         refDoc_ = xmlNewDoc(cXmlVersion);
197         xmlNodePtr rootNode = xmlNewDocNode(refDoc_, NULL, cRootNodeName, NULL);
198         xmlDocSetRootElement(refDoc_, rootNode);
199         xmlNodePtr xslNode = xmlNewDocPI(refDoc_, cXmlStyleSheetNodeName,
200                                          cXmlStyleSheetContent);
201         xmlAddPrevSibling(rootNode, xslNode);
202     }
203     else
204     {
205         refDoc_ = xmlParseFile(fullFilename_.c_str());
206         if (refDoc_ == NULL)
207         {
208             GMX_THROW(TestException("Reference data not parsed successfully: " + fullFilename_));
209         }
210         xmlNodePtr rootNode = xmlDocGetRootElement(refDoc_);
211         if (rootNode == NULL)
212         {
213             xmlFreeDoc(refDoc_);
214             GMX_THROW(TestException("Reference data is empty: " + fullFilename_));
215         }
216         if (xmlStrcmp(rootNode->name, cRootNodeName) != 0)
217         {
218             xmlFreeDoc(refDoc_);
219             GMX_THROW(TestException("Invalid root node type in " + fullFilename_));
220         }
221     }
222 }
223
224
225 TestReferenceData::Impl::~Impl()
226 {
227     if (bWrite_ && bInUse_ && refDoc_ != NULL)
228     {
229         std::string dirname = Path::getParentPath(fullFilename_);
230         if (!Directory::exists(dirname))
231         {
232             if (Directory::create(dirname) != 0)
233             {
234                 ADD_FAILURE() << "Creation of reference data directory failed for " << dirname;
235             }
236         }
237         if (xmlSaveFormatFile(fullFilename_.c_str(), refDoc_, 1) == -1)
238         {
239             ADD_FAILURE() << "Saving reference data failed for " + fullFilename_;
240         }
241     }
242     if (refDoc_ != NULL)
243     {
244         xmlFreeDoc(refDoc_);
245     }
246 }
247
248
249 /********************************************************************
250  * TestReferenceChecker::Impl
251  */
252
253 /*! \internal \brief
254  * Private implementation class for TestReferenceChecker.
255  *
256  * \ingroup module_testutils
257  */
258 class TestReferenceChecker::Impl
259 {
260     public:
261         //! String constant for naming XML elements for boolean values.
262         static const xmlChar * const cBooleanNodeName;
263         //! String constant for naming XML elements for string values.
264         static const xmlChar * const cStringNodeName;
265         //! String constant for naming XML elements for integer values.
266         static const xmlChar * const cIntegerNodeName;
267         //! String constant for naming XML elements for int64 values.
268         static const xmlChar * const cInt64NodeName;
269         //! String constant for naming XML elements for unsigned int64 values.
270         static const xmlChar * const cUInt64NodeName;
271         //! String constant for naming XML elements for floating-point values.
272         static const xmlChar * const cRealNodeName;
273         //! String constant for naming XML attribute for value identifiers.
274         static const xmlChar * const cIdAttrName;
275         //! String constant for naming compounds for vectors.
276         static const char * const    cVectorType;
277         //! String constant for naming compounds for sequences.
278         static const char * const    cSequenceType;
279         //! String constant for value identifier for sequence length.
280         static const char * const    cSequenceLengthName;
281
282         //! Creates a checker that does nothing.
283         explicit Impl(bool bWrite);
284         //! Creates a checker with a given root node.
285         Impl(const std::string &path, xmlNodePtr rootNode, bool bWrite,
286              bool bSelfTestMode, const FloatingPointTolerance &defaultTolerance);
287
288         //! Returns a string for SCOPED_TRACE() for checking element \p id.
289         std::string traceString(const char *id) const;
290         //! Returns the path of this checker with \p id appended.
291         std::string appendPath(const char *id) const;
292
293         /*! \brief
294          * Finds a reference data node.
295          *
296          * \param[in]  name   Type of node to find (can be NULL, in which case
297          *      any type is matched).
298          * \param[in]  id     Unique identifier of the node (can be NULL, in
299          *      which case the next node without an id is matched).
300          * \returns    Matching node, or NULL if no matching node found.
301          *
302          * Searches for a node in the reference data that matches the given
303          * \p name and \p id.  Searching starts from the node that follows the
304          * previously matched node (relevant for performance, and if there are
305          * duplicate ids or nodes without ids).  Note that the match pointer is
306          * not updated by this method.
307          */
308         xmlNodePtr findNode(const xmlChar *name, const char *id) const;
309         /*! \brief
310          * Finds/creates a reference data node to match against.
311          *
312          * \param[in]  name   Type of node to find.
313          * \param[in]  id     Unique identifier of the node (can be NULL, in
314          *      which case the next node without an id is matched).
315          * \param[out] bFound Whether the node was found (false if the node was
316          *      created in write mode).
317          * \returns Matching node, or NULL if no matching node found
318          *      (NULL is never returned in write mode).
319          * \throws  TestException if node creation fails in write mode.
320          *
321          * Finds a node using findNode() and updates the match pointer is a
322          * match is found.  If a match is not found, the method returns NULL in
323          * read mode and creates a new node in write mode.  If the creation
324          * fails in write mode, throws.
325          */
326         xmlNodePtr findOrCreateNode(const xmlChar *name, const char *id,
327                                     bool *bFound);
328         /*! \brief
329          * Helper method for checking a reference data value.
330          *
331          * \param[in]  name   Type of node to find.
332          * \param[in]  id     Unique identifier of the node (can be NULL, in
333          *      which case the next node without an id is matched).
334          * \param[in]  value  String value of the value to be compared.
335          * \param[out] bFound true if a matchin value was found.
336          * \returns String value for the reference value.
337          * \throws  TestException if node creation fails in write mode.
338          *
339          * Performs common tasks in checking a reference value:
340          * finding/creating the correct XML node and reading/writing its string
341          * value.  Caller is responsible for converting the value to and from
342          * string where necessary and performing the actual comparison.
343          *
344          * In read mode, if a value is not found, adds a Google Test failure
345          * and returns an empty string.  If the reference value is found,
346          * returns it (\p value is not used in this case).
347          *
348          * In write mode, creates the node if it is not found, sets its value
349          * as \p value and returns \p value.
350          */
351         std::string processItem(const xmlChar *name, const char *id,
352                                 const char *value, bool *bFound);
353         //! Convenience wrapper that takes a std::string.
354         std::string processItem(const xmlChar *name, const char *id,
355                                 const std::string &value, bool *bFound);
356         /*! \brief
357          * Whether the checker should ignore all validation calls.
358          *
359          * This is used to ignore any calls within compounds for which
360          * reference data could not be found, such that only one error is
361          * issued for the missing compound, instead of every individual value.
362          */
363         bool shouldIgnore() const;
364
365         //! Default floating-point comparison tolerance.
366         FloatingPointTolerance  defaultTolerance_;
367         /*! \brief
368          * Human-readable path to the root node of this checker.
369          *
370          * For the root checker, this will be "/", and for each compound, the
371          * id of the compound is added.  Used for reporting comparison
372          * mismatches.
373          */
374         std::string             path_;
375         /*! \brief
376          * Current node under which reference data is searched.
377          *
378          * Points to either the root of TestReferenceData::Impl::refDoc_, or to
379          * a compound node.
380          *
381          * Can be NULL, in which case this checker does nothing (doesn't even
382          * report errors, see shouldIgnore()).
383          */
384         xmlNodePtr              currNode_;
385         /*! \brief
386          * Points to a child of \a currNode_ that was last found.
387          *
388          * On initialization, is initialized to NULL.  After every check, is
389          * updated to point to the node that was used for the check.
390          * Subsequent checks start the search for the matching node on this
391          * node.
392          *
393          * Is NULL if \a currNode_ contains no children or if no checks have
394          * yet been made.
395          * Otherwise, always points to a direct child of \a currNode_.
396          */
397         xmlNodePtr              prevFoundNode_;
398         /*! \brief
399          * Whether the reference data is being written (true) or compared
400          * (false).
401          */
402         bool                    bWrite_;
403         //! `true` if self-testing (enables extra failure messages).
404         bool                    bSelfTestMode_;
405         /*! \brief
406          * Current number of unnamed elements in a sequence.
407          *
408          * It is the index of the next added unnamed element.
409          */
410         int                     seqIndex_;
411 };
412
413 const xmlChar * const TestReferenceChecker::Impl::cBooleanNodeName =
414     (const xmlChar *)"Bool";
415 const xmlChar * const TestReferenceChecker::Impl::cStringNodeName =
416     (const xmlChar *)"String";
417 const xmlChar * const TestReferenceChecker::Impl::cIntegerNodeName  =
418     (const xmlChar *)"Int";
419 const xmlChar * const TestReferenceChecker::Impl::cInt64NodeName  =
420     (const xmlChar *)"Int64";
421 const xmlChar * const TestReferenceChecker::Impl::cUInt64NodeName  =
422     (const xmlChar *)"UInt64";
423 const xmlChar * const TestReferenceChecker::Impl::cRealNodeName =
424     (const xmlChar *)"Real";
425 const xmlChar * const TestReferenceChecker::Impl::cIdAttrName =
426     (const xmlChar *)"Name";
427 const char * const    TestReferenceChecker::Impl::cVectorType =
428     "Vector";
429 const char * const    TestReferenceChecker::Impl::cSequenceType =
430     "Sequence";
431 const char * const    TestReferenceChecker::Impl::cSequenceLengthName =
432     "Length";
433
434
435 TestReferenceChecker::Impl::Impl(bool bWrite)
436     : defaultTolerance_(defaultRealTolerance()),
437       currNode_(NULL), prevFoundNode_(NULL), bWrite_(bWrite),
438       bSelfTestMode_(false), seqIndex_(0)
439 {
440 }
441
442
443 TestReferenceChecker::Impl::Impl(const std::string &path, xmlNodePtr rootNode,
444                                  bool bWrite, bool bSelfTestMode,
445                                  const FloatingPointTolerance &defaultTolerance)
446     : defaultTolerance_(defaultTolerance), path_(path + "/"),
447       currNode_(rootNode), prevFoundNode_(NULL), bWrite_(bWrite),
448       bSelfTestMode_(bSelfTestMode), seqIndex_(0)
449 {
450 }
451
452
453 std::string
454 TestReferenceChecker::Impl::traceString(const char *id) const
455 {
456     return "Checking '" + appendPath(id) + "'";
457 }
458
459
460 std::string
461 TestReferenceChecker::Impl::appendPath(const char *id) const
462 {
463     std::string printId = (id != NULL) ? id : formatString("[%d]", seqIndex_);
464     return path_ + printId;
465 }
466
467
468 xmlNodePtr
469 TestReferenceChecker::Impl::findNode(const xmlChar *name, const char *id) const
470 {
471     if (currNode_ == NULL || currNode_->children == NULL)
472     {
473         return NULL;
474     }
475     const xmlChar *xmlId = reinterpret_cast<const xmlChar *>(id);
476     xmlNodePtr     node  = prevFoundNode_;
477     bool           bWrap = true;
478     if (node != NULL)
479     {
480         if (id == NULL)
481         {
482             xmlChar *refId = xmlGetProp(node, cIdAttrName);
483             if (refId == NULL)
484             {
485                 if (name == NULL || xmlStrcmp(node->name, name) == 0)
486                 {
487                     bWrap = false;
488                     node  = node->next;
489                     if (node == NULL)
490                     {
491                         return NULL;
492                     }
493                 }
494             }
495             else
496             {
497                 xmlFree(refId);
498             }
499         }
500     }
501     else
502     {
503         node  = currNode_->children;
504         bWrap = false;
505     }
506     do
507     {
508         if (name == NULL || xmlStrcmp(node->name, name) == 0)
509         {
510             xmlChar *refId = xmlGetProp(node, cIdAttrName);
511             if (xmlId == NULL && refId == NULL)
512             {
513                 return node;
514             }
515             if (refId != NULL)
516             {
517                 if (xmlId != NULL && xmlStrcmp(refId, xmlId) == 0)
518                 {
519                     xmlFree(refId);
520                     return node;
521                 }
522                 xmlFree(refId);
523             }
524         }
525         node = node->next;
526         if (bWrap && node == NULL)
527         {
528             node = currNode_->children;
529         }
530     }
531     while (node != NULL && node != prevFoundNode_);
532     return NULL;
533 }
534
535
536 xmlNodePtr
537 TestReferenceChecker::Impl::findOrCreateNode(const xmlChar *name,
538                                              const char    *id,
539                                              bool          *bFound)
540 {
541     *bFound = false;
542     xmlNodePtr node = findNode(name, id);
543     if (node != NULL)
544     {
545         *bFound        = true;
546         prevFoundNode_ = node;
547     }
548     if (node == NULL)
549     {
550         if (bWrite_)
551         {
552             node = xmlNewTextChild(currNode_, NULL, name, NULL);
553             if (node != NULL && id != NULL)
554             {
555                 const xmlChar *xmlId = reinterpret_cast<const xmlChar *>(id);
556                 xmlAttrPtr     prop  = xmlNewProp(node, cIdAttrName, xmlId);
557                 if (prop == NULL)
558                 {
559                     xmlFreeNode(node);
560                     node = NULL;
561                 }
562             }
563             if (node == NULL)
564             {
565                 GMX_THROW(TestException("XML node creation failed"));
566             }
567             prevFoundNode_ = node;
568         }
569         else
570         {
571             ADD_FAILURE() << "Reference data item not found";
572         }
573     }
574     seqIndex_ = (id == NULL) ? seqIndex_+1 : 0;
575
576     return node;
577 }
578
579
580 std::string
581 TestReferenceChecker::Impl::processItem(const xmlChar *name, const char *id,
582                                         const char *value, bool *bFound)
583 {
584     xmlNodePtr node = findOrCreateNode(name, id, bFound);
585     if (node == NULL)
586     {
587         return std::string();
588     }
589     if (bWrite_ && !*bFound)
590     {
591         xmlNodeAddContent(node, reinterpret_cast<const xmlChar *>(value));
592         *bFound = true;
593         return std::string(value);
594     }
595     else
596     {
597         xmlChar    *refXmlValue = xmlNodeGetContent(node);
598         std::string refValue(reinterpret_cast<const char *>(refXmlValue));
599         xmlFree(refXmlValue);
600         return refValue;
601     }
602 }
603
604
605 std::string
606 TestReferenceChecker::Impl::processItem(const xmlChar *name, const char *id,
607                                         const std::string &value, bool *bFound)
608 {
609     return processItem(name, id, value.c_str(), bFound);
610 }
611
612
613 bool
614 TestReferenceChecker::Impl::shouldIgnore() const
615 {
616     return currNode_ == NULL;
617 }
618
619
620 /********************************************************************
621  * TestReferenceData
622  */
623
624 TestReferenceData::TestReferenceData()
625     : impl_(new Impl(getReferenceDataMode(), false))
626 {
627 }
628
629
630 TestReferenceData::TestReferenceData(ReferenceDataMode mode)
631     : impl_(new Impl(mode, true))
632 {
633 }
634
635
636 TestReferenceData::~TestReferenceData()
637 {
638 }
639
640
641 bool TestReferenceData::isWriteMode() const
642 {
643     return impl_->bWrite_;
644 }
645
646
647 TestReferenceChecker TestReferenceData::rootChecker()
648 {
649     if (!isWriteMode() && !impl_->bInUse_ && impl_->refDoc_ == NULL)
650     {
651         ADD_FAILURE() << "Reference data file not found: "
652         << impl_->fullFilename_;
653     }
654     impl_->bInUse_ = true;
655     if (impl_->refDoc_ == NULL)
656     {
657         return TestReferenceChecker(new TestReferenceChecker::Impl(isWriteMode()));
658     }
659     xmlNodePtr rootNode = xmlDocGetRootElement(impl_->refDoc_);
660     // TODO: The default tolerance for double-precision builds that explicitly
661     // call checkFloat() may not be ideal.
662     return TestReferenceChecker(
663             new TestReferenceChecker::Impl("", rootNode, isWriteMode(),
664                                            impl_->bSelfTestMode_,
665                                            defaultRealTolerance()));
666 }
667
668
669 /********************************************************************
670  * TestReferenceChecker
671  */
672
673 TestReferenceChecker::TestReferenceChecker(Impl *impl)
674     : impl_(impl)
675 {
676 }
677
678
679 TestReferenceChecker::TestReferenceChecker(const TestReferenceChecker &other)
680     : impl_(new Impl(*other.impl_))
681 {
682 }
683
684
685 TestReferenceChecker &
686 TestReferenceChecker::operator=(const TestReferenceChecker &other)
687 {
688     impl_.reset(new Impl(*other.impl_));
689     return *this;
690 }
691
692
693 TestReferenceChecker::~TestReferenceChecker()
694 {
695 }
696
697
698 bool TestReferenceChecker::isWriteMode() const
699 {
700     return impl_->bWrite_;
701 }
702
703
704 void TestReferenceChecker::setDefaultTolerance(
705         const FloatingPointTolerance &tolerance)
706 {
707     impl_->defaultTolerance_ = tolerance;
708 }
709
710
711 bool TestReferenceChecker::checkPresent(bool bPresent, const char *id)
712 {
713     if (isWriteMode() || impl_->shouldIgnore())
714     {
715         return bPresent;
716     }
717     xmlNodePtr node   = impl_->findNode(NULL, id);
718     bool       bFound = (node != NULL);
719     if (bFound != bPresent)
720     {
721         ADD_FAILURE() << "Mismatch while checking reference data item '"
722         << impl_->appendPath(id) << "'\n"
723         << "Expected: " << (bPresent ? "it is present.\n" : "it is absent.\n")
724         << "  Actual: " << (bFound ? "it is present." : "it is absent.");
725     }
726     if (bFound && bPresent)
727     {
728         impl_->prevFoundNode_ = node;
729         return true;
730     }
731     return false;
732 }
733
734
735 TestReferenceChecker TestReferenceChecker::checkCompound(const char *type, const char *id)
736 {
737     SCOPED_TRACE(impl_->traceString(id));
738     if (impl_->shouldIgnore())
739     {
740         return TestReferenceChecker(new Impl(isWriteMode()));
741     }
742     const xmlChar *xmlNodeName = reinterpret_cast<const xmlChar *>(type);
743     bool           bFound;
744     xmlNodePtr     newNode     = impl_->findOrCreateNode(xmlNodeName, id, &bFound);
745     if (newNode == NULL)
746     {
747         return TestReferenceChecker(new Impl(isWriteMode()));
748     }
749     return TestReferenceChecker(
750             new Impl(impl_->appendPath(id), newNode, isWriteMode(),
751                      impl_->bSelfTestMode_, impl_->defaultTolerance_));
752 }
753
754
755 void TestReferenceChecker::checkBoolean(bool value, const char *id)
756 {
757     if (impl_->shouldIgnore())
758     {
759         return;
760     }
761     SCOPED_TRACE(impl_->traceString(id));
762     bool        bFound      = false;
763     const char *strValue    = value ? "true" : "false";
764     std::string refStrValue =
765         impl_->processItem(Impl::cBooleanNodeName, id, strValue, &bFound);
766     if (bFound)
767     {
768         EXPECT_EQ(refStrValue, strValue);
769     }
770 }
771
772
773 void TestReferenceChecker::checkString(const char *value, const char *id)
774 {
775     if (impl_->shouldIgnore())
776     {
777         return;
778     }
779     SCOPED_TRACE(impl_->traceString(id));
780     bool        bFound      = false;
781     std::string refStrValue =
782         impl_->processItem(Impl::cStringNodeName, id, value, &bFound);
783     if (bFound)
784     {
785         EXPECT_EQ(refStrValue, value);
786     }
787 }
788
789
790 void TestReferenceChecker::checkString(const std::string &value, const char *id)
791 {
792     checkString(value.c_str(), id);
793 }
794
795
796 void TestReferenceChecker::checkStringBlock(const std::string &value,
797                                             const char        *id)
798 {
799     if (impl_->shouldIgnore())
800     {
801         return;
802     }
803     SCOPED_TRACE(impl_->traceString(id));
804     bool       bFound;
805     xmlNodePtr node = impl_->findOrCreateNode(Impl::cStringNodeName, id, &bFound);
806     if (node == NULL)
807     {
808         return;
809     }
810     // An extra newline is written in the beginning to make lines align
811     // in the output xml (otherwise, the first line would be off by the length
812     // of the starting CDATA tag).
813     if (isWriteMode() && !bFound)
814     {
815         std::string    adjustedValue = "\n" + value;
816         const xmlChar *xmlValue
817             = reinterpret_cast<const xmlChar *>(adjustedValue.c_str());
818         // TODO: Figure out if \r and \r\n can be handled without them changing
819         // to \n in the roundtrip
820         xmlNodePtr cdata
821             = xmlNewCDataBlock(node->doc, xmlValue,
822                                static_cast<int>(adjustedValue.length()));
823         xmlAddChild(node, cdata);
824     }
825     else
826     {
827         xmlNodePtr cdata = node->children;
828         while (cdata != NULL && cdata->type != XML_CDATA_SECTION_NODE)
829         {
830             cdata = cdata->next;
831         }
832         if (cdata == NULL)
833         {
834             ADD_FAILURE() << "Invalid string block element";
835             return;
836         }
837         xmlChar *refXmlValue = xmlNodeGetContent(cdata);
838         if (refXmlValue[0] != '\n')
839         {
840             ADD_FAILURE() << "Invalid string block element";
841             xmlFree(refXmlValue);
842             return;
843         }
844         std::string refValue(reinterpret_cast<const char *>(refXmlValue + 1));
845         xmlFree(refXmlValue);
846         EXPECT_EQ(refValue, value);
847     }
848 }
849
850
851 void TestReferenceChecker::checkInteger(int value, const char *id)
852 {
853     if (impl_->shouldIgnore())
854     {
855         return;
856     }
857     SCOPED_TRACE(impl_->traceString(id));
858     bool        bFound      = false;
859     std::string strValue    = formatString("%d", value);
860     std::string refStrValue =
861         impl_->processItem(Impl::cIntegerNodeName, id, strValue, &bFound);
862     if (bFound)
863     {
864         EXPECT_EQ(refStrValue, strValue);
865     }
866 }
867
868 void TestReferenceChecker::checkInt64(gmx_int64_t value, const char *id)
869 {
870     if (impl_->shouldIgnore())
871     {
872         return;
873     }
874     SCOPED_TRACE(impl_->traceString(id));
875     bool        bFound      = false;
876     std::string strValue    = formatString("%" GMX_PRId64, value);
877     std::string refStrValue =
878         impl_->processItem(Impl::cInt64NodeName, id, strValue, &bFound);
879     if (bFound)
880     {
881         EXPECT_EQ(refStrValue, strValue);
882     }
883 }
884
885 void TestReferenceChecker::checkUInt64(gmx_uint64_t value, const char *id)
886 {
887     if (impl_->shouldIgnore())
888     {
889         return;
890     }
891     SCOPED_TRACE(impl_->traceString(id));
892     bool        bFound      = false;
893     std::string strValue    = formatString("%" GMX_PRIu64, value);
894     std::string refStrValue =
895         impl_->processItem(Impl::cUInt64NodeName, id, strValue, &bFound);
896     if (bFound)
897     {
898         EXPECT_EQ(refStrValue, strValue);
899     }
900 }
901
902 void TestReferenceChecker::checkDouble(double value, const char *id)
903 {
904     if (impl_->shouldIgnore())
905     {
906         return;
907     }
908     SCOPED_TRACE(impl_->traceString(id));
909     bool        bFound      = false;
910     const int   prec        = std::numeric_limits<double>::digits10 + 2;
911     std::string strValue    = formatString("%.*g", prec, value);
912     std::string refStrValue =
913         impl_->processItem(Impl::cRealNodeName, id, strValue, &bFound);
914     if (bFound)
915     {
916         char  *endptr;
917         double refValue = std::strtod(refStrValue.c_str(), &endptr);
918         EXPECT_EQ('\0', *endptr);
919         if (impl_->bSelfTestMode_)
920         {
921             EXPECT_DOUBLE_EQ_TOL(refValue, value, impl_->defaultTolerance_)
922             << "String value: " << strValue << std::endl
923             << " Ref. string: " << refStrValue;
924         }
925         else
926         {
927             EXPECT_DOUBLE_EQ_TOL(refValue, value, impl_->defaultTolerance_);
928         }
929     }
930 }
931
932
933 void TestReferenceChecker::checkFloat(float value, const char *id)
934 {
935     if (impl_->shouldIgnore())
936     {
937         return;
938     }
939     SCOPED_TRACE(impl_->traceString(id));
940     bool        bFound      = false;
941     const int   prec        = std::numeric_limits<float>::digits10 + 2;
942     std::string strValue    = formatString("%.*g", prec, value);
943     std::string refStrValue =
944         impl_->processItem(Impl::cRealNodeName, id, strValue, &bFound);
945     if (bFound)
946     {
947         char  *endptr;
948         float  refValue = static_cast<float>(std::strtod(refStrValue.c_str(), &endptr));
949         EXPECT_EQ('\0', *endptr);
950         if (impl_->bSelfTestMode_)
951         {
952             EXPECT_FLOAT_EQ_TOL(refValue, value, impl_->defaultTolerance_)
953             << "String value: " << strValue << std::endl
954             << " Ref. string: " << refStrValue;
955         }
956         else
957         {
958             EXPECT_FLOAT_EQ_TOL(refValue, value, impl_->defaultTolerance_);
959         }
960     }
961 }
962
963
964 void TestReferenceChecker::checkReal(float value, const char *id)
965 {
966     checkFloat(value, id);
967 }
968
969
970 void TestReferenceChecker::checkReal(double value, const char *id)
971 {
972     checkDouble(value, id);
973 }
974
975
976 void TestReferenceChecker::checkVector(const int value[3], const char *id)
977 {
978     TestReferenceChecker compound(checkCompound(Impl::cVectorType, id));
979     compound.checkInteger(value[0], "X");
980     compound.checkInteger(value[1], "Y");
981     compound.checkInteger(value[2], "Z");
982 }
983
984
985 void TestReferenceChecker::checkVector(const float value[3], const char *id)
986 {
987     TestReferenceChecker compound(checkCompound(Impl::cVectorType, id));
988     compound.checkReal(value[0], "X");
989     compound.checkReal(value[1], "Y");
990     compound.checkReal(value[2], "Z");
991 }
992
993
994 void TestReferenceChecker::checkVector(const double value[3], const char *id)
995 {
996     TestReferenceChecker compound(checkCompound(Impl::cVectorType, id));
997     compound.checkReal(value[0], "X");
998     compound.checkReal(value[1], "Y");
999     compound.checkReal(value[2], "Z");
1000 }
1001
1002
1003 TestReferenceChecker
1004 TestReferenceChecker::checkSequenceCompound(const char *id, size_t length)
1005 {
1006     TestReferenceChecker compound(checkCompound(Impl::cSequenceType, id));
1007     compound.checkInteger(static_cast<int>(length), Impl::cSequenceLengthName);
1008     return compound;
1009 }
1010
1011 } // namespace test
1012 } // namespace gmx