Tests.hpp 5.12 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
#pragma once

#include <iostream>

// inspired by boost/core/lightweight_test.hpp - lightweight test library

#define AMDIS_TEST(expr) \
  ::AMDiS::Impl::_test(expr, #expr, __FILE__, __LINE__)

#define AMDIS_TEST_EQ(expr, value) \
  ::AMDiS::Impl::_test_eq(expr, value, #expr, #value, __FILE__, __LINE__)

#define AMDIS_TEST_NE(expr, value) \
  ::AMDiS::Impl::_test_ne(expr, value, #expr, #value, __FILE__, __LINE__)

#define AMDIS_TEST_TOL 1.e-10
#define AMDIS_TEST_APPROX(expr, value) \
  ::AMDiS::Impl::_test_approx(expr, value, #expr, #value, __FILE__, __LINE__)

#ifdef DEC_NO_THROW
  #define AMDIS_TEST_THROWS(expr)
#else
  #define AMDIS_TEST_THROWS(expr) \
    try { \
      expr; \
      ::AMDiS::Impl::_test_throws(#expr, __FILE__, __LINE__); \
    } catch(...) {}
#endif

namespace AMDiS
{
  namespace Impl
  {
    /// global counter for the number of detected errors
    inline int& num_errors()
    {
      static int num = 0;
      return num;
    }

  } // end namespace Impl


  /// Returns 0 if no errors are detected, otherwise returns 1. Must be called at the end
  /// of main().
  inline int report_errors()
  {
    int errors = Impl::num_errors();

    if (errors == 0) {
      std::cout  << "No errors detected.\n";
      return 0;
    } else {
      std::cerr << errors << " error(s) detected.\n";
      return 1;
    }
  }


  namespace Impl
  {
    /// Tests whether an expression evaluates to true
    inline void _test(bool success,
                      char const* expr, char const* file, size_t line)
    {
      if (!success) {
        std::cerr << file << ":" << line
                 << "  TEST( " << expr << " ) failed\n";
        num_errors()++;
      }
    }

    /// Tests whether the value of an expression is equal to an expected value
    template <class T1, class T2>
    inline void _test_eq(T1 const& expr_value, T2 const& value,
                         char const* expr_str, char const* value_str, char const* file, size_t line)
    {
      using T = std::common_type_t<T1,T2>;
      if (T(expr_value) != T(value)) {
        std::cerr << file << ":" << line
                 << "  TEST( " << expr_str << " == " << value_str << " ) failed:  " << expr_value << " != " << value << "\n";
        num_errors()++;
      }
    }

    /// Tests whether the value of an expression is not equal to an expected value
    template <class T1, class T2>
    inline void _test_ne(T1 const& expr_value, T2 const& value,
                         char const* expr_str, char const* value_str, char const* file, size_t line)
    {
      using T = std::common_type_t<T1,T2>;
      if (T(expr_value) == T(value)) {
        std::cerr << file << ":" << line
                 << "  TEST( " << expr_str << " != " << value_str << " ) failed:  " << expr_value << " == " << value << "\n";
        num_errors()++;
      }
    }

    template <class T>
    constexpr T _abs(T const& x) { return x < T(0) ? -x : x; }

    /// Tests whether the value of an expression is approximately equal to an expected value
    // implementation for scalars
    template <class T1, class T2,
      std::enable_if_t<std::is_arithmetic<T1>::value && std::is_arithmetic<T2>::value, int>* = nullptr>
    inline void _test_approx(T1 const& expr_value, T2 const& value,
                             char const* expr_str, char const* value_str, char const* file, size_t line)
    {
      if (_abs(expr_value - value) > AMDIS_TEST_TOL) {
        std::cerr << file << ":" << line
                 << "  TEST( " << expr_str << " ~= " << value_str << " ) failed:  " << expr_value << " != " << value << "\n";
        num_errors()++;
      }
    }

    // implementation for pair
    template <class T11, class T12, class T21, class T22>
    inline void _test_approx(std::pair<T11,T12> const& expr_value, std::pair<T21,T22> const& value,
                             char const* expr_str, char const* value_str, char const* file, size_t line)
    {
      if (_abs(expr_value.first - value.first) > AMDIS_TEST_TOL ||
          _abs(expr_value.second - value.second) > AMDIS_TEST_TOL)
      {
        std::cerr << file << ":" << line
                 << "  TEST( " << expr_str << " ~= " << value_str << " ) failed:  (" << expr_value.first << "," << expr_value.second << ") != (" << value.first << "," << value.second << ")\n";
        num_errors()++;
      }
    }

    // implementation for ranges
    template <class T1, class T2,
      class = decltype(((void)std::declval<T1>().cbegin(), (void)std::declval<T2>().cbegin())) >
    inline void _test_approx(T1 const& expr_range, T2 const& range,
                             char const* expr_str, char const* value_str, char const* file, size_t line)
    {
      auto it1 = expr_range.cbegin();
      auto it2 = range.cbegin();

      for(; it1 != expr_range.cend(); ++it1, ++it2)
        _test_approx(*it1, *it2, expr_str, value_str, file, line);
    }

    /// Tests whether an expression throws an exception
    template <class T1, class T2>
    inline void _test_throws(char const* expr, char const* file, size_t line)
    {
      std::cerr << file << ":" << line
               << "  EXPR( " << expr << " ) should throw\n";
      num_errors()++;
    }
  } // end namspace Impl

} // end namespace Dec