2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2011,2012,2013,2014,2015,2016,2017,2018,2019, 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/any.h"
58 #include "gromacs/utility/exceptions.h"
59 #include "gromacs/utility/gmxassert.h"
60 #include "gromacs/utility/keyvaluetree.h"
61 #include "gromacs/utility/path.h"
62 #include "gromacs/utility/real.h"
63 #include "gromacs/utility/stringutil.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 int32 values.
392 static const char * const cInt32NodeName;
393 //! String constant for naming XML elements for unsigned int32 values.
394 static const char * const cUInt32NodeName;
395 //! String constant for naming XML elements for int32 values.
396 static const char * const cInt64NodeName;
397 //! String constant for naming XML elements for unsigned int64 values.
398 static const char * const cUInt64NodeName;
399 //! String constant for naming XML elements for floating-point values.
400 static const char * const cRealNodeName;
401 //! String constant for naming XML attribute for value identifiers.
402 static const char * const cIdAttrName;
403 //! String constant for naming compounds for vectors.
404 static const char * const cVectorType;
405 //! String constant for naming compounds for key-value tree objects.
406 static const char * const cObjectType;
407 //! String constant for naming compounds for sequences.
408 static const char * const cSequenceType;
409 //! String constant for value identifier for sequence length.
410 static const char * const cSequenceLengthName;
412 //! Creates a checker that does nothing.
413 explicit Impl(bool initialized);
414 //! Creates a checker with a given root entry.
415 Impl(const std::string &path, ReferenceDataEntry *compareRootEntry,
416 ReferenceDataEntry *outputRootEntry, bool updateMismatchingEntries,
417 bool bSelfTestMode, const FloatingPointTolerance &defaultTolerance);
419 //! Returns the path of this checker with \p id appended.
420 std::string appendPath(const char *id) const;
422 //! Creates an entry with given parameters and fills it with \p checker.
423 ReferenceDataEntry::EntryPointer
424 createEntry(const char *type, const char *id,
425 const IReferenceDataEntryChecker &checker) const
427 ReferenceDataEntry::EntryPointer entry(new ReferenceDataEntry(type, id));
428 checker.fillEntry(entry.get());
431 //! Checks an entry for correct type and using \p checker.
432 ::testing::AssertionResult
433 checkEntry(const ReferenceDataEntry &entry, const std::string &fullId,
434 const char *type, const IReferenceDataEntryChecker &checker) const
436 if (entry.type() != type)
438 return ::testing::AssertionFailure()
439 << "Mismatching reference data item type" << std::endl
440 << " In item: " << fullId << std::endl
441 << " Actual: " << type << std::endl
442 << "Reference: " << entry.type();
444 return checker.checkEntry(entry, fullId);
446 //! Finds an entry by id and updates the last found entry pointer.
447 ReferenceDataEntry *findEntry(const char *id);
449 * Finds/creates a reference data entry to match against.
451 * \param[in] type Type of entry to create.
452 * \param[in] id Unique identifier of the entry (can be NULL, in
453 * which case the next entry without an id is matched).
454 * \param[out] checker Checker to use for filling out created entries.
455 * \returns Matching entry, or NULL if no matching entry found
456 * (NULL is never returned in write mode; new entries are created
460 findOrCreateEntry(const char *type, const char *id,
461 const IReferenceDataEntryChecker &checker);
463 * Helper method for checking a reference data value.
465 * \param[in] name Type of entry to find.
466 * \param[in] id Unique identifier of the entry (can be NULL, in
467 * which case the next entry without an id is matched).
468 * \param[in] checker Checker that provides logic specific to the
470 * \returns Whether the reference data matched, including details
471 * of the mismatch if the comparison failed.
472 * \throws TestException if there is a problem parsing the
475 * Performs common tasks in checking a reference value, such as
476 * finding or creating the correct entry.
477 * Caller needs to provide a checker object that provides the string
478 * value for a newly created entry and performs the actual comparison
479 * against a found entry.
481 ::testing::AssertionResult
482 processItem(const char *name, const char *id,
483 const IReferenceDataEntryChecker &checker);
485 * Whether the checker is initialized.
487 bool initialized() const { return initialized_; }
489 * Whether the checker should ignore all validation calls.
491 * This is used to ignore any calls within compounds for which
492 * reference data could not be found, such that only one error is
493 * issued for the missing compound, instead of every individual value.
495 bool shouldIgnore() const
497 GMX_RELEASE_ASSERT(initialized(),
498 "Accessing uninitialized reference data checker.");
499 return compareRootEntry_ == nullptr;
502 //! Whether initialized with other means than the default constructor.
504 //! Default floating-point comparison tolerance.
505 FloatingPointTolerance defaultTolerance_;
507 * Human-readable path to the root node of this checker.
509 * For the root checker, this will be "/", and for each compound, the
510 * id of the compound is added. Used for reporting comparison
515 * Current entry under which reference data is searched for comparison.
517 * Points to either the TestReferenceDataImpl::compareRootEntry_, or to
518 * a compound entry in the tree rooted at that entry.
520 * Can be NULL, in which case this checker does nothing (doesn't even
521 * report errors, see shouldIgnore()).
523 ReferenceDataEntry *compareRootEntry_;
525 * Current entry under which entries for writing are created.
527 * Points to either the TestReferenceDataImpl::outputRootEntry_, or to
528 * a compound entry in the tree rooted at that entry. NULL if only
529 * comparing, or if shouldIgnore() returns `false`.
531 ReferenceDataEntry *outputRootEntry_;
533 * Iterator to a child of \a compareRootEntry_ that was last found.
535 * If `compareRootEntry_->isValidChild()` returns false, no entry has
537 * After every check, is updated to point to the entry that was used
539 * Subsequent checks start the search for the matching node on this
542 ReferenceDataEntry::ChildIterator lastFoundEntry_;
544 * Whether the reference data is being written (true) or compared
547 bool updateMismatchingEntries_;
548 //! `true` if self-testing (enables extra failure messages).
551 * Current number of unnamed elements in a sequence.
553 * It is the index of the current unnamed element.
558 const char *const TestReferenceChecker::Impl::cBooleanNodeName = "Bool";
559 const char *const TestReferenceChecker::Impl::cStringNodeName = "String";
560 const char *const TestReferenceChecker::Impl::cUCharNodeName = "UChar";
561 const char *const TestReferenceChecker::Impl::cIntegerNodeName = "Int";
562 const char *const TestReferenceChecker::Impl::cInt32NodeName = "Int32";
563 const char *const TestReferenceChecker::Impl::cUInt32NodeName = "UInt32";
564 const char *const TestReferenceChecker::Impl::cInt64NodeName = "Int64";
565 const char *const TestReferenceChecker::Impl::cUInt64NodeName = "UInt64";
566 const char *const TestReferenceChecker::Impl::cRealNodeName = "Real";
567 const char *const TestReferenceChecker::Impl::cIdAttrName = "Name";
568 const char *const TestReferenceChecker::Impl::cVectorType = "Vector";
569 const char *const TestReferenceChecker::Impl::cObjectType = "Object";
570 const char *const TestReferenceChecker::Impl::cSequenceType = "Sequence";
571 const char *const TestReferenceChecker::Impl::cSequenceLengthName = "Length";
574 TestReferenceChecker::Impl::Impl(bool initialized)
575 : initialized_(initialized), defaultTolerance_(defaultRealTolerance()),
576 compareRootEntry_(nullptr), outputRootEntry_(nullptr),
577 updateMismatchingEntries_(false), bSelfTestMode_(false), seqIndex_(-1)
582 TestReferenceChecker::Impl::Impl(const std::string &path,
583 ReferenceDataEntry *compareRootEntry,
584 ReferenceDataEntry *outputRootEntry,
585 bool updateMismatchingEntries, bool bSelfTestMode,
586 const FloatingPointTolerance &defaultTolerance)
587 : initialized_(true), defaultTolerance_(defaultTolerance), path_(path),
588 compareRootEntry_(compareRootEntry), outputRootEntry_(outputRootEntry),
589 lastFoundEntry_(compareRootEntry->children().end()),
590 updateMismatchingEntries_(updateMismatchingEntries),
591 bSelfTestMode_(bSelfTestMode), seqIndex_(-1)
597 TestReferenceChecker::Impl::appendPath(const char *id) const
600 ? formatEntryPath(path_, id)
601 : formatSequenceEntryPath(path_, seqIndex_);
605 ReferenceDataEntry *TestReferenceChecker::Impl::findEntry(const char *id)
607 ReferenceDataEntry::ChildIterator entry = compareRootEntry_->findChild(id, lastFoundEntry_);
608 seqIndex_ = (id == nullptr) ? seqIndex_+1 : -1;
609 if (compareRootEntry_->isValidChild(entry))
611 lastFoundEntry_ = entry;
618 TestReferenceChecker::Impl::findOrCreateEntry(
619 const char *type, const char *id,
620 const IReferenceDataEntryChecker &checker)
622 ReferenceDataEntry *entry = findEntry(id);
623 if (entry == nullptr && outputRootEntry_ != nullptr)
625 lastFoundEntry_ = compareRootEntry_->addChild(createEntry(type, id, checker));
626 entry = lastFoundEntry_->get();
631 ::testing::AssertionResult
632 TestReferenceChecker::Impl::processItem(const char *type, const char *id,
633 const IReferenceDataEntryChecker &checker)
637 return ::testing::AssertionSuccess();
639 std::string fullId = appendPath(id);
640 ReferenceDataEntry *entry = findOrCreateEntry(type, id, checker);
641 if (entry == nullptr)
643 return ::testing::AssertionFailure()
644 << "Reference data item " << fullId << " not found";
647 ::testing::AssertionResult result(checkEntry(*entry, fullId, type, checker));
648 if (outputRootEntry_ != nullptr && entry->correspondingOutputEntry() == nullptr)
650 if (!updateMismatchingEntries_ || result)
652 outputRootEntry_->addChild(entry->cloneToOutputEntry());
656 ReferenceDataEntry::EntryPointer outputEntry(createEntry(type, id, checker));
657 entry->setCorrespondingOutputEntry(outputEntry.get());
658 outputRootEntry_->addChild(move(outputEntry));
659 return ::testing::AssertionSuccess();
662 if (bSelfTestMode_ && !result)
664 ReferenceDataEntry expected(type, id);
665 checker.fillEntry(&expected);
667 << "String value: '" << expected.value() << "'" << std::endl
668 << " Ref. string: '" << entry->value() << "'";
674 /********************************************************************
678 TestReferenceData::TestReferenceData()
679 : impl_(initReferenceDataInstance())
684 TestReferenceData::TestReferenceData(ReferenceDataMode mode)
685 : impl_(initReferenceDataInstanceForSelfTest(mode))
690 TestReferenceData::~TestReferenceData()
695 TestReferenceChecker TestReferenceData::rootChecker()
697 if (!impl_->bInUse_ && !impl_->compareRootEntry_)
699 ADD_FAILURE() << "Reference data file not found: "
700 << impl_->fullFilename_;
702 impl_->bInUse_ = true;
703 if (!impl_->compareRootEntry_)
705 return TestReferenceChecker(new TestReferenceChecker::Impl(true));
707 impl_->compareRootEntry_->setChecked();
708 return TestReferenceChecker(
709 new TestReferenceChecker::Impl("", impl_->compareRootEntry_.get(),
710 impl_->outputRootEntry_.get(),
711 impl_->updateMismatchingEntries_, impl_->bSelfTestMode_,
712 defaultRealTolerance()));
716 /********************************************************************
717 * TestReferenceChecker
720 TestReferenceChecker::TestReferenceChecker()
721 : impl_(new Impl(false))
725 TestReferenceChecker::TestReferenceChecker(Impl *impl)
730 TestReferenceChecker::TestReferenceChecker(const TestReferenceChecker &other)
731 : impl_(new Impl(*other.impl_))
735 TestReferenceChecker::TestReferenceChecker(TestReferenceChecker &&other) noexcept
736 : impl_(std::move(other.impl_))
740 TestReferenceChecker &
741 TestReferenceChecker::operator=(TestReferenceChecker &&other) noexcept
743 impl_ = std::move(other.impl_);
747 TestReferenceChecker::~TestReferenceChecker()
751 bool TestReferenceChecker::isValid() const
753 return impl_->initialized();
757 void TestReferenceChecker::setDefaultTolerance(
758 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();
775 bool TestReferenceChecker::checkPresent(bool bPresent, const char *id)
777 if (impl_->shouldIgnore() || impl_->outputRootEntry_ != nullptr)
781 ReferenceDataEntry::ChildIterator entry
782 = impl_->compareRootEntry_->findChild(id, impl_->lastFoundEntry_);
784 = impl_->compareRootEntry_->isValidChild(entry);
785 if (bFound != bPresent)
787 ADD_FAILURE() << "Mismatch while checking reference data item '"
788 << impl_->appendPath(id) << "'\n"
789 << "Expected: " << (bPresent ? "it is present.\n" : "it is absent.\n")
790 << " Actual: " << (bFound ? "it is present." : "it is absent.");
792 if (bFound && bPresent)
794 impl_->lastFoundEntry_ = entry;
801 TestReferenceChecker TestReferenceChecker::checkCompound(const char *type, const char *id)
803 if (impl_->shouldIgnore())
805 return TestReferenceChecker(new Impl(true));
807 std::string fullId = impl_->appendPath(id);
809 ReferenceDataEntry *entry = impl_->findOrCreateEntry(type, id, checker);
810 if (entry == nullptr)
812 ADD_FAILURE() << "Reference data item " << fullId << " not found";
813 return TestReferenceChecker(new Impl(true));
816 if (impl_->updateMismatchingEntries_)
818 entry->makeCompound(type);
822 ::testing::AssertionResult result(impl_->checkEntry(*entry, fullId, type, checker));
823 EXPECT_PLAIN(result);
826 return TestReferenceChecker(new Impl(true));
829 if (impl_->outputRootEntry_ != nullptr && entry->correspondingOutputEntry() == nullptr)
831 impl_->outputRootEntry_->addChild(entry->cloneToOutputEntry());
833 return TestReferenceChecker(
834 new Impl(fullId, entry, entry->correspondingOutputEntry(),
835 impl_->updateMismatchingEntries_, impl_->bSelfTestMode_,
836 impl_->defaultTolerance_));
839 TestReferenceChecker TestReferenceChecker::checkCompound(const char *type, const std::string &id)
841 return checkCompound(type, id.c_str());
844 /*! \brief Throw a TestException if the caller tries to write particular refdata that can't work.
846 * If the string to write is non-empty and has only whitespace,
847 * TinyXML2 can't read it correctly, so throw an exception for this
848 * case, so that we can't accidentally use it and run into mysterious
851 * \todo Eliminate this limitation of TinyXML2. See
852 * e.g. https://github.com/leethomason/tinyxml2/issues/432
855 throwIfNonEmptyAndOnlyWhitespace(const std::string &s, const char *id)
857 if (!s.empty() && std::all_of(s.cbegin(), s.cend(), [](const char &c){ return std::isspace(c); }))
859 std::string message("String '" + s + "' with ");
860 message += (id != nullptr) ? "null " : "";
862 message += (id != nullptr) ? "" : id;
863 message += " cannot be handled. We must refuse to write a refdata String"
864 "field for a non-empty string that contains only whitespace, "
865 "because it will not be read correctly by TinyXML2.";
866 GMX_THROW(TestException(message));
870 void TestReferenceChecker::checkBoolean(bool value, const char *id)
872 EXPECT_PLAIN(impl_->processItem(Impl::cBooleanNodeName, id,
873 ExactStringChecker(value ? "true" : "false")));
877 void TestReferenceChecker::checkString(const char *value, const char *id)
879 throwIfNonEmptyAndOnlyWhitespace(value, id);
880 EXPECT_PLAIN(impl_->processItem(Impl::cStringNodeName, id,
881 ExactStringChecker(value)));
885 void TestReferenceChecker::checkString(const std::string &value, const char *id)
887 throwIfNonEmptyAndOnlyWhitespace(value, id);
888 EXPECT_PLAIN(impl_->processItem(Impl::cStringNodeName, id,
889 ExactStringChecker(value)));
893 void TestReferenceChecker::checkTextBlock(const std::string &value,
896 EXPECT_PLAIN(impl_->processItem(Impl::cStringNodeName, id,
897 ExactStringBlockChecker(value)));
901 void TestReferenceChecker::checkUChar(unsigned char value, const char *id)
903 EXPECT_PLAIN(impl_->processItem(Impl::cUCharNodeName, id,
904 ExactStringChecker(formatString("%d", value))));
907 void TestReferenceChecker::checkInteger(int value, const char *id)
909 EXPECT_PLAIN(impl_->processItem(Impl::cIntegerNodeName, id,
910 ExactStringChecker(formatString("%d", value))));
913 void TestReferenceChecker::checkInt32(int32_t value, const char *id)
915 EXPECT_PLAIN(impl_->processItem(Impl::cInt32NodeName, id,
916 ExactStringChecker(formatString("%" PRId32, value))));
919 void TestReferenceChecker::checkUInt32(uint32_t value, const char *id)
921 EXPECT_PLAIN(impl_->processItem(Impl::cUInt32NodeName, id,
922 ExactStringChecker(formatString("%" PRIu32, value))));
925 void TestReferenceChecker::checkInt64(int64_t value, const char *id)
927 EXPECT_PLAIN(impl_->processItem(Impl::cInt64NodeName, id,
928 ExactStringChecker(formatString("%" PRId64, value))));
931 void TestReferenceChecker::checkUInt64(uint64_t value, const char *id)
933 EXPECT_PLAIN(impl_->processItem(Impl::cUInt64NodeName, id,
934 ExactStringChecker(formatString("%" PRIu64, value))));
937 void TestReferenceChecker::checkDouble(double value, const char *id)
939 FloatingPointChecker<double> checker(value, impl_->defaultTolerance_);
940 EXPECT_PLAIN(impl_->processItem(Impl::cRealNodeName, id, checker));
944 void TestReferenceChecker::checkFloat(float value, const char *id)
946 FloatingPointChecker<float> checker(value, impl_->defaultTolerance_);
947 EXPECT_PLAIN(impl_->processItem(Impl::cRealNodeName, id, checker));
951 void TestReferenceChecker::checkReal(float value, const char *id)
953 checkFloat(value, id);
957 void TestReferenceChecker::checkReal(double value, const char *id)
959 checkDouble(value, id);
963 void TestReferenceChecker::checkRealFromString(const std::string &value, const char *id)
965 FloatingPointFromStringChecker<real> checker(value, impl_->defaultTolerance_);
966 EXPECT_PLAIN(impl_->processItem(Impl::cRealNodeName, id, checker));
970 void TestReferenceChecker::checkVector(const int value[3], const char *id)
972 TestReferenceChecker compound(checkCompound(Impl::cVectorType, id));
973 compound.checkInteger(value[0], "X");
974 compound.checkInteger(value[1], "Y");
975 compound.checkInteger(value[2], "Z");
979 void TestReferenceChecker::checkVector(const float value[3], const char *id)
981 TestReferenceChecker compound(checkCompound(Impl::cVectorType, id));
982 compound.checkReal(value[0], "X");
983 compound.checkReal(value[1], "Y");
984 compound.checkReal(value[2], "Z");
988 void TestReferenceChecker::checkVector(const double value[3], const char *id)
990 TestReferenceChecker compound(checkCompound(Impl::cVectorType, id));
991 compound.checkReal(value[0], "X");
992 compound.checkReal(value[1], "Y");
993 compound.checkReal(value[2], "Z");
997 void TestReferenceChecker::checkAny(const Any &any, const char *id)
999 if (any.isType<bool>())
1001 checkBoolean(any.cast<bool>(), id);
1003 else if (any.isType<int>())
1005 checkInteger(any.cast<int>(), id);
1007 else if (any.isType<int32_t>())
1009 checkInt32(any.cast<int32_t>(), id);
1011 else if (any.isType<uint32_t>())
1013 checkInt32(any.cast<uint32_t>(), id);
1015 else if (any.isType<int64_t>())
1017 checkInt64(any.cast<int64_t>(), id);
1019 else if (any.isType<uint64_t>())
1021 checkInt64(any.cast<uint64_t>(), id);
1023 else if (any.isType<float>())
1025 checkFloat(any.cast<float>(), id);
1027 else if (any.isType<double>())
1029 checkDouble(any.cast<double>(), id);
1031 else if (any.isType<std::string>())
1033 checkString(any.cast<std::string>(), id);
1037 GMX_THROW(TestException("Unsupported any type"));
1042 void TestReferenceChecker::checkKeyValueTreeObject(const KeyValueTreeObject &tree, const char *id)
1044 TestReferenceChecker compound(checkCompound(Impl::cObjectType, id));
1045 for (const auto &prop : tree.properties())
1047 compound.checkKeyValueTreeValue(prop.value(), prop.key().c_str());
1049 compound.checkUnusedEntries();
1053 void TestReferenceChecker::checkKeyValueTreeValue(const KeyValueTreeValue &value, const char *id)
1055 if (value.isObject())
1057 checkKeyValueTreeObject(value.asObject(), id);
1059 else if (value.isArray())
1061 const auto &values = value.asArray().values();
1062 checkSequence(values.begin(), values.end(), id);
1066 checkAny(value.asAny(), id);
1071 TestReferenceChecker
1072 TestReferenceChecker::checkSequenceCompound(const char *id, size_t length)
1074 TestReferenceChecker compound(checkCompound(Impl::cSequenceType, id));
1075 compound.checkInteger(static_cast<int>(length), Impl::cSequenceLengthName);
1080 unsigned char TestReferenceChecker::readUChar(const char *id)
1082 if (impl_->shouldIgnore())
1084 GMX_THROW(TestException("Trying to read from non-existent reference data value"));
1087 EXPECT_PLAIN(impl_->processItem(Impl::cUCharNodeName, id,
1088 ValueExtractor<int>(&value)));
1093 int TestReferenceChecker::readInteger(const char *id)
1095 if (impl_->shouldIgnore())
1097 GMX_THROW(TestException("Trying to read from non-existent reference data value"));
1100 EXPECT_PLAIN(impl_->processItem(Impl::cIntegerNodeName, id,
1101 ValueExtractor<int>(&value)));
1106 int32_t TestReferenceChecker::readInt32(const char *id)
1108 if (impl_->shouldIgnore())
1110 GMX_THROW(TestException("Trying to read from non-existent reference data value"));
1113 EXPECT_PLAIN(impl_->processItem(Impl::cInt32NodeName, id,
1114 ValueExtractor<int32_t>(&value)));
1119 int64_t TestReferenceChecker::readInt64(const char *id)
1121 if (impl_->shouldIgnore())
1123 GMX_THROW(TestException("Trying to read from non-existent reference data value"));
1126 EXPECT_PLAIN(impl_->processItem(Impl::cInt64NodeName, id,
1127 ValueExtractor<int64_t>(&value)));
1132 float TestReferenceChecker::readFloat(const char *id)
1134 if (impl_->shouldIgnore())
1136 GMX_THROW(TestException("Trying to read from non-existent reference data value"));
1139 EXPECT_PLAIN(impl_->processItem(Impl::cRealNodeName, id,
1140 ValueExtractor<float>(&value)));
1145 double TestReferenceChecker::readDouble(const char *id)
1147 if (impl_->shouldIgnore())
1149 GMX_THROW(TestException("Trying to read from non-existent reference data value"));
1152 EXPECT_PLAIN(impl_->processItem(Impl::cRealNodeName, id,
1153 ValueExtractor<double>(&value)));
1158 std::string TestReferenceChecker::readString(const char *id)
1160 if (impl_->shouldIgnore())
1162 GMX_THROW(TestException("Trying to read from non-existent reference data value"));
1165 EXPECT_PLAIN(impl_->processItem(Impl::cStringNodeName, id,
1166 ValueExtractor<std::string>(&value)));