diff --git a/examples/.tests_cache/arithmetic.mq.json b/examples/.tests_cache/arithmetic.mq.json deleted file mode 100644 index 7f64229..0000000 --- a/examples/.tests_cache/arithmetic.mq.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "returncode": 0, - "stdout": "4\n30\n42\n", - "stderr": "", - "flags": [] -} \ No newline at end of file diff --git a/examples/.tests_cache/arrays.mq.json b/examples/.tests_cache/arrays.mq.json deleted file mode 100644 index 15c656b..0000000 --- a/examples/.tests_cache/arrays.mq.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "returncode": 0, - "stdout": "1\n2\n3\n4\n5\n", - "stderr": "", - "flags": [] -} \ No newline at end of file diff --git a/examples/.tests_cache/assigments-state-management.mq.json b/examples/.tests_cache/assigments-state-management.mq.json deleted file mode 100644 index 0ec0fa6..0000000 --- a/examples/.tests_cache/assigments-state-management.mq.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "returncode": 0, - "stdout": "10\n20\n30\n20\n20\n20\n[1; 2; 3]\n[1; 10; 3]\n[1; 3; 3]\n", - "stderr": "", - "flags": [] -} \ No newline at end of file diff --git a/examples/.tests_cache/church.mq.json b/examples/.tests_cache/church.mq.json deleted file mode 100644 index ec96bbb..0000000 --- a/examples/.tests_cache/church.mq.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "returncode": 0, - "stdout": "100\n200\n120\n", - "stderr": "", - "flags": [] -} \ No newline at end of file diff --git a/examples/.tests_cache/factorial.mq.json b/examples/.tests_cache/factorial.mq.json deleted file mode 100644 index 05a9aac..0000000 --- a/examples/.tests_cache/factorial.mq.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "returncode": 0, - "stdout": "1\n1\n2\n6\n24\n120\n720\n5040\n40320\n362880\n1\n1\n2\n6\n24\n120\n720\n5040\n40320\n362880\n1\n1\n2\n6\n24\n120\n720\n5040\n40320\n362880\n", - "stderr": "", - "flags": [] -} \ No newline at end of file diff --git a/examples/.tests_cache/fib.mq.json b/examples/.tests_cache/fib.mq.json deleted file mode 100644 index f5facfa..0000000 --- a/examples/.tests_cache/fib.mq.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "returncode": 0, - "stdout": "", - "stderr": "", - "flags": [] -} \ No newline at end of file diff --git a/examples/.tests_cache/permutations.mq.json b/examples/.tests_cache/permutations.mq.json deleted file mode 100644 index a1dc3cf..0000000 --- a/examples/.tests_cache/permutations.mq.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "returncode": 0, - "stdout": "[1; 2; 3; 4; 5]\n[1; 2; 3; 5; 4]\n[1; 2; 4; 3; 5]\n[1; 2; 4; 5; 3]\n[1; 2; 5; 3; 4]\n[1; 2; 5; 4; 3]\n[1; 3; 2; 4; 5]\n[1; 3; 2; 5; 4]\n[1; 3; 4; 2; 5]\n[1; 3; 4; 5; 2]\n[1; 3; 5; 2; 4]\n[1; 3; 5; 4; 2]\n[1; 4; 2; 3; 5]\n[1; 4; 2; 5; 3]\n[1; 4; 3; 2; 5]\n[1; 4; 3; 5; 2]\n[1; 4; 5; 2; 3]\n[1; 4; 5; 3; 2]\n[1; 5; 2; 3; 4]\n[1; 5; 2; 4; 3]\n[1; 5; 3; 2; 4]\n[1; 5; 3; 4; 2]\n[1; 5; 4; 2; 3]\n[1; 5; 4; 3; 2]\n[2; 1; 3; 4; 5]\n[2; 1; 3; 5; 4]\n[2; 1; 4; 3; 5]\n[2; 1; 4; 5; 3]\n[2; 1; 5; 3; 4]\n[2; 1; 5; 4; 3]\n[2; 3; 1; 4; 5]\n[2; 3; 1; 5; 4]\n[2; 3; 4; 1; 5]\n[2; 3; 4; 5; 1]\n[2; 3; 5; 1; 4]\n[2; 3; 5; 4; 1]\n[2; 4; 1; 3; 5]\n[2; 4; 1; 5; 3]\n[2; 4; 3; 1; 5]\n[2; 4; 3; 5; 1]\n[2; 4; 5; 1; 3]\n[2; 4; 5; 3; 1]\n[2; 5; 1; 3; 4]\n[2; 5; 1; 4; 3]\n[2; 5; 3; 1; 4]\n[2; 5; 3; 4; 1]\n[2; 5; 4; 1; 3]\n[2; 5; 4; 3; 1]\n[3; 1; 2; 4; 5]\n[3; 1; 2; 5; 4]\n[3; 1; 4; 2; 5]\n[3; 1; 4; 5; 2]\n[3; 1; 5; 2; 4]\n[3; 1; 5; 4; 2]\n[3; 2; 1; 4; 5]\n[3; 2; 1; 5; 4]\n[3; 2; 4; 1; 5]\n[3; 2; 4; 5; 1]\n[3; 2; 5; 1; 4]\n[3; 2; 5; 4; 1]\n[3; 4; 1; 2; 5]\n[3; 4; 1; 5; 2]\n[3; 4; 2; 1; 5]\n[3; 4; 2; 5; 1]\n[3; 4; 5; 1; 2]\n[3; 4; 5; 2; 1]\n[3; 5; 1; 2; 4]\n[3; 5; 1; 4; 2]\n[3; 5; 2; 1; 4]\n[3; 5; 2; 4; 1]\n[3; 5; 4; 1; 2]\n[3; 5; 4; 2; 1]\n[4; 1; 2; 3; 5]\n[4; 1; 2; 5; 3]\n[4; 1; 3; 2; 5]\n[4; 1; 3; 5; 2]\n[4; 1; 5; 2; 3]\n[4; 1; 5; 3; 2]\n[4; 2; 1; 3; 5]\n[4; 2; 1; 5; 3]\n[4; 2; 3; 1; 5]\n[4; 2; 3; 5; 1]\n[4; 2; 5; 1; 3]\n[4; 2; 5; 3; 1]\n[4; 3; 1; 2; 5]\n[4; 3; 1; 5; 2]\n[4; 3; 2; 1; 5]\n[4; 3; 2; 5; 1]\n[4; 3; 5; 1; 2]\n[4; 3; 5; 2; 1]\n[4; 5; 1; 2; 3]\n[4; 5; 1; 3; 2]\n[4; 5; 2; 1; 3]\n[4; 5; 2; 3; 1]\n[4; 5; 3; 1; 2]\n[4; 5; 3; 2; 1]\n[5; 1; 2; 3; 4]\n[5; 1; 2; 4; 3]\n[5; 1; 3; 2; 4]\n[5; 1; 3; 4; 2]\n[5; 1; 4; 2; 3]\n[5; 1; 4; 3; 2]\n[5; 2; 1; 3; 4]\n[5; 2; 1; 4; 3]\n[5; 2; 3; 1; 4]\n[5; 2; 3; 4; 1]\n[5; 2; 4; 1; 3]\n[5; 2; 4; 3; 1]\n[5; 3; 1; 2; 4]\n[5; 3; 1; 4; 2]\n[5; 3; 2; 1; 4]\n[5; 3; 2; 4; 1]\n[5; 3; 4; 1; 2]\n[5; 3; 4; 2; 1]\n[5; 4; 1; 2; 3]\n[5; 4; 1; 3; 2]\n[5; 4; 2; 1; 3]\n[5; 4; 2; 3; 1]\n[5; 4; 3; 1; 2]\n[5; 4; 3; 2; 1]\n", - "stderr": "", - "flags": [] -} \ No newline at end of file diff --git a/examples/.tests_cache/variables.mq.json b/examples/.tests_cache/variables.mq.json deleted file mode 100644 index 8725a7b..0000000 --- a/examples/.tests_cache/variables.mq.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "returncode": 0, - "stdout": "11\n", - "stderr": "", - "flags": [] -} \ No newline at end of file diff --git a/lib/ut/LICENSE.md b/lib/ut/LICENSE.md deleted file mode 100644 index 36b7cd9..0000000 --- a/lib/ut/LICENSE.md +++ /dev/null @@ -1,23 +0,0 @@ -Boost Software License - Version 1.0 - August 17th, 2003 - -Permission is hereby granted, free of charge, to any person or organization -obtaining a copy of the software and accompanying documentation covered by -this license (the "Software") to use, reproduce, display, distribute, -execute, and transmit the Software, and to prepare derivative works of the -Software, and to permit third-parties to whom the Software is furnished to -do so, all subject to the following: - -The copyright notices in the Software and this entire statement, including -the above license grant, this restriction and the following disclaimer, -must be included in all copies of the Software, in whole or in part, and -all derivative works of the Software, unless such copies or derivative -works are solely in the form of machine-executable object code generated by -a source language processor. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT -SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE -FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. diff --git a/lib/ut/README.md b/lib/ut/README.md deleted file mode 100644 index aab2fac..0000000 --- a/lib/ut/README.md +++ /dev/null @@ -1,2027 +0,0 @@ -![Version](https://badge.fury.io/gh/boost-ext%2Fut.svg) -![Linux](https://github.com/boost-ext/ut/actions/workflows/linux.yml/badge.svg) -![MacOs](https://github.com/boost-ext/ut/actions/workflows/macos.yml/badge.svg) -![Windows](https://github.com/boost-ext/ut/actions/workflows/windows.yml/badge.svg) -![Coveralls](https://codecov.io/gh/boost-ext/ut/branch/master/graph/badge.svg) -![Try it online](https://img.shields.io/badge/try%20it-online-blue.svg) -![AUR Badge](https://img.shields.io/aur/version/ut) - -> "If you liked it then you `"should have put a"_test` on it", Beyonce rule - -# UT / Ξt - -| [Motivation](#motivation) | [Quick Start](#quick-start) | [Overview](#overview) | [Tutorial](#tutorial) | [Examples](#examples) | [User Guide](#user-guide) | [FAQ](#faq) | [Benchmarks](#benchmarks) | - -
C++ single header/single module, macro-free Ξ(micro)/Unit Testing Framework -

- -

- -

- - -
Motivation -

- -Testing is a very important part of the Software Development, however, C++ doesn't provide any good testing facilities out of the box, -which often leads into a poor testing experience for develops and/or lack of tests/coverage in general. - -> One should treat testing code as production code! - -Additionally, well established testing practises such as [Test Driven Development (TDD)](https://en.wikipedia.org/wiki/Test-driven_development)/[Behaviour Driven Development (BDD)](https://en.wikipedia.org/wiki/Behavior-driven_development) are often not followed due to the same reasons. - -The following snippet is a common example of testing with projects in C++. - -```cpp -int main() { - // should sum numbers - { - assert(3 == sum(1, 2)); - } -} -``` - -There are quite a few problems with the approach above - -* No names for tests (Hard to follow intentions by further readers) -* No automatic registration of tests (No way to run specific tests) -* Hard to debug (Assertions don't provide any information why it failed) -* Hard to scale (No easy path forward for parameterized tests, multiple suites, parallel execution, etc...) -* Hard to integrate (No easy way to have a custom output such as XML for CI integration) -* Easy to make mistakes (With implicit casting, floating point comparison, pointer comparison for strings, etc...) -* Hard to follow good practises such as `TDD/BDD` (Lack of support for sections and declarative expressions) -* ... - -`UT` is trying to address these issues by simplifying testing experience with a few simple steps: - -* Just get a single [header/module](https://github.com/boost-ext/ut/blob/master/include/boost/ut.hpp) -* Integrate it into your project -* Learn a few simple concepts ([expect, test, suite](#api)) - -And you good to go! - -Okay, great, but why I would use `UT` over other/similar testing frameworks already available in C++? - -* [Boost.Test](https://github.com/boostorg/test) -* [GoogleTest](https://github.com/google/googletest) -* [Catch](https://github.com/catchorg/Catch2) -* [...](https://en.wikipedia.org/wiki/List_of_unit_testing_frameworks#C++) - -Great question! There are a few unique features which makes `UT` worth trying - -* Firstly, it supports all the basic Unit Testing Framework features (automatic registration of tests, assertions, suites, etc...) -* It's easy to integrate (it's just one [header/module](https://github.com/boost-ext/ut/blob/master/include/boost/ut.hpp)) -* It's macro free which makes testing experience that much nicer (it uses modern C++ features instead, macros are opt-in rather than being compulsory - [Can I still use macros?](#macros)) -* It's flexible (all parts of the framework such as: [runner, reporter, printer](#examples) can be customized, basically most other Unit Testing Frameworks can be implemented on top of UT primitives) -* It has smaller learning curve (just a few simple concepts ([expect, test, suite](#api))) -* It leverages C++ features to support more complex testing ([parameterized](#examples)) -* It's faster to compile and execute than similar frameworks which makes it suitable for bigger projects without additional hassle ([Benchmarks](#benchmarks)) -* It supports [TDD/BDD](#examples) workflows -* It supports [Gherkin](#examples) specification -* It supports [Spec](#examples) -* ... - -Sounds intriguing/interesting? Learn more at - -* [Tutorial](#tutorial) -* [Examples](#examples) -* [User-Guide](#user-guide) - -

-
- - -
Quick Start -

- -> https://bit.ly/ut-quick-start (slides) - -

-
- - -
Overview -

- -* No dependencies ([C++20](#cpp-20), Tested Compilers: GCC-9+, Clang-9.0+, Apple Clang-11.0.0+, MSVC-2019+*, Clang-cl-9.0+ -* Single header/module ([boost/ut.hpp](https://github.com/boost-ext/ut/blob/master/include/boost/ut.hpp)) -* Macro-free ([How does it work?](#how-it-works)) -* Easy to use ([Minimal API](#api) - `test, suite, operators, literals, [expect]`) -* Fast to compile/execute ([Benchmarks](#benchmarks)) -* Features ([Assertions](https://github.com/boost-ext/ut/blob/master/example/expect.cpp), [Suites](https://github.com/boost-ext/ut/blob/master/example/suite.cpp), [Tests](https://github.com/boost-ext/ut/blob/master/example/test.cpp), [Sections](https://github.com/boost-ext/ut/blob/master/example/section.cpp), [Parameterized](https://github.com/boost-ext/ut/blob/master/example/parameterized.cpp), [BDD](https://github.com/boost-ext/ut/blob/master/example/BDD.cpp), [Gherkin](https://github.com/boost-ext/ut/blob/master/example/gherkin.cpp), [Spec](https://github.com/boost-ext/ut/blob/master/example/spec.cpp), [Matchers](https://github.com/boost-ext/ut/blob/master/example/matcher.cpp), [Logging](https://github.com/boost-ext/ut/blob/master/example/log.cpp), [Runners](https://github.com/boost-ext/ut/blob/master/example/cfg/runner.cpp), [Reporters](https://github.com/boost-ext/ut/blob/master/example/cfg/reporter.cpp), [...](https://github.com/boost-ext/ut/blob/master/example)) -* Integrations ([ApprovalTests.cpp](https://github.com/approvals/ApprovalTests.cpp/releases/tag/v.7.0.0)) - -

-
- - -
Tutorial -

- -

    Step 0: Get it... -

- -> Get the latest latest header/module from [here!](https://github.com/boost-ext/ut/blob/master/include/boost/ut.hpp) - -> Include/Import - -```cpp -// #include // single header -// import boost.ut; // single module (C++20) - -int main() { } -``` - -> Compile & Run - -``` -$CXX main.cpp && ./a.out -``` - -``` -All tests passed (0 assert in 0 test) -``` - -> [Optional] Install it - -``` -cmake -Bbuild -H. -cd build && make # run tests -cd build && make install # install -``` - -> [Optional] CMake integration - -This project provides a CMake config and target. -Just load `ut` with `find_package` to import the `Boost::ut` target. -Linking against this target will add the necessary include directory for the single header file. -This is demonstrated in the following example. - -```cmake -find_package(ut REQUIRED) -add_library(my_test my_test.cpp) -target_link_libraries(my_test PRIVATE Boost::ut) -``` - -> [Optional] [Conan](https://conan.io) integration - -The [boost-ext-ut](https://conan.io/center/boost-ext-ut) package is available from [Conan Center](https://conan.io/center/). -Just include it in your project's Conanfile with `boost-ext-ut/1.1.9`. - -

-
- -
    Step 1: Expect it... -

- -> Let's write our first assertion, shall we? - -```cpp -int main() { - boost::ut::expect(true); -} -``` - -``` -All tests passed (1 asserts in 0 test) -``` - -> https://godbolt.org/z/vfx-eB - - -> Okay, let's make it fail now? - -```cpp -int main() { - boost::ut::expect(1 == 2); -} -``` - -``` -main.cpp:4:FAILED [false] -=============================================================================== -tests: 0 | 0 failed -asserts: 1 | 0 passed | 1 failed -``` - -> https://godbolt.org/z/7qTePx - -> Notice that expression `1 == 2` hasn't been printed. Instead we got `false`? - -> Let's print it then? - -```cpp -int main() { - using namespace boost::ut; - expect(1_i == 2); -} -``` - -``` -main.cpp:4:FAILED [1 == 2] -=============================================================================== -tests: 0 | 0 failed -asserts: 1 | 0 passed | 1 failed -``` - -> https://godbolt.org/z/7MXVzu - -> Okay, now we have it! `1 == 2` has been printed as expected. -> Notice the User Defined Literal (UDL) `1_i` was used. -> `_i` is a compile-time constant integer value - -* It allows to override comparison operators 👍 -* It disallow comparison of different types 👍 - -See the [User-guide](#user-guide) for more details. - -> Alternatively, a `terse` notation (no expect required) can be used. - -```cpp -int main() { - using namespace boost::ut::literals; - using namespace boost::ut::operators::terse; - - 1_i == 2; // terse notation -} -``` - -``` -main.cpp:7:FAILED [1 == 2] -=============================================================================== -tests: 0 | 0 failed -asserts: 1 | 0 passed | 1 failed -``` - -> https://godbolt.org/z/s77GSm - -> Other expression syntaxes are also available. - -```cpp -expect(1_i == 2); // UDL syntax -expect(1 == 2_i); // UDL syntax -expect(that % 1 == 2); // Matcher syntax -expect(eq(1, 2)); // eq/neq/gt/ge/lt/le -``` - -``` -main.cpp:6:FAILED [1 == 2] -main.cpp:7:FAILED [1 == 2] -main.cpp:8:FAILED [1 == 2] -main.cpp:9:FAILED [1 == 2] -=============================================================================== -tests: 0 | 0 failed -asserts: 4 | 0 passed | 4 failed -``` - -> https://godbolt.org/z/QbgGtc - -> Okay, but what about the case if my assertion is fatal. -> Meaning that the program will crash unless the processing will be terminated. -> Nothing easier, let's just add `>> fatal` after the expected expression to make it fatal. - -```cpp -expect((1 == 2_i) >> fatal); // fatal assertion -expect(1_i == 2); // not executed -``` - -``` -main.cpp:6:FAILED [1 == 2] -=============================================================================== -tests: 1 | 1 failed -asserts: 2 | 0 passed | 2 failed -``` - -> https://godbolt.org/z/WMe8Y1 - -> But my expression is more complex than just simple comparisons. -> Not a problem, logic operators are also supported in the `expect` 👍. - -```cpp -expect(42l == 42_l and 1 == 2_i); // compound expression -``` - -``` -main.cpp:5:FAILED [(42 == 42 and 1 == 2)] -=============================================================================== -tests: 0 | 0 failed -asserts: 1 | 0 passed | 1 failed -``` - -> https://godbolt.org/z/aEhX4t - -> Can I add a custom message though? -> Sure, `expect` calls are streamable! - -```cpp -int main() { - expect(42l == 42_l and 1 == 2_i) << "additional info"; -} -``` - -``` -main.cpp:5:FAILED [(42 == 42 and 1 == 2)] additional info -=============================================================================== -tests: 0 | 0 failed -asserts: 1 | 0 passed | 1 failed -``` - -> https://godbolt.org/z/v2PDuU - -

-
- -
    Step 2: Group it... -

- -> Assertions are great, but how to combine them into more cohesive units? -> `Test cases` are the way to go! They allow to group expectations for the same functionality into coherent units. - -```cpp -"hello world"_test = [] { }; -``` - -> Alternatively `test("hello world") = [] {}` can be used. - -``` -All tests passed (0 asserts in 1 tests) -``` - -> https://godbolt.org/z/Bh-EmY - - -> Notice `1 tests` but `0 asserts`. - -> Let's make our first end-2-end test case, shall we? - -```cpp -int main() { - "hello world"_test = [] { - int i = 43; - expect(42_i == i); - }; -} -``` - -``` -Running "hello world"... - main.cpp:8:FAILED [42 == 43] -FAILED -=============================================================================== -tests: 1 | 1 failed -asserts: 1 | 0 passed | 1 failed -``` - -> https://godbolt.org/z/Y43mXz - -> 👍 We are done here! - -> I'd like to nest my tests, though and share setup/tear-down. -> With lambdas used to represents `tests/sections` we can easily achieve that. -> Let's just take a look at the following example. - -```cpp -int main() { - "[vector]"_test = [] { - std::vector v(5); - - expect((5_ul == std::size(v)) >> fatal); - - should("resize bigger") = [v] { // or "resize bigger"_test - mut(v).resize(10); - expect(10_ul == std::size(v)); - }; - - expect((5_ul == std::size(v)) >> fatal); - - should("resize smaller") = [=]() mutable { // or "resize smaller"_test - v.resize(0); - expect(0_ul == std::size(v)); - }; - } -} -``` - -``` -All tests passed (4 asserts in 1 tests) -``` - -> https://godbolt.org/z/XWAdYt - -> Nice! That was easy, but I'm a believer into Behaviour Driven Development (`BDD`). -> Is there a support for that? -> Yes! Same example as above just with the `BDD` syntax. - -```cpp -int main() { - "vector"_test = [] { - given("I have a vector") = [] { - std::vector v(5); - expect((5_ul == std::size(v)) >> fatal); - - when("I resize bigger") = [=] { - mut(v).resize(10); - - then("The size should increase") = [=] { - expect(10_ul == std::size(v)); - }; - }; - }; - }; -} -``` - -``` -All tests passed (2 asserts in 1 tests) -``` - -> https://godbolt.org/z/dnvxsE - -> On top of that, `feature/scenario` aliases can be leveraged. - -```cpp -int main() { - feature("vector") = [] { - scenario("size") = [] { - given("I have a vector") = [] { - std::vector v(5); - expect((5_ul == std::size(v)) >> fatal); - - when("I resize bigger") = [=] { - mut(v).resize(10); - - then("The size should increase") = [=] { - expect(10_ul == std::size(v)); - }; - }; - }; - }; - }; -} -``` - -``` -All tests passed (2 asserts in 1 tests) -``` - -> https://godbolt.org/z/T4cWss - -> Can I use `Gherkin`? -> Yeah, let's rewrite the example using `Gherkin` specification - -```cpp -int main() { - bdd::gherkin::steps steps = [](auto& steps) { - steps.feature("Vector") = [&] { - steps.scenario("*") = [&] { - steps.given("I have a vector") = [&] { - std::vector v(5); - expect((5_ul == std::size(v)) >> fatal); - - steps.when("I resize bigger") = [&] { - v.resize(10); - }; - - steps.then("The size should increase") = [&] { - expect(10_ul == std::size(v)); - }; - }; - }; - }; - }; - - "Vector"_test = steps | - R"( - Feature: Vector - Scenario: Resize - Given I have a vector - When I resize bigger - Then The size should increase - )"; -} -``` - -``` -All tests passed (2 asserts in 1 tests) -``` - -> https://godbolt.org/z/jb1d8P - -> Nice, is `Spec` notation supported as well? - -```cpp -int main() { - describe("vector") = [] { - std::vector v(5); - expect((5_ul == std::size(v)) >> fatal); - - it("should resize bigger") = [v] { - mut(v).resize(10); - expect(10_ul == std::size(v)); - }; - }; -} -``` - -``` -All tests passed (2 asserts in 1 tests) -``` - -> https://godbolt.org/z/6jKKzT - -> That's great, but how can call the same tests with different arguments/types to be DRY (Don't Repeat Yourself)? -> Parameterized tests to the rescue! - -```cpp -int main() { - for (auto i : std::vector{1, 2, 3}) { - test("parameterized " + std::to_string(i)) = [i] { // 3 tests - expect(that % i > 0); // 3 asserts - }; - } -} -``` - -``` -All tests passed (3 asserts in 3 tests) -``` - -> https://godbolt.org/z/Utnd6X - -> That's it ðŸ˜Ū! -> Alternatively, a convenient test syntax is also provided 👍 - -```cpp -int main() { - "args"_test = [](const auto& arg) { - expect(arg > 0_i) << "all values greater than 0"; - } | std::vector{1, 2, 3}; -} -``` - -``` -All tests passed (3 asserts in 3 tests) -``` - -> https://godbolt.org/z/6FHtpq - -> Check [Examples](#examples) for further reading. - -

-
- -
    Step 3: Scale it... -

- -> Okay, but my project is more complex than that. How can I scale? -> `Test suites` will make that possible. By using `suite` in translation units -> `tests` defined inside will be automatically registered 👍 - -```cpp -suite errors = [] { - "exception"_test = [] { - expect(throws([] { throw 0; })) << "throws any exception"; - }; - - "failure"_test = [] { - expect(aborts([] { assert(false); })); - }; -}; - -int main() { } -``` - -``` -All tests passed (2 asserts in 2 tests) -``` - -> https://godbolt.org/z/_ccGwZ - ---- - -> What's next? -> * [Examples](#examples) -> * [User-Guide](#user-guide) - -

-
- -

-
- - -
Examples -

- -

    Assertions -

- -```cpp -// operators -expect(0_i == sum()); -expect(2_i != sum(1, 2)); -expect(sum(1) >= 0_i); -expect(sum(1) <= 1_i); -``` - -```cpp -// message -expect(3_i == sum(1, 2)) << "wrong sum"; -``` - -```cpp -// expressions -expect(0_i == sum() and 42_i == sum(40, 2)); -expect(0_i == sum() or 1_i == sum()) << "compound"; -``` - -```cpp -// matchers -expect(that % 0 == sum()); -expect(that % 42 == sum(40, 2) and that % (1 + 2) == sum(1, 2)); -expect(that % 1 != 2 or 2_i > 3); -``` - -```cpp -// eq/neq/gt/ge/lt/le -expect(eq(42, sum(40, 2))); -expect(neq(1, 2)); -expect(eq(sum(1), 1) and neq(sum(1, 2), 2)); -expect(eq(1, 1) and that % 1 == 1 and 1_i == 1); -``` - -```cpp -// floating points -expect(42.1_d == 42.101) << "epsilon=0.1"; -expect(42.10_d == 42.101) << "epsilon=0.01"; -expect(42.10000001 == 42.1_d) << "epsilon=0.1"; -``` - -```cpp -// constant -constexpr auto compile_time_v = 42; -auto run_time_v = 99; -expect(constant<42_i == compile_time_v> and run_time_v == 99_i); -``` - -```cpp -// failure -expect(1_i == 2) << "should fail"; -expect(sum() == 1_i or 2_i == sum()) << "sum?"; -``` - -``` -assertions.cpp:53:FAILED [1 == 2] should fail -assertions.cpp:54:FAILED [(0 == 1 or 2 == 0)] sum? -=============================================================================== -tests: 0 | 0 failed -asserts: 20 | 18 passed | 2 failed -``` - -> https://godbolt.org/z/E1c7G5 - -

-
- -
    Tests -

- -

        Run/Skip/Tag -

- -```cpp -"run UDL"_test = [] { - expect(42_i == 42); -}; - -skip / "don't run UDL"_test = [] { - expect(42_i == 43) << "should not fire!"; -}; -``` - -``` -All tests passed (1 asserts in 1 tests) -1 tests skipped -``` - -```cpp -test("run function") = [] { - expect(42_i == 42); -}; - -skip / test("don't run function") = [] { - expect(42_i == 43) << "should not fire!"; -}; -``` - -``` -All tests passed (1 asserts in 1 tests) -1 tests skipped -``` - -```cpp -tag("nightly") / tag("slow") / -"performance"_test= [] { - expect(42_i == 42); -}; - -tag("slow") / -"run slowly"_test= [] { - expect(42_i == 43) << "should not fire!"; -}; -``` - -``` -cfg = {.tag = {"nightly"}}; -``` - -``` -All tests passed (1 asserts in 1 tests) -1 tests skipped -``` - -> https://godbolt.org/z/X3_kG4 - -

-
- -
        Sections -

- -```cpp -"[vector]"_test = [] { - std::vector v(5); - - expect((5_ul == std::size(v)) >> fatal); - - should("resize bigger") = [=] { // or "resize bigger"_test - mut(v).resize(10); - expect(10_ul == std::size(v)); - }; - - expect((5_ul == std::size(v)) >> fatal); - - should("resize smaller") = [=]() mutable { // or "resize smaller"_test - v.resize(0); - expect(0_ul == std::size(v)); - }; -}; -``` - -``` -All tests passed (4 asserts in 1 tests) -``` - -> https://godbolt.org/z/cE91bj - -

-
- -
        Behavior Driven Development (BDD) -

- -```cpp -"Scenario"_test = [] { - given("I have...") = [] { - when("I run...") = [] { - then("I expect...") = [] { expect(1_i == 1); }; - then("I expect...") = [] { expect(1 == 1_i); }; - }; - }; -}; -``` - -``` -All tests passed (2 asserts in 1 tests) -``` - -> https://godbolt.org/z/mNBySr - -

-
- -
        Gherkin -

- -```cpp -int main() { - bdd::gherkin::steps steps = [](auto& steps) { - steps.feature("*") = [&] { - steps.scenario("*") = [&] { - steps.given("I have a number {value}") = [&](int value) { - auto number = value; - steps.when("I add {value} to it") = [&](int value) { - number += value; - }; - steps.then("I expect number to be {value}") = [&](int value) { - expect(that % number == value); - }; - }; - }; - }; - }; - - "Gherkin"_test = steps | - R"( - Feature: Number - Scenario: Addition - Given I have a number 40 - When I add 2 to it - Then I expect number to be 42 - )"; -} -``` - -``` -All tests passed (1 asserts in 1 tests) -``` - -> https://godbolt.org/z/BP3hyt - -

-
- -
        Spec -

- -```cpp -int main() { - describe("equality") = [] { - it("should be equal") = [] { expect(0_i == 0); }; - it("should not be equal") = [] { expect(1_i != 0); }; - }; -} -``` - -``` -All tests passed (2 asserts in 1 tests) -``` - -> https://godbolt.org/z/BXYJ3a - -

-
- -
        Parameterized -

- -```cpp -for (auto i : std::vector{1, 2, 3}) { - test("parameterized " + std::to_string(i)) = [i] { - expect(that % i > 0); - }; -} - -"args"_test = - [](auto arg) { - expect(arg >= 1_i); - } - | std::vector{1, 2, 3}; - -"types"_test = - [] { - expect(std::is_integral_v) << "all types are integrals"; - } - | std::tuple{}; - -"args and types"_test = - [](TArg arg) { - expect(std::is_integral_v >> fatal); - expect(42_i == arg or "is true"_b == arg); - expect(type == type or type == type); - } - | std::tuple{true, 42}; -``` - -``` -All tests passed (14 asserts in 10 tests) -``` - -> https://godbolt.org/z/4xGGdo - - -> And whenever I need to know the specific type for which the test failed, -> I can use `reflection::type_name()`, like this: - -```cpp -"types with type name"_test = - []() { - expect(std::is_unsigned_v) << reflection::type_name() << "is unsigned"; - } - | std::tuple{}; -``` - -``` -Running "types with type name"...PASSED -Running "types with type name"... - :10:FAILED [false] float is unsigned -FAILED -``` - -> https://godbolt.org/z/MEnGnbTY4 - -

-
- - -

-
- -
    Suites -

- -```cpp -namespace ut = boost::ut; - -ut::suite errors = [] { - using namespace ut; - - "throws"_test = [] { - expect(throws([] { throw 0; })); - }; - - "doesn't throw"_test = [] { - expect(nothrow([]{})); - }; -}; - -int main() { } -``` - -``` -All tests passed (2 asserts in 2 tests) -``` - -> https://godbolt.org/z/CFbTP9 - -

-
- -
    Misc -

- -

        Logging -

- -```cpp -"logging"_test = [] { - log << "pre"; - expect(42_i == 43) << "message on failure"; - log << "post"; -}; -``` - -``` -Running "logging"... -pre - logging.cpp:8:FAILED [42 == 43] message on failure -post -FAILED - -=============================================================================== - -tests: 1 | 1 failed -asserts: 1 | 0 passed | 1 failed -``` - -> https://godbolt.org/z/26fPSY - -

-
- -
        Matchers -

- -```cpp -"matchers"_test = [] { - constexpr auto is_between = [](auto lhs, auto rhs) { - return [=](auto value) { - return that % value >= lhs and that % value <= rhs; - }; - }; - - expect(is_between(1, 100)(42)); - expect(not is_between(1, 100)(0)); -}; -``` - -``` -All tests passed (2 asserts in 1 tests) -``` - -> https://godbolt.org/z/4qwrCi - -

-
- -
        Exceptions/Aborts -

- -```cpp -"exceptions/aborts"_test = [] { - expect(throws([] { throw std::runtime_error{""}; })) - << "throws runtime_error"; - expect(throws([] { throw 0; })) << "throws any exception"; - expect(nothrow([]{})) << "doesn't throw"; - expect(aborts([] { assert(false); })); -}; -``` - -``` -All tests passed (4 asserts in 1 tests) -``` - -> https://godbolt.org/z/A2EehK - -

-
- -

-
- -
    Config -

- -

        Runner -

- -```cpp -namespace ut = boost::ut; - -namespace cfg { - class runner { - public: - template auto on(ut::events::test test) { test(); } - template auto on(ut::events::skip) {} - template - auto on(ut::events::assertion) -> bool { return true; } - auto on(ut::events::fatal_assertion) {} - template auto on(ut::events::log) {} - }; -} // namespace cfg - -template<> auto ut::cfg = cfg::runner{}; -``` - -> https://godbolt.org/z/jdg687 - -

-
- -
        Reporter -

- -```cpp -namespace ut = boost::ut; - -namespace cfg { - class reporter { - public: - auto on(ut::events::test_begin) -> void {} - auto on(ut::events::test_run) -> void {} - auto on(ut::events::test_skip) -> void {} - auto on(ut::events::test_end) -> void {} - template auto on(ut::events::log) -> void {} - template - auto on(ut::events::assertion_pass) -> void {} - template - auto on(ut::events::assertion_fail) -> void {} - auto on(ut::events::fatal_assertion) -> void {} - auto on(ut::events::exception) -> void {} - auto on(ut::events::summary) -> void {} - }; -} // namespace cfg - -template <> -auto ut::cfg = ut::runner{}; -``` - -> https://godbolt.org/z/gsAPKg - -

-
- -
        Printer -

- -```cpp -namespace ut = boost::ut; - -namespace cfg { -struct printer : ut::printer { - template - auto& operator<<(T&& t) { - std::cerr << std::forward(t); - return *this; - } -}; -} // namespace cfg - -template <> -auto ut::cfg = ut::runner>{}; - -int main() { - using namespace ut; - "printer"_test = [] {}; -} -``` - -> https://godbolt.org/z/XCscF9 - -

-
- -

-
- -

-
- - - - -
User Guide -

- -

    API -

- -```cpp -export module boost.ut; /// __cpp_modules - -namespace boost::inline ext::ut::inline v1_1_9 { - /** - * Represents test suite object - */ - struct suite final { - /** - * Creates and executes test suite - * @example suite _ = [] {}; - * @param suite test suite function - */ - constexpr explicit(false) suite(auto suite); - }; - - /** - * Creates a test - * @example "name"_test = [] {}; - * @return test object to be executed - */ - constexpr auto operator""_test; - - /** - * Creates a test - * @example test("name") = [] {}; - * @return test object to be executed - */ - constexpr auto test = [](const auto name); - - /** - * Creates a test - * @example should("name") = [] {}; - * @return test object to be executed - */ - constexpr auto should = [](const auto name); - - /** - * Behaviour Driven Development (BDD) helper functions - * @param name step name - * @return test object to be executed - */ - constexpr auto given = [](const auto name); - constexpr auto when = [](const auto name); - constexpr auto then = [](const auto name); - - /** - * Evaluates an expression - * @example expect(42 == 42_i and 1 != 2_i); - * @param expr expression to be evaluated - * @param source location https://en.cppreference.com/w/cpp/utility/source_location - * @return stream - */ - constexpr OStream& expect( - Expression expr, - const std::source_location& location = std::source_location::current() - ); - - struct { - /** - * @example (that % 42 == 42); - * @param expr expression to be evaluated - */ - [[nodiscard]] constexpr auto operator%(Expression expr) const; - } that{}; - - inline namespace literals { - /** - * User defined literals to represent constant values - * @example 42_i, 0_uc, 1.23_d - */ - constexpr auto operator""_i; /// int - constexpr auto operator""_s; /// short - constexpr auto operator""_c; /// char - constexpr auto operator""_l; /// long - constexpr auto operator""_ll; /// long long - constexpr auto operator""_u; /// unsigned - constexpr auto operator""_uc; /// unsigned char - constexpr auto operator""_us; /// unsigned short - constexpr auto operator""_ul; /// unsigned long - constexpr auto operator""_f; /// float - constexpr auto operator""_d; /// double - constexpr auto operator""_ld; /// long double - - /** - * Represents dynamic values - * @example _i(42), _f(42.) - */ - constexpr auto _b(bool); - constexpr auto _c(char); - constexpr auto _s(short); - constexpr auto _i(int); - constexpr auto _l(long); - constexpr auto _ll(long long); - constexpr auto _u(unsigned); - constexpr auto _uc(unsigned char); - constexpr auto _us(unsigned short); - constexpr auto _ul(unsigned long); - constexpr auto _f(float); - constexpr auto _d(double); - constexpr auto _ld(long double); - - /** - * Logical representation of constant boolean (true) value - * @example "is set"_b : true - * not "is set"_b : false - */ - constexpr auto operator ""_b; - } // namespace literals - - inline namespace operators { - /** - * Comparison functions to be used in expressions - * @example eq(42, 42), neq(1, 2) - */ - constexpr auto eq(Operator lhs, Operator rhs); /// == - constexpr auto neq(Operator lhs, Operator rhs); /// != - constexpr auto gt(Operator lhs, Operator rhs); /// > - constexpr auto ge(Operator lhs, Operator rhs); /// >= - constexpr auto lt(Operator lhs, Operator rhs); /// < - constexpr auto le(Operator lhs, Operator rhs); /// <= - - /** - * Overloaded comparison operators to be used in expressions - * @example (42_i != 0) - */ - constexpr auto operator==; - constexpr auto operator!=; - constexpr auto operator>; - constexpr auto operator>=; - constexpr auto operator<; - constexpr auto operator<=; - - /** - * Overloaded logic operators to be used in expressions - * @example (42_i != 0 and 1 == 2_i) - */ - constexpr auto operator and; - constexpr auto operator or; - constexpr auto operator not; - - /** - * Executes parameterized tests - * @example "parameterized"_test = [](auto arg) {} | std::tuple{1, 2, 3}; - */ - constexpr auto operator|; - - /** - * Creates tags - * @example tag("slow") / tag("nightly") / "perf"_test = []{}; - */ - constexpr auto operator/; - - /** - * Creates a `fatal_assertion` from an expression - * @example (42_i == 0) >> fatal - */ - constexpr auto operator>>; - } // namespace operators - - /** - * Creates skippable test object - * @example skip / "don't run"_test = [] { }; - */ - constexpr auto skip = tag("skip"); - - struct { - /** - * @example log << "message!"; - * @param msg stringable message - */ - auto& operator<<(Msg msg); - } log{}; - - /** - * Makes object mutable - * @example mut(object) - * @param t object to be mutated - */ - template auto mut(const T& t) -> T&; - - /** - * Default execution flow policy - */ - class runner { - public: - /** - * @example cfg = { - .filter = "test.section.*", - .colors = { .none = "" }, - .dry__run = true - }; - * @param options.filter {default: "*"} runs all tests which names - matches test.section.* filter - * @param options.colors {default: { - .none = "\033[0m", - .pass = "\033[32m", - .fail = "\033[31m" - } if specified then overrides default color values - * @param options.dry_run {default: false} if true then print test names to be - executed without running them - */ - auto operator=(options); - - /** - * @example suite _ = [] {}; - * @param suite() executes suite - */ - template - auto on(ut::events::suite); - - /** - * @example "name"_test = [] {}; - * @param test.type ["test", "given", "when", "then"] - * @param test.name "name" - * @param test.arg parameterized argument - * @param test() executes test - */ - template - auto on(ut::events::test); - - /** - * @example skip / "don't run"_test = []{}; - * @param skip.type ["test", "given", "when", "then"] - * @param skip.name "don't run" - * @param skip.arg parameterized argument - */ - template - auto on(ut::events::skip); - - /** - * @example file.cpp:42: expect(42_i == 42); - * @param assertion.expr 42_i == 42 - * @param assertion.location { "file.cpp", 42 } - * @return true if expr passes, false otherwise - */ - template - auto on(ut::events::assertion) -> bool; - - /** - * @example expect((2_i == 1) >> fatal) - * @note triggered by `fatal` - * should std::exit - */ - auto on(ut::events::fatal_assertion); - - /** - * @example log << "message" - * @param log.msg "message" - */ - template - auto on(ut::events::log); - - /** - * Explicitly runs registered test suites - * If not called directly test suites are executed with run's destructor - * @example return run({.report_errors = true}) - * @param run_cfg.report_errors {default: false} if true it prints the summary after runnig - */ - auto run(run_cfg); - - /** - * Runs registered test suites if they haven't been explicilty executed already - */ - ~run(); - }; - - /** - * Default reporter policy - */ - class reporter { - public: - /** - * @example file.cpp:42: "name"_test = [] {}; - * @param test_begin.type ["test", "given", "when", "then"] - * @param test_begin.name "name" - * @param test_begin.location { "file.cpp", 42 } - */ - auto on(ut::events::test_begin) -> void; - - /** - * @example "name"_test = [] {}; - * @param test_run.type ["test", "given", "when", "then"] - * @param test_run.name "name" - */ - auto on(ut::events::test_run) -> void; - - /** - * @example "name"_test = [] {}; - * @param test_skip.type ["test", "given", "when", "then"] - * @param test_skip.name "name" - */ - auto on(ut::events::test_skip) -> void; - - /** - * @example "name"_test = [] {}; - * @param test_end.type ["test", "given", "when", "then"] - * @param test_end.name "name" - */ - auto on(ut::events::test_end) -> void; - - /** - * @example log << "message" - * @param log.msg "message" - */ - template - auto on(ut::events::log) -> void; - - /** - * @example file.cpp:42: expect(42_i == 42); - * @param assertion_pass.expr 42_i == 42 - * @param assertion_pass.location { "file.cpp", 42 } - */ - template - auto on(ut::events::assertion_pass) -> void; - - /** - * @example file.cpp:42: expect(42_i != 42); - * @param assertion_fail.expr 42_i != 42 - * @param assertion_fail.location { "file.cpp", 42 } - */ - template - auto on(ut::events::assertion_fail) -> void; - - /** - * @example expect((2_i == 1) >> fatal) - * @note triggered by `fatal` - * should std::exit - */ - auto on(ut::events::fatal_assertion) -> void; - - /** - * @example "exception"_test = [] { throw std::runtime_error{""}; }; - */ - auto on(ut::events::exception) -> void; - - /** - * @note triggered on destruction of runner - */ - auto on(ut::events::summary) -> void; - }; - - /** - * Used to override default running policy - * @example template <> auto cfg = runner{}; - */ - struct override {}; - - /** - * Default UT execution policy - * Can be overwritten with override - */ - template auto cfg = runner{}; -} -``` - -

-
- -
    Configuration -

- -| Option | Description | Example | -|-|-|-| -| `BOOST_UT_VERSION` | Current version | `1'1'9` | - -

-
- -

-
- - -
FAQ -

- - -

    How does it work? -

- -> `suite` - - ```cpp - /** - * Reperesents suite object - * @example suite _ = []{}; - */ - struct suite final { - /** - * Assigns and executes test suite - */ - [[nodiscard]] constexpr explicit(false) suite(Suite suite) { - suite(); - } - }; - ``` - -> `test` - - ```cpp - /** - * Creates named test object - * @example "hello world"_test - * @return test object - */ - [[nodiscard]] constexpr Test operator ""_test(const char* name, std::size_t size) { - return test{{name, size}}; - } - ``` - - ```cpp - /** - * Represents test object - */ - struct test final { - std::string_view name{}; /// test case name - - /** - * Assigns and executes test function - * @param test function - */ - constexpr auto operator=(const Test& test) { - std::cout << "Running... " << name << '\n'; - test(); - } - }; - ``` - -> `expect` - - ```cpp - /** - * Evaluates an expression - * @example expect(42_i == 42); - * @param expr expression to be evaluated - * @param source location https://en.cppreference.com/w/cpp/utility/source_location - * @return stream - */ - constexpr OStream& expect( - Expression expr, - const std::source_location& location = std::source_location::current() - ) { - if (not static_cast(expr) { - std::cerr << location.file() - << ':' - << location.line() - << ":FAILED: " - << expr - << '\n'; - } - - return std::cerr; - } - ``` - - ```cpp - /** - * Creates constant object for which operators can be overloaded - * @example 42_i - * @return integral constant object - */ - template - [[nodiscard]] constexpr Operator operator""_i() -> integral_constant>; - ``` - - ```cpp - /** - * Overloads comparison if at least one of {lhs, rhs} is an Operator - * @example (42_i == 42) - * @param lhs Left-hand side operator - * @param rhs Right-hand side operator - * @return Comparison object - */ - [[nodiscard]] constexpr auto operator==(Operator lhs, Operator rhs) { - return eq{lhs, rhs}; - } - ``` - - ```cpp - /** - * Comparison Operator - */ - template - struct eq final { - TLhs lhs{}; // Left-hand side operator - TRhs rhs{}; // Right-hand side operator - - /** - * Performs comparison operatation - * @return true if expression is succesful - */ - [[nodiscard]] constexpr explicit operator bool() const { - return lhs == rhs; - } - - /** - * Nicely prints the operation - */ - friend auto operator<<(OStream& os, const eq& op) -> Ostream& { - return (os << op.lhs << " == " << op.rhs); - } - }; - ``` - -> `Sections` - - ```cpp - /** - * Convenient aliases for creating test named object - * @example should("return true") = [] {}; - */ - constexpr auto should = [](const auto name) { return test{name}; }; - ``` - -> `Behaviour Driven Development (BDD)` - - ```cpp - /** - * Convenient aliases for creating BDD tests - * @example feature("Feature") = [] {}; - * @example scenario("Scenario") = [] {}; - * @example given("I have an object") = [] {}; - * @example when("I call it") = [] {}; - * @example then("I should get") = [] {}; - */ - constexpr auto feature = [](const auto name) { return test{name}; }; - constexpr auto scenario = [](const auto name) { return test{name}; }; - constexpr auto given = [](const auto name) { return test{name}; }; - constexpr auto when = [](const auto name) { return test{name}; }; - constexpr auto then = [](const auto name) { return test{name}; }; - ``` - -> https://godbolt.org/z/6Nk5Mi - -> `Spec` - - ```cpp - /** - * Convenient aliases for creating Spec tests - * @example describe("test") = [] {}; - * @example it("should...") = [] {}; - */ - constexpr auto describe = [](const auto name) { return test{name}; }; - constexpr auto it = [](const auto name) { return test{name}; }; - ``` - -> [Example implementation](https://github.com/boost-ext/ut/tree/gh-pages/denver-cpp-2020/example) - -> Try it online - -* Header - https://godbolt.org/z/x96n8b -* Module - https://wandbox.org/permlink/LrV7WwIgghTP1nrs - -

-
- - -
    Fast compilation times (Benchmarks)? -

- -> Implementation - -* Leveraging [C++20](#cpp-20) features - -* Avoiding unique types for lambda expressions - -```cpp - template - requires not std::convertible_to> - constexpr auto operator=(Test test); - -vs - - // Compiles 5x faster because it doesn't introduce a new type for each lambda - constexpr auto operator=(void (*test)()); -``` - -* `Type-name` erasure (allows types/function memoization) - -```cpp - eq, int>{ {}, 42 } - -vs - - // Can be memoized - faster to compile - eq{42, 42} -``` - -* Limiting preprocessor work - * Single header/module - * Minimal number of include files - -* Simplified versions of - * `std::function` - * `std::string_view` - -

-
- - -
    C++20 features? -

- -* API - - * [Source Location](https://eel.is/c++draft/support.srcloc#source.location.syn) - * Assertions - `expect(false)` - ` __FILE__:__LINE__:FAILED [false]` - - * [Designated initializers](https://eel.is/c++draft/dcl.init#nt:designated-initializer-list) - * Configuration - `cfg = {.filter = "test"}` - - * [Non-Type Template Parameter](https://eel.is/c++draft/temp.arg.nontype) - * Constant matchers - `constant<42_i == 42>` - - * [Template Parameter List for generic lambdas](https://eel.is/c++draft/expr.prim.lambda) - * Parameterized tests - `"types"_test = []() {};` - - * [Concepts](https://eel.is/c++draft/concepts.lang) - * Operators - `Operator @ Operator` - - * [Modules](https://eel.is/c++draft/module) - * `import boost.ut;` - -

-
- - -
    C++2X integration? -

- -> Parameterized tests with Expansion statements (https://wg21.link/P1306r1) - -```cpp -template for (auto arg : std::tuple{}) { - test("types " + std::to_string(arg)) = [arg] { - expect(type(arg) == type or type(arg) == type); - }; -} -``` - -``` -All tests passed (2 asserts in 2 tests) -``` - -> https://cppx.godbolt.org/z/dMmqmM - -

-
- - -
    Is standardization an option? -

- -> Personally, I believe that C++ standard could benefit from common testing primitives (`expect`, `""_test`) because - -* It lowers the entry-level to the language (no need for third-party libraries) -* It improves the education aspect (one standard way of doing it) -* It makes the language more coherent/stable (consistent design with other features, stable API) -* It makes the testing a first class citizen (shows that the community cares about this aspect of the language) -* It allows to publish tests for the Standard Library (STL) in the standard way (coherency, easier to extend) -* It allows to act as additional documentation as a way to verify whether a particular implementation is conforming (quality, self-verification) -* It helps with establishing standard vocabulary for testing (common across STL and other projects) - -

-
- - -
    Can I still use macros? -

- -> Sure, although please notice that there are negatives of using macros such as - -* Error messages might be not clear and/or point to the wrong line -* Global scope will be polluted -* Type safety will be ignored - -```cpp -#define EXPECT(...) ::boost::ut::expect(::boost::ut::that % __VA_ARGS__) -#define SUITE ::boost::ut::suite _ = [] -#define TEST(name) ::boost::ut::detail::test{"test", name} = [=]() mutable - -SUITE { - TEST("suite") { - EXPECT(42 == 42); - }; -}; - -int main() { - TEST("macro") { - EXPECT(1 != 2); - }; - - TEST("vector") { - std::vector v(5); - - EXPECT((5u == std::size(v)) >> fatal) << "fatal"; - - TEST("resize bigger") { - v.resize(10); - EXPECT(10u == std::size(v)); - }; - }; -} -``` - -``` -All tests passed (4 asserts in 3 tests) -``` - -> https://godbolt.org/z/WcEKTr - -

-
- -
    What about Mocks/Stubs/Fakes? -

- -> Consider using one of the following frameworks - -* https://github.com/cpp-testing/GUnit/blob/master/docs/GMock.md -* https://github.com/eranpeer/FakeIt -* https://github.com/dascandy/hippomocks - -

-
- -
    What about Microbenchmarking? -

- -> [Example benchmark](example/benchmark.cpp) - -> Consider using one of the following frameworks - -* https://github.com/google/benchmark -* https://github.com/DigitalInBlue/Celero -* https://github.com/libnonius/nonius -* https://github.com/martinus/nanobench - -

-
- -
    Related materials/talks? -

- -* [[Boost].UT - Unit Testing Framework - Kris Jusiak](https://boost-ext.github.io/ut/denver-cpp-2019) -* [Future of Testing with C++20 - Kris Jusiak](https://boost-ext.github.io/ut/meeting-cpp-2020) -* [Macro-Free Testing with C++20 - Kris Jusiak](https://www.youtube.com/watch?v=irdgFyxOs_Y) -* ["If you liked it then you `"should have put a"_test` on it", Beyonce rule - Kris Jusiak](https://www.youtube.com/watch?v=yCI8MjvOMeE) -* [Principles of Unit Testing With C++ - Dave Steffen and Kris Jusiak](https://www.youtube.com/watch?v=oOcuJdJJ33g) -* [Empirical Unit Testing - Dave Steffen](https://www.twitch.tv/videos/686512433) - -

-
- - -
    How to contribute? -

- -> [CONTRIBUTING](.github/CONTRIBUTING.md) - -

- -

-
- - -
Benchmarks -

- -| Framework | Version | Standard | License | Linkage | Test configuration | -|-|-|-|-|-|-| -| [Boost.Test](https://github.com/boostorg/test) | [1.71.0](https://www.boost.org/users/history/version_1_71_0.html) | C++03 | Boost 1.0 | single header/library | `static library` | -| [GoogleTest](https://github.com/google/googletest) | [1.10.0](https://github.com/google/googletest/releases/tag/release-1.10.0) | C++11 | BSD-3 | library | `static library` | -| [Catch](https://github.com/catchorg/Catch2) | [2.10.2](https://github.com/catchorg/Catch2/releases/download/v2.10.2/catch.hpp) | C++11 | Boost 1.0 | single header | `CATCH_CONFIG_FAST_COMPILE` | -| [Doctest](https://github.com/onqtam/doctest) | [2.3.5](https://github.com/onqtam/doctest/blob/master/doctest/doctest.h) | C++11 | MIT | single header | `DOCTEST_CONFIG_SUPER_FAST_ASSERTS` | -| [UT](https://github.com/boost-ext/ut) | [1.1.0](https://github.com/boost-ext/ut/blob/master/include/boost/ut.hpp) | C++20 | Boost 1.0 | single header/module | | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Include / 0 tests, 0 asserts, 1 cpp file -
- Assert / 1 test, 1'000'000 asserts, 1 cpp file -
- Test / 1'000 tests, 0 asserts, 1 cpp file -
- Suite / 10'000 tests, 0 asserts, 100 cpp files -
- Suite+Assert / 10'000 tests, 40'000 asserts, 100 cpp files -
- Suite+Assert+STL / 10'000 tests, 20'000 asserts, 100 cpp files -
- Incremental Build - Suite+Assert+STL / 1 cpp file change (1'000 tests, 20'000 asserts, 100 cpp files) -
- Suite+Assert+STL / 10'000 tests, 20'000 asserts, 100 cpp files
(Headers vs Precompiled headers vs C++20 Modules)
-
- -> https://github.com/cpp-testing/ut-benchmark - -

-
- -

-
- ---- - -**Disclaimer** `UT` is not an official Boost library. - -

diff --git a/lib/ut/boost/ut.hpp b/lib/ut/boost/ut.hpp deleted file mode 100644 index 0e5c2f8..0000000 --- a/lib/ut/boost/ut.hpp +++ /dev/null @@ -1,2391 +0,0 @@ -// -// Copyright (c) 2019-2021 Kris Jusiak (kris at jusiak dot net) -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) -// -#if defined(__cpp_modules) && !defined(BOOST_UT_DISABLE_MODULE) -export module boost.ut; -export import std; -#else -#pragma once -#endif - -#if __has_include() -#include // and, or, not, ... -#endif - -#if not defined(__cpp_rvalue_references) -#error "[Boost::ext].UT requires support for rvalue references"; -#elif not defined(__cpp_decltype) -#error "[Boost::ext].UT requires support for decltype"; -#elif not defined(__cpp_return_type_deduction) -#error "[Boost::ext].UT requires support for return type deduction"; -#elif not defined(__cpp_deduction_guides) -#error "[Boost::ext].UT requires support for return deduction guides"; -#elif not defined(__cpp_generic_lambdas) -#error "[Boost::ext].UT requires support for generic lambdas"; -#elif not defined(__cpp_constexpr) -#error "[Boost::ext].UT requires support for constexpr"; -#elif not defined(__cpp_alias_templates) -#error "[Boost::ext].UT requires support for alias templates"; -#elif not defined(__cpp_variadic_templates) -#error "[Boost::ext].UT requires support for variadic templates"; -#elif not defined(__cpp_fold_expressions) -#error "[Boost::ext].UT requires support for return fold expressions"; -#elif not defined(__cpp_static_assert) -#error "[Boost::ext].UT requires support for static assert"; -#else -#define BOOST_UT_VERSION 1'1'9 - -#if defined(__has_builtin) and defined(__GNUC__) and (__GNUC__ < 10) and \ - not defined(__clang__) -#undef __has_builtin -#endif - -#if not defined(__has_builtin) -#if defined(__GNUC__) and (__GNUC__ >= 9) -#define __has___builtin_FILE 1 -#define __has___builtin_LINE 1 -#endif -#define __has_builtin(...) __has_##__VA_ARGS__ -#endif - -#include -#include -#include -#include -#include -#include -#include -#if __has_include() and __has_include() -#include -#include -#endif -#if defined(__cpp_exceptions) -#include -#endif - -#if defined(__cpp_lib_source_location) -#include -#endif - -#if defined(__cpp_modules) && !defined(BOOST_UT_DISABLE_MODULE) -export -#endif - namespace boost::inline ext::ut::inline v1_1_9 { -namespace utility { -template -class function; -template -class function { - public: - constexpr function() = default; - template - constexpr /*explicit(false)*/ function(T data) - : invoke_{invoke_impl}, - destroy_{destroy_impl}, - data_{new T{static_cast(data)}} {} - constexpr function(function&& other) noexcept - : invoke_{static_cast(other.invoke_)}, - destroy_{static_cast(other.destroy_)}, - data_{static_cast(other.data_)} { - other.data_ = {}; - } - constexpr function(const function&) = delete; - ~function() { destroy_(data_); } - - constexpr function& operator=(const function&) = delete; - constexpr function& operator=(function&&) = delete; - [[nodiscard]] constexpr auto operator()(TArgs... args) -> R { - return invoke_(data_, args...); - } - [[nodiscard]] constexpr auto operator()(TArgs... args) const -> R { - return invoke_(data_, args...); - } - - private: - template - [[nodiscard]] static auto invoke_impl(void* data, TArgs... args) -> R { - return (*static_cast(data))(args...); - } - - template - static auto destroy_impl(void* data) -> void { - delete static_cast(data); - } - - R (*invoke_)(void*, TArgs...){}; - void (*destroy_)(void*){}; - void* data_{}; -}; - -[[nodiscard]] inline auto is_match(std::string_view input, - std::string_view pattern) -> bool { - if (std::empty(pattern)) { - return std::empty(input); - } - - if (std::empty(input)) { - return pattern[0] == '*' ? is_match(input, pattern.substr(1)) : false; - } - - if (pattern[0] != '?' and pattern[0] != '*' and pattern[0] != input[0]) { - return false; - } - - if (pattern[0] == '*') { - for (decltype(std::size(input)) i = 0u; i <= std::size(input); ++i) { - if (is_match(input.substr(i), pattern.substr(1))) { - return true; - } - } - return false; - } - - return is_match(input.substr(1), pattern.substr(1)); -} - -template -[[nodiscard]] constexpr auto match(const TPattern& pattern, const TStr& str) - -> std::vector { - std::vector groups{}; - auto pi = 0u; - auto si = 0u; - - const auto matcher = [&](char b, char e, char c = 0) { - const auto match = si; - while (str[si] and str[si] != b and str[si] != c) { - ++si; - } - groups.emplace_back(str.substr(match, si - match)); - while (pattern[pi] and pattern[pi] != e) { - ++pi; - } - pi++; - }; - - while (pi < std::size(pattern) && si < std::size(str)) { - if (pattern[pi] == '\'' and str[si] == '\'' and pattern[pi + 1] == '{') { - ++si; - matcher('\'', '}'); - } else if (pattern[pi] == '{') { - matcher(' ', '}', ','); - } else if (pattern[pi] != str[si]) { - return {}; - } - ++pi; - ++si; - } - - if (si < str.size() or pi < std::size(pattern)) { - return {}; - } - - return groups; -} - -template -[[nodiscard]] inline auto split(T input, TDelim delim) -> std::vector { - std::vector output{}; - std::size_t first{}; - while (first < std::size(input)) { - const auto second = input.find_first_of(delim, first); - if (first != second) { - output.emplace_back(input.substr(first, second - first)); - } - if (second == T::npos) { - break; - } - first = second + 1; - } - return output; -} -} // namespace utility - -namespace reflection { -#if defined(__cpp_lib_source_location) -using source_location = std::source_location; -#else -class source_location { - public: - [[nodiscard]] static constexpr auto current( -#if (__has_builtin(__builtin_FILE) and __has_builtin(__builtin_LINE)) - const char* file = __builtin_FILE(), int line = __builtin_LINE() -#else - const char* file = "unknown", int line = {} -#endif - ) noexcept { - source_location sl{}; - sl.file_ = file; - sl.line_ = line; - return sl; - } - [[nodiscard]] constexpr auto file_name() const noexcept { return file_; } - [[nodiscard]] constexpr auto line() const noexcept { return line_; } - - private: - const char* file_{"unknown"}; - int line_{}; -}; -#endif - -template -[[nodiscard]] constexpr auto type_name() -> std::string_view { -#if defined(_MSC_VER) and not defined(__clang__) - return {&__FUNCSIG__[120], sizeof(__FUNCSIG__) - 128}; -#elif defined(__clang_analyzer__) - return {&__PRETTY_FUNCTION__[57], sizeof(__PRETTY_FUNCTION__) - 59}; -#elif defined(__clang__) and (__clang_major__ >= 13) and defined(__APPLE__) - return {&__PRETTY_FUNCTION__[57], sizeof(__PRETTY_FUNCTION__) - 59}; -#elif defined(__clang__) and (__clang_major__ >= 12) and not defined(__APPLE__) - return {&__PRETTY_FUNCTION__[57], sizeof(__PRETTY_FUNCTION__) - 59}; -#elif defined(__clang__) - return {&__PRETTY_FUNCTION__[70], sizeof(__PRETTY_FUNCTION__) - 72}; -#elif defined(__GNUC__) - return {&__PRETTY_FUNCTION__[85], sizeof(__PRETTY_FUNCTION__) - 136}; -#endif -} -} // namespace reflection - -namespace math { -template -[[nodiscard]] constexpr auto abs(const T t) -> T { - return t < T{} ? -t : t; -} - -template -[[nodiscard]] constexpr auto min_value(const T& lhs, const T& rhs) -> const T& { - return (rhs < lhs) ? rhs : lhs; -} - -template -[[nodiscard]] constexpr auto pow(const T base, const TExp exp) -> T { - return exp ? T(base * pow(base, exp - TExp(1))) : T(1); -} - -template -[[nodiscard]] constexpr auto num() -> T { - static_assert( - ((Cs == '.' or Cs == '\'' or (Cs >= '0' and Cs <= '9')) and ...)); - T result{}; - for (const char c : {Cs...}) { - if (c == '.') { - break; - } - if (c >= '0' and c <= '9') { - result = result * T(10) + T(c - '0'); - } - } - return result; -} - -template -[[nodiscard]] constexpr auto den() -> T { - constexpr const std::array cs{Cs...}; - T result{}; - auto i = 0u; - while (cs[i++] != '.') { - } - - for (auto j = i; j < sizeof...(Cs); ++j) { - result += pow(T(10), sizeof...(Cs) - j) * T(cs[j] - '0'); - } - return result; -} - -template -[[nodiscard]] constexpr auto den_size() -> T { - constexpr const std::array cs{Cs...}; - T i{}; - while (cs[i++] != '.') { - } - - return T(sizeof...(Cs)) - i + T(1); -} - -template -[[nodiscard]] constexpr auto den_size(TValue value) -> T { - constexpr auto precision = TValue(1e-7); - T result{}; - TValue tmp{}; - do { - value *= 10; - tmp = value - T(value); - ++result; - } while (tmp > precision); - - return result; -} - -} // namespace math - -namespace type_traits { -template -struct list {}; - -template -struct identity { - using type = T; -}; - -template -struct function_traits : function_traits {}; - -template -struct function_traits { - using result_type = R; - using args = list; -}; - -template -struct function_traits { - using result_type = R; - using args = list; -}; - -template -struct function_traits { - using result_type = R; - using args = list; -}; - -template -struct function_traits { - using result_type = R; - using args = list; -}; - -template -T&& declval(); -template -constexpr auto is_valid(TExpr expr) - -> decltype(expr(declval()), bool()) { - return true; -} -template -constexpr auto is_valid(...) -> bool { - return false; -} - -template -static constexpr auto is_container_v = - is_valid([](auto t) -> decltype(t.begin(), t.end(), void()) {}); - -template -static constexpr auto has_user_print = - is_valid([](auto t) -> decltype(void(declval() << t)) {}); - -template -static constexpr auto has_value_v = - is_valid([](auto t) -> decltype(void(t.value)) {}); - -template -static constexpr auto has_epsilon_v = - is_valid([](auto t) -> decltype(void(t.epsilon)) {}); - -template -inline constexpr auto is_floating_point_v = false; -template <> -inline constexpr auto is_floating_point_v = true; -template <> -inline constexpr auto is_floating_point_v = true; -template <> -inline constexpr auto is_floating_point_v = true; - -#if defined(__clang__) or defined(_MSC_VER) -template -static constexpr auto is_convertible_v = __is_convertible_to(From, To); -#else -template -constexpr auto is_convertible(int) -> decltype(bool(To(declval()))) { - return true; -} -template -constexpr auto is_convertible(...) { - return false; -} -template -constexpr auto is_convertible_v = is_convertible(0); -#endif - -template -struct requires_ {}; -template <> -struct requires_ { - using type = int; -}; - -template -using requires_t = typename requires_::type; -} // namespace type_traits - -struct none {}; - -namespace events { -struct test_begin { - std::string_view type{}; - std::string_view name{}; - reflection::source_location location{}; -}; -template -struct test { - std::string_view type{}; - std::string_view name{}; - std::vector tag{}; - reflection::source_location location{}; - TArg arg{}; - Test run{}; - - constexpr auto operator()() { run_impl(static_cast(run), arg); } - constexpr auto operator()() const { run_impl(static_cast(run), arg); } - - private: - static constexpr auto run_impl(Test test, const none&) { test(); } - - template - static constexpr auto run_impl(T test, const TArg& arg) - -> decltype(test(arg), void()) { - test(arg); - } - - template - static constexpr auto run_impl(T test, const TArg&) - -> decltype(test.template operator()(), void()) { - test.template operator()(); - } -}; -template -test(std::string_view, std::string_view, std::string_view, - reflection::source_location, TArg, Test) -> test; -template -struct suite { - TSuite run{}; - constexpr auto operator()() { run(); } - constexpr auto operator()() const { run(); } -}; -template -suite(TSuite) -> suite; -struct test_run { - std::string_view type{}; - std::string_view name{}; -}; -template -struct skip { - std::string_view type{}; - std::string_view name{}; - TArg arg{}; -}; -template -skip(std::string_view, std::string_view, TArg) -> skip; -struct test_skip { - std::string_view type{}; - std::string_view name{}; -}; -template -struct assertion { - TExpr expr{}; - reflection::source_location location{}; -}; -template -assertion(TExpr, reflection::source_location) -> assertion; -template -struct assertion_pass { - TExpr expr{}; - reflection::source_location location{}; -}; -template -assertion_pass(TExpr) -> assertion_pass; -template -struct assertion_fail { - TExpr expr{}; - reflection::source_location location{}; -}; -template -assertion_fail(TExpr) -> assertion_fail; -struct test_end { - std::string_view type{}; - std::string_view name{}; -}; -template -struct log { - TMsg msg{}; -}; -template -log(TMsg) -> log; -struct fatal_assertion {}; -struct exception { - const char* msg{}; - [[nodiscard]] auto what() const -> const char* { return msg; } -}; -struct summary {}; -} // namespace events - -namespace detail { -struct op {}; -struct fatal {}; -struct cfg { - static inline reflection::source_location location{}; - static inline bool wip{}; -}; - -template -[[nodiscard]] constexpr auto get_impl(const T& t, int) -> decltype(t.get()) { - return t.get(); -} -template -[[nodiscard]] constexpr auto get_impl(const T& t, ...) -> decltype(auto) { - return t; -} -template -[[nodiscard]] constexpr auto get(const T& t) { - return get_impl(t, 0); -} - -template -struct type_ : op { - template - [[nodiscard]] constexpr auto operator()(const TOther&) const - -> const type_ { - return {}; - } - [[nodiscard]] constexpr auto operator==(type_) -> bool { return true; } - template - [[nodiscard]] constexpr auto operator==(type_) -> bool { - return false; - } - template - [[nodiscard]] constexpr auto operator==(const TOther&) -> bool { - return std::is_same_v; - } - [[nodiscard]] constexpr auto operator!=(type_) -> bool { return true; } - template - [[nodiscard]] constexpr auto operator!=(type_) -> bool { - return true; - } - template - [[nodiscard]] constexpr auto operator!=(const TOther&) -> bool { - return not std::is_same_v; - } -}; - -template -struct value : op { - using value_type = T; - - constexpr /*explicit(false)*/ value(const T& _value) : value_{_value} {} - [[nodiscard]] constexpr explicit operator T() const { return value_; } - [[nodiscard]] constexpr decltype(auto) get() const { return value_; } - - T value_{}; -}; - -template -struct value>> - : op { - using value_type = T; - static inline auto epsilon = T{}; - - constexpr value(const T& _value, const T precision) : value_{_value} { - epsilon = precision; - } - - constexpr /*explicit(false)*/ value(const T& val) - : value{val, T(1) / math::pow(T(10), - math::den_size(val))} {} - [[nodiscard]] constexpr explicit operator T() const { return value_; } - [[nodiscard]] constexpr decltype(auto) get() const { return value_; } - - T value_{}; -}; - -template -class value_location : public detail::value { - public: - constexpr /*explicit(false)*/ value_location( - const T& t, const reflection::source_location& sl = - reflection::source_location::current()) - : detail::value{t} { - cfg::location = sl; - } - - constexpr value_location(const T& t, const T precision, - const reflection::source_location& sl = - reflection::source_location::current()) - : detail::value{t, precision} { - cfg::location = sl; - } -}; - -template -struct integral_constant : op { - using value_type = decltype(N); - static constexpr auto value = N; - - [[nodiscard]] constexpr auto operator-() const { - return integral_constant<-N>{}; - } - [[nodiscard]] constexpr explicit operator value_type() const { return N; } - [[nodiscard]] constexpr auto get() const { return N; } -}; - -template -struct floating_point_constant : op { - using value_type = T; - - static constexpr auto epsilon = T(1) / math::pow(T(10), Size - 1); - static constexpr auto value = T(P) * (T(N) + (T(D) / math::pow(T(10), Size))); - - [[nodiscard]] constexpr auto operator-() const { - return floating_point_constant{}; - } - [[nodiscard]] constexpr explicit operator value_type() const { return value; } - [[nodiscard]] constexpr auto get() const { return value; } -}; - -template -struct eq_ : op { - constexpr eq_(const TLhs& lhs = {}, const TRhs& rhs = {}) - : lhs_{lhs}, rhs_{rhs}, value_{[&] { - using std::operator==; - using std::operator<; - - if constexpr (type_traits::has_value_v and - type_traits::has_value_v) { - return TLhs::value == TRhs::value; - } else if constexpr (type_traits::has_epsilon_v and - type_traits::has_epsilon_v) { - return math::abs(get(lhs) - get(rhs)) < - math::min_value(TLhs::epsilon, TRhs::epsilon); - } else if constexpr (type_traits::has_epsilon_v) { - return math::abs(get(lhs) - get(rhs)) < TLhs::epsilon; - } else if constexpr (type_traits::has_epsilon_v) { - return math::abs(get(lhs) - get(rhs)) < TRhs::epsilon; - } else { - return get(lhs) == get(rhs); - } - }()} {} - - [[nodiscard]] constexpr operator bool() const { return value_; } - [[nodiscard]] constexpr auto lhs() const { return get(lhs_); } - [[nodiscard]] constexpr auto rhs() const { return get(rhs_); } - - const TLhs lhs_{}; - const TRhs rhs_{}; - const bool value_{}; -}; - -template -struct neq_ : op { - constexpr neq_(const TLhs& lhs = {}, const TRhs& rhs = {}) - : lhs_{lhs}, rhs_{rhs}, value_{[&] { - using std::operator==; - using std::operator!=; - using std::operator>; - - if constexpr (type_traits::has_value_v and - type_traits::has_value_v) { - return TLhs::value != TRhs::value; - } else if constexpr (type_traits::has_epsilon_v and - type_traits::has_epsilon_v) { - return math::abs(get(lhs_) - get(rhs_)) > - math::min_value(TLhs::epsilon, TRhs::epsilon); - } else if constexpr (type_traits::has_epsilon_v) { - return math::abs(get(lhs_) - get(rhs_)) > TLhs::epsilon; - } else if constexpr (type_traits::has_epsilon_v) { - return math::abs(get(lhs_) - get(rhs_)) > TRhs::epsilon; - } else { - return get(lhs_) != get(rhs_); - } - }()} {} - - [[nodiscard]] constexpr operator bool() const { return value_; } - [[nodiscard]] constexpr auto lhs() const { return get(lhs_); } - [[nodiscard]] constexpr auto rhs() const { return get(rhs_); } - - const TLhs lhs_{}; - const TRhs rhs_{}; - const bool value_{}; -}; - -template -struct gt_ : op { - constexpr gt_(const TLhs& lhs = {}, const TRhs& rhs = {}) - : lhs_{lhs}, rhs_{rhs}, value_{[&] { - using std::operator>; - - if constexpr (type_traits::has_value_v and - type_traits::has_value_v) { - return TLhs::value > TRhs::value; - } else { - return get(lhs_) > get(rhs_); - } - }()} {} - - [[nodiscard]] constexpr operator bool() const { return value_; } - [[nodiscard]] constexpr auto lhs() const { return get(lhs_); } - [[nodiscard]] constexpr auto rhs() const { return get(rhs_); } - - const TLhs lhs_{}; - const TRhs rhs_{}; - const bool value_{}; -}; - -template -struct ge_ : op { - constexpr ge_(const TLhs& lhs = {}, const TRhs& rhs = {}) - : lhs_{lhs}, rhs_{rhs}, value_{[&] { - using std::operator>=; - - if constexpr (type_traits::has_value_v and - type_traits::has_value_v) { - return TLhs::value >= TRhs::value; - } else { - return get(lhs_) >= get(rhs_); - } - }()} {} - - [[nodiscard]] constexpr operator bool() const { return value_; } - [[nodiscard]] constexpr auto lhs() const { return get(lhs_); } - [[nodiscard]] constexpr auto rhs() const { return get(rhs_); } - - const TLhs lhs_{}; - const TRhs rhs_{}; - const bool value_{}; -}; - -template -struct lt_ : op { - constexpr lt_(const TLhs& lhs = {}, const TRhs& rhs = {}) - : lhs_{lhs}, rhs_{rhs}, value_{[&] { - using std::operator<; - - if constexpr (type_traits::has_value_v and - type_traits::has_value_v) { - return TLhs::value < TRhs::value; - } else { - return get(lhs_) < get(rhs_); - } - }()} {} - - [[nodiscard]] constexpr operator bool() const { return value_; } - [[nodiscard]] constexpr auto lhs() const { return get(lhs_); } - [[nodiscard]] constexpr auto rhs() const { return get(rhs_); } - - private: - const TLhs lhs_{}; - const TRhs rhs_{}; - const bool value_{}; -}; - -template -struct le_ : op { - constexpr le_(const TLhs& lhs = {}, const TRhs& rhs = {}) - : lhs_{lhs}, rhs_{rhs}, value_{[&] { - using std::operator<=; - - if constexpr (type_traits::has_value_v and - type_traits::has_value_v) { - return TLhs::value <= TRhs::value; - } else { - return get(lhs_) <= get(rhs_); - } - }()} {} - - [[nodiscard]] constexpr operator bool() const { return value_; } - [[nodiscard]] constexpr auto lhs() const { return get(lhs_); } - [[nodiscard]] constexpr auto rhs() const { return get(rhs_); } - - const TLhs lhs_{}; - const TRhs rhs_{}; - const bool value_{}; -}; - -template -struct and_ : op { - constexpr and_(const TLhs& lhs = {}, const TRhs& rhs = {}) - : lhs_{lhs}, - rhs_{rhs}, - value_{static_cast(lhs) and static_cast(rhs)} {} - - [[nodiscard]] constexpr operator bool() const { return value_; } - [[nodiscard]] constexpr auto lhs() const { return get(lhs_); } - [[nodiscard]] constexpr auto rhs() const { return get(rhs_); } - - const TLhs lhs_{}; - const TRhs rhs_{}; - const bool value_{}; -}; - -template -struct or_ : op { - constexpr or_(const TLhs& lhs = {}, const TRhs& rhs = {}) - : lhs_{lhs}, - rhs_{rhs}, - value_{static_cast(lhs) or static_cast(rhs)} {} - - [[nodiscard]] constexpr operator bool() const { return value_; } - [[nodiscard]] constexpr auto lhs() const { return get(lhs_); } - [[nodiscard]] constexpr auto rhs() const { return get(rhs_); } - - const TLhs lhs_{}; - const TRhs rhs_{}; - const bool value_{}; -}; - -template -struct not_ : op { - explicit constexpr not_(const T& t = {}) - : t_{t}, value_{not static_cast(t)} {} - - [[nodiscard]] constexpr operator bool() const { return value_; } - [[nodiscard]] constexpr auto value() const { return get(t_); } - - const T t_{}; - const bool value_{}; -}; - -template -struct fatal_; - -#if defined(__cpp_exceptions) -template -struct throws_ : op { - constexpr explicit throws_(const TExpr& expr) - : value_{[&expr] { - try { - expr(); - } catch (const TException&) { - return true; - } catch (...) { - return false; - } - return false; - }()} {} - - [[nodiscard]] constexpr operator bool() const { return value_; } - - const bool value_{}; -}; - -template -struct throws_ : op { - constexpr explicit throws_(const TExpr& expr) - : value_{[&expr] { - try { - expr(); - } catch (...) { - return true; - } - return false; - }()} {} - - [[nodiscard]] constexpr operator bool() const { return value_; } - - const bool value_{}; -}; - -template -struct nothrow_ : op { - constexpr explicit nothrow_(const TExpr& expr) - : value_{[&expr] { - try { - expr(); - } catch (...) { - return false; - } - return true; - }()} {} - - [[nodiscard]] constexpr operator bool() const { return value_; } - - const bool value_{}; -}; -#endif - -#if __has_include() and __has_include() -template -struct aborts_ : op { - constexpr explicit aborts_(const TExpr& expr) - : value_{[&expr]() -> bool { - if (const auto pid = fork(); not pid) { - expr(); - exit(0); - } - auto exit_status = 0; - wait(&exit_status); - return exit_status; - }()} {} - - [[nodiscard]] constexpr operator bool() const { return value_; } - - const bool value_{}; -}; -#endif -} // namespace detail - -namespace type_traits { -template -inline constexpr auto is_op_v = __is_base_of(detail::op, T); -} // namespace type_traits - -struct colors { - std::string_view none = "\033[0m"; - std::string_view pass = "\033[32m"; - std::string_view fail = "\033[31m"; -}; - -class printer { - [[nodiscard]] inline auto color(const bool cond) { - return cond ? colors_.pass : colors_.fail; - } - - public: - printer() = default; - /*explicit(false)*/ printer(const colors colors) : colors_{colors} {} - - template - auto& operator<<(const T& t) { - out_ << detail::get(t); - return *this; - } - - template and - type_traits::is_container_v> = 0> - auto& operator<<(T&& t) { - *this << '{'; - auto first = true; - for (const auto& arg : t) { - *this << (first ? "" : ", ") << arg; - first = false; - } - *this << '}'; - return *this; - } - - auto& operator<<(std::string_view sv) { - out_ << sv; - return *this; - } - - template - auto& operator<<(const detail::eq_& op) { - return (*this << color(op) << op.lhs() << " == " << op.rhs() - << colors_.none); - } - - template - auto& operator<<(const detail::neq_& op) { - return (*this << color(op) << op.lhs() << " != " << op.rhs() - << colors_.none); - } - - template - auto& operator<<(const detail::gt_& op) { - return (*this << color(op) << op.lhs() << " > " << op.rhs() - << colors_.none); - } - - template - auto& operator<<(const detail::ge_& op) { - return (*this << color(op) << op.lhs() << " >= " << op.rhs() - << colors_.none); - } - - template - auto& operator<<(const detail::lt_& op) { - return (*this << color(op) << op.lhs() << " < " << op.rhs() - << colors_.none); - } - - template - auto& operator<<(const detail::le_& op) { - return (*this << color(op) << op.lhs() << " <= " << op.rhs() - << colors_.none); - } - - template - auto& operator<<(const detail::and_& op) { - return (*this << '(' << op.lhs() << color(op) << " and " << colors_.none - << op.rhs() << ')'); - } - - template - auto& operator<<(const detail::or_& op) { - return (*this << '(' << op.lhs() << color(op) << " or " << colors_.none - << op.rhs() << ')'); - } - - template - auto& operator<<(const detail::not_& op) { - return (*this << color(op) << "not " << op.value() << colors_.none); - } - - template - auto& operator<<(const detail::fatal_& fatal) { - return (*this << fatal.get()); - } - -#if defined(__cpp_exceptions) - template - auto& operator<<(const detail::throws_& op) { - return (*this << color(op) << "throws<" - << reflection::type_name() << ">" - << colors_.none); - } - - template - auto& operator<<(const detail::throws_& op) { - return (*this << color(op) << "throws" << colors_.none); - } - - template - auto& operator<<(const detail::nothrow_& op) { - return (*this << color(op) << "nothrow" << colors_.none); - } -#endif - -#if __has_include() and __has_include() - template - auto& operator<<(const detail::aborts_& op) { - return (*this << color(op) << "aborts" << colors_.none); - } -#endif - - template - auto& operator<<(const detail::type_&) { - return (*this << reflection::type_name()); - } - - auto str() const { return out_.str(); } - const auto& colors() const { return colors_; } - - private: - ut::colors colors_{}; - std::ostringstream out_{}; -}; - -template -class reporter { - public: - constexpr auto operator=(TPrinter printer) { - printer_ = static_cast(printer); - } - - auto on(events::test_begin test_begin) -> void { - printer_ << "Running \"" << test_begin.name << "\"..."; - fails_ = asserts_.fail; - } - - auto on(events::test_run test_run) -> void { - printer_ << "\n \"" << test_run.name << "\"..."; - } - - auto on(events::test_skip test_skip) -> void { - printer_ << test_skip.name << "...SKIPPED\n"; - ++tests_.skip; - } - - auto on(events::test_end) -> void { - if (asserts_.fail > fails_) { - ++tests_.fail; - printer_ << '\n' - << printer_.colors().fail << "FAILED" << printer_.colors().none - << '\n'; - } else { - ++tests_.pass; - printer_ << printer_.colors().pass << "PASSED" << printer_.colors().none - << '\n'; - } - } - - template - auto on(events::log l) -> void { - printer_ << l.msg; - } - - auto on(events::exception exception) -> void { - printer_ << "\n " << printer_.colors().fail - << "Unexpected exception with message:\n" - << exception.what() << printer_.colors().none; - ++asserts_.fail; - } - - template - auto on(events::assertion_pass) -> void { - ++asserts_.pass; - } - - template - auto on(events::assertion_fail assertion) -> void { - constexpr auto short_name = [](std::string_view name) { - return name.rfind('/') != std::string_view::npos - ? name.substr(name.rfind('/') + 1) - : name; - }; - printer_ << "\n " << short_name(assertion.location.file_name()) << ':' - << assertion.location.line() << ':' << printer_.colors().fail - << "FAILED" << printer_.colors().none << " [" << std::boolalpha - << assertion.expr << printer_.colors().none << ']'; - ++asserts_.fail; - } - - auto on(events::fatal_assertion) -> void {} - - auto on(events::summary) -> void { - if (static auto once = true; once) { - once = false; - if (tests_.fail or asserts_.fail) { - printer_ << "\n========================================================" - "=======================\n" - << "tests: " << (tests_.pass + tests_.fail) << " | " - << printer_.colors().fail << tests_.fail << " failed" - << printer_.colors().none << '\n' - << "asserts: " << (asserts_.pass + asserts_.fail) << " | " - << asserts_.pass << " passed" - << " | " << printer_.colors().fail << asserts_.fail - << " failed" << printer_.colors().none << '\n'; - std::cerr << printer_.str() << std::endl; - } else { - std::cout << printer_.colors().pass << "All tests passed" - << printer_.colors().none << " (" << asserts_.pass - << " asserts in " << tests_.pass << " tests)\n"; - - if (tests_.skip) { - std::cout << tests_.skip << " tests skipped\n"; - } - - std::cout.flush(); - } - } - } - - protected: - struct { - std::size_t pass{}; - std::size_t fail{}; - std::size_t skip{}; - } tests_{}; - - struct { - std::size_t pass{}; - std::size_t fail{}; - } asserts_{}; - - std::size_t fails_{}; - - TPrinter printer_{}; -}; - -struct options { - std::string_view filter{}; - std::vector tag{}; - ut::colors colors{}; - bool dry_run{}; -}; - -struct run_cfg { - bool report_errors{false}; -}; - -template , auto MaxPathSize = 16> -class runner { - class filter { - static constexpr auto delim = "."; - - public: - constexpr /*explicit(false)*/ filter(std::string_view _filter = {}) - : path_{utility::split(_filter, delim)} {} - - template - constexpr auto operator()(const std::size_t level, const TPath& path) const - -> bool { - for (auto i = 0u; i < math::min_value(level + 1, std::size(path_)); ++i) { - if (not utility::is_match(path[i], path_[i])) { - return false; - } - } - return true; - } - - private: - std::vector path_{}; - }; - - public: - constexpr runner() = default; - constexpr runner(TReporter reporter, std::size_t suites_size) - : reporter_{std::move(reporter)}, suites_(suites_size) {} - - ~runner() { - const auto should_run = not run_; - - if (should_run) { - static_cast(run()); - } - - if (not dry_run_) { - reporter_.on(events::summary{}); - } - - if (should_run and fails_) { - std::exit(-1); - } - } - - auto operator=(const options& options) { - filter_ = options.filter; - tag_ = options.tag; - dry_run_ = options.dry_run; - reporter_ = {options.colors}; - } - - template - auto on(events::suite suite) { - suites_.push_back(suite.run); - } - - template - auto on(events::test test) { - path_[level_] = test.name; - - auto execute = std::empty(test.tag); - for (const auto& tag_element : test.tag) { - if (utility::is_match(tag_element, "skip")) { - on(events::skip<>{.type = test.type, .name = test.name}); - return; - } - - for (const auto& ftag : tag_) { - if (utility::is_match(tag_element, ftag)) { - execute = true; - break; - } - } - } - - if (not execute) { - on(events::skip<>{.type = test.type, .name = test.name}); - return; - } - - if (filter_(level_, path_)) { - if (not level_++) { - reporter_.on(events::test_begin{ - .type = test.type, .name = test.name, .location = test.location}); - } else { - reporter_.on(events::test_run{.type = test.type, .name = test.name}); - } - - if (dry_run_) { - for (auto i = 0u; i < level_; ++i) { - std::cout << (i ? "." : "") << path_[i]; - } - std::cout << '\n'; - } - -#if defined(__cpp_exceptions) - try { -#endif - test(); -#if defined(__cpp_exceptions) - } catch (const events::fatal_assertion&) { - } catch (const std::exception& exception) { - ++fails_; - reporter_.on(events::exception{exception.what()}); - } catch (...) { - ++fails_; - reporter_.on(events::exception{"Unknown exception"}); - } -#endif - - if (not --level_) { - reporter_.on(events::test_end{.type = test.type, .name = test.name}); - } - } - } - - template - auto on(events::skip test) { - reporter_.on(events::test_skip{.type = test.type, .name = test.name}); - } - - template - [[nodiscard]] auto on(events::assertion assertion) -> bool { - if (dry_run_) { - return true; - } - - if (static_cast(assertion.expr)) { - reporter_.on(events::assertion_pass{ - .expr = assertion.expr, .location = assertion.location}); - return true; - } - - ++fails_; - reporter_.on(events::assertion_fail{.expr = assertion.expr, - .location = assertion.location}); - return false; - } - - auto on(events::fatal_assertion fatal_assertion) { - reporter_.on(fatal_assertion); - -#if defined(__cpp_exceptions) - if (not level_) { - reporter_.on(events::summary{}); - } - throw fatal_assertion; -#else - if (level_) { - reporter_.on(events::test_end{}); - } - reporter_.on(events::summary{}); - std::abort(); -#endif - } - - template - auto on(events::log l) { - reporter_.on(l); - } - - [[nodiscard]] auto run(run_cfg rc = {}) -> bool { - run_ = true; - for (const auto& suite : suites_) { - suite(); - } - suites_.clear(); - - if (rc.report_errors) { - reporter_.on(events::summary{}); - } - - return fails_ > 0; - } - - protected: - TReporter reporter_{}; - std::vector suites_{}; - std::size_t level_{}; - bool run_{}; - std::size_t fails_{}; - std::array path_{}; - filter filter_{}; - std::vector tag_{}; - bool dry_run_{}; -}; - -struct override {}; - -template -[[maybe_unused]] inline auto cfg = runner>{}; - -namespace detail { -struct tag { - std::vector name{}; -}; - -template -[[nodiscard]] constexpr decltype(auto) on(TEvent&& event) { - return ut::cfg::type>.on( - static_cast(event)); -} - -template -struct test_location { - template - constexpr test_location(const T& t, - const reflection::source_location& sl = - reflection::source_location::current()) - : test{t}, location{sl} {} - - Test test{}; - reflection::source_location location{}; -}; - -struct test { - std::string_view type{}; - std::string_view name{}; - std::vector tag{}; - - template - constexpr auto operator=(test_location _test) { - on(events::test{.type = type, - .name = name, - .tag = tag, - .location = _test.location, - .arg = none{}, - .run = _test.test}); - return _test.test; - } - - template > = 0> - constexpr auto operator=(Test _test) -> - typename type_traits::identity::type { - on(events::test{.type = type, - .name = name, - .tag = tag, - .location = {}, - .arg = none{}, - .run = static_cast(_test)}); - return _test; - } - - constexpr auto operator=(void (*_test)(std::string_view)) const { - return _test(name); - } - - template > = 0> - constexpr auto operator=(Test _test) - -> decltype(_test(type_traits::declval())) { - return _test(name); - } -}; - -struct log { - struct next { - template - auto& operator<<(const TMsg& msg) { - on(events::log{' '}); - on(events::log{msg}); - return *this; - } - }; - - template - auto operator<<(const TMsg& msg) -> next { - on(events::log{'\n'}); - on(events::log{msg}); - return next{}; - } -}; - -template -class terse_ { - public: - constexpr explicit terse_(const TExpr& expr) : expr_{expr} { cfg::wip = {}; } - - ~terse_() noexcept(false) { - if (static auto once = true; once and not cfg::wip) { - once = {}; - } else { - return; - } - - cfg::wip = true; - - void(detail::on( - events::assertion{.expr = expr_, .location = cfg::location})); - } - - private: - const TExpr& expr_; -}; - -struct that_ { - template - struct expr { - using type = expr; - - constexpr explicit expr(const T& t) : t_{t} {} - - [[nodiscard]] constexpr auto operator!() const { return not_{*this}; } - - template - [[nodiscard]] constexpr auto operator==(const TRhs& rhs) const { - return eq_{t_, rhs}; - } - - template - [[nodiscard]] constexpr auto operator!=(const TRhs& rhs) const { - return neq_{t_, rhs}; - } - - template - [[nodiscard]] constexpr auto operator>(const TRhs& rhs) const { - return gt_{t_, rhs}; - } - - template - [[nodiscard]] constexpr auto operator>=(const TRhs& rhs) const { - return ge_{t_, rhs}; - } - - template - [[nodiscard]] constexpr auto operator<(const TRhs& rhs) const { - return lt_{t_, rhs}; - } - - template - [[nodiscard]] constexpr auto operator<=(const TRhs& rhs) const { - return le_{t_, rhs}; - } - - [[nodiscard]] constexpr operator bool() const { - return static_cast(t_); - } - - const T t_{}; - }; - - template - [[nodiscard]] constexpr auto operator%(const T& t) const { - return expr{t}; - } -}; - -template -struct fatal_ : op { - using type = fatal_; - - constexpr explicit fatal_(const TExpr& expr) : expr_{expr} {} - - [[nodiscard]] constexpr operator bool() const { - if (static_cast(expr_)) { - } else { - cfg::wip = true; - void(on( - events::assertion{.expr = expr_, .location = cfg::location})); - on(events::fatal_assertion{}); - } - return static_cast(expr_); - } - - [[nodiscard]] constexpr decltype(auto) get() const { return expr_; } - - TExpr expr_{}; -}; - -template -struct expect_ { - constexpr explicit expect_(bool value) : value_{value} { cfg::wip = {}; } - - template - auto& operator<<(const TMsg& msg) { - if (not value_) { - on(events::log{' '}); - on(events::log{msg}); - } - return *this; - } - - [[nodiscard]] constexpr operator bool() const { return value_; } - - bool value_{}; -}; -} // namespace detail - -namespace literals { -[[nodiscard]] inline auto operator""_test(const char* name, - decltype(sizeof("")) size) { - return detail::test{"test", std::string_view{name, size}}; -} - -template -[[nodiscard]] constexpr auto operator""_i() { - return detail::integral_constant()>{}; -} - -template -[[nodiscard]] constexpr auto operator""_s() { - return detail::integral_constant()>{}; -} - -template -[[nodiscard]] constexpr auto operator""_c() { - return detail::integral_constant()>{}; -} - -template -[[nodiscard]] constexpr auto operator""_sc() { - return detail::integral_constant()>{}; -} - -template -[[nodiscard]] constexpr auto operator""_l() { - return detail::integral_constant()>{}; -} - -template -[[nodiscard]] constexpr auto operator""_ll() { - return detail::integral_constant()>{}; -} - -template -[[nodiscard]] constexpr auto operator""_u() { - return detail::integral_constant()>{}; -} - -template -[[nodiscard]] constexpr auto operator""_uc() { - return detail::integral_constant()>{}; -} - -template -[[nodiscard]] constexpr auto operator""_us() { - return detail::integral_constant()>{}; -} - -template -[[nodiscard]] constexpr auto operator""_ul() { - return detail::integral_constant()>{}; -} - -template -[[nodiscard]] constexpr auto operator""_ull() { - return detail::integral_constant()>{}; -} - -template -[[nodiscard]] constexpr auto operator""_i8() { - return detail::integral_constant()>{}; -} - -template -[[nodiscard]] constexpr auto operator""_i16() { - return detail::integral_constant()>{}; -} - -template -[[nodiscard]] constexpr auto operator""_i32() { - return detail::integral_constant()>{}; -} - -template -[[nodiscard]] constexpr auto operator""_i64() { - return detail::integral_constant()>{}; -} - -template -[[nodiscard]] constexpr auto operator""_u8() { - return detail::integral_constant()>{}; -} - -template -[[nodiscard]] constexpr auto operator""_u16() { - return detail::integral_constant()>{}; -} - -template -[[nodiscard]] constexpr auto operator""_u32() { - return detail::integral_constant()>{}; -} - -template -[[nodiscard]] constexpr auto operator""_u64() { - return detail::integral_constant()>{}; -} - -template -[[nodiscard]] constexpr auto operator""_f() { - return detail::floating_point_constant< - float, math::num(), - math::den(), - math::den_size()>{}; -} - -template -[[nodiscard]] constexpr auto operator""_d() { - return detail::floating_point_constant< - double, math::num(), - math::den(), - math::den_size()>{}; -} - -template -[[nodiscard]] constexpr auto operator""_ld() { - return detail::floating_point_constant< - long double, math::num(), - math::den(), - math::den_size()>{}; -} - -constexpr auto operator""_b(const char* name, decltype(sizeof("")) size) { - struct named : std::string_view, detail::op { - using value_type = bool; - [[nodiscard]] constexpr operator value_type() const { return true; } - [[nodiscard]] constexpr auto operator==(const named&) const { return true; } - [[nodiscard]] constexpr auto operator==(const bool other) const { - return other; - } - }; - return named{{name, size}, {}}; -} -} // namespace literals - -namespace operators { -[[nodiscard]] constexpr auto operator==(std::string_view lhs, - std::string_view rhs) { - return detail::eq_{lhs, rhs}; -} - -[[nodiscard]] constexpr auto operator!=(std::string_view lhs, - std::string_view rhs) { - return detail::neq_{lhs, rhs}; -} - -template > = 0> -[[nodiscard]] constexpr auto operator==(T&& lhs, T&& rhs) { - return detail::eq_{static_cast(lhs), static_cast(rhs)}; -} - -template > = 0> -[[nodiscard]] constexpr auto operator!=(T&& lhs, T&& rhs) { - return detail::neq_{static_cast(lhs), static_cast(rhs)}; -} - -template or - type_traits::is_op_v> = 0> -[[nodiscard]] constexpr auto operator==(const TLhs& lhs, const TRhs& rhs) { - return detail::eq_{lhs, rhs}; -} - -template or - type_traits::is_op_v> = 0> -[[nodiscard]] constexpr auto operator!=(const TLhs& lhs, const TRhs& rhs) { - return detail::neq_{lhs, rhs}; -} - -template or - type_traits::is_op_v> = 0> -[[nodiscard]] constexpr auto operator>(const TLhs& lhs, const TRhs& rhs) { - return detail::gt_{lhs, rhs}; -} - -template or - type_traits::is_op_v> = 0> -[[nodiscard]] constexpr auto operator>=(const TLhs& lhs, const TRhs& rhs) { - return detail::ge_{lhs, rhs}; -} - -template or - type_traits::is_op_v> = 0> -[[nodiscard]] constexpr auto operator<(const TLhs& lhs, const TRhs& rhs) { - return detail::lt_{lhs, rhs}; -} - -template or - type_traits::is_op_v> = 0> -[[nodiscard]] constexpr auto operator<=(const TLhs& lhs, const TRhs& rhs) { - return detail::le_{lhs, rhs}; -} - -template or - type_traits::is_op_v> = 0> -[[nodiscard]] constexpr auto operator and(const TLhs& lhs, const TRhs& rhs) { - return detail::and_{lhs, rhs}; -} - -template or - type_traits::is_op_v> = 0> -[[nodiscard]] constexpr auto operator or(const TLhs& lhs, const TRhs& rhs) { - return detail::or_{lhs, rhs}; -} - -template > = 0> -[[nodiscard]] constexpr auto operator not(const T& t) { - return detail::not_{t}; -} - -template -[[nodiscard]] inline auto operator>>( - const T& t, const detail::value_location&) { - return detail::fatal_{t}; -} - -template -[[nodiscard]] auto operator/(const detail::tag& tag, Test test) { - for (const auto& name : tag.name) { - test.tag.push_back(name); - } - return test; -} - -[[nodiscard]] inline auto operator/(const detail::tag& lhs, - const detail::tag& rhs) { - std::vector tag{}; - for (const auto& name : lhs.name) { - tag.push_back(name); - } - for (const auto& name : rhs.name) { - tag.push_back(name); - } - return detail::tag{tag}; -} - -template > = 0> -[[nodiscard]] constexpr auto operator|(const F& f, const T& t) { - return [f, t](const auto name) { - for (const auto& arg : t) { - detail::on(events::test{.type = "test", - .name = name, - .tag = {}, - .location = {}, - .arg = arg, - .run = f}); - } - }; -} - -template < - class F, template class T, class... Ts, - type_traits::requires_t>> = 0> -[[nodiscard]] constexpr auto operator|(const F& f, const T& t) { - return [f, t](const auto name) { - apply( - [f, name](const auto&... args) { - (detail::on(events::test{.type = "test", - .name = name, - .tag = {}, - .location = {}, - .arg = args, - .run = f}), - ...); - }, - t); - }; -} - -namespace terse { -#if defined(__clang__) -#pragma clang diagnostic ignored "-Wunused-comparison" -#endif - -[[maybe_unused]] constexpr struct { -} _t; - -template -constexpr auto operator%(const T& t, const decltype(_t)&) { - return detail::value{t}; -} - -template -inline auto operator>>(const T& t, - const detail::value_location&) { - using fatal_t = detail::fatal_; - struct fatal_ : fatal_t, detail::log { - using type [[maybe_unused]] = fatal_t; - using fatal_t::fatal_t; - const detail::terse_ _{*this}; - }; - return fatal_{t}; -} - -template > = 0> -constexpr auto operator==( - const T& lhs, const detail::value_location& rhs) { - using eq_t = detail::eq_>; - struct eq_ : eq_t, detail::log { - using type [[maybe_unused]] = eq_t; - using eq_t::eq_t; - const detail::terse_ _{*this}; - }; - return eq_{lhs, rhs}; -} - -template > = 0> -constexpr auto operator==( - const detail::value_location& lhs, const T& rhs) { - using eq_t = detail::eq_, T>; - struct eq_ : eq_t, detail::log { - using type [[maybe_unused]] = eq_t; - using eq_t::eq_t; - const detail::terse_ _{*this}; - }; - return eq_{lhs, rhs}; -} - -template > = 0> -constexpr auto operator!=( - const T& lhs, const detail::value_location& rhs) { - using neq_t = detail::neq_>; - struct neq_ : neq_t, detail::log { - using type [[maybe_unused]] = neq_t; - using neq_t::neq_t; - const detail::terse_ _{*this}; - }; - return neq_{lhs, rhs}; -} - -template > = 0> -constexpr auto operator!=( - const detail::value_location& lhs, const T& rhs) { - using neq_t = detail::neq_, T>; - struct neq_ : neq_t { - using type [[maybe_unused]] = neq_t; - using neq_t::neq_t; - const detail::terse_ _{*this}; - }; - return neq_{lhs, rhs}; -} - -template > = 0> -constexpr auto operator>( - const T& lhs, const detail::value_location& rhs) { - using gt_t = detail::gt_>; - struct gt_ : gt_t, detail::log { - using type [[maybe_unused]] = gt_t; - using gt_t::gt_t; - const detail::terse_ _{*this}; - }; - return gt_{lhs, rhs}; -} - -template > = 0> -constexpr auto operator>( - const detail::value_location& lhs, const T& rhs) { - using gt_t = detail::gt_, T>; - struct gt_ : gt_t, detail::log { - using type [[maybe_unused]] = gt_t; - using gt_t::gt_t; - const detail::terse_ _{*this}; - }; - return gt_{lhs, rhs}; -} - -template > = 0> -constexpr auto operator>=( - const T& lhs, const detail::value_location& rhs) { - using ge_t = detail::ge_>; - struct ge_ : ge_t, detail::log { - using type [[maybe_unused]] = ge_t; - using ge_t::ge_t; - const detail::terse_ _{*this}; - }; - return ge_{lhs, rhs}; -} - -template > = 0> -constexpr auto operator>=( - const detail::value_location& lhs, const T& rhs) { - using ge_t = detail::ge_, T>; - struct ge_ : ge_t, detail::log { - using type [[maybe_unused]] = ge_t; - using ge_t::ge_t; - const detail::terse_ _{*this}; - }; - return ge_{lhs, rhs}; -} - -template > = 0> -constexpr auto operator<( - const T& lhs, const detail::value_location& rhs) { - using lt_t = detail::lt_>; - struct lt_ : lt_t, detail::log { - using type [[maybe_unused]] = lt_t; - using lt_t::lt_t; - const detail::terse_ _{*this}; - }; - return lt_{lhs, rhs}; -} - -template > = 0> -constexpr auto operator<( - const detail::value_location& lhs, const T& rhs) { - using lt_t = detail::lt_, T>; - struct lt_ : lt_t, detail::log { - using type [[maybe_unused]] = lt_t; - using lt_t::lt_t; - const detail::terse_ _{*this}; - }; - return lt_{lhs, rhs}; -} - -template > = 0> -constexpr auto operator<=( - const T& lhs, const detail::value_location& rhs) { - using le_t = detail::le_>; - struct le_ : le_t, detail::log { - using type [[maybe_unused]] = le_t; - using le_t::le_t; - const detail::terse_ _{*this}; - }; - return le_{lhs, rhs}; -} - -template > = 0> -constexpr auto operator<=( - const detail::value_location& lhs, const T& rhs) { - using le_t = detail::le_, T>; - struct le_ : le_t { - using type [[maybe_unused]] = le_t; - using le_t::le_t; - const detail::terse_ _{*this}; - }; - return le_{lhs, rhs}; -} - -template or - type_traits::is_op_v> = 0> -constexpr auto operator and(const TLhs& lhs, const TRhs& rhs) { - using and_t = detail::and_; - struct and_ : and_t, detail::log { - using type [[maybe_unused]] = and_t; - using and_t::and_t; - const detail::terse_ _{*this}; - }; - return and_{lhs, rhs}; -} - -template or - type_traits::is_op_v> = 0> -constexpr auto operator or(const TLhs& lhs, const TRhs& rhs) { - using or_t = detail::or_; - struct or_ : or_t, detail::log { - using type [[maybe_unused]] = or_t; - using or_t::or_t; - const detail::terse_ _{*this}; - }; - return or_{lhs, rhs}; -} - -template > = 0> -constexpr auto operator not(const T& t) { - using not_t = detail::not_; - struct not_ : not_t, detail::log { - using type [[maybe_unused]] = not_t; - using not_t::not_t; - const detail::terse_ _{*this}; - }; - return not_{t}; -} - -} // namespace terse -} // namespace operators - -template or - type_traits::is_convertible_v> = 0> -constexpr auto expect(const TExpr& expr, - const reflection::source_location& sl = - reflection::source_location::current()) { - return detail::expect_{detail::on( - events::assertion{.expr = expr, .location = sl})}; -} - -[[maybe_unused]] constexpr auto fatal = detail::fatal{}; - -#if defined(__cpp_nontype_template_parameter_class) -template -#else -template -#endif -constexpr auto constant = Constant; - -#if defined(__cpp_exceptions) -template -[[nodiscard]] constexpr auto throws(const TExpr& expr) { - return detail::throws_{expr}; -} - -template -[[nodiscard]] constexpr auto throws(const TExpr& expr) { - return detail::throws_{expr}; -} - -template -[[nodiscard]] constexpr auto nothrow(const TExpr& expr) { - return detail::nothrow_{expr}; -} -#endif - -#if __has_include() and __has_include() -template -[[nodiscard]] constexpr auto aborts(const TExpr& expr) { - return detail::aborts_{expr}; -} -#endif - -using _b = detail::value; -using _c = detail::value; -using _sc = detail::value; -using _s = detail::value; -using _i = detail::value; -using _l = detail::value; -using _ll = detail::value; -using _u = detail::value; -using _uc = detail::value; -using _us = detail::value; -using _ul = detail::value; -using _ull = detail::value; -using _i8 = detail::value; -using _i16 = detail::value; -using _i32 = detail::value; -using _i64 = detail::value; -using _u8 = detail::value; -using _u16 = detail::value; -using _u32 = detail::value; -using _u64 = detail::value; -using _f = detail::value; -using _d = detail::value; -using _ld = detail::value; - -template -struct _t : detail::value { - constexpr explicit _t(const T& t) : detail::value{t} {} -}; - -struct suite { - template - constexpr /*explicit(false)*/ suite(TSuite _suite) { - static_assert(1 == sizeof(_suite)); - detail::on( - events::suite{.run = +_suite}); - } -}; - -[[maybe_unused]] inline auto log = detail::log{}; -[[maybe_unused]] inline auto that = detail::that_{}; -[[maybe_unused]] constexpr auto test = [](const auto name) { - return detail::test{"test", name}; -}; -[[maybe_unused]] constexpr auto should = test; -[[maybe_unused]] inline auto tag = [](const auto name) { - return detail::tag{{name}}; -}; -[[maybe_unused]] inline auto skip = tag("skip"); -template -[[maybe_unused]] constexpr auto type = detail::type_(); - -template -[[nodiscard]] constexpr auto eq(const TLhs& lhs, const TRhs& rhs) { - return detail::eq_{lhs, rhs}; -} -template -[[nodiscard]] constexpr auto neq(const TLhs& lhs, const TRhs& rhs) { - return detail::neq_{lhs, rhs}; -} -template -[[nodiscard]] constexpr auto gt(const TLhs& lhs, const TRhs& rhs) { - return detail::gt_{lhs, rhs}; -} -template -[[nodiscard]] constexpr auto ge(const TLhs& lhs, const TRhs& rhs) { - return detail::ge_{lhs, rhs}; -} -template -[[nodiscard]] constexpr auto lt(const TLhs& lhs, const TRhs& rhs) { - return detail::lt_{lhs, rhs}; -} -template -[[nodiscard]] constexpr auto le(const TLhs& lhs, const TRhs& rhs) { - return detail::le_{lhs, rhs}; -} - -template -[[nodiscard]] constexpr auto mut(const T& t) noexcept -> T& { - return const_cast(t); -} - -namespace bdd { -[[maybe_unused]] constexpr auto feature = [](const auto name) { - return detail::test{"feature", name}; -}; -[[maybe_unused]] constexpr auto scenario = [](const auto name) { - return detail::test{"scenario", name}; -}; -[[maybe_unused]] constexpr auto given = [](const auto name) { - return detail::test{"given", name}; -}; -[[maybe_unused]] constexpr auto when = [](const auto name) { - return detail::test{"when", name}; -}; -[[maybe_unused]] constexpr auto then = [](const auto name) { - return detail::test{"then", name}; -}; - -namespace gherkin { -class steps { - using step_t = std::string; - using steps_t = void (*)(steps&); - using gherkin_t = std::vector; - using call_step_t = utility::function; - using call_steps_t = std::vector>; - - class step { - public: - template - step(steps& steps, const TPattern& pattern) - : steps_{steps}, pattern_{pattern} {} - - ~step() { steps_.next(pattern_); } - - template - auto operator=(const TExpr& expr) -> void { - for (const auto& [pattern, _] : steps_.call_steps()) { - if (pattern_ == pattern) { - return; - } - } - - steps_.call_steps().emplace_back( - pattern_, [expr, pattern = pattern_](const auto& _step) { - [=](type_traits::list) { - log << _step; - auto i = 0u; - const auto& ms = utility::match(pattern, _step); - expr(lexical_cast(ms[i++])...); - } - (typename type_traits::function_traits::args{}); - }); - } - - private: - template - static auto lexical_cast(const std::string& str) { - T t{}; - std::istringstream iss{}; - iss.str(str); - if constexpr (std::is_same_v) { - t = iss.str(); - } else { - iss >> t; - } - return t; - } - - steps& steps_; - std::string pattern_{}; - }; - - public: - template - constexpr /*explicit(false)*/ steps(const TSteps& _steps) : steps_{_steps} {} - - template - auto operator|(const TGherkin& gherkin) { - gherkin_ = utility::split(gherkin, '\n'); - for (auto& _step : gherkin_) { - _step.erase(0, _step.find_first_not_of(" \t")); - } - - return [this] { - step_ = {}; - steps_(*this); - }; - } - auto feature(const std::string& pattern) { - return step{*this, "Feature: " + pattern}; - } - auto scenario(const std::string& pattern) { - return step{*this, "Scenario: " + pattern}; - } - auto given(const std::string& pattern) { - return step{*this, "Given " + pattern}; - } - auto when(const std::string& pattern) { - return step{*this, "When " + pattern}; - } - auto then(const std::string& pattern) { - return step{*this, "Then " + pattern}; - } - - private: - template - auto next(const TPattern& pattern) -> void { - const auto is_scenario = [&pattern](const auto& _step) { - constexpr auto scenario = "Scenario"; - return pattern.find(scenario) == std::string::npos and - _step.find(scenario) != std::string::npos; - }; - - const auto call_steps = [this, is_scenario](const auto& _step, - const auto i) { - for (const auto& [name, call] : call_steps_) { - if (is_scenario(_step)) { - break; - } - - if (utility::is_match(_step, name) or - not std::empty(utility::match(name, _step))) { - step_ = i; - call(_step); - } - } - }; - - decltype(step_) i{}; - for (const auto& _step : gherkin_) { - if (i++ == step_) { - call_steps(_step, i); - } - } - } - - auto call_steps() -> call_steps_t& { return call_steps_; } - - steps_t steps_{}; - gherkin_t gherkin_{}; - call_steps_t call_steps_{}; - decltype(sizeof("")) step_{}; -}; -} // namespace gherkin -} // namespace bdd - -namespace spec { -[[maybe_unused]] constexpr auto describe = [](const auto name) { - return detail::test{"describe", name}; -}; -[[maybe_unused]] constexpr auto it = [](const auto name) { - return detail::test{"it", name}; -}; -} // namespace spec - -using literals::operator""_test; - -using literals::operator""_b; -using literals::operator""_i; -using literals::operator""_s; -using literals::operator""_c; -using literals::operator""_sc; -using literals::operator""_l; -using literals::operator""_ll; -using literals::operator""_u; -using literals::operator""_uc; -using literals::operator""_us; -using literals::operator""_ul; -using literals::operator""_i8; -using literals::operator""_i16; -using literals::operator""_i32; -using literals::operator""_i64; -using literals::operator""_u8; -using literals::operator""_u16; -using literals::operator""_u32; -using literals::operator""_u64; -using literals::operator""_f; -using literals::operator""_d; -using literals::operator""_ld; -using literals::operator""_ull; - -using operators::operator==; -using operators::operator!=; -using operators::operator>; -using operators::operator>=; -using operators::operator<; -using operators::operator<=; -using operators::operator and; -using operators::operator or; -using operators::operator not; -using operators::operator|; -using operators::operator/; -using operators::operator>>; -} // namespace boost::inline ext::ut::inline v1_1_9 -#endif