test. The test can naturally also use its own test assertions for additional
checks, but any mismatch will automatically also fail the test.
+It is also possible to read values of the reference data items using
+gmx::test::TestReferenceChecker, so that they can be used programmatically.
+For this to work, those items should first be written in the same test.
+This supports tests that want to both check data against a reference, and use
+that reference as a persistence layer for storing information. This is useful
+at least for serialization tests.
+This is currently not supported for all use cases, but with some caveats, it is
+possible to use this for testing.
+
When using floating-point values in reference data, the tolerance for the
comparison can be influenced with
gmx::test::TestReferenceChecker::setDefaultTolerance().
#include "gromacs/utility/basedefinitions.h"
#include "gromacs/utility/exceptions.h"
+#include "gromacs/utility/strconvert.h"
#include "gromacs/utility/stringutil.h"
#include "testutils/refdata-impl.h"
std::string value_;
};
-//! Helper function to parse a floating-point value.
-// TODO: Move this into src/gromacs/utility/, and consolidate with similar code
-// elsewhere.
-double convertDouble(const std::string &value)
-{
- char *endptr;
- double convertedValue = std::strtod(value.c_str(), &endptr);
- // TODO: Check for overflow
- if (*endptr != '\0')
- {
- GMX_THROW(InvalidInputError("Invalid floating-point value: " + value));
- }
- return convertedValue;
-}
//! Helper function to parse a floating-point reference data value.
double convertDoubleReferenceValue(const std::string &value)
{
try
{
- return convertDouble(value);
+ return fromString<double>(value);
}
catch (const InvalidInputError &ex)
{
virtual ::testing::AssertionResult
checkEntry(const ReferenceDataEntry &entry, const std::string &fullId) const
{
- FloatType value = static_cast<FloatType>(convertDouble(value_));
+ FloatType value = fromString<FloatType>(value_);
FloatType refValue = static_cast<FloatType>(convertDoubleReferenceValue(entry.value()));
FloatingPointDifference diff(refValue, value);
if (tolerance_.isWithin(diff))
FloatingPointTolerance tolerance_;
};
+template <typename ValueType>
+class ValueExtractor : public IReferenceDataEntryChecker
+{
+ public:
+ explicit ValueExtractor(ValueType *value)
+ : value_(value)
+ {
+ }
+
+ virtual void fillEntry(ReferenceDataEntry *) const
+ {
+ GMX_THROW(TestException("Extracting value from non-existent reference data entry"));
+ }
+ virtual ::testing::AssertionResult
+ checkEntry(const ReferenceDataEntry &entry, const std::string &) const
+ {
+ extractValue(entry.value());
+ return ::testing::AssertionSuccess();
+ }
+
+ void extractValue(const std::string &value) const
+ {
+ *value_ = fromString<ValueType>(value);
+ }
+
+ private:
+ ValueType *value_;
+};
+
+template <> inline void
+ValueExtractor<std::string>::extractValue(const std::string &value) const
+{
+ *value_ = value;
+}
+
} // namespace test
} // namespace gmx
static const char * const cBooleanNodeName;
//! String constant for naming XML elements for string values.
static const char * const cStringNodeName;
+ //! String constant for naming XML elements for unsigned char values.
+ static const char * const cUCharNodeName;
//! String constant for naming XML elements for integer values.
static const char * const cIntegerNodeName;
//! String constant for naming XML elements for int64 values.
const char *const TestReferenceChecker::Impl::cBooleanNodeName = "Bool";
const char *const TestReferenceChecker::Impl::cStringNodeName = "String";
+const char *const TestReferenceChecker::Impl::cUCharNodeName = "UChar";
const char *const TestReferenceChecker::Impl::cIntegerNodeName = "Int";
const char *const TestReferenceChecker::Impl::cInt64NodeName = "Int64";
const char *const TestReferenceChecker::Impl::cUInt64NodeName = "UInt64";
}
+void TestReferenceChecker::checkUChar(unsigned char value, const char *id)
+{
+ EXPECT_PLAIN(impl_->processItem(Impl::cUCharNodeName, id,
+ ExactStringChecker(formatString("%d", value))));
+}
+
void TestReferenceChecker::checkInteger(int value, const char *id)
{
EXPECT_PLAIN(impl_->processItem(Impl::cIntegerNodeName, id,
return compound;
}
+
+unsigned char TestReferenceChecker::readUChar(const char *id)
+{
+ if (impl_->shouldIgnore())
+ {
+ GMX_THROW(TestException("Trying to read from non-existent reference data value"));
+ }
+ int value = 0;
+ EXPECT_PLAIN(impl_->processItem(Impl::cUCharNodeName, id,
+ ValueExtractor<int>(&value)));
+ return value;
+}
+
+
+int TestReferenceChecker::readInteger(const char *id)
+{
+ if (impl_->shouldIgnore())
+ {
+ GMX_THROW(TestException("Trying to read from non-existent reference data value"));
+ }
+ int value = 0;
+ EXPECT_PLAIN(impl_->processItem(Impl::cIntegerNodeName, id,
+ ValueExtractor<int>(&value)));
+ return value;
+}
+
+
+float TestReferenceChecker::readFloat(const char *id)
+{
+ if (impl_->shouldIgnore())
+ {
+ GMX_THROW(TestException("Trying to read from non-existent reference data value"));
+ }
+ float value = 0;
+ EXPECT_PLAIN(impl_->processItem(Impl::cRealNodeName, id,
+ ValueExtractor<float>(&value)));
+ return value;
+}
+
+
+double TestReferenceChecker::readDouble(const char *id)
+{
+ if (impl_->shouldIgnore())
+ {
+ GMX_THROW(TestException("Trying to read from non-existent reference data value"));
+ }
+ double value = 0;
+ EXPECT_PLAIN(impl_->processItem(Impl::cRealNodeName, id,
+ ValueExtractor<double>(&value)));
+ return value;
+}
+
+
+std::string TestReferenceChecker::readString(const char *id)
+{
+ if (impl_->shouldIgnore())
+ {
+ GMX_THROW(TestException("Trying to read from non-existent reference data value"));
+ }
+ std::string value;
+ EXPECT_PLAIN(impl_->processItem(Impl::cStringNodeName, id,
+ ValueExtractor<std::string>(&value)));
+ return value;
+}
+
} // namespace test
} // namespace gmx
* is easier to edit by hand to set the desired output formatting.
*/
void checkTextBlock(const std::string &value, const char *id);
+ //! Check a single unsigned char value.
+ void checkUChar(unsigned char value, const char *id);
//! Check a single integer value.
void checkInteger(int value, const char *id);
//! Check a single int64 value.
//! Checks a generic key-value tree value.
void checkKeyValueTreeValue(const KeyValueTreeValue &value, const char *id);
+ /*! \name Methods to read values from reference data
+ *
+ * These methods assume that a value with the given `id` has already
+ * been created in the test with `check*()` methods, and that it has
+ * the correct type.
+ *
+ * Currently, these methods do not work correctly if the reference data
+ * file does not exist, so a test using them may fail with exceptions
+ * before the reference data has been generated.
+ * \{
+ */
+ //! Reads an unsigned char value.
+ unsigned char readUChar(const char *id);
+ //! Reads an integer value.
+ int readInteger(const char *id);
+ //! Reads a float value.
+ float readFloat(const char *id);
+ //! Reads a double value.
+ double readDouble(const char *id);
+ //! Reads a string value.
+ std::string readString(const char *id);
+ //! \}
+
/*! \name Overloaded versions of simple checker methods
*
* These methods provide overloads under a single name for all the
}
+TEST(ReferenceDataTest, HandlesReadingValues)
+{
+ {
+ TestReferenceData data(gmx::test::erefdataUpdateAll);
+ TestReferenceChecker checker(data.rootChecker());
+ checker.checkUChar('A', "char");
+ checker.checkInteger(1, "int");
+ checker.checkString("Test", "string");
+ }
+ {
+ TestReferenceData data(gmx::test::erefdataCompare);
+ TestReferenceChecker checker(data.rootChecker());
+ EXPECT_EQ('A', checker.readUChar("char"));
+ EXPECT_EQ(1, checker.readInteger("int"));
+ EXPECT_EQ("Test", checker.readString("string"));
+ }
+}
+
+
TEST(ReferenceDataTest, HandlesUpdateChangedWithoutChanges)
{
{