Add support for reading values from refdata
authorTeemu Murtola <teemu.murtola@gmail.com>
Sun, 9 Oct 2016 18:33:06 +0000 (21:33 +0300)
committerRoland Schulz <roland.schulz@intel.com>
Mon, 17 Oct 2016 10:07:14 +0000 (12:07 +0200)
This is the easiest way to support tests in child changes for
serialization.  Some alternative approach may be possible in the future,
when the serialization approach matures, but I seem to recall there have
been requests for this functionality also in other contexts.  There is
some less-than-ideal behavior (in particular, a newly created test fails
if it uses this functionality, and only succeeds when you actually
create the reference data).

Also add support for a separate type of unsigned char reference values,
to support serialization tests.

Change-Id: I35ee9d43a0737a342f756cda0e7a789273ce1d9e

docs/doxygen/lib/refdata.md
src/testutils/refdata-checkers.h
src/testutils/refdata.cpp
src/testutils/refdata.h
src/testutils/tests/refdata_tests.cpp

index 7cca467b7bf7e6682613bac683f2497e994376c2..81c4501d8b8cf07ed29f2625f2b357092e051db6 100644 (file)
@@ -77,6 +77,15 @@ reference data, it will generate a non-fatal Google Test failure in the current
 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().
index 493771c866b5a6b1c565b16d4d9759d124f01e64..ca933a5a82d03b1344af645cd747b8be3bf6b35c 100644 (file)
@@ -52,6 +52,7 @@
 
 #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"
@@ -143,27 +144,13 @@ class ExactStringBlockChecker : public IReferenceDataEntryChecker
         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)
     {
@@ -224,7 +211,7 @@ class FloatingPointFromStringChecker : public IReferenceDataEntryChecker
         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))
@@ -244,6 +231,41 @@ class FloatingPointFromStringChecker : public IReferenceDataEntryChecker
         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
 
index 0824cb93f46ed698a6919651fb3e9b8cb5eb81f2..9a37881d871baff25aaaba55c12c1c507c31aa93 100644 (file)
@@ -384,6 +384,8 @@ class TestReferenceChecker::Impl
         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.
@@ -551,6 +553,7 @@ class TestReferenceChecker::Impl
 
 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";
@@ -885,6 +888,12 @@ void TestReferenceChecker::checkTextBlock(const std::string &value,
 }
 
 
+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,
@@ -1029,5 +1038,70 @@ TestReferenceChecker::checkSequenceCompound(const char *id, size_t length)
     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
index 61904f6b40c304978f1a7ba41f413f44ede3aada..1fd128ecdc0d3eeb1a111969b281a7415f4caebf 100644 (file)
@@ -348,6 +348,8 @@ class TestReferenceChecker
          * 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.
@@ -377,6 +379,29 @@ class TestReferenceChecker
         //! 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
index d89226439aa79c5baef2fce8185e60f5586c52ac..52b4ba7b0b5ff785772490c6e2bae31a0c99694e 100644 (file)
@@ -668,6 +668,25 @@ TEST(ReferenceDataTest, HandlesMultipleComparisonsAgainstNullIds)
 }
 
 
+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)
 {
     {