2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2011,2012,2013,2014,2015,2016,2017,2018, 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
53 #include <gtest/gtest.h>
55 #include "gromacs/options/basicoptions.h"
56 #include "gromacs/options/ioptionscontainer.h"
57 #include "gromacs/utility/exceptions.h"
58 #include "gromacs/utility/gmxassert.h"
59 #include "gromacs/utility/keyvaluetree.h"
60 #include "gromacs/utility/path.h"
61 #include "gromacs/utility/real.h"
62 #include "gromacs/utility/stringutil.h"
63 #include "gromacs/utility/variant.h"
65 #include "testutils/refdata-checkers.h"
66 #include "testutils/refdata-impl.h"
67 #include "testutils/refdata-xml.h"
68 #include "testutils/testasserts.h"
69 #include "testutils/testexceptions.h"
70 #include "testutils/testfilemanager.h"
77 /********************************************************************
78 * TestReferenceData::Impl declaration
85 * Private implementation class for TestReferenceData.
87 * \ingroup module_testutils
89 class TestReferenceDataImpl
92 //! Initializes a checker in the given mode.
93 TestReferenceDataImpl(ReferenceDataMode mode
, bool bSelfTestMode
);
95 //! Performs final reference data processing when test ends.
96 void onTestEnd(bool testPassed
);
98 //! Full path of the reference data file.
99 std::string fullFilename_
;
101 * Root entry for comparing the reference data.
103 * Null after construction iff in compare mode and reference data was
104 * not loaded successfully.
105 * In all write modes, copies are present for nodes added to
106 * \a outputRootEntry_, and ReferenceDataEntry::correspondingOutputEntry()
107 * points to the copy in the output tree.
109 ReferenceDataEntry::EntryPointer compareRootEntry_
;
111 * Root entry for writing new reference data.
113 * Null if only comparing against existing data. Otherwise, starts
115 * When creating new reference data, this is maintained as a copy of
116 * \a compareRootEntry_.
117 * When updating existing data, entries are added either by copying
118 * from \a compareRootEntry_ (if they exist and comparison passes), or
119 * by creating new ones.
121 ReferenceDataEntry::EntryPointer outputRootEntry_
;
123 * Whether updating existing reference data.
125 bool updateMismatchingEntries_
;
126 //! `true` if self-testing (enables extra failure messages).
129 * Whether any reference checkers have been created for this data.
134 } // namespace internal
136 /********************************************************************
143 //! Convenience typedef for a smart pointer to TestReferenceDataImpl.
144 typedef std::shared_ptr
<internal::TestReferenceDataImpl
>
145 TestReferenceDataImplPointer
;
148 * Global reference data instance.
150 * The object is created when the test creates a TestReferenceData, and the
151 * object is destructed (and other post-processing is done) at the end of each
152 * test by ReferenceDataTestEventListener (which is installed as a Google Test
155 TestReferenceDataImplPointer g_referenceData
;
156 //! Global reference data mode set with setReferenceDataMode().
157 ReferenceDataMode g_referenceDataMode
= erefdataCompare
;
159 //! Returns the global reference data mode.
160 ReferenceDataMode
getReferenceDataMode()
162 return g_referenceDataMode
;
165 //! Returns a reference to the global reference data object.
166 TestReferenceDataImplPointer
initReferenceDataInstance()
168 GMX_RELEASE_ASSERT(!g_referenceData
,
169 "Test cannot create multiple TestReferenceData instances");
170 g_referenceData
.reset(new internal::TestReferenceDataImpl(getReferenceDataMode(), false));
171 return g_referenceData
;
174 //! Handles reference data creation for self-tests.
175 TestReferenceDataImplPointer
initReferenceDataInstanceForSelfTest(ReferenceDataMode mode
)
179 GMX_RELEASE_ASSERT(g_referenceData
.unique(),
180 "Test cannot create multiple TestReferenceData instances");
181 g_referenceData
->onTestEnd(true);
182 g_referenceData
.reset();
184 g_referenceData
.reset(new internal::TestReferenceDataImpl(mode
, true));
185 return g_referenceData
;
188 class ReferenceDataTestEventListener
: public ::testing::EmptyTestEventListener
191 void OnTestEnd(const ::testing::TestInfo
&test_info
) override
195 GMX_RELEASE_ASSERT(g_referenceData
.unique(),
196 "Test leaked TestRefeferenceData objects");
197 g_referenceData
->onTestEnd(test_info
.result()->Passed());
198 g_referenceData
.reset();
202 void OnTestProgramEnd(const ::testing::UnitTest
& /*unused*/) override
204 // Could be used e.g. to free internal buffers allocated by an XML parsing library
208 //! Formats a path to a reference data entry with a non-null id.
209 std::string
formatEntryPath(const std::string
&prefix
, const std::string
&id
)
211 return prefix
+ "/" + id
;
214 //! Formats a path to a reference data entry with a null id.
215 std::string
formatSequenceEntryPath(const std::string
&prefix
, int seqIndex
)
217 return formatString("%s/[%d]", prefix
.c_str(), seqIndex
+1);
220 //! Finds all entries that have not been checked under a given root.
221 void gatherUnusedEntries(const ReferenceDataEntry
&root
,
222 const std::string
&rootPath
,
223 std::vector
<std::string
> *unusedPaths
)
225 if (!root
.hasBeenChecked())
227 unusedPaths
->push_back(rootPath
);
231 for (const auto &child
: root
.children())
234 if (child
->id().empty())
236 path
= formatSequenceEntryPath(rootPath
, seqIndex
);
241 path
= formatEntryPath(rootPath
, child
->id());
243 gatherUnusedEntries(*child
, path
, unusedPaths
);
247 //! Produces a GTest assertion of any entries under given root have not been checked.
248 void checkUnusedEntries(const ReferenceDataEntry
&root
, const std::string
&rootPath
)
250 std::vector
<std::string
> unusedPaths
;
251 gatherUnusedEntries(root
, rootPath
, &unusedPaths
);
252 if (!unusedPaths
.empty())
255 if (unusedPaths
.size() > 5)
257 paths
= joinStrings(unusedPaths
.begin(), unusedPaths
.begin() + 5, "\n ");
258 paths
= " " + paths
+ "\n ...";
262 paths
= joinStrings(unusedPaths
.begin(), unusedPaths
.end(), "\n ");
265 ADD_FAILURE() << "Reference data items not used in test:" << std::endl
<< paths
;
271 void initReferenceData(IOptionsContainer
*options
)
273 // Needs to correspond to the enum order in refdata.h.
274 const char *const refDataEnum
[] =
275 { "check", "create", "update-changed", "update-all" };
277 EnumOption
<ReferenceDataMode
>("ref-data")
278 .enumValue(refDataEnum
).store(&g_referenceDataMode
)
279 .description("Operation mode for tests that use reference data"));
280 ::testing::UnitTest::GetInstance()->listeners().Append(
281 new ReferenceDataTestEventListener
);
284 /********************************************************************
285 * TestReferenceDataImpl definition
291 TestReferenceDataImpl::TestReferenceDataImpl(
292 ReferenceDataMode mode
, bool bSelfTestMode
)
293 : updateMismatchingEntries_(false), bSelfTestMode_(bSelfTestMode
), bInUse_(false)
295 const std::string dirname
=
297 ? TestFileManager::getGlobalOutputTempDirectory()
298 : TestFileManager::getInputDataDirectory();
299 const std::string filename
= TestFileManager::getTestSpecificFileName(".xml");
300 fullFilename_
= Path::join(dirname
, "refdata", filename
);
304 case erefdataCompare
:
305 if (File::exists(fullFilename_
, File::throwOnError
))
307 compareRootEntry_
= readReferenceDataFile(fullFilename_
);
310 case erefdataCreateMissing
:
311 if (File::exists(fullFilename_
, File::throwOnError
))
313 compareRootEntry_
= readReferenceDataFile(fullFilename_
);
317 compareRootEntry_
= ReferenceDataEntry::createRoot();
318 outputRootEntry_
= ReferenceDataEntry::createRoot();
321 case erefdataUpdateChanged
:
322 if (File::exists(fullFilename_
, File::throwOnError
))
324 compareRootEntry_
= readReferenceDataFile(fullFilename_
);
328 compareRootEntry_
= ReferenceDataEntry::createRoot();
330 outputRootEntry_
= ReferenceDataEntry::createRoot();
331 updateMismatchingEntries_
= true;
333 case erefdataUpdateAll
:
334 compareRootEntry_
= ReferenceDataEntry::createRoot();
335 outputRootEntry_
= ReferenceDataEntry::createRoot();
340 void TestReferenceDataImpl::onTestEnd(bool testPassed
)
346 // TODO: Only write the file with update-changed if there were actual changes.
347 if (outputRootEntry_
)
351 std::string dirname
= Path::getParentPath(fullFilename_
);
352 if (!Directory::exists(dirname
))
354 if (Directory::create(dirname
) != 0)
356 GMX_THROW(TestException("Creation of reference data directory failed: " + dirname
));
359 writeReferenceDataFile(fullFilename_
, *outputRootEntry_
);
362 else if (compareRootEntry_
)
364 checkUnusedEntries(*compareRootEntry_
, "");
368 } // namespace internal
371 /********************************************************************
372 * TestReferenceChecker::Impl
376 * Private implementation class for TestReferenceChecker.
378 * \ingroup module_testutils
380 class TestReferenceChecker::Impl
383 //! String constant for naming XML elements for boolean values.
384 static const char * const cBooleanNodeName
;
385 //! String constant for naming XML elements for string values.
386 static const char * const cStringNodeName
;
387 //! String constant for naming XML elements for unsigned char values.
388 static const char * const cUCharNodeName
;
389 //! String constant for naming XML elements for integer values.
390 static const char * const cIntegerNodeName
;
391 //! String constant for naming XML elements for int64 values.
392 static const char * const cInt64NodeName
;
393 //! String constant for naming XML elements for unsigned int64 values.
394 static const char * const cUInt64NodeName
;
395 //! String constant for naming XML elements for floating-point values.
396 static const char * const cRealNodeName
;
397 //! String constant for naming XML attribute for value identifiers.
398 static const char * const cIdAttrName
;
399 //! String constant for naming compounds for vectors.
400 static const char * const cVectorType
;
401 //! String constant for naming compounds for key-value tree objects.
402 static const char * const cObjectType
;
403 //! String constant for naming compounds for sequences.
404 static const char * const cSequenceType
;
405 //! String constant for value identifier for sequence length.
406 static const char * const cSequenceLengthName
;
408 //! Creates a checker that does nothing.
409 explicit Impl(bool initialized
);
410 //! Creates a checker with a given root entry.
411 Impl(const std::string
&path
, ReferenceDataEntry
*compareRootEntry
,
412 ReferenceDataEntry
*outputRootEntry
, bool updateMismatchingEntries
,
413 bool bSelfTestMode
, const FloatingPointTolerance
&defaultTolerance
);
415 //! Returns the path of this checker with \p id appended.
416 std::string
appendPath(const char *id
) const;
418 //! Creates an entry with given parameters and fills it with \p checker.
419 ReferenceDataEntry::EntryPointer
420 createEntry(const char *type
, const char *id
,
421 const IReferenceDataEntryChecker
&checker
) const
423 ReferenceDataEntry::EntryPointer
entry(new ReferenceDataEntry(type
, id
));
424 checker
.fillEntry(entry
.get());
427 //! Checks an entry for correct type and using \p checker.
428 ::testing::AssertionResult
429 checkEntry(const ReferenceDataEntry
&entry
, const std::string
&fullId
,
430 const char *type
, const IReferenceDataEntryChecker
&checker
) const
432 if (entry
.type() != type
)
434 return ::testing::AssertionFailure()
435 << "Mismatching reference data item type" << std::endl
436 << " In item: " << fullId
<< std::endl
437 << " Actual: " << type
<< std::endl
438 << "Reference: " << entry
.type();
440 return checker
.checkEntry(entry
, fullId
);
442 //! Finds an entry by id and updates the last found entry pointer.
443 ReferenceDataEntry
*findEntry(const char *id
);
445 * Finds/creates a reference data entry to match against.
447 * \param[in] type Type of entry to create.
448 * \param[in] id Unique identifier of the entry (can be NULL, in
449 * which case the next entry without an id is matched).
450 * \param[out] checker Checker to use for filling out created entries.
451 * \returns Matching entry, or NULL if no matching entry found
452 * (NULL is never returned in write mode; new entries are created
456 findOrCreateEntry(const char *type
, const char *id
,
457 const IReferenceDataEntryChecker
&checker
);
459 * Helper method for checking a reference data value.
461 * \param[in] name Type of entry to find.
462 * \param[in] id Unique identifier of the entry (can be NULL, in
463 * which case the next entry without an id is matched).
464 * \param[in] checker Checker that provides logic specific to the
466 * \returns Whether the reference data matched, including details
467 * of the mismatch if the comparison failed.
468 * \throws TestException if there is a problem parsing the
471 * Performs common tasks in checking a reference value, such as
472 * finding or creating the correct entry.
473 * Caller needs to provide a checker object that provides the string
474 * value for a newly created entry and performs the actual comparison
475 * against a found entry.
477 ::testing::AssertionResult
478 processItem(const char *name
, const char *id
,
479 const IReferenceDataEntryChecker
&checker
);
481 * Whether the checker is initialized.
483 bool initialized() const { return initialized_
; }
485 * Whether the checker should ignore all validation calls.
487 * This is used to ignore any calls within compounds for which
488 * reference data could not be found, such that only one error is
489 * issued for the missing compound, instead of every individual value.
491 bool shouldIgnore() const
493 GMX_RELEASE_ASSERT(initialized(),
494 "Accessing uninitialized reference data checker.");
495 return compareRootEntry_
== nullptr;
498 //! Whether initialized with other means than the default constructor.
500 //! Default floating-point comparison tolerance.
501 FloatingPointTolerance defaultTolerance_
;
503 * Human-readable path to the root node of this checker.
505 * For the root checker, this will be "/", and for each compound, the
506 * id of the compound is added. Used for reporting comparison
511 * Current entry under which reference data is searched for comparison.
513 * Points to either the TestReferenceDataImpl::compareRootEntry_, or to
514 * a compound entry in the tree rooted at that entry.
516 * Can be NULL, in which case this checker does nothing (doesn't even
517 * report errors, see shouldIgnore()).
519 ReferenceDataEntry
*compareRootEntry_
;
521 * Current entry under which entries for writing are created.
523 * Points to either the TestReferenceDataImpl::outputRootEntry_, or to
524 * a compound entry in the tree rooted at that entry. NULL if only
525 * comparing, or if shouldIgnore() returns `false`.
527 ReferenceDataEntry
*outputRootEntry_
;
529 * Iterator to a child of \a compareRootEntry_ that was last found.
531 * If `compareRootEntry_->isValidChild()` returns false, no entry has
533 * After every check, is updated to point to the entry that was used
535 * Subsequent checks start the search for the matching node on this
538 ReferenceDataEntry::ChildIterator lastFoundEntry_
;
540 * Whether the reference data is being written (true) or compared
543 bool updateMismatchingEntries_
;
544 //! `true` if self-testing (enables extra failure messages).
547 * Current number of unnamed elements in a sequence.
549 * It is the index of the current unnamed element.
554 const char *const TestReferenceChecker::Impl::cBooleanNodeName
= "Bool";
555 const char *const TestReferenceChecker::Impl::cStringNodeName
= "String";
556 const char *const TestReferenceChecker::Impl::cUCharNodeName
= "UChar";
557 const char *const TestReferenceChecker::Impl::cIntegerNodeName
= "Int";
558 const char *const TestReferenceChecker::Impl::cInt64NodeName
= "Int64";
559 const char *const TestReferenceChecker::Impl::cUInt64NodeName
= "UInt64";
560 const char *const TestReferenceChecker::Impl::cRealNodeName
= "Real";
561 const char *const TestReferenceChecker::Impl::cIdAttrName
= "Name";
562 const char *const TestReferenceChecker::Impl::cVectorType
= "Vector";
563 const char *const TestReferenceChecker::Impl::cObjectType
= "Object";
564 const char *const TestReferenceChecker::Impl::cSequenceType
= "Sequence";
565 const char *const TestReferenceChecker::Impl::cSequenceLengthName
= "Length";
568 TestReferenceChecker::Impl::Impl(bool initialized
)
569 : initialized_(initialized
), defaultTolerance_(defaultRealTolerance()),
570 compareRootEntry_(nullptr), outputRootEntry_(nullptr),
571 updateMismatchingEntries_(false), bSelfTestMode_(false), seqIndex_(-1)
576 TestReferenceChecker::Impl::Impl(const std::string
&path
,
577 ReferenceDataEntry
*compareRootEntry
,
578 ReferenceDataEntry
*outputRootEntry
,
579 bool updateMismatchingEntries
, bool bSelfTestMode
,
580 const FloatingPointTolerance
&defaultTolerance
)
581 : initialized_(true), defaultTolerance_(defaultTolerance
), path_(path
),
582 compareRootEntry_(compareRootEntry
), outputRootEntry_(outputRootEntry
),
583 lastFoundEntry_(compareRootEntry
->children().end()),
584 updateMismatchingEntries_(updateMismatchingEntries
),
585 bSelfTestMode_(bSelfTestMode
), seqIndex_(-1)
591 TestReferenceChecker::Impl::appendPath(const char *id
) const
594 ? formatEntryPath(path_
, id
)
595 : formatSequenceEntryPath(path_
, seqIndex_
);
599 ReferenceDataEntry
*TestReferenceChecker::Impl::findEntry(const char *id
)
601 ReferenceDataEntry::ChildIterator entry
= compareRootEntry_
->findChild(id
, lastFoundEntry_
);
602 seqIndex_
= (id
== nullptr) ? seqIndex_
+1 : -1;
603 if (compareRootEntry_
->isValidChild(entry
))
605 lastFoundEntry_
= entry
;
612 TestReferenceChecker::Impl::findOrCreateEntry(
613 const char *type
, const char *id
,
614 const IReferenceDataEntryChecker
&checker
)
616 ReferenceDataEntry
*entry
= findEntry(id
);
617 if (entry
== nullptr && outputRootEntry_
!= nullptr)
619 lastFoundEntry_
= compareRootEntry_
->addChild(createEntry(type
, id
, checker
));
620 entry
= lastFoundEntry_
->get();
625 ::testing::AssertionResult
626 TestReferenceChecker::Impl::processItem(const char *type
, const char *id
,
627 const IReferenceDataEntryChecker
&checker
)
631 return ::testing::AssertionSuccess();
633 std::string fullId
= appendPath(id
);
634 ReferenceDataEntry
*entry
= findOrCreateEntry(type
, id
, checker
);
635 if (entry
== nullptr)
637 return ::testing::AssertionFailure()
638 << "Reference data item " << fullId
<< " not found";
641 ::testing::AssertionResult
result(checkEntry(*entry
, fullId
, type
, checker
));
642 if (outputRootEntry_
!= nullptr && entry
->correspondingOutputEntry() == nullptr)
644 if (!updateMismatchingEntries_
|| result
)
646 outputRootEntry_
->addChild(entry
->cloneToOutputEntry());
650 ReferenceDataEntry::EntryPointer
outputEntry(createEntry(type
, id
, checker
));
651 entry
->setCorrespondingOutputEntry(outputEntry
.get());
652 outputRootEntry_
->addChild(move(outputEntry
));
653 return ::testing::AssertionSuccess();
656 if (bSelfTestMode_
&& !result
)
658 ReferenceDataEntry
expected(type
, id
);
659 checker
.fillEntry(&expected
);
661 << "String value: '" << expected
.value() << "'" << std::endl
662 << " Ref. string: '" << entry
->value() << "'";
668 /********************************************************************
672 TestReferenceData::TestReferenceData()
673 : impl_(initReferenceDataInstance())
678 TestReferenceData::TestReferenceData(ReferenceDataMode mode
)
679 : impl_(initReferenceDataInstanceForSelfTest(mode
))
684 TestReferenceData::~TestReferenceData()
689 TestReferenceChecker
TestReferenceData::rootChecker()
691 if (!impl_
->bInUse_
&& !impl_
->compareRootEntry_
)
693 ADD_FAILURE() << "Reference data file not found: "
694 << impl_
->fullFilename_
;
696 impl_
->bInUse_
= true;
697 if (!impl_
->compareRootEntry_
)
699 return TestReferenceChecker(new TestReferenceChecker::Impl(true));
701 impl_
->compareRootEntry_
->setChecked();
702 return TestReferenceChecker(
703 new TestReferenceChecker::Impl("", impl_
->compareRootEntry_
.get(),
704 impl_
->outputRootEntry_
.get(),
705 impl_
->updateMismatchingEntries_
, impl_
->bSelfTestMode_
,
706 defaultRealTolerance()));
710 /********************************************************************
711 * TestReferenceChecker
714 TestReferenceChecker::TestReferenceChecker()
715 : impl_(new Impl(false))
719 TestReferenceChecker::TestReferenceChecker(Impl
*impl
)
724 TestReferenceChecker::TestReferenceChecker(const TestReferenceChecker
&other
)
725 : impl_(new Impl(*other
.impl_
))
729 TestReferenceChecker::TestReferenceChecker(TestReferenceChecker
&&other
) noexcept
730 : impl_(std::move(other
.impl_
))
734 TestReferenceChecker
&
735 TestReferenceChecker::operator=(TestReferenceChecker
&&other
) noexcept
737 impl_
= std::move(other
.impl_
);
741 TestReferenceChecker::~TestReferenceChecker()
745 bool TestReferenceChecker::isValid() const
747 return impl_
->initialized();
751 void TestReferenceChecker::setDefaultTolerance(
752 const FloatingPointTolerance
&tolerance
)
754 impl_
->defaultTolerance_
= tolerance
;
758 void TestReferenceChecker::checkUnusedEntries()
760 if (impl_
->compareRootEntry_
)
762 gmx::test::checkUnusedEntries(*impl_
->compareRootEntry_
, impl_
->path_
);
763 // Mark them checked so that they are reported only once.
764 impl_
->compareRootEntry_
->setCheckedIncludingChildren();
769 bool TestReferenceChecker::checkPresent(bool bPresent
, const char *id
)
771 if (impl_
->shouldIgnore() || impl_
->outputRootEntry_
!= nullptr)
775 ReferenceDataEntry::ChildIterator entry
776 = impl_
->compareRootEntry_
->findChild(id
, impl_
->lastFoundEntry_
);
778 = impl_
->compareRootEntry_
->isValidChild(entry
);
779 if (bFound
!= bPresent
)
781 ADD_FAILURE() << "Mismatch while checking reference data item '"
782 << impl_
->appendPath(id
) << "'\n"
783 << "Expected: " << (bPresent
? "it is present.\n" : "it is absent.\n")
784 << " Actual: " << (bFound
? "it is present." : "it is absent.");
786 if (bFound
&& bPresent
)
788 impl_
->lastFoundEntry_
= entry
;
795 TestReferenceChecker
TestReferenceChecker::checkCompound(const char *type
, const char *id
)
797 if (impl_
->shouldIgnore())
799 return TestReferenceChecker(new Impl(true));
801 std::string fullId
= impl_
->appendPath(id
);
803 ReferenceDataEntry
*entry
= impl_
->findOrCreateEntry(type
, id
, checker
);
804 if (entry
== nullptr)
806 ADD_FAILURE() << "Reference data item " << fullId
<< " not found";
807 return TestReferenceChecker(new Impl(true));
810 if (impl_
->updateMismatchingEntries_
)
812 entry
->makeCompound(type
);
816 ::testing::AssertionResult
result(impl_
->checkEntry(*entry
, fullId
, type
, checker
));
817 EXPECT_PLAIN(result
);
820 return TestReferenceChecker(new Impl(true));
823 if (impl_
->outputRootEntry_
!= nullptr && entry
->correspondingOutputEntry() == nullptr)
825 impl_
->outputRootEntry_
->addChild(entry
->cloneToOutputEntry());
827 return TestReferenceChecker(
828 new Impl(fullId
, entry
, entry
->correspondingOutputEntry(),
829 impl_
->updateMismatchingEntries_
, impl_
->bSelfTestMode_
,
830 impl_
->defaultTolerance_
));
833 TestReferenceChecker
TestReferenceChecker::checkCompound(const char *type
, const std::string
&id
)
835 return checkCompound(type
, id
.c_str());
838 /*! \brief Throw a TestException if the caller tries to write particular refdata that can't work.
840 * If the string to write is non-empty and has only whitespace,
841 * TinyXML2 can't read it correctly, so throw an exception for this
842 * case, so that we can't accidentally use it and run into mysterious
845 * \todo Eliminate this limitation of TinyXML2. See
846 * e.g. https://github.com/leethomason/tinyxml2/issues/432
849 throwIfNonEmptyAndOnlyWhitespace(const std::string
&s
, const char *id
)
851 if (!s
.empty() && std::all_of(s
.cbegin(), s
.cend(), [](const char &c
){ return std::isspace(c
); }))
853 std::string
message("String '" + s
+ "' with ");
854 message
+= (id
!= nullptr) ? "null " : "";
856 message
+= (id
!= nullptr) ? "" : id
;
857 message
+= " cannot be handled. We must refuse to write a refdata String"
858 "field for a non-empty string that contains only whitespace, "
859 "because it will not be read correctly by TinyXML2.";
860 GMX_THROW(TestException(message
));
864 void TestReferenceChecker::checkBoolean(bool value
, const char *id
)
866 EXPECT_PLAIN(impl_
->processItem(Impl::cBooleanNodeName
, id
,
867 ExactStringChecker(value
? "true" : "false")));
871 void TestReferenceChecker::checkString(const char *value
, const char *id
)
873 throwIfNonEmptyAndOnlyWhitespace(value
, id
);
874 EXPECT_PLAIN(impl_
->processItem(Impl::cStringNodeName
, id
,
875 ExactStringChecker(value
)));
879 void TestReferenceChecker::checkString(const std::string
&value
, const char *id
)
881 throwIfNonEmptyAndOnlyWhitespace(value
, id
);
882 EXPECT_PLAIN(impl_
->processItem(Impl::cStringNodeName
, id
,
883 ExactStringChecker(value
)));
887 void TestReferenceChecker::checkTextBlock(const std::string
&value
,
890 EXPECT_PLAIN(impl_
->processItem(Impl::cStringNodeName
, id
,
891 ExactStringBlockChecker(value
)));
895 void TestReferenceChecker::checkUChar(unsigned char value
, const char *id
)
897 EXPECT_PLAIN(impl_
->processItem(Impl::cUCharNodeName
, id
,
898 ExactStringChecker(formatString("%d", value
))));
901 void TestReferenceChecker::checkInteger(int value
, const char *id
)
903 EXPECT_PLAIN(impl_
->processItem(Impl::cIntegerNodeName
, id
,
904 ExactStringChecker(formatString("%d", value
))));
907 void TestReferenceChecker::checkInt64(int64_t value
, const char *id
)
909 EXPECT_PLAIN(impl_
->processItem(Impl::cInt64NodeName
, id
,
910 ExactStringChecker(formatString("%" PRId64
, value
))));
913 void TestReferenceChecker::checkUInt64(uint64_t value
, const char *id
)
915 EXPECT_PLAIN(impl_
->processItem(Impl::cUInt64NodeName
, id
,
916 ExactStringChecker(formatString("%" PRIu64
, value
))));
919 void TestReferenceChecker::checkDouble(double value
, const char *id
)
921 FloatingPointChecker
<double> checker(value
, impl_
->defaultTolerance_
);
922 EXPECT_PLAIN(impl_
->processItem(Impl::cRealNodeName
, id
, checker
));
926 void TestReferenceChecker::checkFloat(float value
, const char *id
)
928 FloatingPointChecker
<float> checker(value
, impl_
->defaultTolerance_
);
929 EXPECT_PLAIN(impl_
->processItem(Impl::cRealNodeName
, id
, checker
));
933 void TestReferenceChecker::checkReal(float value
, const char *id
)
935 checkFloat(value
, id
);
939 void TestReferenceChecker::checkReal(double value
, const char *id
)
941 checkDouble(value
, id
);
945 void TestReferenceChecker::checkRealFromString(const std::string
&value
, const char *id
)
947 FloatingPointFromStringChecker
<real
> checker(value
, impl_
->defaultTolerance_
);
948 EXPECT_PLAIN(impl_
->processItem(Impl::cRealNodeName
, id
, checker
));
952 void TestReferenceChecker::checkVector(const int value
[3], const char *id
)
954 TestReferenceChecker
compound(checkCompound(Impl::cVectorType
, id
));
955 compound
.checkInteger(value
[0], "X");
956 compound
.checkInteger(value
[1], "Y");
957 compound
.checkInteger(value
[2], "Z");
961 void TestReferenceChecker::checkVector(const float value
[3], const char *id
)
963 TestReferenceChecker
compound(checkCompound(Impl::cVectorType
, id
));
964 compound
.checkReal(value
[0], "X");
965 compound
.checkReal(value
[1], "Y");
966 compound
.checkReal(value
[2], "Z");
970 void TestReferenceChecker::checkVector(const double value
[3], const char *id
)
972 TestReferenceChecker
compound(checkCompound(Impl::cVectorType
, id
));
973 compound
.checkReal(value
[0], "X");
974 compound
.checkReal(value
[1], "Y");
975 compound
.checkReal(value
[2], "Z");
979 void TestReferenceChecker::checkVariant(const Variant
&variant
, const char *id
)
981 if (variant
.isType
<bool>())
983 checkBoolean(variant
.cast
<bool>(), id
);
985 else if (variant
.isType
<int>())
987 checkInteger(variant
.cast
<int>(), id
);
989 else if (variant
.isType
<int64_t>())
991 checkInt64(variant
.cast
<int64_t>(), id
);
993 else if (variant
.isType
<float>())
995 checkFloat(variant
.cast
<float>(), id
);
997 else if (variant
.isType
<double>())
999 checkDouble(variant
.cast
<double>(), id
);
1001 else if (variant
.isType
<std::string
>())
1003 checkString(variant
.cast
<std::string
>(), id
);
1007 GMX_THROW(TestException("Unsupported variant type"));
1012 void TestReferenceChecker::checkKeyValueTreeObject(const KeyValueTreeObject
&tree
, const char *id
)
1014 TestReferenceChecker
compound(checkCompound(Impl::cObjectType
, id
));
1015 for (const auto &prop
: tree
.properties())
1017 compound
.checkKeyValueTreeValue(prop
.value(), prop
.key().c_str());
1019 compound
.checkUnusedEntries();
1023 void TestReferenceChecker::checkKeyValueTreeValue(const KeyValueTreeValue
&value
, const char *id
)
1025 if (value
.isObject())
1027 checkKeyValueTreeObject(value
.asObject(), id
);
1029 else if (value
.isArray())
1031 const auto &values
= value
.asArray().values();
1032 checkSequence(values
.begin(), values
.end(), id
);
1036 checkVariant(value
.asVariant(), id
);
1041 TestReferenceChecker
1042 TestReferenceChecker::checkSequenceCompound(const char *id
, size_t length
)
1044 TestReferenceChecker
compound(checkCompound(Impl::cSequenceType
, id
));
1045 compound
.checkInteger(static_cast<int>(length
), Impl::cSequenceLengthName
);
1050 unsigned char TestReferenceChecker::readUChar(const char *id
)
1052 if (impl_
->shouldIgnore())
1054 GMX_THROW(TestException("Trying to read from non-existent reference data value"));
1057 EXPECT_PLAIN(impl_
->processItem(Impl::cUCharNodeName
, id
,
1058 ValueExtractor
<int>(&value
)));
1063 int TestReferenceChecker::readInteger(const char *id
)
1065 if (impl_
->shouldIgnore())
1067 GMX_THROW(TestException("Trying to read from non-existent reference data value"));
1070 EXPECT_PLAIN(impl_
->processItem(Impl::cIntegerNodeName
, id
,
1071 ValueExtractor
<int>(&value
)));
1076 int64_t TestReferenceChecker::readInt64(const char *id
)
1078 if (impl_
->shouldIgnore())
1080 GMX_THROW(TestException("Trying to read from non-existent reference data value"));
1083 EXPECT_PLAIN(impl_
->processItem(Impl::cInt64NodeName
, id
,
1084 ValueExtractor
<int64_t>(&value
)));
1089 float TestReferenceChecker::readFloat(const char *id
)
1091 if (impl_
->shouldIgnore())
1093 GMX_THROW(TestException("Trying to read from non-existent reference data value"));
1096 EXPECT_PLAIN(impl_
->processItem(Impl::cRealNodeName
, id
,
1097 ValueExtractor
<float>(&value
)));
1102 double TestReferenceChecker::readDouble(const char *id
)
1104 if (impl_
->shouldIgnore())
1106 GMX_THROW(TestException("Trying to read from non-existent reference data value"));
1109 EXPECT_PLAIN(impl_
->processItem(Impl::cRealNodeName
, id
,
1110 ValueExtractor
<double>(&value
)));
1115 std::string
TestReferenceChecker::readString(const char *id
)
1117 if (impl_
->shouldIgnore())
1119 GMX_THROW(TestException("Trying to read from non-existent reference data value"));
1122 EXPECT_PLAIN(impl_
->processItem(Impl::cStringNodeName
, id
,
1123 ValueExtractor
<std::string
>(&value
)));