//============================================================================ // 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 #include #include #include #ifdef VTKM_GCC #include #endif // 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__, __func__, __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__, __func__, __VA_ARGS__) class TestEqualResult { public: void PushMessage(const std::string& msg) { this->Messages.push_back(msg); } const std::vector& GetMessages() const { return this->Messages; } std::string GetMergedMessage() const { std::string msg; std::for_each(this->Messages.rbegin(), this->Messages.rend(), [&](const std::string& next) { msg += (msg.empty() ? "" : ": "); msg += next; }); return msg; } operator bool() const { return this->Messages.empty(); } private: std::vector Messages; }; namespace vtkm { namespace testing { // TODO: Move these 2 functions to the testing library. // Note: We are explicitly not trapping FE_INEXACT and FE_UNDERFLOW. Inexact numbers are too common // to completely remove (that is the nature of floating point, especially when converting from // integers), and underflows are considered normal in rendering (for example, the specular // highlight essentially goes to zero most places). inline void FloatingPointExceptionTrapEnable() { // Turn on floating point exception trapping where available #ifdef VTKM_GCC feenableexcept(FE_DIVBYZERO | FE_OVERFLOW | FE_INVALID); #endif } inline void FloatingPointExceptionTrapDisable() { // Turn on floating point exception trapping where available #ifdef VTKM_GCC fedisableexcept(FE_DIVBYZERO | FE_OVERFLOW | FE_INVALID); #endif } // 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, name) \ template <> \ struct TypeName \ { \ static std::string Name() { return #name; } \ } VTK_M_BASIC_TYPE(vtkm::Float32, F32); VTK_M_BASIC_TYPE(vtkm::Float64, F64); VTK_M_BASIC_TYPE(vtkm::Int8, I8); VTK_M_BASIC_TYPE(vtkm::UInt8, UI8); VTK_M_BASIC_TYPE(vtkm::Int16, I16); VTK_M_BASIC_TYPE(vtkm::UInt16, UI16); VTK_M_BASIC_TYPE(vtkm::Int32, I32); VTK_M_BASIC_TYPE(vtkm::UInt32, UI32); VTK_M_BASIC_TYPE(vtkm::Int64, I64); VTK_M_BASIC_TYPE(vtkm::UInt64, UI64); // types without vtkm::typedefs: VTK_M_BASIC_TYPE(bool, bool); VTK_M_BASIC_TYPE(char, char); VTK_M_BASIC_TYPE(long, long); VTK_M_BASIC_TYPE(unsigned long, unsigned long); #define VTK_M_BASIC_TYPE_HELPER(type) VTK_M_BASIC_TYPE(vtkm::type, type) // Special containers: VTK_M_BASIC_TYPE_HELPER(Bounds); VTK_M_BASIC_TYPE_HELPER(Range); // Special Vec types: VTK_M_BASIC_TYPE_HELPER(Vec2f_32); VTK_M_BASIC_TYPE_HELPER(Vec2f_64); VTK_M_BASIC_TYPE_HELPER(Vec2i_8); VTK_M_BASIC_TYPE_HELPER(Vec2i_16); VTK_M_BASIC_TYPE_HELPER(Vec2i_32); VTK_M_BASIC_TYPE_HELPER(Vec2i_64); VTK_M_BASIC_TYPE_HELPER(Vec2ui_8); VTK_M_BASIC_TYPE_HELPER(Vec2ui_16); VTK_M_BASIC_TYPE_HELPER(Vec2ui_32); VTK_M_BASIC_TYPE_HELPER(Vec2ui_64); VTK_M_BASIC_TYPE_HELPER(Vec3f_32); VTK_M_BASIC_TYPE_HELPER(Vec3f_64); VTK_M_BASIC_TYPE_HELPER(Vec3i_8); VTK_M_BASIC_TYPE_HELPER(Vec3i_16); VTK_M_BASIC_TYPE_HELPER(Vec3i_32); VTK_M_BASIC_TYPE_HELPER(Vec3i_64); VTK_M_BASIC_TYPE_HELPER(Vec3ui_8); VTK_M_BASIC_TYPE_HELPER(Vec3ui_16); VTK_M_BASIC_TYPE_HELPER(Vec3ui_32); VTK_M_BASIC_TYPE_HELPER(Vec3ui_64); VTK_M_BASIC_TYPE_HELPER(Vec4f_32); VTK_M_BASIC_TYPE_HELPER(Vec4f_64); VTK_M_BASIC_TYPE_HELPER(Vec4i_8); VTK_M_BASIC_TYPE_HELPER(Vec4i_16); VTK_M_BASIC_TYPE_HELPER(Vec4i_32); VTK_M_BASIC_TYPE_HELPER(Vec4i_64); VTK_M_BASIC_TYPE_HELPER(Vec4ui_8); VTK_M_BASIC_TYPE_HELPER(Vec4ui_16); VTK_M_BASIC_TYPE_HELPER(Vec4ui_32); VTK_M_BASIC_TYPE_HELPER(Vec4ui_64); #undef VTK_M_BASIC_TYPE template struct TypeName> { static std::string Name() { std::stringstream stream; stream << "Vec<" << TypeName::Name() << ", " << Size << ">"; return stream.str(); } }; template struct TypeName> { static std::string Name() { std::stringstream stream; stream << "Matrix<" << TypeName::Name() << ", " << numRows << ", " << numCols << ">"; return stream.str(); } }; template struct TypeName> { static std::string Name() { std::stringstream stream; stream << "Pair<" << TypeName::Name() << ", " << TypeName::Name() << ">"; return stream.str(); } }; template struct TypeName> { static std::string Name() { std::stringstream stream; stream << "Bitset<" << TypeName::Name() << ">"; return stream.str(); } }; template struct TypeName> { static std::string Name() { std::initializer_list subtypeStrings = { TypeName::Name()... }; std::stringstream stream; stream << "List<" << TypeName::Name(); for (auto&& subtype : subtypeStrings) { stream << ", " << subtype; } stream << ">"; return stream.str(); } }; template <> struct TypeName { static std::string Name() { return "ListEmpty"; } }; template <> struct TypeName { static std::string Name() { return "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, const char* func, Ts&&... messages) : File(file) , Line(line) , Func(func) { 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 char* GetFunction() const { return this->Func; } 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; const char* Func; std::string Message; }; template static VTKM_CONT void Assert(const std::string& conditionString, const std::string& file, vtkm::Id line, const char* func, bool condition, Ts&&... messages) { if (condition) { // Do nothing. } else { throw TestFailure( file, line, func, std::forward(messages)..., " (", conditionString, ")"); } } static VTKM_CONT void Assert(const std::string& conditionString, const std::string& file, vtkm::Id line, const char* func, bool condition) { Assert(conditionString, file, line, func, condition, "Test assertion failed"); } static VTKM_CONT void Assert(const std::string& conditionString, const std::string& file, vtkm::Id line, const char* func, const TestEqualResult& result) { Assert(conditionString, file, line, func, static_cast(result), result.GetMergedMessage()); } template static VTKM_CONT void TestFail(const std::string& file, vtkm::Id line, const char* func, Ts&&... messages) { throw TestFailure(file, line, func, 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); } // Some simulations trap floating point exceptions, and we want to be able to run in them vtkm::testing::FloatingPointExceptionTrapEnable(); try { function(); } catch (TestFailure const& error) { std::cerr << "***** Test failed @ " << error.GetFile() << ":" << error.GetLine() << ":" << error.GetFunction() << "\n" << error.GetMessage() << "\n"; return 1; } catch (std::exception const& error) { std::cerr << "***** STL exception throw.\n" << error.what() << "\n"; return 1; } catch (...) { std::cerr << "***** Unidentified exception thrown.\n"; 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 { template VTKM_EXEC_CONT bool DoIt(T1 vector1, T2 vector2, vtkm::Float64 tolerance, Dimensionality1, Dimensionality2) const { 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 scalar1, T2 scalar2, vtkm::Float64 tolerance, vtkm::TypeTraitsScalarTag, vtkm::TypeTraitsScalarTag) const { // Do all comparisons using 64-bit floats. return test_equal( static_cast(scalar1), static_cast(scalar2), tolerance); } VTKM_EXEC_CONT bool operator()(T1 value1, T2 value2, vtkm::Float64 tolerance) const { return this->DoIt(value1, value2, tolerance, typename vtkm::TypeTraits::DimensionalityTag(), typename vtkm::TypeTraits::DimensionalityTag()); } }; template <> struct TestEqualImpl { VTKM_EXEC_CONT bool operator()(vtkm::Float64 value1, vtkm::Float64 value2, vtkm::Float64 tolerance) const { // Handle non-finites. Normally, non-finites are never "equal" to each other (for valid // mathematical reasons), but for testing purposes if the two values are the same type of // non-finite, then they are the same in the sense that they gave the same result. if (vtkm::IsNan(value1) && vtkm::IsNan(value2)) { return true; } if (vtkm::IsInf(value1) && vtkm::IsInf(value2) && (vtkm::IsNegative(value1) == vtkm::IsNegative(value2))) { return true; } 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; } } }; /// 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. Expected: " << expectedValue << ", Found: " << foundValue << "\n"; 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