2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2011-2018, The GROMACS development team.
5 * Copyright (c) 2019,2020,2021, by the GROMACS development team, led by
6 * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
7 * and including many others, as listed in the AUTHORS file in the
8 * top-level source directory and at http://www.gromacs.org.
10 * GROMACS is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public License
12 * as published by the Free Software Foundation; either version 2.1
13 * of the License, or (at your option) any later version.
15 * GROMACS is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with GROMACS; if not, see
22 * http://www.gnu.org/licenses, or write to the Free Software Foundation,
23 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 * If you want to redistribute modifications to GROMACS, please
26 * consider that scientific software is very special. Version
27 * control is crucial - bugs must be traceable. We will be happy to
28 * consider code for inclusion in the official distribution, but
29 * derived work must not be called official GROMACS. Details are found
30 * in the README & COPYING files - if they are missing, get the
31 * official version at http://www.gromacs.org.
33 * To help us fund GROMACS development, we humbly ask that you cite
34 * the research papers on the package. Check out http://www.gromacs.org.
38 * Implements classes and functions from refdata.h.
40 * \author Teemu Murtola <teemu.murtola@gmail.com>
41 * \author Mark Abraham <mark.j.abraham@gmail.com>
42 * \ingroup module_testutils
46 #include "testutils/refdata.h"
56 #include <gtest/gtest.h>
58 #include "gromacs/math/vectypes.h"
59 #include "gromacs/options/basicoptions.h"
60 #include "gromacs/options/ioptionscontainer.h"
61 #include "gromacs/utility/any.h"
62 #include "gromacs/utility/exceptions.h"
63 #include "gromacs/utility/gmxassert.h"
64 #include "gromacs/utility/keyvaluetree.h"
65 #include "gromacs/utility/path.h"
66 #include "gromacs/utility/real.h"
67 #include "gromacs/utility/stringutil.h"
69 #include "testutils/testasserts.h"
70 #include "testutils/testexceptions.h"
71 #include "testutils/testfilemanager.h"
73 #include "refdata_checkers.h"
74 #include "refdata_impl.h"
75 #include "refdata_xml.h"
82 /********************************************************************
83 * TestReferenceData::Impl declaration
90 * Private implementation class for TestReferenceData.
92 * \ingroup module_testutils
94 class TestReferenceDataImpl
97 //! Initializes a checker in the given mode.
98 TestReferenceDataImpl(ReferenceDataMode mode, bool bSelfTestMode, std::optional<std::string> testNameOverride);
100 //! Performs final reference data processing when test ends.
101 void onTestEnd(bool testPassed) const;
103 //! Full path of the reference data file.
104 std::string fullFilename_;
106 * Root entry for comparing the reference data.
108 * Null after construction iff in compare mode and reference data was
109 * not loaded successfully.
110 * In all write modes, copies are present for nodes added to
111 * \a outputRootEntry_, and ReferenceDataEntry::correspondingOutputEntry()
112 * points to the copy in the output tree.
114 ReferenceDataEntry::EntryPointer compareRootEntry_;
116 * Root entry for writing new reference data.
118 * Null if only comparing against existing data. Otherwise, starts
120 * When creating new reference data, this is maintained as a copy of
121 * \a compareRootEntry_.
122 * When updating existing data, entries are added either by copying
123 * from \a compareRootEntry_ (if they exist and comparison passes), or
124 * by creating new ones.
126 ReferenceDataEntry::EntryPointer outputRootEntry_;
128 * Whether updating existing reference data.
130 bool updateMismatchingEntries_;
131 //! `true` if self-testing (enables extra failure messages).
134 * Whether any reference checkers have been created for this data.
139 } // namespace internal
141 /********************************************************************
148 //! Convenience typedef for a smart pointer to TestReferenceDataImpl.
149 typedef std::shared_ptr<internal::TestReferenceDataImpl> TestReferenceDataImplPointer;
152 * Global reference data instance.
154 * The object is created when the test creates a TestReferenceData, and the
155 * object is destructed (and other post-processing is done) at the end of each
156 * test by ReferenceDataTestEventListener (which is installed as a Google Test
159 TestReferenceDataImplPointer g_referenceData;
160 //! Global reference data mode set with setReferenceDataMode().
161 ReferenceDataMode g_referenceDataMode = ReferenceDataMode::Compare;
163 //! Returns the global reference data mode.
164 ReferenceDataMode getReferenceDataMode()
166 return g_referenceDataMode;
169 //! Returns a reference to the global reference data object.
170 TestReferenceDataImplPointer initReferenceDataInstance(std::optional<std::string> testNameOverride)
172 GMX_RELEASE_ASSERT(!g_referenceData, "Test cannot create multiple TestReferenceData instances");
173 g_referenceData.reset(new internal::TestReferenceDataImpl(
174 getReferenceDataMode(), false, std::move(testNameOverride)));
175 return g_referenceData;
178 //! Handles reference data creation for self-tests.
179 TestReferenceDataImplPointer initReferenceDataInstanceForSelfTest(ReferenceDataMode mode)
183 GMX_RELEASE_ASSERT(g_referenceData.unique(),
184 "Test cannot create multiple TestReferenceData instances");
185 g_referenceData->onTestEnd(true);
186 g_referenceData.reset();
188 g_referenceData.reset(new internal::TestReferenceDataImpl(mode, true, std::nullopt));
189 return g_referenceData;
192 class ReferenceDataTestEventListener : public ::testing::EmptyTestEventListener
195 void OnTestEnd(const ::testing::TestInfo& test_info) override
199 GMX_RELEASE_ASSERT(g_referenceData.unique(), "Test leaked TestRefeferenceData objects");
200 g_referenceData->onTestEnd(test_info.result()->Passed());
201 g_referenceData.reset();
205 void OnTestProgramEnd(const ::testing::UnitTest& /*unused*/) override
207 // Could be used e.g. to free internal buffers allocated by an XML parsing library
211 //! Formats a path to a reference data entry with a non-null id.
212 std::string formatEntryPath(const std::string& prefix, const std::string& id)
214 return prefix + "/" + id;
217 //! Formats a path to a reference data entry with a null id.
218 std::string formatSequenceEntryPath(const std::string& prefix, int seqIndex)
220 return formatString("%s/[%d]", prefix.c_str(), seqIndex + 1);
223 //! Finds all entries that have not been checked under a given root.
224 void gatherUnusedEntries(const ReferenceDataEntry& root,
225 const std::string& rootPath,
226 std::vector<std::string>* unusedPaths)
228 if (!root.hasBeenChecked())
230 unusedPaths->push_back(rootPath);
234 for (const auto& child : root.children())
237 if (child->id().empty())
239 path = formatSequenceEntryPath(rootPath, seqIndex);
244 path = formatEntryPath(rootPath, child->id());
246 gatherUnusedEntries(*child, path, unusedPaths);
250 //! Produces a GTest assertion of any entries under given root have not been checked.
251 void checkUnusedEntries(const ReferenceDataEntry& root, const std::string& rootPath)
253 std::vector<std::string> unusedPaths;
254 gatherUnusedEntries(root, rootPath, &unusedPaths);
255 if (!unusedPaths.empty())
258 if (unusedPaths.size() > 5)
260 paths = joinStrings(unusedPaths.begin(), unusedPaths.begin() + 5, "\n ");
261 paths = " " + paths + "\n ...";
265 paths = joinStrings(unusedPaths.begin(), unusedPaths.end(), "\n ");
268 ADD_FAILURE() << "Reference data items not used in test:" << std::endl << paths;
274 void initReferenceData(IOptionsContainer* options)
276 static const gmx::EnumerationArray<ReferenceDataMode, const char*> s_refDataNames = {
277 { "check", "create", "update-changed", "update-all" }
279 options->addOption(EnumOption<ReferenceDataMode>("ref-data")
280 .enumValue(s_refDataNames)
281 .store(&g_referenceDataMode)
282 .description("Operation mode for tests that use reference data"));
283 ::testing::UnitTest::GetInstance()->listeners().Append(new ReferenceDataTestEventListener);
286 /********************************************************************
287 * TestReferenceDataImpl definition
293 TestReferenceDataImpl::TestReferenceDataImpl(ReferenceDataMode mode,
295 std::optional<std::string> testNameOverride) :
296 updateMismatchingEntries_(false), bSelfTestMode_(bSelfTestMode), bInUse_(false)
298 const std::string dirname = bSelfTestMode ? TestFileManager::getGlobalOutputTempDirectory()
299 : TestFileManager::getInputDataDirectory();
300 const std::string filename = testNameOverride.has_value()
301 ? testNameOverride.value()
302 : TestFileManager::getTestSpecificFileName(".xml");
303 fullFilename_ = Path::join(dirname, "refdata", filename);
307 case ReferenceDataMode::Compare:
308 if (File::exists(fullFilename_, File::throwOnError))
310 compareRootEntry_ = readReferenceDataFile(fullFilename_);
313 case ReferenceDataMode::CreateMissing:
314 if (File::exists(fullFilename_, File::throwOnError))
316 compareRootEntry_ = readReferenceDataFile(fullFilename_);
320 compareRootEntry_ = ReferenceDataEntry::createRoot();
321 outputRootEntry_ = ReferenceDataEntry::createRoot();
324 case ReferenceDataMode::UpdateChanged:
325 if (File::exists(fullFilename_, File::throwOnError))
327 compareRootEntry_ = readReferenceDataFile(fullFilename_);
331 compareRootEntry_ = ReferenceDataEntry::createRoot();
333 outputRootEntry_ = ReferenceDataEntry::createRoot();
334 updateMismatchingEntries_ = true;
336 case ReferenceDataMode::UpdateAll:
337 compareRootEntry_ = ReferenceDataEntry::createRoot();
338 outputRootEntry_ = ReferenceDataEntry::createRoot();
340 case ReferenceDataMode::Count: GMX_THROW(InternalError("Invalid reference data mode"));
344 void TestReferenceDataImpl::onTestEnd(bool testPassed) const
350 // TODO: Only write the file with update-changed if there were actual changes.
351 if (outputRootEntry_)
355 std::string dirname = Path::getParentPath(fullFilename_);
356 if (!Directory::exists(dirname))
358 if (Directory::create(dirname) != 0)
360 GMX_THROW(TestException("Creation of reference data directory failed: " + dirname));
363 writeReferenceDataFile(fullFilename_, *outputRootEntry_);
366 else if (compareRootEntry_)
368 checkUnusedEntries(*compareRootEntry_, "");
372 } // namespace internal
375 /********************************************************************
376 * TestReferenceChecker::Impl
380 * Private implementation class for TestReferenceChecker.
382 * \ingroup module_testutils
384 class TestReferenceChecker::Impl
387 //! String constant for naming XML elements for boolean values.
388 static const char* const cBooleanNodeName;
389 //! String constant for naming XML elements for string values.
390 static const char* const cStringNodeName;
391 //! String constant for naming XML elements for unsigned char values.
392 static const char* const cUCharNodeName;
393 //! String constant for naming XML elements for integer values.
394 static const char* const cIntegerNodeName;
395 //! String constant for naming XML elements for int32 values.
396 static const char* const cInt32NodeName;
397 //! String constant for naming XML elements for unsigned int32 values.
398 static const char* const cUInt32NodeName;
399 //! String constant for naming XML elements for int32 values.
400 static const char* const cInt64NodeName;
401 //! String constant for naming XML elements for unsigned int64 values.
402 static const char* const cUInt64NodeName;
403 //! String constant for naming XML elements for floating-point values.
404 static const char* const cRealNodeName;
405 //! String constant for naming XML attribute for value identifiers.
406 static const char* const cIdAttrName;
407 //! String constant for naming compounds for vectors.
408 static const char* const cVectorType;
409 //! String constant for naming compounds for key-value tree objects.
410 static const char* const cObjectType;
411 //! String constant for naming compounds for sequences.
412 static const char* const cSequenceType;
413 //! String constant for value identifier for sequence length.
414 static const char* const cSequenceLengthName;
416 //! Creates a checker that does nothing.
417 explicit Impl(bool initialized);
418 //! Creates a checker with a given root entry.
419 Impl(const std::string& path,
420 ReferenceDataEntry* compareRootEntry,
421 ReferenceDataEntry* outputRootEntry,
422 bool updateMismatchingEntries,
424 const FloatingPointTolerance& defaultTolerance);
426 //! Returns the path of this checker with \p id appended.
427 std::string appendPath(const char* id) const;
429 //! Creates an entry with given parameters and fills it with \p checker.
430 static ReferenceDataEntry::EntryPointer createEntry(const char* type,
432 const IReferenceDataEntryChecker& checker)
434 ReferenceDataEntry::EntryPointer entry(new ReferenceDataEntry(type, id));
435 checker.fillEntry(entry.get());
438 //! Checks an entry for correct type and using \p checker.
439 static ::testing::AssertionResult checkEntry(const ReferenceDataEntry& entry,
440 const std::string& fullId,
442 const IReferenceDataEntryChecker& checker)
444 if (entry.type() != type)
446 return ::testing::AssertionFailure() << "Mismatching reference data item type" << std::endl
447 << " In item: " << fullId << std::endl
448 << " Actual: " << type << std::endl
449 << "Reference: " << entry.type();
451 return checker.checkEntry(entry, fullId);
453 //! Finds an entry by id and updates the last found entry pointer.
454 ReferenceDataEntry* findEntry(const char* id);
456 * Finds/creates a reference data entry to match against.
458 * \param[in] type Type of entry to create.
459 * \param[in] id Unique identifier of the entry (can be NULL, in
460 * which case the next entry without an id is matched).
461 * \param[out] checker Checker to use for filling out created entries.
462 * \returns Matching entry, or NULL if no matching entry found
463 * (NULL is never returned in write mode; new entries are created
466 ReferenceDataEntry* findOrCreateEntry(const char* type,
468 const IReferenceDataEntryChecker& checker);
470 * Helper method for checking a reference data value.
472 * \param[in] name Type of entry to find.
473 * \param[in] id Unique identifier of the entry (can be NULL, in
474 * which case the next entry without an id is matched).
475 * \param[in] checker Checker that provides logic specific to the
477 * \returns Whether the reference data matched, including details
478 * of the mismatch if the comparison failed.
479 * \throws TestException if there is a problem parsing the
482 * Performs common tasks in checking a reference value, such as
483 * finding or creating the correct entry.
484 * Caller needs to provide a checker object that provides the string
485 * value for a newly created entry and performs the actual comparison
486 * against a found entry.
488 ::testing::AssertionResult processItem(const char* name,
490 const IReferenceDataEntryChecker& checker);
492 * Whether the checker is initialized.
494 bool initialized() const { return initialized_; }
496 * Whether the checker should ignore all validation calls.
498 * This is used to ignore any calls within compounds for which
499 * reference data could not be found, such that only one error is
500 * issued for the missing compound, instead of every individual value.
502 bool shouldIgnore() const
504 GMX_RELEASE_ASSERT(initialized(), "Accessing uninitialized reference data checker.");
505 return compareRootEntry_ == nullptr;
508 //! Whether initialized with other means than the default constructor.
510 //! Default floating-point comparison tolerance.
511 FloatingPointTolerance defaultTolerance_;
513 * Human-readable path to the root node of this checker.
515 * For the root checker, this will be "/", and for each compound, the
516 * id of the compound is added. Used for reporting comparison
521 * Current entry under which reference data is searched for comparison.
523 * Points to either the TestReferenceDataImpl::compareRootEntry_, or to
524 * a compound entry in the tree rooted at that entry.
526 * Can be NULL, in which case this checker does nothing (doesn't even
527 * report errors, see shouldIgnore()).
529 ReferenceDataEntry* compareRootEntry_;
531 * Current entry under which entries for writing are created.
533 * Points to either the TestReferenceDataImpl::outputRootEntry_, or to
534 * a compound entry in the tree rooted at that entry. NULL if only
535 * comparing, or if shouldIgnore() returns `false`.
537 ReferenceDataEntry* outputRootEntry_;
539 * Iterator to a child of \a compareRootEntry_ that was last found.
541 * If `compareRootEntry_->isValidChild()` returns false, no entry has
543 * After every check, is updated to point to the entry that was used
545 * Subsequent checks start the search for the matching node on this
548 ReferenceDataEntry::ChildIterator lastFoundEntry_;
550 * Whether the reference data is being written (true) or compared
553 bool updateMismatchingEntries_;
554 //! `true` if self-testing (enables extra failure messages).
557 * Current number of unnamed elements in a sequence.
559 * It is the index of the current unnamed element.
564 const char* const TestReferenceChecker::Impl::cBooleanNodeName = "Bool";
565 const char* const TestReferenceChecker::Impl::cStringNodeName = "String";
566 const char* const TestReferenceChecker::Impl::cUCharNodeName = "UChar";
567 const char* const TestReferenceChecker::Impl::cIntegerNodeName = "Int";
568 const char* const TestReferenceChecker::Impl::cInt32NodeName = "Int32";
569 const char* const TestReferenceChecker::Impl::cUInt32NodeName = "UInt32";
570 const char* const TestReferenceChecker::Impl::cInt64NodeName = "Int64";
571 const char* const TestReferenceChecker::Impl::cUInt64NodeName = "UInt64";
572 const char* const TestReferenceChecker::Impl::cRealNodeName = "Real";
573 const char* const TestReferenceChecker::Impl::cIdAttrName = "Name";
574 const char* const TestReferenceChecker::Impl::cVectorType = "Vector";
575 const char* const TestReferenceChecker::Impl::cObjectType = "Object";
576 const char* const TestReferenceChecker::Impl::cSequenceType = "Sequence";
577 const char* const TestReferenceChecker::Impl::cSequenceLengthName = "Length";
580 TestReferenceChecker::Impl::Impl(bool initialized) :
581 initialized_(initialized),
582 defaultTolerance_(defaultRealTolerance()),
583 compareRootEntry_(nullptr),
584 outputRootEntry_(nullptr),
585 updateMismatchingEntries_(false),
586 bSelfTestMode_(false),
592 TestReferenceChecker::Impl::Impl(const std::string& path,
593 ReferenceDataEntry* compareRootEntry,
594 ReferenceDataEntry* outputRootEntry,
595 bool updateMismatchingEntries,
597 const FloatingPointTolerance& defaultTolerance) :
599 defaultTolerance_(defaultTolerance),
601 compareRootEntry_(compareRootEntry),
602 outputRootEntry_(outputRootEntry),
603 lastFoundEntry_(compareRootEntry->children().end()),
604 updateMismatchingEntries_(updateMismatchingEntries),
605 bSelfTestMode_(bSelfTestMode),
611 std::string TestReferenceChecker::Impl::appendPath(const char* id) const
613 return id != nullptr ? formatEntryPath(path_, id) : formatSequenceEntryPath(path_, seqIndex_);
617 ReferenceDataEntry* TestReferenceChecker::Impl::findEntry(const char* id)
619 ReferenceDataEntry::ChildIterator entry = compareRootEntry_->findChild(id, lastFoundEntry_);
620 seqIndex_ = (id == nullptr) ? seqIndex_ + 1 : -1;
621 if (compareRootEntry_->isValidChild(entry))
623 lastFoundEntry_ = entry;
629 ReferenceDataEntry* TestReferenceChecker::Impl::findOrCreateEntry(const char* type,
631 const IReferenceDataEntryChecker& checker)
633 ReferenceDataEntry* entry = findEntry(id);
634 if (entry == nullptr && outputRootEntry_ != nullptr)
636 lastFoundEntry_ = compareRootEntry_->addChild(createEntry(type, id, checker));
637 entry = lastFoundEntry_->get();
642 ::testing::AssertionResult TestReferenceChecker::Impl::processItem(const char* type,
644 const IReferenceDataEntryChecker& checker)
648 return ::testing::AssertionSuccess();
650 std::string fullId = appendPath(id);
651 ReferenceDataEntry* entry = findOrCreateEntry(type, id, checker);
652 if (entry == nullptr)
654 return ::testing::AssertionFailure() << "Reference data item " << fullId << " not found";
657 ::testing::AssertionResult result(checkEntry(*entry, fullId, type, checker));
658 if (outputRootEntry_ != nullptr && entry->correspondingOutputEntry() == nullptr)
660 if (!updateMismatchingEntries_ || result)
662 outputRootEntry_->addChild(entry->cloneToOutputEntry());
666 ReferenceDataEntry::EntryPointer outputEntry(createEntry(type, id, checker));
667 entry->setCorrespondingOutputEntry(outputEntry.get());
668 outputRootEntry_->addChild(move(outputEntry));
669 return ::testing::AssertionSuccess();
672 if (bSelfTestMode_ && !result)
674 ReferenceDataEntry expected(type, id);
675 checker.fillEntry(&expected);
677 << "String value: '" << expected.value() << "'" << std::endl
678 << " Ref. string: '" << entry->value() << "'";
684 /********************************************************************
688 TestReferenceData::TestReferenceData() : impl_(initReferenceDataInstance(std::nullopt)) {}
691 TestReferenceData::TestReferenceData(std::string testNameOverride) :
692 impl_(initReferenceDataInstance(std::move(testNameOverride)))
696 TestReferenceData::TestReferenceData(ReferenceDataMode mode) :
697 impl_(initReferenceDataInstanceForSelfTest(mode))
702 TestReferenceData::~TestReferenceData() {}
705 TestReferenceChecker TestReferenceData::rootChecker()
707 if (!impl_->bInUse_ && !impl_->compareRootEntry_)
709 ADD_FAILURE() << "Reference data file not found: " << impl_->fullFilename_;
711 impl_->bInUse_ = true;
712 if (!impl_->compareRootEntry_)
714 return TestReferenceChecker(new TestReferenceChecker::Impl(true));
716 impl_->compareRootEntry_->setChecked();
717 return TestReferenceChecker(new TestReferenceChecker::Impl("",
718 impl_->compareRootEntry_.get(),
719 impl_->outputRootEntry_.get(),
720 impl_->updateMismatchingEntries_,
721 impl_->bSelfTestMode_,
722 defaultRealTolerance()));
726 /********************************************************************
727 * TestReferenceChecker
730 TestReferenceChecker::TestReferenceChecker() : impl_(new Impl(false)) {}
732 TestReferenceChecker::TestReferenceChecker(Impl* impl) : impl_(impl) {}
734 TestReferenceChecker::TestReferenceChecker(const TestReferenceChecker& other) :
735 impl_(new Impl(*other.impl_))
739 TestReferenceChecker::TestReferenceChecker(TestReferenceChecker&& other) noexcept :
740 impl_(std::move(other.impl_))
744 TestReferenceChecker& TestReferenceChecker::operator=(TestReferenceChecker&& other) noexcept
746 impl_ = std::move(other.impl_);
750 TestReferenceChecker::~TestReferenceChecker() {}
752 bool TestReferenceChecker::isValid() const
754 return impl_->initialized();
758 void TestReferenceChecker::setDefaultTolerance(const FloatingPointTolerance& tolerance)
760 impl_->defaultTolerance_ = tolerance;
764 void TestReferenceChecker::checkUnusedEntries()
766 if (impl_->compareRootEntry_)
768 gmx::test::checkUnusedEntries(*impl_->compareRootEntry_, impl_->path_);
769 // Mark them checked so that they are reported only once.
770 impl_->compareRootEntry_->setCheckedIncludingChildren();
774 void TestReferenceChecker::disableUnusedEntriesCheck()
776 if (impl_->compareRootEntry_)
778 impl_->compareRootEntry_->setCheckedIncludingChildren();
783 bool TestReferenceChecker::checkPresent(bool bPresent, const char* id)
785 if (impl_->shouldIgnore() || impl_->outputRootEntry_ != nullptr)
789 ReferenceDataEntry::ChildIterator entry =
790 impl_->compareRootEntry_->findChild(id, impl_->lastFoundEntry_);
791 const bool bFound = impl_->compareRootEntry_->isValidChild(entry);
792 if (bFound != bPresent)
794 ADD_FAILURE() << "Mismatch while checking reference data item '" << impl_->appendPath(id) << "'\n"
795 << "Expected: " << (bPresent ? "it is present.\n" : "it is absent.\n")
796 << " Actual: " << (bFound ? "it is present." : "it is absent.");
798 if (bFound && bPresent)
800 impl_->lastFoundEntry_ = entry;
807 TestReferenceChecker TestReferenceChecker::checkCompound(const char* type, const char* id)
809 if (impl_->shouldIgnore())
811 return TestReferenceChecker(new Impl(true));
813 std::string fullId = impl_->appendPath(id);
815 ReferenceDataEntry* entry = impl_->findOrCreateEntry(type, id, checker);
816 if (entry == nullptr)
818 ADD_FAILURE() << "Reference data item " << fullId << " not found";
819 return TestReferenceChecker(new Impl(true));
822 if (impl_->updateMismatchingEntries_)
824 entry->makeCompound(type);
828 ::testing::AssertionResult result(impl_->checkEntry(*entry, fullId, type, checker));
829 EXPECT_PLAIN(result);
832 return TestReferenceChecker(new Impl(true));
835 if (impl_->outputRootEntry_ != nullptr && entry->correspondingOutputEntry() == nullptr)
837 impl_->outputRootEntry_->addChild(entry->cloneToOutputEntry());
839 return TestReferenceChecker(new Impl(fullId,
841 entry->correspondingOutputEntry(),
842 impl_->updateMismatchingEntries_,
843 impl_->bSelfTestMode_,
844 impl_->defaultTolerance_));
847 TestReferenceChecker TestReferenceChecker::checkCompound(const char* type, const std::string& id)
849 return checkCompound(type, id.c_str());
852 /*! \brief Throw a TestException if the caller tries to write particular refdata that can't work.
854 * If the string to write is non-empty and has only whitespace,
855 * TinyXML2 can't read it correctly, so throw an exception for this
856 * case, so that we can't accidentally use it and run into mysterious
859 * \todo Eliminate this limitation of TinyXML2. See
860 * e.g. https://github.com/leethomason/tinyxml2/issues/432
862 static void throwIfNonEmptyAndOnlyWhitespace(const std::string& s, const char* id)
864 if (!s.empty() && std::all_of(s.cbegin(), s.cend(), [](const char& c) { return std::isspace(c); }))
866 std::string message("String '" + s + "' with ");
867 message += (id != nullptr) ? "null " : "";
869 message += (id != nullptr) ? "" : id;
871 " cannot be handled. We must refuse to write a refdata String"
872 "field for a non-empty string that contains only whitespace, "
873 "because it will not be read correctly by TinyXML2.";
874 GMX_THROW(TestException(message));
878 void TestReferenceChecker::checkBoolean(bool value, const char* id)
880 EXPECT_PLAIN(impl_->processItem(
881 Impl::cBooleanNodeName, id, ExactStringChecker(value ? "true" : "false")));
885 void TestReferenceChecker::checkString(const char* value, const char* id)
887 throwIfNonEmptyAndOnlyWhitespace(value, id);
888 EXPECT_PLAIN(impl_->processItem(Impl::cStringNodeName, id, ExactStringChecker(value)));
892 void TestReferenceChecker::checkString(const std::string& value, const char* id)
894 throwIfNonEmptyAndOnlyWhitespace(value, id);
895 EXPECT_PLAIN(impl_->processItem(Impl::cStringNodeName, id, ExactStringChecker(value)));
899 void TestReferenceChecker::checkTextBlock(const std::string& value, const char* id)
901 EXPECT_PLAIN(impl_->processItem(Impl::cStringNodeName, id, ExactStringBlockChecker(value)));
905 void TestReferenceChecker::checkUChar(unsigned char value, const char* id)
907 EXPECT_PLAIN(impl_->processItem(
908 Impl::cUCharNodeName, id, ExactStringChecker(formatString("%d", value))));
911 void TestReferenceChecker::checkInteger(int value, const char* id)
913 EXPECT_PLAIN(impl_->processItem(
914 Impl::cIntegerNodeName, id, ExactStringChecker(formatString("%d", value))));
917 void TestReferenceChecker::checkInt32(int32_t value, const char* id)
919 EXPECT_PLAIN(impl_->processItem(
920 Impl::cInt32NodeName, id, ExactStringChecker(formatString("%" PRId32, value))));
923 void TestReferenceChecker::checkUInt32(uint32_t value, const char* id)
925 EXPECT_PLAIN(impl_->processItem(
926 Impl::cUInt32NodeName, id, ExactStringChecker(formatString("%" PRIu32, value))));
929 void TestReferenceChecker::checkInt64(int64_t value, const char* id)
931 EXPECT_PLAIN(impl_->processItem(
932 Impl::cInt64NodeName, id, ExactStringChecker(formatString("%" PRId64, value))));
935 void TestReferenceChecker::checkUInt64(uint64_t value, const char* id)
937 EXPECT_PLAIN(impl_->processItem(
938 Impl::cUInt64NodeName, id, ExactStringChecker(formatString("%" PRIu64, value))));
941 void TestReferenceChecker::checkDouble(double value, const char* id)
943 FloatingPointChecker<double> checker(value, impl_->defaultTolerance_);
944 EXPECT_PLAIN(impl_->processItem(Impl::cRealNodeName, id, checker));
948 void TestReferenceChecker::checkFloat(float value, const char* id)
950 FloatingPointChecker<float> checker(value, impl_->defaultTolerance_);
951 EXPECT_PLAIN(impl_->processItem(Impl::cRealNodeName, id, checker));
955 void TestReferenceChecker::checkReal(float value, const char* id)
957 checkFloat(value, id);
961 void TestReferenceChecker::checkReal(double value, const char* id)
963 checkDouble(value, id);
967 void TestReferenceChecker::checkRealFromString(const std::string& value, const char* id)
969 FloatingPointFromStringChecker<real> checker(value, impl_->defaultTolerance_);
970 EXPECT_PLAIN(impl_->processItem(Impl::cRealNodeName, id, checker));
974 void TestReferenceChecker::checkVector(const BasicVector<int>& value, const char* id)
976 TestReferenceChecker compound(checkCompound(Impl::cVectorType, id));
977 compound.checkInteger(value[0], "X");
978 compound.checkInteger(value[1], "Y");
979 compound.checkInteger(value[2], "Z");
983 void TestReferenceChecker::checkVector(const BasicVector<float>& value, const char* id)
985 TestReferenceChecker compound(checkCompound(Impl::cVectorType, id));
986 compound.checkReal(value[0], "X");
987 compound.checkReal(value[1], "Y");
988 compound.checkReal(value[2], "Z");
992 void TestReferenceChecker::checkVector(const BasicVector<double>& value, const char* id)
994 TestReferenceChecker compound(checkCompound(Impl::cVectorType, id));
995 compound.checkReal(value[0], "X");
996 compound.checkReal(value[1], "Y");
997 compound.checkReal(value[2], "Z");
1001 void TestReferenceChecker::checkVector(const int value[3], const char* id)
1003 checkVector(BasicVector<int>(value), id);
1007 void TestReferenceChecker::checkVector(const float value[3], const char* id)
1009 checkVector(BasicVector<float>(value), id);
1013 void TestReferenceChecker::checkVector(const double value[3], const char* id)
1015 checkVector(BasicVector<double>(value), id);
1019 void TestReferenceChecker::checkAny(const Any& any, const char* id)
1021 if (any.isType<bool>())
1023 checkBoolean(any.cast<bool>(), id);
1025 else if (any.isType<int>())
1027 checkInteger(any.cast<int>(), id);
1029 else if (any.isType<int32_t>())
1031 checkInt32(any.cast<int32_t>(), id);
1033 else if (any.isType<uint32_t>())
1035 checkInt32(any.cast<uint32_t>(), id);
1037 else if (any.isType<int64_t>())
1039 checkInt64(any.cast<int64_t>(), id);
1041 else if (any.isType<uint64_t>())
1043 checkInt64(any.cast<uint64_t>(), id);
1045 else if (any.isType<float>())
1047 checkFloat(any.cast<float>(), id);
1049 else if (any.isType<double>())
1051 checkDouble(any.cast<double>(), id);
1053 else if (any.isType<std::string>())
1055 checkString(any.cast<std::string>(), id);
1059 GMX_THROW(TestException("Unsupported any type"));
1064 void TestReferenceChecker::checkKeyValueTreeObject(const KeyValueTreeObject& tree, const char* id)
1066 TestReferenceChecker compound(checkCompound(Impl::cObjectType, id));
1067 for (const auto& prop : tree.properties())
1069 compound.checkKeyValueTreeValue(prop.value(), prop.key().c_str());
1071 compound.checkUnusedEntries();
1075 void TestReferenceChecker::checkKeyValueTreeValue(const KeyValueTreeValue& value, const char* id)
1077 if (value.isObject())
1079 checkKeyValueTreeObject(value.asObject(), id);
1081 else if (value.isArray())
1083 const auto& values = value.asArray().values();
1084 checkSequence(values.begin(), values.end(), id);
1088 checkAny(value.asAny(), id);
1093 TestReferenceChecker TestReferenceChecker::checkSequenceCompound(const char* id, size_t length)
1095 TestReferenceChecker compound(checkCompound(Impl::cSequenceType, id));
1096 compound.checkInteger(static_cast<int>(length), Impl::cSequenceLengthName);
1101 unsigned char TestReferenceChecker::readUChar(const char* id)
1103 if (impl_->shouldIgnore())
1105 GMX_THROW(TestException("Trying to read from non-existent reference data value"));
1108 EXPECT_PLAIN(impl_->processItem(Impl::cUCharNodeName, id, ValueExtractor<int>(&value)));
1113 int TestReferenceChecker::readInteger(const char* id)
1115 if (impl_->shouldIgnore())
1117 GMX_THROW(TestException("Trying to read from non-existent reference data value"));
1120 EXPECT_PLAIN(impl_->processItem(Impl::cIntegerNodeName, id, ValueExtractor<int>(&value)));
1125 int32_t TestReferenceChecker::readInt32(const char* id)
1127 if (impl_->shouldIgnore())
1129 GMX_THROW(TestException("Trying to read from non-existent reference data value"));
1132 EXPECT_PLAIN(impl_->processItem(Impl::cInt32NodeName, id, ValueExtractor<int32_t>(&value)));
1137 int64_t TestReferenceChecker::readInt64(const char* id)
1139 if (impl_->shouldIgnore())
1141 GMX_THROW(TestException("Trying to read from non-existent reference data value"));
1144 EXPECT_PLAIN(impl_->processItem(Impl::cInt64NodeName, id, ValueExtractor<int64_t>(&value)));
1149 float TestReferenceChecker::readFloat(const char* id)
1151 if (impl_->shouldIgnore())
1153 GMX_THROW(TestException("Trying to read from non-existent reference data value"));
1156 EXPECT_PLAIN(impl_->processItem(Impl::cRealNodeName, id, ValueExtractor<float>(&value)));
1161 double TestReferenceChecker::readDouble(const char* id)
1163 if (impl_->shouldIgnore())
1165 GMX_THROW(TestException("Trying to read from non-existent reference data value"));
1168 EXPECT_PLAIN(impl_->processItem(Impl::cRealNodeName, id, ValueExtractor<double>(&value)));
1173 std::string TestReferenceChecker::readString(const char* id)
1175 if (impl_->shouldIgnore())
1177 GMX_THROW(TestException("Trying to read from non-existent reference data value"));
1180 EXPECT_PLAIN(impl_->processItem(Impl::cStringNodeName, id, ValueExtractor<std::string>(&value)));