Moved generic simulationdatabase code to testutils.
[gromacs.git] / src / testutils / refdata.cpp
blobaf51f8a0280b788e618b0a4dfd18e91aa9858720
1 /*
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.
35 /*! \internal \file
36 * \brief
37 * Implements classes and functions from refdata.h.
39 * \author Teemu Murtola <teemu.murtola@gmail.com>
40 * \ingroup module_testutils
42 #include "gmxpre.h"
44 #include "refdata.h"
46 #include <cctype>
47 #include <cstdlib>
49 #include <algorithm>
50 #include <limits>
51 #include <string>
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"
72 namespace gmx
74 namespace test
77 /********************************************************************
78 * TestReferenceData::Impl declaration
81 namespace internal
84 /*! \internal \brief
85 * Private implementation class for TestReferenceData.
87 * \ingroup module_testutils
89 class TestReferenceDataImpl
91 public:
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_;
100 /*! \brief
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_;
110 /*! \brief
111 * Root entry for writing new reference data.
113 * Null if only comparing against existing data. Otherwise, starts
114 * always as empty.
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_;
122 /*! \brief
123 * Whether updating existing reference data.
125 bool updateMismatchingEntries_;
126 //! `true` if self-testing (enables extra failure messages).
127 bool bSelfTestMode_;
128 /*! \brief
129 * Whether any reference checkers have been created for this data.
131 bool bInUse_;
134 } // namespace internal
136 /********************************************************************
137 * Internal helpers
140 namespace
143 //! Convenience typedef for a smart pointer to TestReferenceDataImpl.
144 typedef std::shared_ptr<internal::TestReferenceDataImpl>
145 TestReferenceDataImplPointer;
147 /*! \brief
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
153 * test listener).
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)
177 if (g_referenceData)
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
190 public:
191 void OnTestEnd(const ::testing::TestInfo &test_info) override
193 if (g_referenceData)
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);
228 return;
230 int seqIndex = 0;
231 for (const auto &child : root.children())
233 std::string path;
234 if (child->id().empty())
236 path = formatSequenceEntryPath(rootPath, seqIndex);
237 ++seqIndex;
239 else
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())
254 std::string paths;
255 if (unusedPaths.size() > 5)
257 paths = joinStrings(unusedPaths.begin(), unusedPaths.begin() + 5, "\n ");
258 paths = " " + paths + "\n ...";
260 else
262 paths = joinStrings(unusedPaths.begin(), unusedPaths.end(), "\n ");
263 paths = " " + paths;
265 ADD_FAILURE() << "Reference data items not used in test:" << std::endl << paths;
269 } // namespace
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" };
276 options->addOption(
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
288 namespace internal
291 TestReferenceDataImpl::TestReferenceDataImpl(
292 ReferenceDataMode mode, bool bSelfTestMode)
293 : updateMismatchingEntries_(false), bSelfTestMode_(bSelfTestMode), bInUse_(false)
295 const std::string dirname =
296 bSelfTestMode
297 ? TestFileManager::getGlobalOutputTempDirectory()
298 : TestFileManager::getInputDataDirectory();
299 const std::string filename = TestFileManager::getTestSpecificFileName(".xml");
300 fullFilename_ = Path::join(dirname, "refdata", filename);
302 switch (mode)
304 case erefdataCompare:
305 if (File::exists(fullFilename_, File::throwOnError))
307 compareRootEntry_ = readReferenceDataFile(fullFilename_);
309 break;
310 case erefdataCreateMissing:
311 if (File::exists(fullFilename_, File::throwOnError))
313 compareRootEntry_ = readReferenceDataFile(fullFilename_);
315 else
317 compareRootEntry_ = ReferenceDataEntry::createRoot();
318 outputRootEntry_ = ReferenceDataEntry::createRoot();
320 break;
321 case erefdataUpdateChanged:
322 if (File::exists(fullFilename_, File::throwOnError))
324 compareRootEntry_ = readReferenceDataFile(fullFilename_);
326 else
328 compareRootEntry_ = ReferenceDataEntry::createRoot();
330 outputRootEntry_ = ReferenceDataEntry::createRoot();
331 updateMismatchingEntries_ = true;
332 break;
333 case erefdataUpdateAll:
334 compareRootEntry_ = ReferenceDataEntry::createRoot();
335 outputRootEntry_ = ReferenceDataEntry::createRoot();
336 break;
340 void TestReferenceDataImpl::onTestEnd(bool testPassed)
342 if (!bInUse_)
344 return;
346 // TODO: Only write the file with update-changed if there were actual changes.
347 if (outputRootEntry_)
349 if (testPassed)
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
375 /*! \internal \brief
376 * Private implementation class for TestReferenceChecker.
378 * \ingroup module_testutils
380 class TestReferenceChecker::Impl
382 public:
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());
425 return entry;
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);
444 /*! \brief
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
453 * instead).
455 ReferenceDataEntry *
456 findOrCreateEntry(const char *type, const char *id,
457 const IReferenceDataEntryChecker &checker);
458 /*! \brief
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
465 * type of the entry.
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
469 * reference data.
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);
480 /*! \brief
481 * Whether the checker is initialized.
483 bool initialized() const { return initialized_; }
484 /*! \brief
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.
499 bool initialized_;
500 //! Default floating-point comparison tolerance.
501 FloatingPointTolerance defaultTolerance_;
502 /*! \brief
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
507 * mismatches.
509 std::string path_;
510 /*! \brief
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_;
520 /*! \brief
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_;
528 /*! \brief
529 * Iterator to a child of \a compareRootEntry_ that was last found.
531 * If `compareRootEntry_->isValidChild()` returns false, no entry has
532 * been found yet.
533 * After every check, is updated to point to the entry that was used
534 * for the check.
535 * Subsequent checks start the search for the matching node on this
536 * node.
538 ReferenceDataEntry::ChildIterator lastFoundEntry_;
539 /*! \brief
540 * Whether the reference data is being written (true) or compared
541 * (false).
543 bool updateMismatchingEntries_;
544 //! `true` if self-testing (enables extra failure messages).
545 bool bSelfTestMode_;
546 /*! \brief
547 * Current number of unnamed elements in a sequence.
549 * It is the index of the current unnamed element.
551 int seqIndex_;
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)
590 std::string
591 TestReferenceChecker::Impl::appendPath(const char *id) const
593 return id != nullptr
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;
606 return entry->get();
608 return nullptr;
611 ReferenceDataEntry *
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();
622 return entry;
625 ::testing::AssertionResult
626 TestReferenceChecker::Impl::processItem(const char *type, const char *id,
627 const IReferenceDataEntryChecker &checker)
629 if (shouldIgnore())
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";
640 entry->setChecked();
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());
648 else
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);
660 result << std::endl
661 << "String value: '" << expected.value() << "'" << std::endl
662 << " Ref. string: '" << entry->value() << "'";
664 return result;
668 /********************************************************************
669 * TestReferenceData
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)
720 : 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_);
738 return *this;
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)
773 return bPresent;
775 ReferenceDataEntry::ChildIterator entry
776 = impl_->compareRootEntry_->findChild(id, impl_->lastFoundEntry_);
777 const bool bFound
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;
789 return true;
791 return false;
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);
802 NullChecker checker;
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));
809 entry->setChecked();
810 if (impl_->updateMismatchingEntries_)
812 entry->makeCompound(type);
814 else
816 ::testing::AssertionResult result(impl_->checkEntry(*entry, fullId, type, checker));
817 EXPECT_PLAIN(result);
818 if (!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
843 * problems.
845 * \todo Eliminate this limitation of TinyXML2. See
846 * e.g. https://github.com/leethomason/tinyxml2/issues/432
848 static void
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 " : "";
855 message += "ID ";
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,
888 const char *id)
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);
1005 else
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);
1034 else
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);
1046 return compound;
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"));
1056 int value = 0;
1057 EXPECT_PLAIN(impl_->processItem(Impl::cUCharNodeName, id,
1058 ValueExtractor<int>(&value)));
1059 return 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"));
1069 int value = 0;
1070 EXPECT_PLAIN(impl_->processItem(Impl::cIntegerNodeName, id,
1071 ValueExtractor<int>(&value)));
1072 return 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"));
1082 int64_t value = 0;
1083 EXPECT_PLAIN(impl_->processItem(Impl::cInt64NodeName, id,
1084 ValueExtractor<int64_t>(&value)));
1085 return 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"));
1095 float value = 0;
1096 EXPECT_PLAIN(impl_->processItem(Impl::cRealNodeName, id,
1097 ValueExtractor<float>(&value)));
1098 return 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"));
1108 double value = 0;
1109 EXPECT_PLAIN(impl_->processItem(Impl::cRealNodeName, id,
1110 ValueExtractor<double>(&value)));
1111 return 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"));
1121 std::string value;
1122 EXPECT_PLAIN(impl_->processItem(Impl::cStringNodeName, id,
1123 ValueExtractor<std::string>(&value)));
1124 return value;
1127 } // namespace test
1128 } // namespace gmx