64828951721eabdb8951fa789c72cc823d597e08
[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, 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 "refdata.h"
43
44 #include <cstdio>
45 #include <cstdlib>
46
47 #include <limits>
48 #include <string>
49
50 #include <gtest/gtest.h>
51 #include <libxml/parser.h>
52 #include <libxml/xmlmemory.h>
53
54 #include "gromacs/options/basicoptions.h"
55 #include "gromacs/options/options.h"
56 #include "gromacs/utility/exceptions.h"
57 #include "gromacs/utility/gmxassert.h"
58 #include "gromacs/utility/path.h"
59 #include "gromacs/utility/stringutil.h"
60
61 #include "testutils/testasserts.h"
62 #include "testutils/testexceptions.h"
63 #include "testutils/testfilemanager.h"
64
65 namespace
66 {
67
68 /*! \internal \brief
69  * Global test environment for freeing up libxml2 internal buffers.
70  */
71 class TestReferenceDataEnvironment : public ::testing::Environment
72 {
73     public:
74         //! Frees internal buffers allocated by libxml2.
75         virtual void TearDown()
76         {
77             xmlCleanupParser();
78         }
79 };
80
81 //! Global reference data mode set with gmx::test::setReferenceDataMode().
82 // TODO: Make this a real enum; requires solving a TODO in StringOption.
83 int g_referenceDataMode = gmx::test::erefdataCompare;
84
85 } // namespace
86
87 namespace gmx
88 {
89 namespace test
90 {
91
92 ReferenceDataMode getReferenceDataMode()
93 {
94     return static_cast<ReferenceDataMode>(g_referenceDataMode);
95 }
96
97 void setReferenceDataMode(ReferenceDataMode mode)
98 {
99     g_referenceDataMode = mode;
100 }
101
102 std::string getReferenceDataPath()
103 {
104     return TestFileManager::getInputFilePath("refdata");
105 }
106
107 void initReferenceData(Options *options)
108 {
109     // Needs to correspond to the enum order in refdata.h.
110     const char *const refDataEnum[] = { "check", "create", "update" };
111     options->addOption(
112             StringOption("ref-data").enumValue(refDataEnum)
113                 .defaultEnumIndex(0)
114                 .storeEnumIndex(&g_referenceDataMode)
115                 .description("Operation mode for tests that use reference data"));
116     ::testing::AddGlobalTestEnvironment(new TestReferenceDataEnvironment);
117 }
118
119 /********************************************************************
120  * TestReferenceData::Impl
121  */
122
123 /*! \internal \brief
124  * Private implementation class for TestReferenceData.
125  *
126  * \ingroup module_testutils
127  */
128 class TestReferenceData::Impl
129 {
130     public:
131         //! String constant for output XML version string.
132         static const xmlChar * const cXmlVersion;
133         //! String constant for XML stylesheet processing instruction name.
134         static const xmlChar * const cXmlStyleSheetNodeName;
135         //! String constant for XML stylesheet reference.
136         static const xmlChar * const cXmlStyleSheetContent;
137         //! String constant for naming the root XML element.
138         static const xmlChar * const cRootNodeName;
139
140         //! Initializes a checker in the given mode.
141         Impl(ReferenceDataMode mode, bool bSelfTestMode);
142         ~Impl();
143
144         //! Full path of the reference data file.
145         std::string             fullFilename_;
146         /*! \brief
147          * XML document for the reference data.
148          *
149          * May be NULL if there was an I/O error in initialization.
150          */
151         xmlDocPtr               refDoc_;
152         /*! \brief
153          * Whether the reference data is being written (true) or compared
154          * (false).
155          */
156         bool                    bWrite_;
157         //! `true` if self-testing (enables extra failure messages).
158         bool                    bSelfTestMode_;
159         /*! \brief
160          * Whether any reference checkers have been created for this data.
161          */
162         bool                    bInUse_;
163 };
164
165 const xmlChar * const TestReferenceData::Impl::cXmlVersion =
166     (const xmlChar *)"1.0";
167 const xmlChar * const TestReferenceData::Impl::cXmlStyleSheetNodeName =
168     (const xmlChar *)"xml-stylesheet";
169 const xmlChar * const TestReferenceData::Impl::cXmlStyleSheetContent =
170     (const xmlChar *)"type=\"text/xsl\" href=\"referencedata.xsl\"";
171 const xmlChar * const TestReferenceData::Impl::cRootNodeName =
172     (const xmlChar *)"ReferenceData";
173
174
175 TestReferenceData::Impl::Impl(ReferenceDataMode mode, bool bSelfTestMode)
176     : refDoc_(NULL), bWrite_(false), bSelfTestMode_(bSelfTestMode), bInUse_(false)
177 {
178     std::string dirname  = getReferenceDataPath();
179     std::string filename = TestFileManager::getTestSpecificFileName(".xml");
180     fullFilename_ = Path::join(dirname, filename);
181
182     bWrite_ = true;
183     if (mode != erefdataUpdateAll)
184     {
185         FILE *fp = std::fopen(fullFilename_.c_str(), "r");
186         if (fp != NULL)
187         {
188             bWrite_ = false;
189             fclose(fp);
190         }
191         else if (mode == erefdataCompare)
192         {
193             bWrite_ = false;
194             return;
195         }
196     }
197     if (bWrite_)
198     {
199         // TODO: Error checking
200         refDoc_ = xmlNewDoc(cXmlVersion);
201         xmlNodePtr rootNode = xmlNewDocNode(refDoc_, NULL, cRootNodeName, NULL);
202         xmlDocSetRootElement(refDoc_, rootNode);
203         xmlNodePtr xslNode = xmlNewDocPI(refDoc_, cXmlStyleSheetNodeName,
204                                          cXmlStyleSheetContent);
205         xmlAddPrevSibling(rootNode, xslNode);
206     }
207     else
208     {
209         refDoc_ = xmlParseFile(fullFilename_.c_str());
210         if (refDoc_ == NULL)
211         {
212             GMX_THROW(TestException("Reference data not parsed successfully: " + fullFilename_));
213         }
214         xmlNodePtr rootNode = xmlDocGetRootElement(refDoc_);
215         if (rootNode == NULL)
216         {
217             xmlFreeDoc(refDoc_);
218             GMX_THROW(TestException("Reference data is empty: " + fullFilename_));
219         }
220         if (xmlStrcmp(rootNode->name, cRootNodeName) != 0)
221         {
222             xmlFreeDoc(refDoc_);
223             GMX_THROW(TestException("Invalid root node type in " + fullFilename_));
224         }
225     }
226 }
227
228
229 TestReferenceData::Impl::~Impl()
230 {
231     if (bWrite_ && bInUse_ && refDoc_ != NULL)
232     {
233         std::string dirname = getReferenceDataPath();
234         if (!Directory::exists(dirname))
235         {
236             if (Directory::create(dirname) != 0)
237             {
238                 ADD_FAILURE() << "Creation of reference data directory failed for " << dirname;
239             }
240         }
241         if (xmlSaveFormatFile(fullFilename_.c_str(), refDoc_, 1) == -1)
242         {
243             ADD_FAILURE() << "Saving reference data failed for " + fullFilename_;
244         }
245     }
246     if (refDoc_ != NULL)
247     {
248         xmlFreeDoc(refDoc_);
249     }
250 }
251
252
253 /********************************************************************
254  * TestReferenceChecker::Impl
255  */
256
257 /*! \internal \brief
258  * Private implementation class for TestReferenceChecker.
259  *
260  * \ingroup module_testutils
261  */
262 class TestReferenceChecker::Impl
263 {
264     public:
265         //! String constant for naming XML elements for boolean values.
266         static const xmlChar * const cBooleanNodeName;
267         //! String constant for naming XML elements for string values.
268         static const xmlChar * const cStringNodeName;
269         //! String constant for naming XML elements for integer values.
270         static const xmlChar * const cIntegerNodeName;
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::cRealNodeName =
420     (const xmlChar *)"Real";
421 const xmlChar * const TestReferenceChecker::Impl::cIdAttrName =
422     (const xmlChar *)"Name";
423 const char * const    TestReferenceChecker::Impl::cVectorType =
424     "Vector";
425 const char * const    TestReferenceChecker::Impl::cSequenceType =
426     "Sequence";
427 const char * const    TestReferenceChecker::Impl::cSequenceLengthName =
428     "Length";
429
430
431 TestReferenceChecker::Impl::Impl(bool bWrite)
432     : defaultTolerance_(defaultRealTolerance()),
433       currNode_(NULL), prevFoundNode_(NULL), bWrite_(bWrite),
434       bSelfTestMode_(false), seqIndex_(0)
435 {
436 }
437
438
439 TestReferenceChecker::Impl::Impl(const std::string &path, xmlNodePtr rootNode,
440                                  bool bWrite, bool bSelfTestMode,
441                                  const FloatingPointTolerance &defaultTolerance)
442     : defaultTolerance_(defaultTolerance), path_(path + "/"),
443       currNode_(rootNode), prevFoundNode_(NULL), bWrite_(bWrite),
444       bSelfTestMode_(bSelfTestMode), seqIndex_(0)
445 {
446 }
447
448
449 std::string
450 TestReferenceChecker::Impl::traceString(const char *id) const
451 {
452     return "Checking '" + appendPath(id) + "'";
453 }
454
455
456 std::string
457 TestReferenceChecker::Impl::appendPath(const char *id) const
458 {
459     std::string printId = (id != NULL) ? id : formatString("[%d]", seqIndex_);
460     return path_ + printId;
461 }
462
463
464 xmlNodePtr
465 TestReferenceChecker::Impl::findNode(const xmlChar *name, const char *id) const
466 {
467     if (currNode_ == NULL || currNode_->children == NULL)
468     {
469         return NULL;
470     }
471     const xmlChar *xmlId = reinterpret_cast<const xmlChar *>(id);
472     xmlNodePtr     node  = prevFoundNode_;
473     bool           bWrap = true;
474     if (node != NULL)
475     {
476         if (id == NULL)
477         {
478             xmlChar *refId = xmlGetProp(node, cIdAttrName);
479             if (refId == NULL)
480             {
481                 if (name == NULL || xmlStrcmp(node->name, name) == 0)
482                 {
483                     bWrap = false;
484                     node  = node->next;
485                     if (node == NULL)
486                     {
487                         return NULL;
488                     }
489                 }
490             }
491             else
492             {
493                 xmlFree(refId);
494             }
495         }
496     }
497     else
498     {
499         node  = currNode_->children;
500         bWrap = false;
501     }
502     do
503     {
504         if (name == NULL || xmlStrcmp(node->name, name) == 0)
505         {
506             xmlChar *refId = xmlGetProp(node, cIdAttrName);
507             if (xmlId == NULL && refId == NULL)
508             {
509                 return node;
510             }
511             if (refId != NULL)
512             {
513                 if (xmlId != NULL && xmlStrcmp(refId, xmlId) == 0)
514                 {
515                     xmlFree(refId);
516                     return node;
517                 }
518                 xmlFree(refId);
519             }
520         }
521         node = node->next;
522         if (bWrap && node == NULL)
523         {
524             node = currNode_->children;
525         }
526     }
527     while (node != NULL && node != prevFoundNode_);
528     return NULL;
529 }
530
531
532 xmlNodePtr
533 TestReferenceChecker::Impl::findOrCreateNode(const xmlChar *name,
534                                              const char    *id,
535                                              bool          *bFound)
536 {
537     *bFound = false;
538     xmlNodePtr node = findNode(name, id);
539     if (node != NULL)
540     {
541         *bFound        = true;
542         prevFoundNode_ = node;
543     }
544     if (node == NULL)
545     {
546         if (bWrite_)
547         {
548             node = xmlNewTextChild(currNode_, NULL, name, NULL);
549             if (node != NULL && id != NULL)
550             {
551                 const xmlChar *xmlId = reinterpret_cast<const xmlChar *>(id);
552                 xmlAttrPtr     prop  = xmlNewProp(node, cIdAttrName, xmlId);
553                 if (prop == NULL)
554                 {
555                     xmlFreeNode(node);
556                     node = NULL;
557                 }
558             }
559             if (node == NULL)
560             {
561                 GMX_THROW(TestException("XML node creation failed"));
562             }
563             prevFoundNode_ = node;
564         }
565         else
566         {
567             ADD_FAILURE() << "Reference data item not found";
568         }
569     }
570     seqIndex_ = (id == NULL) ? seqIndex_+1 : 0;
571
572     return node;
573 }
574
575
576 std::string
577 TestReferenceChecker::Impl::processItem(const xmlChar *name, const char *id,
578                                         const char *value, bool *bFound)
579 {
580     xmlNodePtr node = findOrCreateNode(name, id, bFound);
581     if (node == NULL)
582     {
583         return std::string();
584     }
585     if (bWrite_ && !*bFound)
586     {
587         xmlNodeAddContent(node, reinterpret_cast<const xmlChar *>(value));
588         *bFound = true;
589         return std::string(value);
590     }
591     else
592     {
593         xmlChar    *refXmlValue = xmlNodeGetContent(node);
594         std::string refValue(reinterpret_cast<const char *>(refXmlValue));
595         xmlFree(refXmlValue);
596         return refValue;
597     }
598 }
599
600
601 std::string
602 TestReferenceChecker::Impl::processItem(const xmlChar *name, const char *id,
603                                         const std::string &value, bool *bFound)
604 {
605     return processItem(name, id, value.c_str(), bFound);
606 }
607
608
609 bool
610 TestReferenceChecker::Impl::shouldIgnore() const
611 {
612     return currNode_ == NULL;
613 }
614
615
616 /********************************************************************
617  * TestReferenceData
618  */
619
620 TestReferenceData::TestReferenceData()
621     : impl_(new Impl(getReferenceDataMode(), false))
622 {
623 }
624
625
626 TestReferenceData::TestReferenceData(ReferenceDataMode mode)
627     : impl_(new Impl(mode, true))
628 {
629 }
630
631
632 TestReferenceData::~TestReferenceData()
633 {
634 }
635
636
637 bool TestReferenceData::isWriteMode() const
638 {
639     return impl_->bWrite_;
640 }
641
642
643 TestReferenceChecker TestReferenceData::rootChecker()
644 {
645     if (!isWriteMode() && !impl_->bInUse_ && impl_->refDoc_ == NULL)
646     {
647         ADD_FAILURE() << "Reference data file not found: "
648         << impl_->fullFilename_;
649     }
650     impl_->bInUse_ = true;
651     if (impl_->refDoc_ == NULL)
652     {
653         return TestReferenceChecker(new TestReferenceChecker::Impl(isWriteMode()));
654     }
655     xmlNodePtr rootNode = xmlDocGetRootElement(impl_->refDoc_);
656     // TODO: The default tolerance for double-precision builds that explicitly
657     // call checkFloat() may not be ideal.
658     return TestReferenceChecker(
659             new TestReferenceChecker::Impl("", rootNode, isWriteMode(),
660                                            impl_->bSelfTestMode_,
661                                            defaultRealTolerance()));
662 }
663
664
665 /********************************************************************
666  * TestReferenceChecker
667  */
668
669 TestReferenceChecker::TestReferenceChecker(Impl *impl)
670     : impl_(impl)
671 {
672 }
673
674
675 TestReferenceChecker::TestReferenceChecker(const TestReferenceChecker &other)
676     : impl_(new Impl(*other.impl_))
677 {
678 }
679
680
681 TestReferenceChecker &
682 TestReferenceChecker::operator=(const TestReferenceChecker &other)
683 {
684     impl_.reset(new Impl(*other.impl_));
685     return *this;
686 }
687
688
689 TestReferenceChecker::~TestReferenceChecker()
690 {
691 }
692
693
694 bool TestReferenceChecker::isWriteMode() const
695 {
696     return impl_->bWrite_;
697 }
698
699
700 void TestReferenceChecker::setDefaultTolerance(
701         const FloatingPointTolerance &tolerance)
702 {
703     impl_->defaultTolerance_ = tolerance;
704 }
705
706
707 bool TestReferenceChecker::checkPresent(bool bPresent, const char *id)
708 {
709     if (isWriteMode() || impl_->shouldIgnore())
710     {
711         return bPresent;
712     }
713     xmlNodePtr node   = impl_->findNode(NULL, id);
714     bool       bFound = (node != NULL);
715     if (bFound != bPresent)
716     {
717         ADD_FAILURE() << "Mismatch while checking reference data item '"
718         << impl_->appendPath(id) << "'\n"
719         << "Expected: " << (bPresent ? "it is present.\n" : "it is absent.\n")
720         << "  Actual: " << (bFound ? "it is present." : "it is absent.");
721     }
722     if (bFound && bPresent)
723     {
724         impl_->prevFoundNode_ = node;
725         return true;
726     }
727     return false;
728 }
729
730
731 TestReferenceChecker TestReferenceChecker::checkCompound(const char *type, const char *id)
732 {
733     SCOPED_TRACE(impl_->traceString(id));
734     if (impl_->shouldIgnore())
735     {
736         return TestReferenceChecker(new Impl(isWriteMode()));
737     }
738     const xmlChar *xmlNodeName = reinterpret_cast<const xmlChar *>(type);
739     bool           bFound;
740     xmlNodePtr     newNode     = impl_->findOrCreateNode(xmlNodeName, id, &bFound);
741     if (newNode == NULL)
742     {
743         return TestReferenceChecker(new Impl(isWriteMode()));
744     }
745     return TestReferenceChecker(
746             new Impl(impl_->appendPath(id), newNode, isWriteMode(),
747                      impl_->bSelfTestMode_, impl_->defaultTolerance_));
748 }
749
750
751 void TestReferenceChecker::checkBoolean(bool value, const char *id)
752 {
753     if (impl_->shouldIgnore())
754     {
755         return;
756     }
757     SCOPED_TRACE(impl_->traceString(id));
758     bool        bFound      = false;
759     const char *strValue    = value ? "true" : "false";
760     std::string refStrValue =
761         impl_->processItem(Impl::cBooleanNodeName, id, strValue, &bFound);
762     if (bFound)
763     {
764         EXPECT_EQ(refStrValue, strValue);
765     }
766 }
767
768
769 void TestReferenceChecker::checkString(const char *value, const char *id)
770 {
771     if (impl_->shouldIgnore())
772     {
773         return;
774     }
775     SCOPED_TRACE(impl_->traceString(id));
776     bool        bFound      = false;
777     std::string refStrValue =
778         impl_->processItem(Impl::cStringNodeName, id, value, &bFound);
779     if (bFound)
780     {
781         EXPECT_EQ(refStrValue, value);
782     }
783 }
784
785
786 void TestReferenceChecker::checkString(const std::string &value, const char *id)
787 {
788     checkString(value.c_str(), id);
789 }
790
791
792 void TestReferenceChecker::checkStringBlock(const std::string &value,
793                                             const char        *id)
794 {
795     if (impl_->shouldIgnore())
796     {
797         return;
798     }
799     SCOPED_TRACE(impl_->traceString(id));
800     bool       bFound;
801     xmlNodePtr node = impl_->findOrCreateNode(Impl::cStringNodeName, id, &bFound);
802     if (node == NULL)
803     {
804         return;
805     }
806     // An extra newline is written in the beginning to make lines align
807     // in the output xml (otherwise, the first line would be off by the length
808     // of the starting CDATA tag).
809     if (isWriteMode() && !bFound)
810     {
811         std::string    adjustedValue = "\n" + value;
812         const xmlChar *xmlValue
813             = reinterpret_cast<const xmlChar *>(adjustedValue.c_str());
814         // TODO: Figure out if \r and \r\n can be handled without them changing
815         // to \n in the roundtrip
816         xmlNodePtr cdata
817             = xmlNewCDataBlock(node->doc, xmlValue,
818                                static_cast<int>(adjustedValue.length()));
819         xmlAddChild(node, cdata);
820     }
821     else
822     {
823         xmlNodePtr cdata = node->children;
824         while (cdata != NULL && cdata->type != XML_CDATA_SECTION_NODE)
825         {
826             cdata = cdata->next;
827         }
828         if (cdata == NULL)
829         {
830             ADD_FAILURE() << "Invalid string block element";
831             return;
832         }
833         xmlChar *refXmlValue = xmlNodeGetContent(cdata);
834         if (refXmlValue[0] != '\n')
835         {
836             ADD_FAILURE() << "Invalid string block element";
837             xmlFree(refXmlValue);
838             return;
839         }
840         std::string refValue(reinterpret_cast<const char *>(refXmlValue + 1));
841         xmlFree(refXmlValue);
842         EXPECT_EQ(refValue, value);
843     }
844 }
845
846
847 void TestReferenceChecker::checkInteger(int value, const char *id)
848 {
849     if (impl_->shouldIgnore())
850     {
851         return;
852     }
853     SCOPED_TRACE(impl_->traceString(id));
854     bool        bFound      = false;
855     std::string strValue    = formatString("%d", value);
856     std::string refStrValue =
857         impl_->processItem(Impl::cIntegerNodeName, id, strValue, &bFound);
858     if (bFound)
859     {
860         EXPECT_EQ(refStrValue, strValue);
861     }
862 }
863
864
865 void TestReferenceChecker::checkDouble(double value, const char *id)
866 {
867     if (impl_->shouldIgnore())
868     {
869         return;
870     }
871     SCOPED_TRACE(impl_->traceString(id));
872     bool        bFound      = false;
873     const int   prec        = std::numeric_limits<double>::digits10 + 2;
874     std::string strValue    = formatString("%.*g", prec, value);
875     std::string refStrValue =
876         impl_->processItem(Impl::cRealNodeName, id, strValue, &bFound);
877     if (bFound)
878     {
879         char  *endptr;
880         double refValue = std::strtod(refStrValue.c_str(), &endptr);
881         EXPECT_EQ('\0', *endptr);
882         if (impl_->bSelfTestMode_)
883         {
884             EXPECT_DOUBLE_EQ_TOL(refValue, value, impl_->defaultTolerance_)
885             << "String value: " << strValue << std::endl
886             << " Ref. string: " << refStrValue;
887         }
888         else
889         {
890             EXPECT_DOUBLE_EQ_TOL(refValue, value, impl_->defaultTolerance_);
891         }
892     }
893 }
894
895
896 void TestReferenceChecker::checkFloat(float value, const char *id)
897 {
898     if (impl_->shouldIgnore())
899     {
900         return;
901     }
902     SCOPED_TRACE(impl_->traceString(id));
903     bool        bFound      = false;
904     const int   prec        = std::numeric_limits<float>::digits10 + 2;
905     std::string strValue    = formatString("%.*g", prec, value);
906     std::string refStrValue =
907         impl_->processItem(Impl::cRealNodeName, id, strValue, &bFound);
908     if (bFound)
909     {
910         char  *endptr;
911         float  refValue = static_cast<float>(std::strtod(refStrValue.c_str(), &endptr));
912         EXPECT_EQ('\0', *endptr);
913         if (impl_->bSelfTestMode_)
914         {
915             EXPECT_FLOAT_EQ_TOL(refValue, value, impl_->defaultTolerance_)
916             << "String value: " << strValue << std::endl
917             << " Ref. string: " << refStrValue;
918         }
919         else
920         {
921             EXPECT_FLOAT_EQ_TOL(refValue, value, impl_->defaultTolerance_);
922         }
923     }
924 }
925
926
927 void TestReferenceChecker::checkReal(float value, const char *id)
928 {
929     checkFloat(value, id);
930 }
931
932
933 void TestReferenceChecker::checkReal(double value, const char *id)
934 {
935     checkDouble(value, id);
936 }
937
938
939 void TestReferenceChecker::checkVector(const int value[3], const char *id)
940 {
941     TestReferenceChecker compound(checkCompound(Impl::cVectorType, id));
942     compound.checkInteger(value[0], "X");
943     compound.checkInteger(value[1], "Y");
944     compound.checkInteger(value[2], "Z");
945 }
946
947
948 void TestReferenceChecker::checkVector(const float value[3], const char *id)
949 {
950     TestReferenceChecker compound(checkCompound(Impl::cVectorType, id));
951     compound.checkReal(value[0], "X");
952     compound.checkReal(value[1], "Y");
953     compound.checkReal(value[2], "Z");
954 }
955
956
957 void TestReferenceChecker::checkVector(const double value[3], const char *id)
958 {
959     TestReferenceChecker compound(checkCompound(Impl::cVectorType, id));
960     compound.checkReal(value[0], "X");
961     compound.checkReal(value[1], "Y");
962     compound.checkReal(value[2], "Z");
963 }
964
965
966 TestReferenceChecker
967 TestReferenceChecker::checkSequenceCompound(const char *id, size_t length)
968 {
969     TestReferenceChecker compound(checkCompound(Impl::cSequenceType, id));
970     compound.checkInteger(static_cast<int>(length), Impl::cSequenceLengthName);
971     return compound;
972 }
973
974 } // namespace test
975 } // namespace gmx