Git Product home page Git Product logo

ut2's Introduction

// Overview / Examples / API / FAQ

UT: C++20 Unit-Testing library

"If you liked it then you "should have put a"_test on it", Beyonce rule

MIT Licence Version build Try it online

https://en.wikipedia.org/wiki/Unit_testing

Features

Based on the constexpr ability of given compiler/standard

Requirements


Overview

Hello world (https://godbolt.org/z/MG5cjnsbM)

#include <ut>
#include <iostream> // output at run-time

constexpr auto sum(auto... args) { return (args + ...); }

int main() {
  using namespace ut;

  "sum"_test = [] {
    expect(sum(1) == 1_i);
    expect(sum(1, 2) == 3_i);
    expect(sum(1, 2, 3) == 6_i);
  };
}
$CXX example.cpp -std=c++20 -o example && ./example
PASSED: tests: 1 (1 passed, 0 failed, 1 compile-time), asserts: 3 (3 passed, 0 failed)

Execution model (https://godbolt.org/z/31Gc151Mf)

static_assert(("sum"_test = [] { // compile-time only
  expect(sum(1, 2, 3) == 6_i);
}));

int main() {
  "sum"_test = [] {              // compile time and run-time
    expect(sum(1, 2, 3) == 5_i);
  };

  "sum"_test = [] constexpr {    // compile-time and run-time
    expect(sum(1, 2, 3) == 6_i);
  };

  "sum"_test = [] mutable {      // run-time only
    expect(sum(1, 2, 3) == 6_i);
  };

  "sum"_test = [] consteval {    // compile-time only
    expect(sum(1, 2, 3) == 6_i);
  };
}
$CXX example.cpp -std=c++20 # -DUT_COMPILE_TIME_ONLY
ut:156:25: error: static_assert((test(), "[FAILED]"));
example.cpp:13:44: note:"sum"_test
example.cpp:14:5:  note: in call to 'expect.operator()<ut::eq<int, int>>({6, 5})'
$CXX example.cpp -std=c++20 -o example -DUT_RUNTIME_ONLY && ./example
example.cpp:14:FAILED:"sum": 6 == 5
FAILED: tests: 3 (2 passed, 1 failed, 0 compile-time), asserts: 2 (1 passed, 1 failed)

Constant evaluation (https://godbolt.org/z/6E86YdbdT)

constexpr auto test() {
  if consteval { return 42; } else { return 87; }
}

int main() {
  "compile-time"_test = [] consteval {
    expect(42_i == test());
  };

  "run-time"_test = [] mutable {
    expect(87_i == test());
  };
}
$CXX example.cpp -std=c++20 -o example && ./example
PASSED: tests: 2 (2 passed, 0 failed, 1 compile-time), asserts: 1 (1 passed, 0 failed)

Suites/Sub-tests (https://godbolt.org/z/1oT3Gre93)

ut::suite test_suite = [] {
  "vector [sub-tests]"_test = [] {
    std::vector<int> v(5);
    expect(v.size() == 5_ul);
    expect(v.capacity() >= 5_ul);

    "resizing bigger changes size and capacity"_test = [=] {
      mut(v).resize(10);
      expect(v.size() == 10_ul);
      expect(v.capacity() >= 10_ul);
    };
  };
};

int main() { }
$CXX example.cpp -std=c++20 -o example && ./example
PASSED: tests: 2 (2 passed, 0 failed, 1 compile-time), asserts: 4 (4 passed, 0 failed)

Assertions (https://godbolt.org/z/79M7o355a)

int main() {
  "expect"_test = [] {
    "different ways"_test = [] {
      expect(42_i == 42);
      expect(eq(42, 42))   << "same as expect(42_i == 42)";
      expect(_i(42) == 42) << "same as expect(42_i == 42)";
    };

    "floating point"_test = [] {
      expect((4.2 == 4.2_d)(.01)) << "floating point comparison with .01 epsilon precision";
    };

    "fatal"_test = [] mutable { // at run-time
      std::vector<int> v{1};
      expect[v.size() > 1_ul] << "fatal, aborts further execution";
      expect(v[1] == 42_i); // not executed
    };

    "compile-time expression"_test = [] {
      expect(constant<42 == 42_i>) << "requires compile-time expression";
    };
  };
}
$CXX example.cpp -std=c++20 -o example && ./example
example.cpp:21:FAILED:"fatal": 1 > 1
FAILED: tests: 3 (2 passed, 1 failed, 3 compile-time), asserts: 5 (4 passed, 1 failed)

Errors/Checks (https://godbolt.org/z/Tvnce9j4d)

int main() {
  "leak"_test = [] {
    new int; // compile-time error
  };

  "ub"_test = [] {
    int* i{};
    *i = 42; // compile-time error
  };

  "errors"_test = [] {
    expect(42_i == short(42)); // [ERROR] Comparision of different types is not allowed
    expect(42 == 42);          // [ERROR] Expression required: expect(42_i == 42)
    expect(4.2 == 4.2_d);      // [ERROR] Epsilon is required: expect((4.2 == 4.2_d)(.01))
  };
}

Examples

Reflection integration (https://godbolt.org/z/v8GG4hfbW)

int main() {
  struct foo { int a; int b; };
  struct bar { int a; int b; };

  "reflection"_test = [] {
    auto f = foo{.a=1, .b=2};
    expect(eq(foo{1, 2}, f));
    expect(members(foo{1, 2}) == members(f));
    expect(names(foo{}) == names(bar{}));
  };
};
$CXX example.cpp -std=c++20 -o example && ./example
PASSED: tests: 1 (1 passed, 0 failed, 1 compile-time), asserts: 3 (3 passed, 0 failed)

Custom configuration (https://godbolt.org/z/6MrEEvqja)

struct outputter {
  template<ut::events::mode Mode>
  constexpr auto on(const ut::events::test_begin<Mode>&) { }
  template<ut::events::mode Mode>
  constexpr auto on(const ut::events::test_end<Mode>&) { }
  template<class TExpr>
  constexpr auto on(const ut::events::assert_pass<TExpr>&) { }
  template<class TExpr>
  constexpr auto on(const ut::events::assert_fail<TExpr>&) { }
  constexpr auto on(const ut::events::fatal&) { }
  constexpr auto on(const ut::events::summary&) { }
  template<class TMsg>
  constexpr auto on(const ut::events::log<TMsg>&) { }
};

struct custom_config {
  ::outputter outputter{};
  ut::reporter<decltype(outputter)> reporter{outputter};
  ut::runner<decltype(reporter)> runner{reporter};
};

template<>
auto ut::cfg<ut::override> = custom_config{};

int main() {
  "config"_test = [] mutable {
    expect(42 == 43_i); // no output
  };
};
$CXX example.cpp -std=c++20 -o example && ./example
echo $? # 139 # no output

Compilation times

Include - no iostream (https://raw.githubusercontent.com/qlibs/ut/main/ut)

time $CXX -x c++ -std=c++20 ut -c -DNTEST          # 0.028s
time $CXX -x c++ -std=c++20 ut -c                  # 0.049s

Benchmark - 100 tests, 1000 asserts (https://godbolt.org/z/zs5Ee3E7o)

[ut]: time $CXX benchmark.cpp -std=c++20           # 0m0.813s
[ut]: time $CXX benchmark.cpp -std=c++20 -DNTEST   # 0m0.758s
-------------------------------------------------------------------------
[ut] https://github.com/qlibs/ut/releases/tag/v2.1.2

Performance

Benchmark - 100 tests, 1000 asserts (https://godbolt.org/z/xKx45s4xq)

time ./benchmark # 0m0.002s (-O3)
time ./benchmark # 0m0.013s (-g)

X86-64 assembly -O3 (https://godbolt.org/z/rqbsafaE6)

int main() {
  "sum"_test = [] {
    expect(42_i == 42);
  };
}
main:
  mov  rax, qword ptr [rip + cfg<ut::override>+136]
  inc  dword ptr [rax + 24]
  mov  ecx, dword ptr [rax + 8]
  mov  edx, dword ptr [rax + 92]
  lea  esi, [rdx + 1]
  mov  dword ptr [rax + 92], esi
  mov  dword ptr [rax + 4*rdx + 28], ecx
  mov  rax, qword ptr [rax]
  lea  rcx, [rip + .L.str]
  mov  qword ptr [rax + 8], rcx
  mov  dword ptr [rax + 16], 6
  lea  rcx, [rip + template parameter object for fixed_string
  mov  qword ptr [rax + 24], rcx
  inc  dword ptr [rip + ut::v2_1_1::cfg<ut::v2_1_1::override>+52]
  mov  rax, qword ptr [rip + ut::cfg<ut::override>+136]
  mov  ecx, dword ptr [rax + 8]
  mov  edx, dword ptr [rax + 92]
  dec  edx
  mov  dword ptr [rax + 92], edx
  xor  esi, esi
  cmp  ecx, dword ptr [rax + 4*rdx + 28]
  sete sil
  inc  dword ptr [rax + 4*rsi + 16]
  xor  eax, eax
  ret

API

/**
 * Assert definition
 * @code
 * expect(42 == 42_i);
 * expect(42 == 42_i) << "log";
 * expect[42 == 42_i]; // fatal assertion, aborts further execution
 * @endcode
 */
inline constexpr struct {
  constexpr auto operator()(auto expr);
  constexpr auto operator[](auto expr);
} expect{};
/**
 * Test suite definition
 * @code
 * suite test_suite = [] { ... };
 * @encode
 */
struct suite;
/**
 * Test definition
 * @code
 * "foo"_test = []          { ... }; // compile-time and run-time
 * "foo"_test = [] mutable  { ... }; // run-time only
 * "foo"_test = [] constval { ... }; // compile-time only
 * @endcode
 */
template<fixed_string Str>
[[nodiscard]] constexpr auto operator""_test();
/**
 * Compile time expression
 * @code
 * expect(constant<42_i == 42>); // forces compile-time evaluation and run-time check
 * auto i = 0;
 * expect(constant<i == 42_i>);  // compile-time error
 * @encode
 */
template<auto Expr> inline constexpr auto constant;
/**
 * Allows mutating object (by default lambdas are immutable)
 * @code
 * "foo"_test = [] {
 *   int i = 0;
 *   "sub"_test = [i] {
 *     mut(i) = 42;
 *   };
 *   expect(i == 42_i);
 * };
 * @endcode
 */
template<class T> [[nodiscard]] constexpr auto& mut(const T&);
template<class TLhs, class TRhs> struct eq;  // equal
template<class TLhs, class TRhs> struct neq; // not equal
template<class TLhs, class TRhs> struct gt;  // greater
template<class TLhs, class TRhs> struct ge;  // greater equal
template<class TLhs, class TRhs> struct lt;  // less
template<class TLhs, class TRhs> struct le;  // less equal
template<class TLhs, class TRhs> struct nt;  // not
constexpr auto operator==(const auto& lhs, const auto& rhs) -> decltype(eq{lhs, rhs});
constexpr auto operator!=(const auto& lhs, const auto& rhs) -> decltype(neq{lhs, rhs});
constexpr auto operator> (const auto& lhs, const auto& rhs) -> decltype(gt{lhs, rhs});
constexpr auto operator>=(const auto& lhs, const auto& rhs) -> decltype(ge{lhs, rhs});
constexpr auto operator< (const auto& lhs, const auto& rhs) -> decltype(lt{lhs, rhs});
constexpr auto operator<=(const auto& lhs, const auto& rhs) -> decltype(le{lhs, rhs});
constexpr auto operator! (const auto& t)                    -> decltype(nt{t});
struct _b;      // bool (true_b = _b{true}, false_b = _b{false})
struct _c;      // char
struct _sc;     // signed char
struct _s;      // short
struct _i;      // int
struct _l;      // long
struct _ll;     // long long
struct _u;      // unsigned
struct _uc;     // unsigned char
struct _us;     // unsigned short
struct _ul;     // unsigned long
struct _ull;    // unsigned long long
struct _f;      // float
struct _d;      // double
struct _ld;     // long double
struct _i8;     // int8_t
struct _i16;    // int16_t
struct _i32;    // int32_t
struct _i64;    // int64_t
struct _u8;     // uint8_t
struct _u16;    // uint16_t
struct _u32;    // uint32_t
struct _u64;    // uint64_t
struct _string; // const char*
constexpr auto operator""_i(auto value)   -> decltype(_i(value));
constexpr auto operator""_s(auto value)   -> decltype(_s(value));
constexpr auto operator""_c(auto value)   -> decltype(_c(value));
constexpr auto operator""_sc(auto value)  -> decltype(_sc(value));
constexpr auto operator""_l(auto value)   -> decltype(_l(value));
constexpr auto operator""_ll(auto value)  -> decltype(_ll(value));
constexpr auto operator""_u(auto value)   -> decltype(_u(value));
constexpr auto operator""_uc(auto value)  -> decltype(_uc(value));
constexpr auto operator""_us(auto value)  -> decltype(_us(value));
constexpr auto operator""_ul(auto value)  -> decltype(_ul(value));
constexpr auto operator""_ull(auto value) -> decltype(_ull(value));
constexpr auto operator""_f(auto value)   -> decltype(_f(value));
constexpr auto operator""_d(auto value)   -> decltype(_d(value));
constexpr auto operator""_ld(auto value)  -> decltype(_ld(value));
constexpr auto operator""_i8(auto value)  -> decltype(_i8(value));
constexpr auto operator""_i16(auto value) -> decltype(_i16(value));
constexpr auto operator""_i32(auto value) -> decltype(_i32(value));
constexpr auto operator""_i64(auto value) -> decltype(_i64(value));
constexpr auto operator""_u8(auto value)  -> decltype(_u8(value));
constexpr auto operator""_u16(auto value) -> decltype(_u16(value));
constexpr auto operator""_u32(auto value) -> decltype(_u32(value));
constexpr auto operator""_u64(auto value) -> decltype(_u64(value));
template<fixed_string Str>
[[nodiscard]] constexpr auto operator""_s() -> decltype(_string(Str));

Configuration

namespace events {
enum class mode {
  run_time,
  compile_time
};

template<mode Mode>
struct test_begin {
  const char* file_name{};
  int line{}; const char* name{};
};

template<mode Mode>
struct test_end {
  const char* file_name{};
  int line{};
  const char* name{};
  enum { FAILED, PASSED, COMPILE_TIME } result{};
};

template<class TExpr>
struct assert_pass {
  const char* file_name{};
  int line{};
  TExpr expr{};
};

template<class TExpr>
struct assert_fail {
  const char* file_name{};
  int line{};
  TExpr expr{};
};

struct fatal { };

template<class TMsg>
struct log {
  const TMsg& msg;
  bool result{};
};

struct summary {
  enum { FAILED, PASSED, COMPILE_TIME };
  unsigned asserts[2]{}; /* FAILED, PASSED */
  unsigned tests[3]{}; /* FAILED, PASSED, COMPILE_TIME */
};
} // namespace events
struct outputter {
  template<events::mode Mode> constexpr auto on(const events::test_begin<Mode>&);
  constexpr auto on(const events::test_begin<events::mode::run_time>& event);
  template<events::mode Mode> constexpr auto on(const events::test_end<Mode>&);
  template<class TExpr> constexpr auto on(const events::assert_pass<TExpr>&);
  template<class TExpr> constexpr auto on(const events::assert_fail<TExpr>&);
  constexpr auto on(const events::fatal&);
  template<class TMsg> constexpr auto on(const events::log<TMsg>&);
  constexpr auto on(const events::summary& event);
};
struct reporter {
  constexpr auto on(const events::test_begin<events::mode::run_time>&);
  constexpr auto on(const events::test_end<events::mode::run_time>&);
  constexpr auto on(const events::test_begin<events::mode::compile_time>&);
  constexpr auto on(const events::test_end<events::mode::compile_time>&);
  template<class TExpr> constexpr auto on(const events::assert_pass<TExpr>&);
  template<class TExpr> constexpr auto on(const events::assert_fail<TExpr>&);
  constexpr auto on(const events::fatal& event);
};
struct runner {
  template<class Test> constexpr auto on(Test test) -> bool;
};
/**
 * Customization point to override the default configuration
 * @code
 * template<class... Ts> auto ut::cfg<ut::override, Ts...> = my_config{};
 * @endcode
 */
struct override { }; /// to override configuration by users
struct default_cfg;  /// default configuration
template <class...> inline auto cfg = default_cfg{};
#define UT_RUN_TIME_ONLY        // If defined tests will be executed
                                // at run-time + static_assert tests
#define UT_COMPILE_TIME_ONLY    // If defined only compile-time tests
                                // will be executed

FAQ

  • Can I disable running tests at compile-time for faster compilation times?

    When NTEST is defined static_asserts tests wont be executed upon inclusion. Note: Use with caution as disabling tests means that there are no guarantees upon inclusion that the given compiler/env combination works as expected.

  • How to integrate with CMake/CPM?

    CPMAddPackage(
      Name ut
      GITHUB_REPOSITORY qlibs/ut
      GIT_TAG v2.1.2
    )
    add_library(ut INTERFACE)
    target_include_directories(ut SYSTEM INTERFACE ${ut_SOURCE_DIR})
    add_library(qlibs::ut ALIAS ut)
    
    target_link_libraries(${PROJECT_NAME} qlibs::ut);
    
  • Similar projects?

    ut, catch2, googletest, gunit, boost.test

ut2's People

Contributors

krzysztof-jusiak avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

ut2's Issues

parallel_runner support

I tried to upgrade to the latest v2.1.2 and I was using the parallel_runner example that was available before. The api changed a bit and I'm not quite sure how to get that to work again or if that's still possible. Any way to provide an example on to set that up?

Optional std::iostream?

This is neat, but I'm curious how the standard library is optional when you have the following code:

#if __has_include(<iostream>)
#include <iostream> // [optional] std::clog
#endif

#if __has_include(<cstdint>)
#include <cstdint> // [optional] std::int8_t, std::int16_t, std::int32_t, std::int64_t, std::uint8_t, std::uint32_t, std::uint64_t
#endif

These __has_include checks will return true on compilers with these headers. So, I understand how they are optional in the sense that ut2 could be used on a platform without these headers, but it doesn't seem to be optional to avoid including these headers on a typical C++ compiler, as they will always return true.

Could there be a way to make them optional (configurable) for common C++ compilers?

reference to `basic_ostream` is ambiguous when using libc++ and `#include <iostream>` first

This library fails to compile with libc++ when #include <iostream> in front of #include <ut2>.

godbolt

/Users/feignclaims/.conan2/p/b/boost44f8b84116680/p/include/ut2:169:8: error: reference to 'basic_ostream' is ambiguous
  169 | extern basic_ostream<char, char_traits<char>> clog; // only used if defined
      |        ^
/Users/feignclaims/.conan2/p/b/boost44f8b84116680/p/include/ut2:168:30: note: candidate found by name lookup is 'std::basic_ostream'
  168 | template<class, class> class basic_ostream;
      |                              ^
/opt/homebrew/opt/llvm/bin/../include/c++/v1/ostream:208:28: note: candidate found by name lookup is 'std::__1::basic_ostream'
  208 | class _LIBCPP_TEMPLATE_VIS basic_ostream : virtual public basic_ios<_CharT, _Traits> {
      |                            ^
In file included from /Users/feignclaims/code/cpp/cpp-practice/test/main.cpp:2:
/Users/feignclaims/.conan2/p/b/boost44f8b84116680/p/include/ut2:169:28: error: reference to 'char_traits' is ambiguous
  169 | extern basic_ostream<char, char_traits<char>> clog; // only used if defined
      |                            ^
/Users/feignclaims/.conan2/p/b/boost44f8b84116680/p/include/ut2:167:24: note: candidate found by name lookup is 'std::char_traits'
  167 | template<class> struct char_traits;
      |                        ^
/opt/homebrew/opt/llvm/bin/../include/c++/v1/__string/char_traits.h:83:92: note: candidate found by name lookup is 'std::__1::char_traits'
   83 |     "for a temporary period. It will be removed in LLVM 19, so please migrate off of it.") char_traits {
      |                                                                                            ^
In file included from /Users/feignclaims/code/cpp/cpp-practice/test/main.cpp:2:
/Users/feignclaims/.conan2/p/b/boost44f8b84116680/p/include/ut2:180:39: error: reference to 'clog' is ambiguous
  180 |         static_assert(requires { std::clog << t; }, "[ERROR] No output supported: Consider #include <iostream> | ut::cfg<ut::override> = custom_cfg{} | #define UT_COMPILE_TIME_ONLY");
      |                                  ~~~~~^
/opt/homebrew/opt/llvm/bin/../include/c++/v1/iostream:57:42: note: candidate found by name lookup is 'std::__1::clog'
   57 | extern _LIBCPP_EXPORTED_FROM_ABI ostream clog;
      |                                          ^
/Users/feignclaims/.conan2/p/b/boost44f8b84116680/p/include/ut2:169:47: note: candidate found by name lookup is 'std::clog'
  169 | extern basic_ostream<char, char_traits<char>> clog; // only used if defined
      |                                               ^
/Users/feignclaims/.conan2/p/b/boost44f8b84116680/p/include/ut2:181:22: error: reference to 'clog' is ambiguous
  181 |         return (std::clog << t);
      |                 ~~~~~^
/opt/homebrew/opt/llvm/bin/../include/c++/v1/iostream:57:42: note: candidate found by name lookup is 'std::__1::clog'
   57 | extern _LIBCPP_EXPORTED_FROM_ABI ostream clog;
      |                                          ^
/Users/feignclaims/.conan2/p/b/boost44f8b84116680/p/include/ut2:169:47: note: candidate found by name lookup is 'std::clog'
  169 | extern basic_ostream<char, char_traits<char>> clog; // only used if defined
      |                                               ^
/Users/feignclaims/.conan2/p/b/boost44f8b84116680/p/include/ut2:181:22: error: unknown type name 'clog'
  181 |         return (std::clog << t);
      |                      ^
/Users/feignclaims/.conan2/p/b/boost44f8b84116680/p/include/ut2:181:27: error: expected ')'
  181 |         return (std::clog << t);
      |                           ^
/Users/feignclaims/.conan2/p/b/boost44f8b84116680/p/include/ut2:181:16: note: to match this '('
  181 |         return (std::clog << t);
      |                ^
6 errors generated.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.