//============================================================================ // Copyright (c) Kitware, Inc. // All rights reserved. // See LICENSE.txt for details. // // This software is distributed WITHOUT ANY WARRANTY; without even // the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // PURPOSE. See the above copyright notice for more information. //============================================================================ #ifndef vtk_m_testing_Testing_h #define vtk_m_testing_Testing_h #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Try to enforce using the correct testing version. (Those that include the // control environment have more possible exceptions.) This is not guaranteed // to work. To make it more likely, place the Testing.h include last. #ifdef vtk_m_cont_Error_h #ifndef vtk_m_cont_testing_Testing_h #error Use vtkm::cont::testing::Testing instead of vtkm::testing::Testing. #else #define VTKM_TESTING_IN_CONT #endif #endif /// \def VTKM_STRINGIFY_FIRST(...) /// /// A utility macro that takes 1 or more arguments and converts it into the C string version /// of the first argument. #define VTKM_STRINGIFY_FIRST(...) VTKM_EXPAND(VTK_M_STRINGIFY_FIRST_IMPL(__VA_ARGS__, dummy)) #define VTK_M_STRINGIFY_FIRST_IMPL(first, ...) #first /// \def VTKM_TEST_ASSERT(condition, messages..) /// /// Asserts a condition for a test to pass. A passing condition is when \a /// condition resolves to true. If \a condition is false, then the test is /// aborted and failure is returned. If one or more message arguments are /// given, they are printed out by concatinating them. If no messages are /// given, a generic message is given. In any case, the condition that failed /// is written out. #define VTKM_TEST_ASSERT(...) \ ::vtkm::testing::Testing::Assert( \ VTKM_STRINGIFY_FIRST(__VA_ARGS__), __FILE__, __LINE__, __VA_ARGS__) /// \def VTKM_TEST_FAIL(messages..) /// /// Causes a test to fail with the given \a messages. At least one argument must be given. #define VTKM_TEST_FAIL(...) ::vtkm::testing::Testing::TestFail(__FILE__, __LINE__, __VA_ARGS__) namespace vtkm { namespace testing { // If you get an error about this class definition being incomplete, it means // that you tried to get the name of a type that is not specified. You can // either not use that type, not try to get the string name, or add it to the // list. template struct TypeName; #define VTK_M_BASIC_TYPE(type) \ template <> \ struct TypeName \ { \ static std::string Name() { return #type; } \ } VTK_M_BASIC_TYPE(vtkm::Float32); VTK_M_BASIC_TYPE(vtkm::Float64); VTK_M_BASIC_TYPE(vtkm::Int8); VTK_M_BASIC_TYPE(vtkm::UInt8); VTK_M_BASIC_TYPE(vtkm::Int16); VTK_M_BASIC_TYPE(vtkm::UInt16); VTK_M_BASIC_TYPE(vtkm::Int32); VTK_M_BASIC_TYPE(vtkm::UInt32); VTK_M_BASIC_TYPE(vtkm::Int64); VTK_M_BASIC_TYPE(vtkm::UInt64); VTK_M_BASIC_TYPE(char); VTK_M_BASIC_TYPE(vtkm::Bounds); VTK_M_BASIC_TYPE(vtkm::Range); #undef VTK_M_BASIC_TYPE template struct TypeName> { static std::string Name() { std::stringstream stream; stream << "vtkm::Vec< " << TypeName::Name() << ", " << Size << " >"; return stream.str(); } }; template struct TypeName> { static std::string Name() { std::stringstream stream; stream << "vtkm::Matrix< " << TypeName::Name() << ", " << numRows << ", " << numCols << " >"; return stream.str(); } }; template struct TypeName> { static std::string Name() { std::stringstream stream; stream << "vtkm::Pair< " << TypeName::Name() << ", " << TypeName::Name() << " >"; return stream.str(); } }; template struct TypeName> { static std::string Name() { std::stringstream stream; stream << "vtkm::Bitset< " << TypeName::Name() << " >"; return stream.str(); } }; template struct TypeName> { static std::string Name() { std::initializer_list subtypeStrings = { TypeName::Name()... }; std::stringstream stream; stream << "vtkm::List<" << TypeName::Name(); for (auto&& subtype : subtypeStrings) { stream << ", " << subtype; } stream << ">"; return stream.str(); } }; template <> struct TypeName { static std::string Name() { return "vtkm::ListEmpty"; } }; template <> struct TypeName { static std::string Name() { return "vtkm::ListUniversal"; } }; namespace detail { template struct InternalTryCellShape { template void operator()(const FunctionType& function) const { this->PrintAndInvoke(function, typename vtkm::CellShapeIdToTag::valid()); InternalTryCellShape()(function); } private: template void PrintAndInvoke(const FunctionType& function, std::true_type) const { using CellShapeTag = typename vtkm::CellShapeIdToTag::Tag; std::cout << "*** " << vtkm::GetCellShapeName(CellShapeTag()) << " ***************" << std::endl; function(CellShapeTag()); } template void PrintAndInvoke(const FunctionType&, std::false_type) const { // Not a valid cell shape. Do nothing. } }; template <> struct InternalTryCellShape { template void operator()(const FunctionType&) const { // Done processing cell sets. Do nothing and return. } }; } // namespace detail struct Testing { public: class TestFailure { public: template VTKM_CONT TestFailure(const std::string& file, vtkm::Id line, Ts&&... messages) : File(file) , Line(line) { std::stringstream messageStream; this->AppendMessages(messageStream, std::forward(messages)...); this->Message = messageStream.str(); } VTKM_CONT const std::string& GetFile() const { return this->File; } VTKM_CONT vtkm::Id GetLine() const { return this->Line; } VTKM_CONT const std::string& GetMessage() const { return this->Message; } private: template VTKM_CONT void AppendMessages(std::stringstream& messageStream, T1&& m1) { messageStream << m1; } template VTKM_CONT void AppendMessages(std::stringstream& messageStream, T1&& m1, T2&& m2) { messageStream << m1 << m2; } template VTKM_CONT void AppendMessages(std::stringstream& messageStream, T1&& m1, T2&& m2, T3&& m3) { messageStream << m1 << m2 << m3; } template VTKM_CONT void AppendMessages(std::stringstream& messageStream, T1&& m1, T2&& m2, T3&& m3, T4&& m4) { messageStream << m1 << m2 << m3 << m4; } template VTKM_CONT void AppendMessages(std::stringstream& messageStream, T1&& m1, T2&& m2, T3&& m3, T4&& m4, Ts&&... ms) { messageStream << m1 << m2 << m3 << m4; this->AppendMessages(messageStream, std::forward(ms)...); } std::string File; vtkm::Id Line; std::string Message; }; template static VTKM_CONT void Assert(const std::string& conditionString, const std::string& file, vtkm::Id line, bool condition, Ts&&... messages) { if (condition) { // Do nothing. } else { throw TestFailure(file, line, std::forward(messages)..., " (", conditionString, ")"); } } static VTKM_CONT void Assert(const std::string& conditionString, const std::string& file, vtkm::Id line, bool condition) { Assert(conditionString, file, line, condition, "Test assertion failed"); } template static VTKM_CONT void TestFail(const std::string& file, vtkm::Id line, Ts&&... messages) { throw TestFailure(file, line, std::forward(messages)...); } #ifndef VTKM_TESTING_IN_CONT /// Calls the test function \a function with no arguments. Catches any errors /// generated by VTKM_TEST_ASSERT or VTKM_TEST_FAIL, reports the error, and /// returns "1" (a failure status for a program's main). Returns "0" (a /// success status for a program's main). /// /// The intention is to implement a test's main function with this. For /// example, the implementation of UnitTestFoo might look something like /// this. /// /// \code /// #include /// /// namespace { /// /// void TestFoo() /// { /// // Do actual test, which checks in VTKM_TEST_ASSERT or VTKM_TEST_FAIL. /// } /// /// } // anonymous namespace /// /// int UnitTestFoo(int, char *[]) /// { /// return vtkm::testing::Testing::Run(TestFoo); /// } /// \endcode /// template static VTKM_CONT int Run(Func function, int& argc, char* argv[]) { if (argc == 0 || argv == nullptr) { vtkm::cont::InitLogging(); } else { vtkm::cont::InitLogging(argc, argv); } try { function(); } catch (TestFailure& error) { std::cout << "***** Test failed @ " << error.GetFile() << ":" << error.GetLine() << std::endl << error.GetMessage() << std::endl; return 1; } catch (std::exception& error) { std::cout << "***** STL exception throw." << std::endl << error.what() << std::endl; } catch (...) { std::cout << "***** Unidentified exception thrown." << std::endl; return 1; } return 0; } #endif template struct InternalPrintTypeAndInvoke { InternalPrintTypeAndInvoke(FunctionType function) : Function(function) { } template void operator()(T t) const { std::cout << "*** " << vtkm::testing::TypeName::Name() << " ***************" << std::endl; this->Function(t); } private: FunctionType Function; }; /// Runs template \p function on all the types in the given list. If no type /// list is given, then an exemplar list of types is used. /// template static void TryTypes(const FunctionType& function, TypeList) { vtkm::ListForEach(InternalPrintTypeAndInvoke(function), TypeList()); } using TypeListExemplarTypes = vtkm::List; template static void TryTypes(const FunctionType& function) { TryTypes(function, TypeListExemplarTypes()); } // Disabled: This very long list results is very long compile times. // /// Runs templated \p function on all the basic types defined in VTK-m. This // /// is helpful to test templated functions that should work on all types. If // /// the function is supposed to work on some subset of types, then use // /// \c TryTypes to restrict the call to some other list of types. // /// // template // static void TryAllTypes(const FunctionType &function) // { // TryTypes(function, vtkm::TypeListAll()); // } /// Runs templated \p function on all cell shapes defined in VTK-m. This is /// helpful to test templated functions that should work on all cell types. /// template static void TryAllCellShapes(const FunctionType& function) { detail::InternalTryCellShape<0>()(function); } }; } } // namespace vtkm::internal namespace detail { // Forward declaration template struct TestEqualImpl; } // namespace detail /// Helper function to test two quanitites for equality accounting for slight /// variance due to floating point numerical inaccuracies. /// template static inline VTKM_EXEC_CONT bool test_equal(T1 value1, T2 value2, vtkm::Float64 tolerance = 0.00001) { return detail::TestEqualImpl()(value1, value2, tolerance); } namespace detail { template struct TestEqualImpl { VTKM_EXEC_CONT bool DoIt(T1 vector1, T2 vector2, vtkm::Float64 tolerance, vtkm::TypeTraitsVectorTag) const { // If you get a compiler error here, it means you are comparing a vector to // a scalar, in which case the types are non-comparable. VTKM_STATIC_ASSERT_MSG((std::is_same::DimensionalityTag, vtkm::TypeTraitsVectorTag>::type::value) || (std::is_same::DimensionalityTag, vtkm::TypeTraitsMatrixTag>::type::value), "Trying to compare a vector with a scalar."); using Traits1 = vtkm::VecTraits; using Traits2 = vtkm::VecTraits; // If vectors have different number of components, then they cannot be equal. if (Traits1::GetNumberOfComponents(vector1) != Traits2::GetNumberOfComponents(vector2)) { return false; } for (vtkm::IdComponent component = 0; component < Traits1::GetNumberOfComponents(vector1); ++component) { bool componentEqual = test_equal(Traits1::GetComponent(vector1, component), Traits2::GetComponent(vector2, component), tolerance); if (!componentEqual) { return false; } } return true; } VTKM_EXEC_CONT bool DoIt(T1 matrix1, T2 matrix2, vtkm::Float64 tolerance, vtkm::TypeTraitsMatrixTag) const { // For the purposes of comparison, treat matrices the same as vectors. return this->DoIt(matrix1, matrix2, tolerance, vtkm::TypeTraitsVectorTag()); } VTKM_EXEC_CONT bool DoIt(T1 scalar1, T2 scalar2, vtkm::Float64 tolerance, vtkm::TypeTraitsScalarTag) const { // If you get a compiler error here, it means you are comparing a scalar to // a vector, in which case the types are non-comparable. VTKM_STATIC_ASSERT_MSG((std::is_same::DimensionalityTag, vtkm::TypeTraitsScalarTag>::type::value), "Trying to compare a scalar with a vector."); // Do all comparisons using 64-bit floats. vtkm::Float64 value1 = vtkm::Float64(scalar1); vtkm::Float64 value2 = vtkm::Float64(scalar2); if (vtkm::Abs(value1 - value2) <= tolerance) { return true; } // We are using a ratio to compare the relative tolerance of two numbers. // Using an ULP based comparison (comparing the bits as integers) might be // a better way to go, but this has been working pretty well so far. vtkm::Float64 ratio; if ((vtkm::Abs(value2) > tolerance) && (value2 != 0)) { ratio = value1 / value2; } else { // If we are here, it means that value2 is close to 0 but value1 is not. // These cannot be within tolerance, so just return false. return false; } if ((ratio > vtkm::Float64(1.0) - tolerance) && (ratio < vtkm::Float64(1.0) + tolerance)) { // This component is OK. The condition is checked in this way to // correctly handle non-finites that fail all comparisons. Thus, if a // non-finite is encountered, this condition will fail and false will be // returned. return true; } else { return false; } } VTKM_EXEC_CONT bool operator()(T1 value1, T2 value2, vtkm::Float64 tolerance) const { return this->DoIt( value1, value2, tolerance, typename vtkm::TypeTraits::DimensionalityTag()); } }; // Special cases of test equal where a scalar is compared with a Vec of size 1, // which we will allow. template struct TestEqualImpl, T> { VTKM_EXEC_CONT bool operator()(vtkm::Vec value1, T value2, vtkm::Float64 tolerance) const { return test_equal(value1[0], value2, tolerance); } }; template struct TestEqualImpl> { VTKM_EXEC_CONT bool operator()(T value1, vtkm::Vec value2, vtkm::Float64 tolerance) const { return test_equal(value1, value2[0], tolerance); } }; /// Special implementation of test_equal for strings, which don't fit a model /// of fixed length vectors of numbers. /// template <> struct TestEqualImpl { VTKM_CONT bool operator()(const std::string& string1, const std::string& string2, vtkm::Float64 vtkmNotUsed(tolerance)) const { return string1 == string2; } }; template struct TestEqualImpl { VTKM_CONT bool operator()(const char* string1, T value2, vtkm::Float64 tolerance) const { return TestEqualImpl()(string1, value2, tolerance); } }; template struct TestEqualImpl { VTKM_CONT bool operator()(T value1, const char* string2, vtkm::Float64 tolerance) const { return TestEqualImpl()(value1, string2, tolerance); } }; template <> struct TestEqualImpl { VTKM_CONT bool operator()(const char* string1, const char* string2, vtkm::Float64 tolerance) const { return TestEqualImpl()(string1, string2, tolerance); } }; /// Special implementation of test_equal for Pairs, which are a bit different /// than a vector of numbers of the same type. /// template struct TestEqualImpl, vtkm::Pair> { VTKM_EXEC_CONT bool operator()(const vtkm::Pair& pair1, const vtkm::Pair& pair2, vtkm::Float64 tolerance) const { return test_equal(pair1.first, pair2.first, tolerance) && test_equal(pair1.second, pair2.second, tolerance); } }; /// Special implementation of test_equal for Ranges. /// template <> struct TestEqualImpl { VTKM_EXEC_CONT bool operator()(const vtkm::Range& range1, const vtkm::Range& range2, vtkm::Float64 tolerance) const { return (test_equal(range1.Min, range2.Min, tolerance) && test_equal(range1.Max, range2.Max, tolerance)); } }; /// Special implementation of test_equal for Bounds. /// template <> struct TestEqualImpl { VTKM_EXEC_CONT bool operator()(const vtkm::Bounds& bounds1, const vtkm::Bounds& bounds2, vtkm::Float64 tolerance) const { return (test_equal(bounds1.X, bounds2.X, tolerance) && test_equal(bounds1.Y, bounds2.Y, tolerance) && test_equal(bounds1.Z, bounds2.Z, tolerance)); } }; /// Special implementation of test_equal for booleans. /// template <> struct TestEqualImpl { VTKM_EXEC_CONT bool operator()(bool bool1, bool bool2, vtkm::Float64 vtkmNotUsed(tolerance)) { return bool1 == bool2; } }; } // namespace detail namespace detail { template struct TestValueImpl; } // namespace detail /// Many tests involve getting and setting values in some index-based structure /// (like an array). These tests also often involve trying many types. The /// overloaded TestValue function returns some unique value for an index for a /// given type. Different types might give different values. /// template static inline VTKM_EXEC_CONT T TestValue(vtkm::Id index, T) { return detail::TestValueImpl()(index); } namespace detail { template struct TestValueImpl { VTKM_EXEC_CONT T DoIt(vtkm::Id index, vtkm::TypeTraitsIntegerTag) const { constexpr bool larger_than_2bytes = sizeof(T) > 2; if (larger_than_2bytes) { return T(index * 100); } else { return T(index + 100); } } VTKM_EXEC_CONT T DoIt(vtkm::Id index, vtkm::TypeTraitsRealTag) const { return T(0.01f * static_cast(index) + 1.001f); } VTKM_EXEC_CONT T operator()(vtkm::Id index) const { return this->DoIt(index, typename vtkm::TypeTraits::NumericTag()); } }; template struct TestValueImpl> { VTKM_EXEC_CONT vtkm::Vec operator()(vtkm::Id index) const { vtkm::Vec value; for (vtkm::IdComponent i = 0; i < N; i++) { value[i] = TestValue(index * N + i, T()); } return value; } }; template struct TestValueImpl> { VTKM_EXEC_CONT vtkm::Pair operator()(vtkm::Id index) const { return vtkm::Pair(TestValue(2 * index, U()), TestValue(2 * index + 1, V())); } }; template struct TestValueImpl> { VTKM_EXEC_CONT vtkm::Matrix operator()(vtkm::Id index) const { vtkm::Matrix value; vtkm::Id runningIndex = index * NumRow * NumCol; for (vtkm::IdComponent row = 0; row < NumRow; ++row) { for (vtkm::IdComponent col = 0; col < NumCol; ++col) { value(row, col) = TestValue(runningIndex, T()); ++runningIndex; } } return value; } }; template <> struct TestValueImpl { VTKM_CONT std::string operator()(vtkm::Id index) const { std::stringstream stream; stream << index; return stream.str(); } }; } // namespace detail /// Verifies that the contents of the given array portal match the values /// returned by vtkm::testing::TestValue. /// template static inline VTKM_CONT void CheckPortal(const PortalType& portal) { using ValueType = typename PortalType::ValueType; for (vtkm::Id index = 0; index < portal.GetNumberOfValues(); index++) { ValueType expectedValue = TestValue(index, ValueType()); ValueType foundValue = portal.Get(index); if (!test_equal(expectedValue, foundValue)) { std::stringstream message; message << "Got unexpected value in array." << std::endl << "Expected: " << expectedValue << ", Found: " << foundValue << std::endl; VTKM_TEST_FAIL(message.str().c_str()); } } } /// Sets all the values in a given array portal to be the values returned /// by vtkm::testing::TestValue. The ArrayPortal must be allocated first. /// template static inline VTKM_CONT void SetPortal(const PortalType& portal) { using ValueType = typename PortalType::ValueType; for (vtkm::Id index = 0; index < portal.GetNumberOfValues(); index++) { portal.Set(index, TestValue(index, ValueType())); } } /// Verifies that the contents of the two portals are the same. /// template static inline VTKM_CONT bool test_equal_portals(const PortalType1& portal1, const PortalType2& portal2) { if (portal1.GetNumberOfValues() != portal2.GetNumberOfValues()) { return false; } for (vtkm::Id index = 0; index < portal1.GetNumberOfValues(); index++) { if (!test_equal(portal1.Get(index), portal2.Get(index))) { return false; } } return true; } #endif //vtk_m_testing_Testing_h