diff options
| author | Lexi Winter <lexi@le-fay.org> | 2025-06-29 19:25:29 +0100 |
|---|---|---|
| committer | Lexi Winter <lexi@le-fay.org> | 2025-06-29 19:25:29 +0100 |
| commit | bc524d70253a4ab2fe40c3ca3e5666e267c0a4d1 (patch) | |
| tree | 1e629e7b46b1d9972a973bc93fd100bcebd395be /tests/SelfTest/UsageTests | |
| download | nihil-548ea226e1944e077d3ff305df43ef6b366b03f4.tar.gz nihil-548ea226e1944e077d3ff305df43ef6b366b03f4.tar.bz2 | |
import catch2 3.8.1vendor/catch2/3.8.1vendor/catch2
Diffstat (limited to 'tests/SelfTest/UsageTests')
26 files changed, 6432 insertions, 0 deletions
diff --git a/tests/SelfTest/UsageTests/Approx.tests.cpp b/tests/SelfTest/UsageTests/Approx.tests.cpp new file mode 100644 index 0000000..c3d41cb --- /dev/null +++ b/tests/SelfTest/UsageTests/Approx.tests.cpp @@ -0,0 +1,218 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 + +#include <catch2/catch_test_macros.hpp> +#include <catch2/catch_approx.hpp> + +#include <cmath> + +using Catch::Approx; + +namespace { + static double divide(double a, double b) { + return a / b; + } + + class StrongDoubleTypedef { + double d_ = 0.0; + + public: + explicit StrongDoubleTypedef(double d) : d_(d) {} + explicit operator double() const { return d_; } + }; + + static std::ostream& operator<<(std::ostream& os, StrongDoubleTypedef td) { + return os << "StrongDoubleTypedef(" << static_cast<double>(td) << ")"; + } +} // end unnamed namespace + +using namespace Catch::literals; + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE( "A comparison that uses literals instead of the normal constructor", "[Approx]" ) { + double d = 1.23; + + REQUIRE( d == 1.23_a ); + REQUIRE( d != 1.22_a ); + REQUIRE( -d == -1.23_a ); + + REQUIRE( d == 1.2_a .epsilon(.1) ); + REQUIRE( d != 1.2_a .epsilon(.001) ); + REQUIRE( d == 1_a .epsilon(.3) ); +} + +TEST_CASE( "Some simple comparisons between doubles", "[Approx]" ) { + double d = 1.23; + + REQUIRE( d == Approx( 1.23 ) ); + REQUIRE( d != Approx( 1.22 ) ); + REQUIRE( d != Approx( 1.24 ) ); + + REQUIRE( d == 1.23_a ); + REQUIRE( d != 1.22_a ); + + REQUIRE( Approx( d ) == 1.23 ); + REQUIRE( Approx( d ) != 1.22 ); + REQUIRE( Approx( d ) != 1.24 ); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE( "Approximate comparisons with different epsilons", "[Approx]" ) { + double d = 1.23; + + REQUIRE( d != Approx( 1.231 ) ); + REQUIRE( d == Approx( 1.231 ).epsilon( 0.1 ) ); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE( "Less-than inequalities with different epsilons", "[Approx]" ) { + double d = 1.23; + + REQUIRE( d <= Approx( 1.24 ) ); + REQUIRE( d <= Approx( 1.23 ) ); + REQUIRE_FALSE( d <= Approx( 1.22 ) ); + REQUIRE( d <= Approx( 1.22 ).epsilon(0.1) ); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE( "Greater-than inequalities with different epsilons", "[Approx]" ) { + double d = 1.23; + + REQUIRE( d >= Approx( 1.22 ) ); + REQUIRE( d >= Approx( 1.23 ) ); + REQUIRE_FALSE( d >= Approx( 1.24 ) ); + REQUIRE( d >= Approx( 1.24 ).epsilon(0.1) ); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE( "Approximate comparisons with floats", "[Approx]" ) { + REQUIRE( 1.23f == Approx( 1.23f ) ); + REQUIRE( 0.0f == Approx( 0.0f ) ); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE( "Approximate comparisons with ints", "[Approx]" ) { + REQUIRE( 1 == Approx( 1 ) ); + REQUIRE( 0 == Approx( 0 ) ); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE( "Approximate comparisons with mixed numeric types", "[Approx]" ) { + const double dZero = 0; + const double dSmall = 0.00001; + const double dMedium = 1.234; + + REQUIRE( 1.0f == Approx( 1 ) ); + REQUIRE( 0 == Approx( dZero) ); + REQUIRE( 0 == Approx( dSmall ).margin( 0.001 ) ); + REQUIRE( 1.234f == Approx( dMedium ) ); + REQUIRE( dMedium == Approx( 1.234f ) ); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE( "Use a custom approx", "[Approx][custom]" ) { + double d = 1.23; + + Approx approx = Approx::custom().epsilon( 0.01 ); + + REQUIRE( d == approx( 1.23 ) ); + REQUIRE( d == approx( 1.22 ) ); + REQUIRE( d == approx( 1.24 ) ); + REQUIRE( d != approx( 1.25 ) ); + + REQUIRE( approx( d ) == 1.23 ); + REQUIRE( approx( d ) == 1.22 ); + REQUIRE( approx( d ) == 1.24 ); + REQUIRE( approx( d ) != 1.25 ); +} + +TEST_CASE( "Approximate PI", "[Approx][PI]" ) { + REQUIRE( divide( 22, 7 ) == Approx( 3.141 ).epsilon( 0.001 ) ); + REQUIRE( divide( 22, 7 ) != Approx( 3.141 ).epsilon( 0.0001 ) ); +} + +/////////////////////////////////////////////////////////////////////////////// + +TEST_CASE( "Absolute margin", "[Approx]" ) { + REQUIRE( 104.0 != Approx(100.0) ); + REQUIRE( 104.0 == Approx(100.0).margin(5) ); + REQUIRE( 104.0 == Approx(100.0).margin(4) ); + REQUIRE( 104.0 != Approx(100.0).margin(3) ); + REQUIRE( 100.3 != Approx(100.0) ); + REQUIRE( 100.3 == Approx(100.0).margin(0.5) ); +} + +TEST_CASE("Approx with exactly-representable margin", "[Approx]") { + CHECK( 0.25f == Approx(0.0f).margin(0.25f) ); + + CHECK( 0.0f == Approx(0.25f).margin(0.25f) ); + CHECK( 0.5f == Approx(0.25f).margin(0.25f) ); + + CHECK( 245.0f == Approx(245.25f).margin(0.25f) ); + CHECK( 245.5f == Approx(245.25f).margin(0.25f) ); +} + +TEST_CASE("Approx setters validate their arguments", "[Approx]") { + REQUIRE_NOTHROW(Approx(0).margin(0)); + REQUIRE_NOTHROW(Approx(0).margin(1234656)); + + REQUIRE_THROWS_AS(Approx(0).margin(-2), std::domain_error); + + REQUIRE_NOTHROW(Approx(0).epsilon(0)); + REQUIRE_NOTHROW(Approx(0).epsilon(1)); + + REQUIRE_THROWS_AS(Approx(0).epsilon(-0.001), std::domain_error); + REQUIRE_THROWS_AS(Approx(0).epsilon(1.0001), std::domain_error); +} + +TEST_CASE("Default scale is invisible to comparison", "[Approx]") { + REQUIRE(101.000001 != Approx(100).epsilon(0.01)); + REQUIRE(std::pow(10, -5) != Approx(std::pow(10, -7))); +} + +TEST_CASE("Epsilon only applies to Approx's value", "[Approx]") { + REQUIRE(101.01 != Approx(100).epsilon(0.01)); +} + +TEST_CASE("Assorted miscellaneous tests", "[Approx][approvals]") { + REQUIRE(INFINITY == Approx(INFINITY)); + REQUIRE(-INFINITY != Approx(INFINITY)); + REQUIRE(1 != Approx(INFINITY)); + REQUIRE(INFINITY != Approx(1)); + REQUIRE(NAN != Approx(NAN)); + REQUIRE_FALSE(NAN == Approx(NAN)); +} + +TEST_CASE( "Comparison with explicitly convertible types", "[Approx]" ) +{ + StrongDoubleTypedef td(10.0); + + REQUIRE(td == Approx(10.0)); + REQUIRE(Approx(10.0) == td); + + REQUIRE(td != Approx(11.0)); + REQUIRE(Approx(11.0) != td); + + REQUIRE(td <= Approx(10.0)); + REQUIRE(td <= Approx(11.0)); + REQUIRE(Approx(10.0) <= td); + REQUIRE(Approx(9.0) <= td); + + REQUIRE(td >= Approx(9.0)); + REQUIRE(td >= Approx(td)); + REQUIRE(Approx(td) >= td); + REQUIRE(Approx(11.0) >= td); + +} + +TEST_CASE("Approx::operator() is const correct", "[Approx][.approvals]") { + const Approx ap = Approx(0.0).margin(0.01); + + // As long as this compiles, the test should be considered passing + REQUIRE(1.0 == ap(1.0)); +} diff --git a/tests/SelfTest/UsageTests/BDD.tests.cpp b/tests/SelfTest/UsageTests/BDD.tests.cpp new file mode 100644 index 0000000..5ac8605 --- /dev/null +++ b/tests/SelfTest/UsageTests/BDD.tests.cpp @@ -0,0 +1,106 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 + +#include <catch2/catch_test_macros.hpp> + +namespace { + + static bool itDoesThis() { return true; } + + static bool itDoesThat() { return true; } + + // a trivial fixture example to support SCENARIO_METHOD tests + struct Fixture { + Fixture(): d_counter( 0 ) {} + + int counter() { return d_counter++; } + + int d_counter; + }; + +} + + +SCENARIO("Do that thing with the thing", "[Tags]") { + GIVEN("This stuff exists") { + // make stuff exist + AND_GIVEN("And some assumption") { + // Validate assumption + WHEN("I do this") { + // do this + THEN("it should do this") { + REQUIRE(itDoesThis()); + AND_THEN("do that") { + REQUIRE(itDoesThat()); + } + } + } + } + } +} + +SCENARIO( "Vector resizing affects size and capacity", + "[vector][bdd][size][capacity]" ) { + GIVEN( "an empty vector" ) { + std::vector<int> v; + REQUIRE( v.size() == 0 ); + + WHEN( "it is made larger" ) { + v.resize( 10 ); + THEN( "the size and capacity go up" ) { + REQUIRE( v.size() == 10 ); + REQUIRE( v.capacity() >= 10 ); + + AND_WHEN( "it is made smaller again" ) { + v.resize( 5 ); + THEN( + "the size goes down but the capacity stays the same" ) { + REQUIRE( v.size() == 5 ); + REQUIRE( v.capacity() >= 10 ); + } + } + } + } + + WHEN( "we reserve more space" ) { + v.reserve( 10 ); + THEN( "The capacity is increased but the size remains the same" ) { + REQUIRE( v.capacity() >= 10 ); + REQUIRE( v.size() == 0 ); + } + } + } +} + +SCENARIO("This is a really long scenario name to see how the list command deals with wrapping", + "[very long tags][lots][long][tags][verbose]" + "[one very long tag name that should cause line wrapping writing out using the list command]" + "[anotherReallyLongTagNameButThisOneHasNoObviousWrapPointsSoShouldSplitWithinAWordUsingADashCharacter]") { + GIVEN("A section name that is so long that it cannot fit in a single console width") { + WHEN("The test headers are printed as part of the normal running of the scenario") { + THEN("The, deliberately very long and overly verbose (you see what I did there?) section names must wrap, along with an indent") { + SUCCEED("boo!"); + } + } + } +} + +SCENARIO_METHOD(Fixture, + "BDD tests requiring Fixtures to provide commonly-accessed data or methods", + "[bdd][fixtures]") { + const int before(counter()); + GIVEN("No operations precede me") { + REQUIRE(before == 0); + WHEN("We get the count") { + const int after(counter()); + THEN("Subsequently values are higher") { + REQUIRE(after > before); + } + } + } +} diff --git a/tests/SelfTest/UsageTests/Benchmark.tests.cpp b/tests/SelfTest/UsageTests/Benchmark.tests.cpp new file mode 100644 index 0000000..c489eaa --- /dev/null +++ b/tests/SelfTest/UsageTests/Benchmark.tests.cpp @@ -0,0 +1,173 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 +// Adapted from donated nonius code. + +#include <catch2/catch_test_macros.hpp> +#include <catch2/benchmark/catch_benchmark.hpp> +#include <catch2/benchmark/catch_constructor.hpp> +#include <catch2/generators/catch_generators_range.hpp> + +#include <map> + +namespace { + std::uint64_t Fibonacci(std::uint64_t number) { + return number < 2 ? number : Fibonacci(number - 1) + Fibonacci(number - 2); + } +} + +TEST_CASE("Benchmark Fibonacci", "[!benchmark]") { + CHECK(Fibonacci(0) == 0); + // some more asserts.. + CHECK(Fibonacci(5) == 5); + // some more asserts.. + + REQUIRE( Fibonacci( 20 ) == 6'765 ); + BENCHMARK( "Fibonacci 20" ) { + return Fibonacci(20); + }; + + REQUIRE( Fibonacci( 25 ) == 75'025 ); + BENCHMARK( "Fibonacci 25" ) { + return Fibonacci(25); + }; + + BENCHMARK("Fibonacci 30") { + return Fibonacci(30); + }; + + BENCHMARK("Fibonacci 35") { + return Fibonacci(35); + }; +} + +TEST_CASE("Benchmark containers", "[!benchmark]") { + static const int size = 100; + + std::vector<int> v; + std::map<int, int> m; + + SECTION("without generator") { + BENCHMARK("Load up a vector") { + v = std::vector<int>(); + for (int i = 0; i < size; ++i) + v.push_back(i); + }; + REQUIRE(v.size() == size); + + // test optimizer control + BENCHMARK("Add up a vector's content") { + uint64_t add = 0; + for (int i = 0; i < size; ++i) + add += v[i]; + return add; + }; + + BENCHMARK("Load up a map") { + m = std::map<int, int>(); + for (int i = 0; i < size; ++i) + m.insert({ i, i + 1 }); + }; + REQUIRE(m.size() == size); + + BENCHMARK("Reserved vector") { + v = std::vector<int>(); + v.reserve(size); + for (int i = 0; i < size; ++i) + v.push_back(i); + }; + REQUIRE(v.size() == size); + + BENCHMARK("Resized vector") { + v = std::vector<int>(); + v.resize(size); + for (int i = 0; i < size; ++i) + v[i] = i; + }; + REQUIRE(v.size() == size); + + int array[size] {}; + BENCHMARK("A fixed size array that should require no allocations") { + for (int i = 0; i < size; ++i) + array[i] = i; + }; + int sum = 0; + for (int val : array) + sum += val; + REQUIRE(sum > size); + + SECTION("XYZ") { + + BENCHMARK_ADVANCED("Load up vector with chronometer")(Catch::Benchmark::Chronometer meter) { + std::vector<int> k; + meter.measure([&](int idx) { + k = std::vector<int>(); + for (int i = 0; i < size; ++i) + k.push_back(idx); + }); + REQUIRE(k.size() == size); + }; + + int runs = 0; + BENCHMARK("Fill vector indexed", benchmarkIndex) { + v = std::vector<int>(); + v.resize(size); + for (int i = 0; i < size; ++i) + v[i] = benchmarkIndex; + runs = benchmarkIndex; + }; + + for (int val : v) { + REQUIRE(val == runs); + } + } + } + + SECTION("with generator") { + auto generated = GENERATE(range(0, 10)); + BENCHMARK("Fill vector generated") { + v = std::vector<int>(); + v.resize(size); + for (int i = 0; i < size; ++i) + v[i] = generated; + }; + for (int val : v) { + REQUIRE(val == generated); + } + } + + SECTION("construct and destroy example") { + BENCHMARK_ADVANCED("construct")(Catch::Benchmark::Chronometer meter) { + std::vector<Catch::Benchmark::storage_for<std::string>> storage(meter.runs()); + meter.measure([&](int i) { storage[i].construct("thing"); }); + }; + + BENCHMARK_ADVANCED("destroy")(Catch::Benchmark::Chronometer meter) { + std::vector<Catch::Benchmark::destructable_object<std::string>> storage(meter.runs()); + for(auto&& o : storage) + o.construct("thing"); + meter.measure([&](int i) { storage[i].destruct(); }); + }; + } +} + +TEST_CASE("Skip benchmark macros", "[!benchmark]") { + std::vector<int> v; + BENCHMARK("fill vector") { + v.emplace_back(1); + v.emplace_back(2); + v.emplace_back(3); + }; + REQUIRE(v.size() == 0); + + std::size_t counter{0}; + BENCHMARK_ADVANCED("construct vector")(Catch::Benchmark::Chronometer meter) { + std::vector<Catch::Benchmark::storage_for<std::string>> storage(meter.runs()); + meter.measure([&](int i) { storage[i].construct("thing"); counter++; }); + }; + REQUIRE(counter == 0); +} diff --git a/tests/SelfTest/UsageTests/Class.tests.cpp b/tests/SelfTest/UsageTests/Class.tests.cpp new file mode 100644 index 0000000..75510f1 --- /dev/null +++ b/tests/SelfTest/UsageTests/Class.tests.cpp @@ -0,0 +1,159 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 + +#if defined( __GNUC__ ) || defined( __clang__ ) +# pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + +#include <catch2/catch_test_macros.hpp> +#include <catch2/catch_template_test_macros.hpp> +#include <array> + +namespace { + + class TestClass { + std::string s; + + public: + TestClass(): s( "hello" ) {} + + void succeedingCase() { REQUIRE( s == "hello" ); } + void failingCase() { REQUIRE( s == "world" ); } + }; + + struct Fixture { + Fixture(): m_a( 1 ) {} + + int m_a; + }; + + struct Persistent_Fixture { + mutable int m_a = 0; + }; + + template <typename T> struct Template_Fixture { + Template_Fixture(): m_a( 1 ) {} + + T m_a; + }; + + template <typename T> struct Template_Fixture_2 { + Template_Fixture_2() = default; + + T m_a; + }; + + template <typename T> struct Template_Foo { + size_t size() { return 0; } + }; + + template <typename T, size_t V> struct Template_Foo_2 { + size_t size() { return V; } + }; + + template <int V> struct Nttp_Fixture { int value = V; }; + +} // end unnamed namespace + +METHOD_AS_TEST_CASE( TestClass::succeedingCase, "A METHOD_AS_TEST_CASE based test run that succeeds", "[class]" ) +METHOD_AS_TEST_CASE( TestClass::failingCase, "A METHOD_AS_TEST_CASE based test run that fails", "[.][class][failing]" ) + +TEST_CASE_METHOD( Fixture, "A TEST_CASE_METHOD based test run that succeeds", "[class]" ) +{ + REQUIRE( m_a == 1 ); +} + +TEST_CASE_PERSISTENT_FIXTURE( Persistent_Fixture, "A TEST_CASE_PERSISTENT_FIXTURE based test run that succeeds", "[class]" ) +{ + SECTION( "First partial run" ) { + REQUIRE( m_a++ == 0 ); + } + + SECTION( "Second partial run" ) { + REQUIRE( m_a == 1 ); + } +} + +TEMPLATE_TEST_CASE_METHOD(Template_Fixture, "A TEMPLATE_TEST_CASE_METHOD based test run that succeeds", "[class][template]", int, float, double) { + REQUIRE( Template_Fixture<TestType>::m_a == 1 ); +} + +TEMPLATE_TEST_CASE_METHOD_SIG(Nttp_Fixture, "A TEMPLATE_TEST_CASE_METHOD_SIG based test run that succeeds", "[class][template][nttp]",((int V), V), 1, 3, 6) { + REQUIRE(Nttp_Fixture<V>::value > 0); +} + +TEMPLATE_PRODUCT_TEST_CASE_METHOD(Template_Fixture_2, "A TEMPLATE_PRODUCT_TEST_CASE_METHOD based test run that succeeds","[class][template][product]",(std::vector,Template_Foo),(int,float)) +{ + REQUIRE( Template_Fixture_2<TestType>::m_a.size() == 0 ); +} + +TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG(Template_Fixture_2, "A TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG based test run that succeeds", "[class][template][product][nttp]", ((typename T, size_t S), T, S),(std::array, Template_Foo_2), ((int,2), (float,6))) +{ + REQUIRE(Template_Fixture_2<TestType>{}.m_a.size() >= 2); +} + +using MyTypes = std::tuple<int, char, double>; +TEMPLATE_LIST_TEST_CASE_METHOD(Template_Fixture, "Template test case method with test types specified inside std::tuple", "[class][template][list]", MyTypes) +{ + REQUIRE( Template_Fixture<TestType>::m_a == 1 ); +} + +// We should be able to write our tests within a different namespace +namespace Inner +{ + TEST_CASE_METHOD( Fixture, "A TEST_CASE_METHOD based test run that fails", "[.][class][failing]" ) + { + REQUIRE( m_a == 2 ); + } + + TEST_CASE_PERSISTENT_FIXTURE( Persistent_Fixture, "A TEST_CASE_PERSISTENT_FIXTURE based test run that fails", "[.][class][failing]" ) + { + SECTION( "First partial run" ) { + REQUIRE( m_a++ == 0 ); + } + + SECTION( "Second partial run" ) { + REQUIRE( m_a == 0 ); + } + } + + TEMPLATE_TEST_CASE_METHOD(Template_Fixture,"A TEMPLATE_TEST_CASE_METHOD based test run that fails", "[.][class][template][failing]", int, float, double) + { + REQUIRE( Template_Fixture<TestType>::m_a == 2 ); + } + + TEMPLATE_TEST_CASE_METHOD_SIG(Nttp_Fixture, "A TEMPLATE_TEST_CASE_METHOD_SIG based test run that fails", "[.][class][template][nttp][failing]", ((int V), V), 1, 3, 6) { + REQUIRE(Nttp_Fixture<V>::value == 0); + } + + TEMPLATE_PRODUCT_TEST_CASE_METHOD(Template_Fixture_2, "A TEMPLATE_PRODUCT_TEST_CASE_METHOD based test run that fails","[.][class][template][product][failing]",(std::vector,Template_Foo),(int,float)) + { + REQUIRE( Template_Fixture_2<TestType>::m_a.size() == 1 ); + } + + TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG(Template_Fixture_2, "A TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG based test run that fails", "[.][class][template][product][nttp][failing]", ((typename T, size_t S), T, S), (std::array, Template_Foo_2), ((int, 2), (float, 6))) + { + REQUIRE(Template_Fixture_2<TestType>{}.m_a.size() < 2); + } +} // namespace + + +// We want a class in nested namespace so we can test JUnit's classname normalization. +namespace { + namespace A { + namespace B { + class TestClass {}; + } + } +} // namespace + +TEST_CASE_METHOD( A::B::TestClass, + "A TEST_CASE_METHOD testing junit classname normalization", + "[class][approvals]" ) { + SUCCEED(); +} diff --git a/tests/SelfTest/UsageTests/Compilation.tests.cpp b/tests/SelfTest/UsageTests/Compilation.tests.cpp new file mode 100644 index 0000000..7a6a1f2 --- /dev/null +++ b/tests/SelfTest/UsageTests/Compilation.tests.cpp @@ -0,0 +1,525 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 + +#include <helpers/type_with_lit_0_comparisons.hpp> + +#include <array> +#include <type_traits> + +// Setup for #1403 -- look for global overloads of operator << for classes +// in a different namespace. +#include <ostream> + +namespace foo { + struct helper_1403 { + bool operator==(helper_1403) const { return true; } + }; +} + +namespace bar { + template <typename... Ts> + struct TypeList {}; +} + +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wmissing-declarations" +#endif +static std::ostream& operator<<(std::ostream& out, foo::helper_1403 const&) { + return out << "[1403 helper]"; +} +/////////////////////////////// + +#include <catch2/catch_test_macros.hpp> +#include <catch2/generators/catch_generators_range.hpp> +#include <catch2/matchers/catch_matchers_string.hpp> + +#include <cstring> + +// Comparison operators can return non-booleans. +// This is unusual, but should be supported. +struct logic_t { + logic_t operator< (logic_t) const { return {}; } + logic_t operator<=(logic_t) const { return {}; } + logic_t operator> (logic_t) const { return {}; } + logic_t operator>=(logic_t) const { return {}; } + logic_t operator==(logic_t) const { return {}; } + logic_t operator!=(logic_t) const { return {}; } + explicit operator bool() const { return true; } +}; + + +static void throws_int(bool b) { + if (b) { + throw 1; + } +} + +template<typename T> +bool templated_tests(T t) { + int a = 3; + REQUIRE(a == t); + CHECK(a == t); + REQUIRE_THROWS(throws_int(true)); + CHECK_THROWS_AS(throws_int(true), int); + REQUIRE_NOTHROW(throws_int(false)); + REQUIRE_THAT("aaa", Catch::Matchers::EndsWith("aaa")); + return true; +} + +struct A {}; + +static std::ostream &operator<<(std::ostream &o, const A &) { return o << 0; } + +struct B : private A { + bool operator==(int) const { return true; } +}; + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-function" +#endif +#ifdef __GNUC__ +// Note that because -~GCC~-, this warning cannot be silenced temporarily, by pushing diagnostic stack... +// Luckily it is firing in test files and thus can be silenced for the whole file, without losing much. +#pragma GCC diagnostic ignored "-Wunused-function" +#endif + +B f(); + +std::ostream g(); + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +template <typename, typename> +struct Fixture_1245 {}; + +// This is a minimal example for an issue we have found in 1.7.0 +struct dummy_809 { + int i; +}; + +template<typename T> +bool operator==(const T& val, dummy_809 f) { + return val == f.i; +} + +TEST_CASE("#809") { + dummy_809 f; + f.i = 42; + REQUIRE(42 == f); +} + + +// ------------------------------------------------------------------ +// Changes to REQUIRE_THROWS_AS made it stop working in a template in +// an unfixable way (as long as C++03 compatibility is being kept). +// To prevent these from happening in the future, this needs to compile + + TEST_CASE("#833") { + REQUIRE(templated_tests<int>(3)); + } + + +// Test containing example where original stream insertable check breaks compilation +TEST_CASE("#872") { + A dummy; + CAPTURE(dummy); + B x; + REQUIRE (x == 4); +} + +TEST_CASE("#1027: Bitfields can be captured") { + struct Y { + uint32_t v : 1; + }; + Y y{ 0 }; + REQUIRE(y.v == 0); + REQUIRE(0 == y.v); +} + +// Comparison operators can return non-booleans. +// This is unusual, but should be supported. +TEST_CASE("#1147") { + logic_t t1, t2; + REQUIRE(t1 == t2); + REQUIRE(t1 != t2); + REQUIRE(t1 < t2); + REQUIRE(t1 > t2); + REQUIRE(t1 <= t2); + REQUIRE(t1 >= t2); +} + +// unsigned array +TEST_CASE("#1238") { + unsigned char uarr[] = "123"; + CAPTURE(uarr); + signed char sarr[] = "456"; + CAPTURE(sarr); + + REQUIRE(std::memcmp(uarr, "123", sizeof(uarr)) == 0); + REQUIRE(std::memcmp(sarr, "456", sizeof(sarr)) == 0); +} + +TEST_CASE_METHOD((Fixture_1245<int, int>), "#1245", "[compilation]") { + SUCCEED(); +} + +TEST_CASE("#1403", "[compilation]") { + ::foo::helper_1403 h1, h2; + REQUIRE(h1 == h2); +} + +TEST_CASE("Optionally static assertions", "[compilation]") { + STATIC_REQUIRE( std::is_void<void>::value ); + STATIC_REQUIRE_FALSE( std::is_void<int>::value ); + STATIC_CHECK( std::is_void<void>::value ); + STATIC_CHECK_FALSE( std::is_void<int>::value ); +} + +TEST_CASE("#1548", "[compilation]") { + using namespace bar; + REQUIRE(std::is_same<TypeList<int>, TypeList<int>>::value); +} + + // #925 + using signal_t = void (*) (void*); + + struct TestClass { + signal_t testMethod_uponComplete_arg = nullptr; + }; + + namespace utility { + inline static void synchronizing_callback( void * ) { } + } + +#if defined (_MSC_VER) +#pragma warning(push) +// The function pointer comparison below triggers warning because of +// calling conventions +#pragma warning(disable:4244) +#endif + TEST_CASE("#925: comparing function pointer to function address failed to compile", "[!nonportable]" ) { + TestClass test; + REQUIRE(utility::synchronizing_callback != test.testMethod_uponComplete_arg); + } +#if defined (_MSC_VER) +#pragma warning(pop) +#endif + +TEST_CASE( "#1319: Sections can have description (even if it is not saved", + "[compilation]" ) { + SECTION( "SectionName", "This is a long form section description" ) { + SUCCEED(); + } +} + +TEST_CASE("Lambdas in assertions") { + REQUIRE([]() { return true; }()); +} + +namespace { + struct HasBitOperators { + int value; + + friend HasBitOperators operator| (HasBitOperators lhs, HasBitOperators rhs) { + return { lhs.value | rhs.value }; + } + friend HasBitOperators operator& (HasBitOperators lhs, HasBitOperators rhs) { + return { lhs.value & rhs.value }; + } + friend HasBitOperators operator^ (HasBitOperators lhs, HasBitOperators rhs) { + return { lhs.value ^ rhs.value }; + } + explicit operator bool() const { + return !!value; + } + + friend std::ostream& operator<<(std::ostream& out, HasBitOperators val) { + out << "Val: " << val.value; + return out; + } + }; +} + +TEST_CASE("Assertion macros support bit operators and bool conversions", "[compilation][bitops]") { + HasBitOperators lhs{ 1 }, rhs{ 2 }; + REQUIRE(lhs | rhs); + REQUIRE_FALSE(lhs & rhs); + REQUIRE(HasBitOperators{ 1 } & HasBitOperators{ 1 }); + REQUIRE(lhs ^ rhs); + REQUIRE_FALSE(lhs ^ lhs); +} + +namespace { + struct ImmovableType { + ImmovableType() = default; + + ImmovableType(ImmovableType const&) = delete; + ImmovableType& operator=(ImmovableType const&) = delete; + ImmovableType(ImmovableType&&) = delete; + ImmovableType& operator=(ImmovableType&&) = delete; + + friend bool operator==(ImmovableType const&, ImmovableType const&) { + return true; + } + }; +} + +TEST_CASE("Immovable types are supported in basic assertions", "[compilation][.approvals]") { + REQUIRE(ImmovableType{} == ImmovableType{}); +} + +namespace adl { + +struct always_true { + explicit operator bool() const { return true; } +}; + +#define COMPILATION_TEST_DEFINE_UNIVERSAL_OPERATOR(op) \ +template <class T, class U> \ +auto operator op (T&&, U&&) { \ + return always_true{}; \ +} + +COMPILATION_TEST_DEFINE_UNIVERSAL_OPERATOR(==) +COMPILATION_TEST_DEFINE_UNIVERSAL_OPERATOR(!=) +COMPILATION_TEST_DEFINE_UNIVERSAL_OPERATOR(<) +COMPILATION_TEST_DEFINE_UNIVERSAL_OPERATOR(>) +COMPILATION_TEST_DEFINE_UNIVERSAL_OPERATOR(<=) +COMPILATION_TEST_DEFINE_UNIVERSAL_OPERATOR(>=) +COMPILATION_TEST_DEFINE_UNIVERSAL_OPERATOR(|) +COMPILATION_TEST_DEFINE_UNIVERSAL_OPERATOR(&) +COMPILATION_TEST_DEFINE_UNIVERSAL_OPERATOR(^) + +#undef COMPILATION_TEST_DEFINE_UNIVERSAL_OPERATOR + +} + +TEST_CASE("ADL universal operators don't hijack expression deconstruction", "[compilation][.approvals]") { + REQUIRE(adl::always_true{}); + REQUIRE(0 == adl::always_true{}); + REQUIRE(0 != adl::always_true{}); + REQUIRE(0 < adl::always_true{}); + REQUIRE(0 > adl::always_true{}); + REQUIRE(0 <= adl::always_true{}); + REQUIRE(0 >= adl::always_true{}); + REQUIRE(0 | adl::always_true{}); + REQUIRE(0 & adl::always_true{}); + REQUIRE(0 ^ adl::always_true{}); +} + +TEST_CASE( "#2555 - types that can only be compared with 0 literal implemented as pointer conversion are supported", + "[compilation][approvals]" ) { + REQUIRE( TypeWithLit0Comparisons{} < 0 ); + REQUIRE_FALSE( 0 < TypeWithLit0Comparisons{} ); + REQUIRE( TypeWithLit0Comparisons{} <= 0 ); + REQUIRE_FALSE( 0 <= TypeWithLit0Comparisons{} ); + + REQUIRE( TypeWithLit0Comparisons{} > 0 ); + REQUIRE_FALSE( 0 > TypeWithLit0Comparisons{} ); + REQUIRE( TypeWithLit0Comparisons{} >= 0 ); + REQUIRE_FALSE( 0 >= TypeWithLit0Comparisons{} ); + + REQUIRE( TypeWithLit0Comparisons{} == 0 ); + REQUIRE_FALSE( 0 == TypeWithLit0Comparisons{} ); + REQUIRE( TypeWithLit0Comparisons{} != 0 ); + REQUIRE_FALSE( 0 != TypeWithLit0Comparisons{} ); +} + +// These tests require `consteval` to propagate through `constexpr` calls +// which is a late DR against C++20. +#if defined( CATCH_CPP20_OR_GREATER ) && defined( __cpp_consteval ) && \ + __cpp_consteval >= 202211L +// Can't have internal linkage to avoid warnings +void ZeroLiteralErrorFunc(); +namespace { + struct ZeroLiteralConsteval { + template <class T, std::enable_if_t<std::is_same_v<T, int>, int> = 0> + consteval ZeroLiteralConsteval( T zero ) noexcept { + if ( zero != 0 ) { ZeroLiteralErrorFunc(); } + } + }; + + // Should only be constructible from literal 0. Uses the propagating + // consteval constructor trick (currently used by MSVC, might be used + // by libc++ in the future as well). + struct TypeWithConstevalLit0Comparison { +# define DEFINE_COMP_OP( op ) \ + constexpr friend bool operator op( TypeWithConstevalLit0Comparison, \ + ZeroLiteralConsteval ) { \ + return true; \ + } \ + constexpr friend bool operator op( ZeroLiteralConsteval, \ + TypeWithConstevalLit0Comparison ) { \ + return false; \ + } \ + /* std::orderings only have these for ==, but we add them for all \ + operators so we can test all overloads for decomposer */ \ + constexpr friend bool operator op( TypeWithConstevalLit0Comparison, \ + TypeWithConstevalLit0Comparison ) { \ + return true; \ + } + + DEFINE_COMP_OP( < ) + DEFINE_COMP_OP( <= ) + DEFINE_COMP_OP( > ) + DEFINE_COMP_OP( >= ) + DEFINE_COMP_OP( == ) + DEFINE_COMP_OP( != ) + +#undef DEFINE_COMP_OP + }; + +} // namespace + +namespace Catch { + template <> + struct capture_by_value<TypeWithConstevalLit0Comparison> : std::true_type {}; +} + +TEST_CASE( "#2555 - types that can only be compared with 0 literal implemented as consteval check are supported", + "[compilation][approvals]" ) { + REQUIRE( TypeWithConstevalLit0Comparison{} < 0 ); + REQUIRE_FALSE( 0 < TypeWithConstevalLit0Comparison{} ); + REQUIRE( TypeWithConstevalLit0Comparison{} <= 0 ); + REQUIRE_FALSE( 0 <= TypeWithConstevalLit0Comparison{} ); + + REQUIRE( TypeWithConstevalLit0Comparison{} > 0 ); + REQUIRE_FALSE( 0 > TypeWithConstevalLit0Comparison{} ); + REQUIRE( TypeWithConstevalLit0Comparison{} >= 0 ); + REQUIRE_FALSE( 0 >= TypeWithConstevalLit0Comparison{} ); + + REQUIRE( TypeWithConstevalLit0Comparison{} == 0 ); + REQUIRE_FALSE( 0 == TypeWithConstevalLit0Comparison{} ); + REQUIRE( TypeWithConstevalLit0Comparison{} != 0 ); + REQUIRE_FALSE( 0 != TypeWithConstevalLit0Comparison{} ); +} + +// We check all comparison ops to test, even though orderings, the primary +// motivation for this functionality, only have self-comparison (and thus +// have the ambiguity issue) for `==` and `!=`. +TEST_CASE( "Comparing const instances of type registered with capture_by_value", + "[regression][approvals][compilation]" ) { + SECTION("Type with consteval-int constructor") { + auto const const_Lit0Type_1 = TypeWithConstevalLit0Comparison{}; + auto const const_Lit0Type_2 = TypeWithConstevalLit0Comparison{}; + REQUIRE( const_Lit0Type_1 == const_Lit0Type_2 ); + REQUIRE( const_Lit0Type_1 <= const_Lit0Type_2 ); + REQUIRE( const_Lit0Type_1 < const_Lit0Type_2 ); + REQUIRE( const_Lit0Type_1 >= const_Lit0Type_2 ); + REQUIRE( const_Lit0Type_1 > const_Lit0Type_2 ); + REQUIRE( const_Lit0Type_1 != const_Lit0Type_2 ); + } + SECTION("Type with constexpr-int constructor") { + auto const const_Lit0Type_1 = TypeWithLit0Comparisons{}; + auto const const_Lit0Type_2 = TypeWithLit0Comparisons{}; + REQUIRE( const_Lit0Type_1 == const_Lit0Type_2 ); + REQUIRE( const_Lit0Type_1 <= const_Lit0Type_2 ); + REQUIRE( const_Lit0Type_1 < const_Lit0Type_2 ); + REQUIRE( const_Lit0Type_1 >= const_Lit0Type_2 ); + REQUIRE( const_Lit0Type_1 > const_Lit0Type_2 ); + REQUIRE( const_Lit0Type_1 != const_Lit0Type_2 ); + } +} + +#endif // C++20 consteval + + +namespace { + struct MultipleImplicitConstructors { + MultipleImplicitConstructors( double ) {} + MultipleImplicitConstructors( int64_t ) {} + bool operator==( MultipleImplicitConstructors ) const { return true; } + bool operator!=( MultipleImplicitConstructors ) const { return true; } + bool operator<( MultipleImplicitConstructors ) const { return true; } + bool operator<=( MultipleImplicitConstructors ) const { return true; } + bool operator>( MultipleImplicitConstructors ) const { return true; } + bool operator>=( MultipleImplicitConstructors ) const { return true; } + }; +} +TEST_CASE("#2571 - tests compile types that have multiple implicit constructors from lit 0", + "[compilation][approvals]") { + MultipleImplicitConstructors mic1( 0.0 ); + MultipleImplicitConstructors mic2( 0.0 ); + REQUIRE( mic1 == mic2 ); + REQUIRE( mic1 != mic2 ); + REQUIRE( mic1 < mic2 ); + REQUIRE( mic1 <= mic2 ); + REQUIRE( mic1 > mic2 ); + REQUIRE( mic1 >= mic2 ); +} + +#if defined( CATCH_CONFIG_CPP20_COMPARE_OVERLOADS ) +// This test does not test all the related codepaths, but it is the original +// reproducer +TEST_CASE( "Comparing const std::weak_ordering instances must compile", + "[compilation][approvals][regression]" ) { + auto const const_ordering_1 = std::weak_ordering::less; + auto const const_ordering_2 = std::weak_ordering::less; + auto plain_ordering_1 = std::weak_ordering::less; + REQUIRE( const_ordering_1 == plain_ordering_1 ); + REQUIRE( const_ordering_1 == const_ordering_2 ); + REQUIRE( plain_ordering_1 == const_ordering_1 ); +} +#endif + +// Reproduce issue with yaml-cpp iterators, where the `const_iterator` +// for Node type has `const T` as the value_type. This is wrong for +// multitude of reasons, but there might be other libraries in the wild +// that share this issue, and the workaround needed to support +// `from_range(iter, iter)` helper with those libraries is easy enough. +class HasBadIterator { + std::array<int, 10> m_arr{}; + +public: + class iterator { + const int* m_ptr = nullptr; + + public: + iterator( const int* ptr ): m_ptr( ptr ) {} + + using difference_type = std::ptrdiff_t; + using value_type = const int; + using pointer = const int*; + using reference = const int&; + using iterator_category = std::input_iterator_tag; + + iterator& operator++() { + ++m_ptr; + return *this; + } + + iterator operator++( int ) { + auto ret( *this ); + ++( *this ); + return ret; + } + + friend bool operator==( iterator lhs, iterator rhs ) { + return lhs.m_ptr == rhs.m_ptr; + } + friend bool operator!=( iterator lhs, iterator rhs ) { + return !( lhs == rhs ); + } + + int operator*() const { return *m_ptr; } + }; + + iterator cbegin() const { return { m_arr.data() }; } + iterator cend() const { return { m_arr.data() + m_arr.size() }; } +}; + +TEST_CASE("from_range(iter, iter) supports const_iterators", "[generators][from-range][approvals]") { + using namespace Catch::Generators; + + HasBadIterator data; + auto gen = from_range(data.cbegin(), data.cend()); + (void)gen; +} diff --git a/tests/SelfTest/UsageTests/Condition.tests.cpp b/tests/SelfTest/UsageTests/Condition.tests.cpp new file mode 100644 index 0000000..211dd3b --- /dev/null +++ b/tests/SelfTest/UsageTests/Condition.tests.cpp @@ -0,0 +1,334 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 + +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wpadded" +// Wdouble-promotion is not supported until 3.8 +# if (__clang_major__ > 3) || (__clang_major__ == 3 && __clang_minor__ > 7) +# pragma clang diagnostic ignored "-Wdouble-promotion" +# endif +#endif + +#include <catch2/catch_approx.hpp> +#include <catch2/catch_test_macros.hpp> + +using Catch::Approx; + +#include <string> +#include <limits> +#include <cstdint> + +namespace { + + struct TestData { + int int_seven = 7; + std::string str_hello = "hello"; + float float_nine_point_one = 9.1f; + double double_pi = 3.1415926535; + }; + + static const char* returnsConstNull() { return nullptr; } + static char* returnsNull() { return nullptr; } + +} // end unnamed namespace + +// The "failing" tests all use the CHECK macro, which continues if the specific test fails. +// This allows us to see all results, even if an earlier check fails + +// Equality tests +TEST_CASE( "Equality checks that should succeed" ) +{ + TestData data; + + REQUIRE( data.int_seven == 7 ); + REQUIRE( data.float_nine_point_one == Approx( 9.1f ) ); + REQUIRE( data.double_pi == Approx( 3.1415926535 ) ); + REQUIRE( data.str_hello == "hello" ); + REQUIRE( "hello" == data.str_hello ); + REQUIRE( data.str_hello.size() == 5 ); + + double x = 1.1 + 0.1 + 0.1; + REQUIRE( x == Approx( 1.3 ) ); +} + +TEST_CASE( "Equality checks that should fail", "[.][failing][!mayfail]" ) +{ + TestData data; + + CHECK( data.int_seven == 6 ); + CHECK( data.int_seven == 8 ); + CHECK( data.int_seven == 0 ); + CHECK( data.float_nine_point_one == Approx( 9.11f ) ); + CHECK( data.float_nine_point_one == Approx( 9.0f ) ); + CHECK( data.float_nine_point_one == Approx( 1 ) ); + CHECK( data.float_nine_point_one == Approx( 0 ) ); + CHECK( data.double_pi == Approx( 3.1415 ) ); + CHECK( data.str_hello == "goodbye" ); + CHECK( data.str_hello == "hell" ); + CHECK( data.str_hello == "hello1" ); + CHECK( data.str_hello.size() == 6 ); + + double x = 1.1 + 0.1 + 0.1; + CHECK( x == Approx( 1.301 ) ); +} + +// Needed to test junit reporter's handling of mayfail test cases and sections +TEST_CASE("Mayfail test case with nested sections", "[!mayfail]") { + SECTION("A") { + SECTION("1") { FAIL(); } + SECTION("2") { FAIL(); } + } + SECTION("B") { + SECTION("1") { FAIL(); } + SECTION("2") { FAIL(); } + } +} + + +TEST_CASE( "Inequality checks that should succeed" ) +{ + TestData data; + + REQUIRE( data.int_seven != 6 ); + REQUIRE( data.int_seven != 8 ); + REQUIRE( data.float_nine_point_one != Approx( 9.11f ) ); + REQUIRE( data.float_nine_point_one != Approx( 9.0f ) ); + REQUIRE( data.float_nine_point_one != Approx( 1 ) ); + REQUIRE( data.float_nine_point_one != Approx( 0 ) ); + REQUIRE( data.double_pi != Approx( 3.1415 ) ); + REQUIRE( data.str_hello != "goodbye" ); + REQUIRE( data.str_hello != "hell" ); + REQUIRE( data.str_hello != "hello1" ); + REQUIRE( data.str_hello.size() != 6 ); +} + +TEST_CASE( "Inequality checks that should fail", "[.][failing][!shouldfail]" ) +{ + TestData data; + + CHECK( data.int_seven != 7 ); + CHECK( data.float_nine_point_one != Approx( 9.1f ) ); + CHECK( data.double_pi != Approx( 3.1415926535 ) ); + CHECK( data.str_hello != "hello" ); + CHECK( data.str_hello.size() != 5 ); +} + +// Ordering comparison tests +TEST_CASE( "Ordering comparison checks that should succeed" ) +{ + TestData data; + + REQUIRE( data.int_seven < 8 ); + REQUIRE( data.int_seven > 6 ); + REQUIRE( data.int_seven > 0 ); + REQUIRE( data.int_seven > -1 ); + + REQUIRE( data.int_seven >= 7 ); + REQUIRE( data.int_seven >= 6 ); + REQUIRE( data.int_seven <= 7 ); + REQUIRE( data.int_seven <= 8 ); + + REQUIRE( data.float_nine_point_one > 9 ); + REQUIRE( data.float_nine_point_one < 10 ); + REQUIRE( data.float_nine_point_one < 9.2 ); + + REQUIRE( data.str_hello <= "hello" ); + REQUIRE( data.str_hello >= "hello" ); + + REQUIRE( data.str_hello < "hellp" ); + REQUIRE( data.str_hello < "zebra" ); + REQUIRE( data.str_hello > "hellm" ); + REQUIRE( data.str_hello > "a" ); +} + +TEST_CASE( "Ordering comparison checks that should fail", "[.][failing]" ) +{ + TestData data; + + CHECK( data.int_seven > 7 ); + CHECK( data.int_seven < 7 ); + CHECK( data.int_seven > 8 ); + CHECK( data.int_seven < 6 ); + CHECK( data.int_seven < 0 ); + CHECK( data.int_seven < -1 ); + + CHECK( data.int_seven >= 8 ); + CHECK( data.int_seven <= 6 ); + + CHECK( data.float_nine_point_one < 9 ); + CHECK( data.float_nine_point_one > 10 ); + CHECK( data.float_nine_point_one > 9.2 ); + + CHECK( data.str_hello > "hello" ); + CHECK( data.str_hello < "hello" ); + CHECK( data.str_hello > "hellp" ); + CHECK( data.str_hello > "z" ); + CHECK( data.str_hello < "hellm" ); + CHECK( data.str_hello < "a" ); + + CHECK( data.str_hello >= "z" ); + CHECK( data.str_hello <= "a" ); +} + +#ifdef __clang__ +# pragma clang diagnostic pop +#endif + + +// Comparisons with int literals +TEST_CASE( "Comparisons with int literals don't warn when mixing signed/ unsigned" ) +{ + int i = 1; + unsigned int ui = 2; + long l = 3; + unsigned long ul = 4; + char c = 5; + unsigned char uc = 6; + + REQUIRE( i == 1 ); + REQUIRE( ui == 2 ); + REQUIRE( l == 3 ); + REQUIRE( ul == 4 ); + REQUIRE( c == 5 ); + REQUIRE( uc == 6 ); + + REQUIRE( 1 == i ); + REQUIRE( 2 == ui ); + REQUIRE( 3 == l ); + REQUIRE( 4 == ul ); + REQUIRE( 5 == c ); + REQUIRE( 6 == uc ); + + REQUIRE( (std::numeric_limits<uint32_t>::max)() > ul ); +} + +// Disable warnings about sign conversions for the next two tests +// (as we are deliberately invoking them) +// - Currently only disabled for GCC/ LLVM. Should add VC++ too +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsign-compare" +#pragma GCC diagnostic ignored "-Wsign-conversion" +#endif +#ifdef _MSC_VER +#pragma warning(disable:4389) // '==' : signed/unsigned mismatch +#endif + +TEST_CASE( "comparisons between int variables" ) +{ + long long_var = 1L; + unsigned char unsigned_char_var = 1; + unsigned short unsigned_short_var = 1; + unsigned int unsigned_int_var = 1; + unsigned long unsigned_long_var = 1L; + + REQUIRE( long_var == unsigned_char_var ); + REQUIRE( long_var == unsigned_short_var ); + REQUIRE( long_var == unsigned_int_var ); + REQUIRE( long_var == unsigned_long_var ); +} + +TEST_CASE( "comparisons between const int variables" ) +{ + const unsigned char unsigned_char_var = 1; + const unsigned short unsigned_short_var = 1; + const unsigned int unsigned_int_var = 1; + const unsigned long unsigned_long_var = 1L; + + REQUIRE( unsigned_char_var == 1 ); + REQUIRE( unsigned_short_var == 1 ); + REQUIRE( unsigned_int_var == 1 ); + REQUIRE( unsigned_long_var == 1 ); +} + +TEST_CASE( "Comparisons between unsigned ints and negative signed ints match c++ standard behaviour" ) +{ + CHECK( ( -1 > 2u ) ); + CHECK( -1 > 2u ); + + CHECK( ( 2u < -1 ) ); + CHECK( 2u < -1 ); + + const int minInt = (std::numeric_limits<int>::min)(); + CHECK( ( minInt > 2u ) ); + CHECK( minInt > 2u ); +} + +TEST_CASE( "Comparisons between ints where one side is computed" ) +{ + CHECK( 54 == 6*9 ); +} + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +TEST_CASE( "Pointers can be compared to null" ) +{ + TestData* p = nullptr; + TestData* pNULL = nullptr; + + REQUIRE( p == nullptr ); + REQUIRE( p == pNULL ); + + TestData data; + p = &data; + + REQUIRE( p != nullptr ); + + const TestData* cp = p; + REQUIRE( cp != nullptr ); + + const TestData* const cpc = p; + REQUIRE( cpc != nullptr ); + + REQUIRE( returnsNull() == nullptr ); + REQUIRE( returnsConstNull() == nullptr ); + + REQUIRE( nullptr != p ); +} + +// Not (!) tests +// The problem with the ! operator is that it has right-to-left associativity. +// This means we can't isolate it when we decompose. The simple REQUIRE( !false ) form, therefore, +// cannot have the operand value extracted. The test will work correctly, and the situation +// is detected and a warning issued. +// An alternative form of the macros (CHECK_FALSE and REQUIRE_FALSE) can be used instead to capture +// the operand value. +TEST_CASE( "'Not' checks that should succeed" ) +{ + bool falseValue = false; + + REQUIRE( false == false ); + REQUIRE( true == true ); + REQUIRE( !false ); + REQUIRE_FALSE( false ); + + REQUIRE( !falseValue ); + REQUIRE_FALSE( falseValue ); + + REQUIRE( !(1 == 2) ); + REQUIRE_FALSE( 1 == 2 ); +} + +TEST_CASE( "'Not' checks that should fail", "[.][failing]" ) +{ + bool trueValue = true; + + CHECK( false != false ); + CHECK( true != true ); + CHECK( !true ); + CHECK_FALSE( true ); + + CHECK( !trueValue ); + CHECK_FALSE( trueValue ); + + CHECK( !(1 == 1) ); + CHECK_FALSE( 1 == 1 ); +} diff --git a/tests/SelfTest/UsageTests/Decomposition.tests.cpp b/tests/SelfTest/UsageTests/Decomposition.tests.cpp new file mode 100644 index 0000000..e92f740 --- /dev/null +++ b/tests/SelfTest/UsageTests/Decomposition.tests.cpp @@ -0,0 +1,41 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 + +#include <iostream> +#include <cstdio> + +namespace { + +struct truthy { + truthy(bool b):m_value(b){} + operator bool() const { + return false; + } + bool m_value; +}; + +std::ostream& operator<<(std::ostream& o, truthy) { + o << "Hey, its truthy!"; + return o; +} + +} // end anonymous namespace + +#include <catch2/catch_test_macros.hpp> + +TEST_CASE( "Reconstruction should be based on stringification: #914" , "[Decomposition][failing][.]") { + CHECK(truthy(false)); +} + +TEST_CASE("#1005: Comparing pointer to int and long (NULL can be either on various systems)", "[Decomposition][approvals]") { + FILE* fptr = nullptr; + REQUIRE( fptr == 0 ); + REQUIRE_FALSE( fptr != 0 ); + REQUIRE( fptr == 0l ); + REQUIRE_FALSE( fptr != 0l ); +} diff --git a/tests/SelfTest/UsageTests/EnumToString.tests.cpp b/tests/SelfTest/UsageTests/EnumToString.tests.cpp new file mode 100644 index 0000000..268a7ca --- /dev/null +++ b/tests/SelfTest/UsageTests/EnumToString.tests.cpp @@ -0,0 +1,108 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 + +#include <catch2/catch_test_macros.hpp> +#include <catch2/internal/catch_enum_values_registry.hpp> + + +namespace { +// Enum without user-provided stream operator +enum Enum1 { Enum1Value0, Enum1Value1 }; + +// Enum with user-provided stream operator +enum Enum2 { Enum2Value0, Enum2Value1 }; + +std::ostream& operator<<( std::ostream& os, Enum2 v ) { + return os << "E2{" << static_cast<int>(v) << "}"; +} +} // end anonymous namespace + +TEST_CASE( "toString(enum)", "[toString][enum]" ) { + Enum1 e0 = Enum1Value0; + CHECK( ::Catch::Detail::stringify(e0) == "0" ); + Enum1 e1 = Enum1Value1; + CHECK( ::Catch::Detail::stringify(e1) == "1" ); +} + +TEST_CASE( "toString(enum w/operator<<)", "[toString][enum]" ) { + Enum2 e0 = Enum2Value0; + CHECK( ::Catch::Detail::stringify(e0) == "E2{0}" ); + Enum2 e1 = Enum2Value1; + CHECK( ::Catch::Detail::stringify(e1) == "E2{1}" ); +} + +// Enum class without user-provided stream operator +namespace { +enum class EnumClass1 { EnumClass1Value0, EnumClass1Value1 }; + +// Enum class with user-provided stream operator +enum class EnumClass2 { EnumClass2Value0, EnumClass2Value1 }; + +std::ostream& operator<<( std::ostream& os, EnumClass2 e2 ) { + switch( static_cast<int>( e2 ) ) { + case static_cast<int>( EnumClass2::EnumClass2Value0 ): + return os << "E2/V0"; + case static_cast<int>( EnumClass2::EnumClass2Value1 ): + return os << "E2/V1"; + default: + return os << "Unknown enum value " << static_cast<int>( e2 ); + } +} + +} // end anonymous namespace + +TEST_CASE( "toString(enum class)", "[toString][enum][enumClass]" ) { + EnumClass1 e0 = EnumClass1::EnumClass1Value0; + CHECK( ::Catch::Detail::stringify(e0) == "0" ); + EnumClass1 e1 = EnumClass1::EnumClass1Value1; + CHECK( ::Catch::Detail::stringify(e1) == "1" ); +} + + +TEST_CASE( "toString(enum class w/operator<<)", "[toString][enum][enumClass]" ) { + EnumClass2 e0 = EnumClass2::EnumClass2Value0; + CHECK( ::Catch::Detail::stringify(e0) == "E2/V0" ); + EnumClass2 e1 = EnumClass2::EnumClass2Value1; + CHECK( ::Catch::Detail::stringify(e1) == "E2/V1" ); + + auto e3 = static_cast<EnumClass2>(10); + CHECK( ::Catch::Detail::stringify(e3) == "Unknown enum value 10" ); +} + +enum class EnumClass3 { Value1, Value2, Value3, Value4 }; + +CATCH_REGISTER_ENUM( EnumClass3, EnumClass3::Value1, EnumClass3::Value2, EnumClass3::Value3 ) + + +TEST_CASE( "Enums can quickly have stringification enabled using CATCH_REGISTER_ENUM" ) { + using Catch::Detail::stringify; + REQUIRE( stringify( EnumClass3::Value1 ) == "Value1" ); + REQUIRE( stringify( EnumClass3::Value2 ) == "Value2" ); + REQUIRE( stringify( EnumClass3::Value3 ) == "Value3" ); + REQUIRE( stringify( EnumClass3::Value4 ) == "{** unexpected enum value **}" ); + + EnumClass3 ec3 = EnumClass3::Value2; + REQUIRE( stringify( ec3 ) == "Value2" ); +} + +namespace Bikeshed { + enum class Colours { Red, Green, Blue }; +} + +// Important!: This macro must appear at top level scope - not inside a namespace +// You can fully qualify the names, or use a using if you prefer +CATCH_REGISTER_ENUM( Bikeshed::Colours, + Bikeshed::Colours::Red, + Bikeshed::Colours::Green, + Bikeshed::Colours::Blue ) + +TEST_CASE( "Enums in namespaces can quickly have stringification enabled using CATCH_REGISTER_ENUM" ) { + using Catch::Detail::stringify; + REQUIRE( stringify( Bikeshed::Colours::Red ) == "Red" ); + REQUIRE( stringify( Bikeshed::Colours::Blue ) == "Blue" ); +} diff --git a/tests/SelfTest/UsageTests/Exception.tests.cpp b/tests/SelfTest/UsageTests/Exception.tests.cpp new file mode 100644 index 0000000..7c6b0c8 --- /dev/null +++ b/tests/SelfTest/UsageTests/Exception.tests.cpp @@ -0,0 +1,204 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 + +#include <catch2/catch_test_macros.hpp> +#include <catch2/catch_translate_exception.hpp> +#include <catch2/matchers/catch_matchers_string.hpp> + +#include <string> +#include <stdexcept> + +#ifdef _MSC_VER +#pragma warning(disable:4702) // Unreachable code -- unconditional throws and so on +#endif +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wweak-vtables" +#pragma clang diagnostic ignored "-Wmissing-noreturn" +#pragma clang diagnostic ignored "-Wunreachable-code-return" +#endif + +namespace { + + int thisThrows() { + throw std::domain_error("expected exception"); + return 1; + } + + int thisDoesntThrow() { + return 0; + } + + class CustomException { + public: + explicit CustomException(const std::string& msg) + : m_msg(msg) {} + + std::string const& getMessage() const { + return m_msg; + } + + private: + std::string m_msg; + }; + + class CustomStdException : public std::exception { + public: + explicit CustomStdException(const std::string& msg) + : m_msg(msg) {} + ~CustomStdException() noexcept override = default; + + CustomStdException( CustomStdException const& ) = default; + CustomStdException& operator=( CustomStdException const& ) = default; + + std::string const& getMessage() const { + return m_msg; + } + + private: + std::string m_msg; + }; + + [[noreturn]] void throwCustom() { + throw CustomException("custom exception - not std"); + } + +} + +TEST_CASE( "When checked exceptions are thrown they can be expected or unexpected", "[!throws]" ) { + REQUIRE_THROWS_AS( thisThrows(), std::domain_error ); + REQUIRE_NOTHROW( thisDoesntThrow() ); + REQUIRE_THROWS( thisThrows() ); +} + +TEST_CASE( "Expected exceptions that don't throw or unexpected exceptions fail the test", "[.][failing][!throws]" ) { + CHECK_THROWS_AS( thisThrows(), std::string ); + CHECK_THROWS_AS( thisDoesntThrow(), std::domain_error ); + CHECK_NOTHROW( thisThrows() ); +} + +TEST_CASE( "When unchecked exceptions are thrown directly they are always failures", "[.][failing][!throws]" ) { + throw std::domain_error( "unexpected exception" ); +} + +TEST_CASE( "An unchecked exception reports the line of the last assertion", "[.][failing][!throws]" ) { + CHECK( 1 == 1 ); + throw std::domain_error( "unexpected exception" ); +} + +TEST_CASE( "When unchecked exceptions are thrown from sections they are always failures", "[.][failing][!throws]" ) { + SECTION( "section name" ) { + throw std::domain_error("unexpected exception"); + } +} + +TEST_CASE( "When unchecked exceptions are thrown from functions they are always failures", "[.][failing][!throws]" ) { + CHECK( thisThrows() == 0 ); +} + +TEST_CASE( "When unchecked exceptions are thrown during a REQUIRE the test should abort fail", "[.][failing][!throws]" ) { + REQUIRE( thisThrows() == 0 ); + FAIL( "This should never happen" ); +} + +TEST_CASE( "When unchecked exceptions are thrown during a CHECK the test should continue", "[.][failing][!throws]" ) { + try { + CHECK(thisThrows() == 0); + } + catch(...) { + FAIL( "This should never happen" ); + } +} + +TEST_CASE( "When unchecked exceptions are thrown, but caught, they do not affect the test", "[!throws]" ) { + try { + throw std::domain_error( "unexpected exception" ); + } + catch(...) {} // NOLINT(bugprone-empty-catch) +} + + +CATCH_TRANSLATE_EXCEPTION( CustomException const& ex ) { + return ex.getMessage(); +} + +CATCH_TRANSLATE_EXCEPTION( CustomStdException const& ex ) { + return ex.getMessage(); +} + +CATCH_TRANSLATE_EXCEPTION( double const& ex ) { + return Catch::Detail::stringify( ex ); +} + +TEST_CASE("Non-std exceptions can be translated", "[.][failing][!throws]" ) { + throw CustomException( "custom exception" ); +} + +TEST_CASE("Custom std-exceptions can be custom translated", "[.][failing][!throws]" ) { + throw CustomStdException( "custom std exception" ); +} + +TEST_CASE( "Custom exceptions can be translated when testing for nothrow", "[.][failing][!throws]" ) { + REQUIRE_NOTHROW( throwCustom() ); +} + +TEST_CASE( "Custom exceptions can be translated when testing for throwing as something else", "[.][failing][!throws]" ) { + REQUIRE_THROWS_AS( throwCustom(), std::exception ); +} + +TEST_CASE( "Unexpected exceptions can be translated", "[.][failing][!throws]" ) { + throw double( 3.14 ); // NOLINT(readability-redundant-casting): the type is important here, so we want to be explicit +} + +TEST_CASE("Thrown string literals are translated", "[.][failing][!throws]") { + throw "For some reason someone is throwing a string literal!"; +} + +TEST_CASE("thrown std::strings are translated", "[.][failing][!throws]") { + throw std::string{ "Why would you throw a std::string?" }; +} + + +TEST_CASE( "Exception messages can be tested for", "[!throws]" ) { + using namespace Catch::Matchers; + SECTION( "exact match" ) + REQUIRE_THROWS_WITH( thisThrows(), "expected exception" ); + SECTION( "different case" ) + REQUIRE_THROWS_WITH( thisThrows(), Equals( "expecteD Exception", Catch::CaseSensitive::No ) ); + SECTION( "wildcarded" ) { + REQUIRE_THROWS_WITH( thisThrows(), StartsWith( "expected" ) ); + REQUIRE_THROWS_WITH( thisThrows(), EndsWith( "exception" ) ); + REQUIRE_THROWS_WITH( thisThrows(), ContainsSubstring( "except" ) ); + REQUIRE_THROWS_WITH( thisThrows(), ContainsSubstring( "exCept", Catch::CaseSensitive::No ) ); + } +} + +TEST_CASE( "Mismatching exception messages failing the test", "[.][failing][!throws]" ) { + REQUIRE_THROWS_WITH( thisThrows(), "expected exception" ); + REQUIRE_THROWS_WITH( thisThrows(), "should fail" ); + REQUIRE_THROWS_WITH( thisThrows(), "expected exception" ); +} + +TEST_CASE( "#748 - captures with unexpected exceptions", "[.][failing][!throws][!shouldfail]" ) { + int answer = 42; + CAPTURE( answer ); + // the message should be printed on the first two sections but not on the third + SECTION( "outside assertions" ) { + thisThrows(); + } + SECTION( "inside REQUIRE_NOTHROW" ) { + REQUIRE_NOTHROW( thisThrows() ); + } + SECTION( "inside REQUIRE_THROWS" ) { + REQUIRE_THROWS( thisThrows() ); + } +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif diff --git a/tests/SelfTest/UsageTests/Generators.tests.cpp b/tests/SelfTest/UsageTests/Generators.tests.cpp new file mode 100644 index 0000000..f04cf4f --- /dev/null +++ b/tests/SelfTest/UsageTests/Generators.tests.cpp @@ -0,0 +1,323 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 + +#include <catch2/catch_test_macros.hpp> +#include <catch2/generators/catch_generator_exception.hpp> +#include <catch2/generators/catch_generators_adapters.hpp> +#include <catch2/generators/catch_generators_random.hpp> +#include <catch2/generators/catch_generators_range.hpp> + +#include <cstring> + + +// Generators and sections can be nested freely +TEST_CASE("Generators -- simple", "[generators]") { + auto i = GENERATE(1, 2, 3); + SECTION("one") { + auto j = GENERATE(values({ -3, -2, -1 })); + REQUIRE(j < i); + } + + SECTION("two") { + // You can also explicitly set type for generators via Catch::Generators::as + auto str = GENERATE(as<std::string>{}, "a", "bb", "ccc"); + REQUIRE(4u * i > str.size()); + } +} + +// You can create a cartesian-product of generators by creating multiple ones +TEST_CASE("3x3x3 ints", "[generators]") { + auto x = GENERATE(1, 2, 3); + auto y = GENERATE(4, 5, 6); + auto z = GENERATE(7, 8, 9); + // These assertions will be run 27 times (3x3x3) + CHECK(x < y); + CHECK(y < z); + REQUIRE(x < z); +} + +// You can also create data tuples +TEST_CASE("tables", "[generators]") { + // Note that this will not compile with libstdc++ older than libstdc++6 + // See https://stackoverflow.com/questions/12436586/tuple-vector-and-initializer-list + // for possible workarounds + // auto data = GENERATE(table<char const*, int>({ + // {"first", 5}, + // {"second", 6}, + // {"third", 5}, + // {"etc...", 6} + // })); + + // Workaround for the libstdc++ bug mentioned above + using tuple_type = std::tuple<char const*, int>; + auto data = GENERATE(table<char const*, int>({ + tuple_type{"first", 5}, + tuple_type{"second", 6}, + tuple_type{"third", 5}, + tuple_type{"etc...", 6} + })); + + REQUIRE(strlen(std::get<0>(data)) == static_cast<size_t>(std::get<1>(data))); +} + + +#ifdef __cpp_structured_bindings + +// Structured bindings make the table utility much nicer to use +TEST_CASE( "strlen2", "[approvals][generators]" ) { + using tuple_type = std::tuple<std::string, int>; // see above workaround + auto [test_input, expected] = + GENERATE( table<std::string, size_t>( { tuple_type{ "one", 3 }, + tuple_type{ "two", 3 }, + tuple_type{ "three", 5 }, + tuple_type{ "four", 4 } } ) ); + + REQUIRE( test_input.size() == expected ); +} +#endif + + +// An alternate way of doing data tables without structured bindings +struct Data { std::string str; size_t len; }; + +TEST_CASE( "strlen3", "[generators]" ) { + auto data = GENERATE( values<Data>({ + {"one", 3}, + {"two", 3}, + {"three", 5}, + {"four", 4} + })); + + REQUIRE( data.str.size() == data.len ); +} + + + +#ifdef __cpp_structured_bindings + +// Based on example from https://docs.cucumber.io/gherkin/reference/#scenario-outline +// (thanks to https://github.com/catchorg/Catch2/issues/850#issuecomment-399504851) + +// Note that GIVEN, WHEN, and THEN now forward onto DYNAMIC_SECTION instead of SECTION. +// DYNAMIC_SECTION takes its name as a stringstream-style expression, so can be formatted using +// variables in scope - such as the generated variables here. This reads quite nicely in the +// test name output (the full scenario description). + +static auto eatCucumbers( int start, int eat ) -> int { return start-eat; } + +SCENARIO("Eating cucumbers", "[generators][approvals]") { + using tuple_type = std::tuple<int, int, int>; + auto [start, eat, left] = GENERATE( table<int, int, int>( + { tuple_type{ 12, 5, 7 }, tuple_type{ 20, 5, 15 } } ) ); + + GIVEN( "there are " << start << " cucumbers" ) + WHEN( "I eat " << eat << " cucumbers" ) + THEN( "I should have " << left << " cucumbers" ) { + REQUIRE( eatCucumbers( start, eat ) == left ); + } +} +#endif + +// There are also some generic generator manipulators +TEST_CASE("Generators -- adapters", "[generators][generic]") { + // TODO: This won't work yet, introduce GENERATE_VAR? + //auto numbers = Catch::Generators::values({ 1, 2, 3, 4, 5, 6 }); + SECTION("Filtering by predicate") { + SECTION("Basic usage") { + // This filters out all odd (false) numbers, giving [2, 4, 6] + auto i = GENERATE(filter([] (int val) { return val % 2 == 0; }, values({ 1, 2, 3, 4, 5, 6 }))); + REQUIRE(i % 2 == 0); + } + SECTION("Throws if there are no matching values") { + using namespace Catch::Generators; + REQUIRE_THROWS_AS(filter([] (int) {return false; }, value(1)), Catch::GeneratorException); + } + } + SECTION("Shortening a range") { + // This takes the first 3 elements from the values, giving back [1, 2, 3] + auto i = GENERATE(take(3, values({ 1, 2, 3, 4, 5, 6 }))); + REQUIRE(i < 4); + } + SECTION("Transforming elements") { + SECTION("Same type") { + // This doubles values [1, 2, 3] into [2, 4, 6] + auto i = GENERATE(map([] (int val) { return val * 2; }, values({ 1, 2, 3 }))); + REQUIRE(i % 2 == 0); + } + SECTION("Different type") { + // This takes a generator that returns ints and maps them into strings + auto i = GENERATE(map<std::string>([] (int val) { return std::to_string(val); }, values({ 1, 2, 3 }))); + REQUIRE(i.size() == 1); + } + SECTION("Different deduced type") { + // This takes a generator that returns ints and maps them into strings + auto i = GENERATE(map([] (int val) { return std::to_string(val); }, values({ 1, 2, 3 }))); + REQUIRE(i.size() == 1); + } + } + SECTION("Repeating a generator") { + // This will return values [1, 2, 3, 1, 2, 3] + auto j = GENERATE(repeat(2, values({ 1, 2, 3 }))); + REQUIRE(j > 0); + } + SECTION("Chunking a generator into sized pieces") { + SECTION("Number of elements in source is divisible by chunk size") { + auto chunk2 = GENERATE(chunk(2, values({ 1, 1, 2, 2, 3, 3 }))); + REQUIRE(chunk2.size() == 2); + REQUIRE(chunk2.front() == chunk2.back()); + } + SECTION("Number of elements in source is not divisible by chunk size") { + auto chunk2 = GENERATE(chunk(2, values({ 1, 1, 2, 2, 3 }))); + REQUIRE(chunk2.size() == 2); + REQUIRE(chunk2.front() == chunk2.back()); + REQUIRE(chunk2.front() < 3); + } + SECTION("Chunk size of zero") { + auto chunk2 = GENERATE(take(3, chunk(0, value(1)))); + REQUIRE(chunk2.size() == 0); + } + SECTION("Throws on too small generators") { + using namespace Catch::Generators; + REQUIRE_THROWS_AS(chunk(2, value(1)), Catch::GeneratorException); + } + } +} + +// Note that because of the non-reproducibility of distributions, +// anything involving the random generators cannot be part of approvals +TEST_CASE("Random generator", "[generators][approvals]") { + SECTION("Infer int from integral arguments") { + auto val = GENERATE(take(4, random(0, 1))); + STATIC_REQUIRE(std::is_same<decltype(val), int>::value); + REQUIRE(0 <= val); + REQUIRE(val <= 1); + } + SECTION("Infer double from double arguments") { + auto val = GENERATE(take(4, random(0., 1.))); + STATIC_REQUIRE(std::is_same<decltype(val), double>::value); + REQUIRE(0. <= val); + REQUIRE(val < 1); + } +} + + +TEST_CASE("Nested generators and captured variables", "[generators]") { + // Workaround for old libstdc++ + using record = std::tuple<int, int>; + // Set up 3 ranges to generate numbers from + auto extent = GENERATE(table<int, int>({ + record{3, 7}, + record{-5, -3}, + record{90, 100} + })); + + auto from = std::get<0>(extent); + auto to = std::get<1>(extent); + + auto values = GENERATE_COPY(range(from, to)); + REQUIRE(values > -6); +} + +namespace { + size_t call_count = 0; + size_t test_count = 0; + std::vector<int> make_data() { + return { 1, 3, 5, 7, 9, 11 }; + } + std::vector<int> make_data_counted() { + ++call_count; + return make_data(); + } +} + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + +TEST_CASE("Copy and then generate a range", "[generators]") { + SECTION("from var and iterators") { + static auto data = make_data(); + + // It is important to notice that a generator is only initialized + // **once** per run. What this means is that modifying data will not + // modify the underlying generator. + auto elem = GENERATE_REF(from_range(data.begin(), data.end())); + REQUIRE(elem % 2 == 1); + } + SECTION("From a temporary container") { + auto elem = GENERATE(from_range(make_data_counted())); + ++test_count; + REQUIRE(elem % 2 == 1); + } + SECTION("Final validation") { + REQUIRE(call_count == 1); + REQUIRE(make_data().size() == test_count); + } +} + +#if defined( __clang__ ) +# pragma clang diagnostic pop +#endif + +TEST_CASE("#1913 - GENERATE inside a for loop should not keep recreating the generator", "[regression][generators]") { + static int counter = 0; + for (int i = 0; i < 3; ++i) { + int _ = GENERATE(1, 2); + (void)_; + ++counter; + } + // There should be at most 6 (3 * 2) counter increments + REQUIRE(counter < 7); +} + +TEST_CASE("#1913 - GENERATEs can share a line", "[regression][generators]") { + int i = GENERATE(1, 2); int j = GENERATE(3, 4); + REQUIRE(i != j); +} + +namespace { + class test_generator : public Catch::Generators::IGenerator<int> { + public: + [[noreturn]] explicit test_generator() { + // removing the following line will cause the program to terminate + // gracefully. + throw Catch::GeneratorException( "failure to init" ); + } + + auto get() const -> int const& override { + static constexpr int value = 1; + return value; + } + + auto next() -> bool override { return false; } + }; + + static auto make_test_generator() + -> Catch::Generators::GeneratorWrapper<int> { + return { new test_generator() }; + } + +} // namespace + +TEST_CASE( "#2615 - Throwing in constructor generator fails test case but does not abort", + "[!shouldfail][regression][generators]" ) { + // this should fail the test case, but not abort the application + auto sample = GENERATE( make_test_generator() ); + // this assertion shouldn't trigger + REQUIRE( sample == 0 ); +} + +TEST_CASE( "GENERATE can combine literals and generators", "[generators]" ) { + auto i = GENERATE( 2, + 4, + take( 2, + filter( []( int val ) { return val % 2 == 0; }, + random( -100, 100 ) ) ) ); + REQUIRE( i % 2 == 0 ); +} diff --git a/tests/SelfTest/UsageTests/Matchers.tests.cpp b/tests/SelfTest/UsageTests/Matchers.tests.cpp new file mode 100644 index 0000000..7c4501c --- /dev/null +++ b/tests/SelfTest/UsageTests/Matchers.tests.cpp @@ -0,0 +1,1144 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 + +#include <catch2/catch_test_macros.hpp> +#include <catch2/catch_template_test_macros.hpp> +#include <catch2/matchers/catch_matchers_exception.hpp> +#include <catch2/matchers/catch_matchers_floating_point.hpp> +#include <catch2/matchers/catch_matchers_predicate.hpp> +#include <catch2/matchers/catch_matchers_string.hpp> +#include <catch2/matchers/catch_matchers_vector.hpp> +#include <catch2/matchers/catch_matchers_templated.hpp> + +#include <algorithm> +#include <exception> +#include <cmath> +#include <list> +#include <sstream> + +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wweak-vtables" +# pragma clang diagnostic ignored "-Wpadded" +#endif + +namespace { + + static const char* testStringForMatching() { + return "this string contains 'abc' as a substring"; + } + + static const char* testStringForMatching2() { + return "some completely different text that contains one common word"; + } + + static bool alwaysTrue( int ) { return true; } + static bool alwaysFalse( int ) { return false; } + +#ifdef _MSC_VER +# pragma warning( disable : 4702 ) // Unreachable code -- MSVC 19 (VS 2015) + // sees right through the indirection +#endif + + struct SpecialException : std::exception { + SpecialException( int i_ ): i( i_ ) {} + + char const* what() const noexcept override { + return "SpecialException::what"; + } + + int i; + }; + + struct DerivedException : std::exception { + char const* what() const noexcept override { + return "DerivedException::what"; + } + }; + + static void doesNotThrow() {} + + [[noreturn]] static void throwsSpecialException( int i ) { + throw SpecialException{ i }; + } + + [[noreturn]] static void throwsAsInt( int i ) { throw i; } + + [[noreturn]] static void throwsDerivedException() { + throw DerivedException{}; + } + + class ExceptionMatcher + : public Catch::Matchers::MatcherBase<SpecialException> { + int m_expected; + + public: + ExceptionMatcher( int i ): m_expected( i ) {} + + bool match( SpecialException const& se ) const override { + return se.i == m_expected; + } + + std::string describe() const override { + std::ostringstream ss; + ss << "special exception has value of " << m_expected; + return ss.str(); + } + }; + + using namespace Catch::Matchers; + +#ifdef __DJGPP__ + static float nextafter( float from, float to ) { + return ::nextafterf( from, to ); + } + + static double nextafter( double from, double to ) { + return ::nextafter( from, to ); + } +#else + using std::nextafter; +#endif + +} // end unnamed namespace + +TEST_CASE( "String matchers", "[matchers]" ) { + REQUIRE_THAT( testStringForMatching(), ContainsSubstring( "string" ) ); + REQUIRE_THAT( testStringForMatching(), + ContainsSubstring( "string", Catch::CaseSensitive::No ) ); + CHECK_THAT( testStringForMatching(), ContainsSubstring( "abc" ) ); + CHECK_THAT( testStringForMatching(), + ContainsSubstring( "aBC", Catch::CaseSensitive::No ) ); + + CHECK_THAT( testStringForMatching(), StartsWith( "this" ) ); + CHECK_THAT( testStringForMatching(), + StartsWith( "THIS", Catch::CaseSensitive::No ) ); + CHECK_THAT( testStringForMatching(), EndsWith( "substring" ) ); + CHECK_THAT( testStringForMatching(), + EndsWith( " SuBsTrInG", Catch::CaseSensitive::No ) ); +} + +TEST_CASE( "Contains string matcher", "[.][failing][matchers]" ) { + CHECK_THAT( testStringForMatching(), + ContainsSubstring( "not there", Catch::CaseSensitive::No ) ); + CHECK_THAT( testStringForMatching(), ContainsSubstring( "STRING" ) ); +} + +TEST_CASE( "StartsWith string matcher", "[.][failing][matchers]" ) { + CHECK_THAT( testStringForMatching(), StartsWith( "This String" ) ); + CHECK_THAT( testStringForMatching(), + StartsWith( "string", Catch::CaseSensitive::No ) ); +} + +TEST_CASE( "EndsWith string matcher", "[.][failing][matchers]" ) { + CHECK_THAT( testStringForMatching(), EndsWith( "Substring" ) ); + CHECK_THAT( testStringForMatching(), + EndsWith( "this", Catch::CaseSensitive::No ) ); +} + +TEST_CASE( "Equals string matcher", "[.][failing][matchers]" ) { + CHECK_THAT( testStringForMatching(), + Equals( "this string contains 'ABC' as a substring" ) ); + CHECK_THAT( testStringForMatching(), + Equals( "something else", Catch::CaseSensitive::No ) ); +} + +TEST_CASE( "Equals", "[matchers]" ) { + CHECK_THAT( testStringForMatching(), + Equals( "this string contains 'abc' as a substring" ) ); + CHECK_THAT( testStringForMatching(), + Equals( "this string contains 'ABC' as a substring", + Catch::CaseSensitive::No ) ); +} + +TEST_CASE( "Regex string matcher -- libstdc++-4.8 workaround", + "[matchers][approvals]" ) { +// DJGPP has similar problem with its regex support as libstdc++ 4.8 +#ifndef __DJGPP__ + REQUIRE_THAT( testStringForMatching(), + Matches( "this string contains 'abc' as a substring" ) ); + REQUIRE_THAT( testStringForMatching(), + Matches( "this string CONTAINS 'abc' as a substring", + Catch::CaseSensitive::No ) ); + REQUIRE_THAT( testStringForMatching(), + Matches( "^this string contains 'abc' as a substring$" ) ); + REQUIRE_THAT( testStringForMatching(), Matches( "^.* 'abc' .*$" ) ); + REQUIRE_THAT( testStringForMatching(), + Matches( "^.* 'ABC' .*$", Catch::CaseSensitive::No ) ); +#endif + + REQUIRE_THAT( testStringForMatching2(), + !Matches( "this string contains 'abc' as a substring" ) ); +} + +TEST_CASE( "Regex string matcher", "[matchers][.failing]" ) { + CHECK_THAT( testStringForMatching(), + Matches( "this STRING contains 'abc' as a substring" ) ); + CHECK_THAT( testStringForMatching(), + Matches( "contains 'abc' as a substring" ) ); + CHECK_THAT( testStringForMatching(), + Matches( "this string contains 'abc' as a" ) ); +} + +TEST_CASE( "Matchers can be (AllOf) composed with the && operator", + "[matchers][operators][operator&&]" ) { + CHECK_THAT( testStringForMatching(), + ContainsSubstring( "string" ) && ContainsSubstring( "abc" ) && + ContainsSubstring( "substring" ) && ContainsSubstring( "contains" ) ); +} + +TEST_CASE( "Matchers can be (AnyOf) composed with the || operator", + "[matchers][operators][operator||]" ) { + CHECK_THAT( testStringForMatching(), + ContainsSubstring( "string" ) || ContainsSubstring( "different" ) || + ContainsSubstring( "random" ) ); + CHECK_THAT( testStringForMatching2(), + ContainsSubstring( "string" ) || ContainsSubstring( "different" ) || + ContainsSubstring( "random" ) ); +} + +TEST_CASE( "Matchers can be composed with both && and ||", + "[matchers][operators][operator||][operator&&]" ) { + CHECK_THAT( testStringForMatching(), + ( ContainsSubstring( "string" ) || ContainsSubstring( "different" ) ) && + ContainsSubstring( "substring" ) ); +} + +TEST_CASE( "Matchers can be composed with both && and || - failing", + "[matchers][operators][operator||][operator&&][.failing]" ) { + CHECK_THAT( testStringForMatching(), + ( ContainsSubstring( "string" ) || ContainsSubstring( "different" ) ) && + ContainsSubstring( "random" ) ); +} + +TEST_CASE( "Matchers can be negated (Not) with the ! operator", + "[matchers][operators][not]" ) { + CHECK_THAT( testStringForMatching(), !ContainsSubstring( "different" ) ); +} + +TEST_CASE( "Matchers can be negated (Not) with the ! operator - failing", + "[matchers][operators][not][.failing]" ) { + CHECK_THAT( testStringForMatching(), !ContainsSubstring( "substring" ) ); +} + +template <typename T> struct CustomAllocator : private std::allocator<T> { + using size_type = size_t; + using difference_type = ptrdiff_t; + using pointer = T*; + using const_pointer = const T*; + using reference = T&; + using const_reference = const T&; + using value_type = T; + + template <typename U> struct rebind { using other = CustomAllocator<U>; }; + + using propagate_on_container_move_assignment = std::true_type; + using is_always_equal = std::true_type; + + CustomAllocator() = default; + + CustomAllocator( const CustomAllocator& other ): + std::allocator<T>( other ) {} + + template <typename U> CustomAllocator( const CustomAllocator<U>& ) {} + + ~CustomAllocator() = default; + + using std::allocator<T>::allocate; + using std::allocator<T>::deallocate; +}; + +TEST_CASE( "Vector matchers", "[matchers][vector]" ) { + std::vector<int> v; + v.push_back( 1 ); + v.push_back( 2 ); + v.push_back( 3 ); + + std::vector<int> v2; + v2.push_back( 1 ); + v2.push_back( 2 ); + + std::vector<double> v3; + v3.push_back( 1 ); + v3.push_back( 2 ); + v3.push_back( 3 ); + + std::vector<double> v4; + v4.push_back( 1 + 1e-8 ); + v4.push_back( 2 + 1e-8 ); + v4.push_back( 3 + 1e-8 ); + + std::vector<int, CustomAllocator<int>> v5; + v5.push_back( 1 ); + v5.push_back( 2 ); + v5.push_back( 3 ); + + std::vector<int, CustomAllocator<int>> v6; + v6.push_back( 1 ); + v6.push_back( 2 ); + + std::vector<int> empty; + + SECTION( "Contains (element)" ) { + CHECK_THAT( v, VectorContains( 1 ) ); + CHECK_THAT( v, VectorContains( 2 ) ); + CHECK_THAT( v5, ( VectorContains<int, CustomAllocator<int>>( 2 ) ) ); + } + SECTION( "Contains (vector)" ) { + CHECK_THAT( v, Contains( v2 ) ); + CHECK_THAT( v, Contains<int>( { 1, 2 } ) ); + CHECK_THAT( v5, + ( Contains<int, std::allocator<int>, CustomAllocator<int>>( + v2 ) ) ); + + v2.push_back( 3 ); // now exactly matches + CHECK_THAT( v, Contains( v2 ) ); + + CHECK_THAT( v, Contains( empty ) ); + CHECK_THAT( empty, Contains( empty ) ); + + CHECK_THAT( v5, + ( Contains<int, std::allocator<int>, CustomAllocator<int>>( + v2 ) ) ); + CHECK_THAT( v5, Contains( v6 ) ); + } + SECTION( "Contains (element), composed" ) { + CHECK_THAT( v, VectorContains( 1 ) && VectorContains( 2 ) ); + } + + SECTION( "Equals" ) { + + // Same vector + CHECK_THAT( v, Equals( v ) ); + + CHECK_THAT( empty, Equals( empty ) ); + + // Different vector with same elements + CHECK_THAT( v, Equals<int>( { 1, 2, 3 } ) ); + v2.push_back( 3 ); + CHECK_THAT( v, Equals( v2 ) ); + + CHECK_THAT( + v5, + ( Equals<int, std::allocator<int>, CustomAllocator<int>>( v2 ) ) ); + + v6.push_back( 3 ); + CHECK_THAT( v5, Equals( v6 ) ); + } + SECTION( "UnorderedEquals" ) { + CHECK_THAT( v, UnorderedEquals( v ) ); + CHECK_THAT( v, UnorderedEquals<int>( { 3, 2, 1 } ) ); + CHECK_THAT( empty, UnorderedEquals( empty ) ); + + auto permuted = v; + std::next_permutation( begin( permuted ), end( permuted ) ); + REQUIRE_THAT( permuted, UnorderedEquals( v ) ); + + std::reverse( begin( permuted ), end( permuted ) ); + REQUIRE_THAT( permuted, UnorderedEquals( v ) ); + + CHECK_THAT( + v5, + ( UnorderedEquals<int, std::allocator<int>, CustomAllocator<int>>( + permuted ) ) ); + + auto v5_permuted = v5; + std::next_permutation( begin( v5_permuted ), end( v5_permuted ) ); + CHECK_THAT( v5_permuted, UnorderedEquals( v5 ) ); + } +} + +TEST_CASE( "Vector matchers that fail", "[matchers][vector][.][failing]" ) { + std::vector<int> v; + v.push_back( 1 ); + v.push_back( 2 ); + v.push_back( 3 ); + + std::vector<int> v2; + v2.push_back( 1 ); + v2.push_back( 2 ); + + std::vector<double> v3; + v3.push_back( 1 ); + v3.push_back( 2 ); + v3.push_back( 3 ); + + std::vector<double> v4; + v4.push_back( 1.1 ); + v4.push_back( 2.1 ); + v4.push_back( 3.1 ); + + std::vector<int> empty; + + SECTION( "Contains (element)" ) { + CHECK_THAT( v, VectorContains( -1 ) ); + CHECK_THAT( empty, VectorContains( 1 ) ); + } + SECTION( "Contains (vector)" ) { + CHECK_THAT( empty, Contains( v ) ); + v2.push_back( 4 ); + CHECK_THAT( v, Contains( v2 ) ); + } + + SECTION( "Equals" ) { + + CHECK_THAT( v, Equals( v2 ) ); + CHECK_THAT( v2, Equals( v ) ); + CHECK_THAT( empty, Equals( v ) ); + CHECK_THAT( v, Equals( empty ) ); + } + SECTION( "UnorderedEquals" ) { + CHECK_THAT( v, UnorderedEquals( empty ) ); + CHECK_THAT( empty, UnorderedEquals( v ) ); + + auto permuted = v; + std::next_permutation( begin( permuted ), end( permuted ) ); + permuted.pop_back(); + CHECK_THAT( permuted, UnorderedEquals( v ) ); + + std::reverse( begin( permuted ), end( permuted ) ); + CHECK_THAT( permuted, UnorderedEquals( v ) ); + } +} + +namespace { + struct SomeType { + int i; + friend bool operator==( SomeType lhs, SomeType rhs ) { + return lhs.i == rhs.i; + } + }; +} // end anonymous namespace + +TEST_CASE( "Vector matcher with elements without !=", "[matchers][vector][approvals]" ) { + std::vector<SomeType> lhs, rhs; + lhs.push_back( { 1 } ); + lhs.push_back( { 2 } ); + rhs.push_back( { 1 } ); + rhs.push_back( { 1 } ); + + REQUIRE_THAT( lhs, !Equals(rhs) ); +} + +TEST_CASE( "Exception matchers that succeed", + "[matchers][exceptions][!throws]" ) { + CHECK_THROWS_MATCHES( + throwsSpecialException( 1 ), SpecialException, ExceptionMatcher{ 1 } ); + REQUIRE_THROWS_MATCHES( + throwsSpecialException( 2 ), SpecialException, ExceptionMatcher{ 2 } ); +} + +TEST_CASE( "Exception matchers that fail", + "[matchers][exceptions][!throws][.failing]" ) { + SECTION( "No exception" ) { + CHECK_THROWS_MATCHES( + doesNotThrow(), SpecialException, ExceptionMatcher{ 1 } ); + REQUIRE_THROWS_MATCHES( + doesNotThrow(), SpecialException, ExceptionMatcher{ 1 } ); + } + SECTION( "Type mismatch" ) { + CHECK_THROWS_MATCHES( + throwsAsInt( 1 ), SpecialException, ExceptionMatcher{ 1 } ); + REQUIRE_THROWS_MATCHES( + throwsAsInt( 1 ), SpecialException, ExceptionMatcher{ 1 } ); + } + SECTION( "Contents are wrong" ) { + CHECK_THROWS_MATCHES( throwsSpecialException( 3 ), + SpecialException, + ExceptionMatcher{ 1 } ); + REQUIRE_THROWS_MATCHES( throwsSpecialException( 4 ), + SpecialException, + ExceptionMatcher{ 1 } ); + } +} + +TEST_CASE( "Floating point matchers: float", "[matchers][floating-point]" ) { + SECTION( "Relative" ) { + REQUIRE_THAT( 10.f, WithinRel( 11.1f, 0.1f ) ); + REQUIRE_THAT( 10.f, !WithinRel( 11.2f, 0.1f ) ); + REQUIRE_THAT( 1.f, !WithinRel( 0.f, 0.99f ) ); + REQUIRE_THAT( -0.f, WithinRel( 0.f ) ); + SECTION( "Some subnormal values" ) { + auto v1 = std::numeric_limits<float>::min(); + auto v2 = v1; + for ( int i = 0; i < 5; ++i ) { + v2 = std::nextafter( v1, 0.f ); + } + REQUIRE_THAT( v1, WithinRel( v2 ) ); + } + } + SECTION( "Margin" ) { + REQUIRE_THAT( 1.f, WithinAbs( 1.f, 0 ) ); + REQUIRE_THAT( 0.f, WithinAbs( 1.f, 1 ) ); + + REQUIRE_THAT( 0.f, !WithinAbs( 1.f, 0.99f ) ); + REQUIRE_THAT( 0.f, !WithinAbs( 1.f, 0.99f ) ); + + REQUIRE_THAT( 0.f, WithinAbs( -0.f, 0 ) ); + + REQUIRE_THAT( 11.f, !WithinAbs( 10.f, 0.5f ) ); + REQUIRE_THAT( 10.f, !WithinAbs( 11.f, 0.5f ) ); + REQUIRE_THAT( -10.f, WithinAbs( -10.f, 0.5f ) ); + REQUIRE_THAT( -10.f, WithinAbs( -9.6f, 0.5f ) ); + } + SECTION( "ULPs" ) { + REQUIRE_THAT( 1.f, WithinULP( 1.f, 0 ) ); + REQUIRE_THAT(-1.f, WithinULP( -1.f, 0 ) ); + + REQUIRE_THAT( nextafter( 1.f, 2.f ), WithinULP( 1.f, 1 ) ); + REQUIRE_THAT( 0.f, WithinULP( nextafter( 0.f, 1.f ), 1 ) ); + REQUIRE_THAT( 1.f, WithinULP( nextafter( 1.f, 0.f ), 1 ) ); + REQUIRE_THAT( 1.f, !WithinULP( nextafter( 1.f, 2.f ), 0 ) ); + + REQUIRE_THAT( 1.f, WithinULP( 1.f, 0 ) ); + REQUIRE_THAT( -0.f, WithinULP( 0.f, 0 ) ); + } + SECTION( "Composed" ) { + REQUIRE_THAT( 1.f, WithinAbs( 1.f, 0.5 ) || WithinULP( 1.f, 1 ) ); + REQUIRE_THAT( 1.f, WithinAbs( 2.f, 0.5 ) || WithinULP( 1.f, 0 ) ); + REQUIRE_THAT( 0.0001f, + WithinAbs( 0.f, 0.001f ) || WithinRel( 0.f, 0.1f ) ); + } + SECTION( "Constructor validation" ) { + REQUIRE_NOTHROW( WithinAbs( 1.f, 0.f ) ); + REQUIRE_THROWS_AS( WithinAbs( 1.f, -1.f ), std::domain_error ); + + REQUIRE_NOTHROW( WithinULP( 1.f, 0 ) ); + REQUIRE_THROWS_AS( WithinULP( 1.f, static_cast<uint64_t>( -1 ) ), + std::domain_error ); + + REQUIRE_NOTHROW( WithinRel( 1.f, 0.f ) ); + REQUIRE_THROWS_AS( WithinRel( 1.f, -0.2f ), std::domain_error ); + REQUIRE_THROWS_AS( WithinRel( 1.f, 1.f ), std::domain_error ); + } + SECTION( "IsNaN" ) { + REQUIRE_THAT( 1., !IsNaN() ); + } +} + +TEST_CASE( "Floating point matchers: double", "[matchers][floating-point]" ) { + SECTION( "Relative" ) { + REQUIRE_THAT( 10., WithinRel( 11.1, 0.1 ) ); + REQUIRE_THAT( 10., !WithinRel( 11.2, 0.1 ) ); + REQUIRE_THAT( 1., !WithinRel( 0., 0.99 ) ); + REQUIRE_THAT( -0., WithinRel( 0. ) ); + SECTION( "Some subnormal values" ) { + auto v1 = std::numeric_limits<double>::min(); + auto v2 = v1; + for ( int i = 0; i < 5; ++i ) { + v2 = std::nextafter( v1, 0 ); + } + REQUIRE_THAT( v1, WithinRel( v2 ) ); + } + } + SECTION( "Margin" ) { + REQUIRE_THAT( 1., WithinAbs( 1., 0 ) ); + REQUIRE_THAT( 0., WithinAbs( 1., 1 ) ); + + REQUIRE_THAT( 0., !WithinAbs( 1., 0.99 ) ); + REQUIRE_THAT( 0., !WithinAbs( 1., 0.99 ) ); + + REQUIRE_THAT( 11., !WithinAbs( 10., 0.5 ) ); + REQUIRE_THAT( 10., !WithinAbs( 11., 0.5 ) ); + REQUIRE_THAT( -10., WithinAbs( -10., 0.5 ) ); + REQUIRE_THAT( -10., WithinAbs( -9.6, 0.5 ) ); + } + SECTION( "ULPs" ) { + REQUIRE_THAT( 1., WithinULP( 1., 0 ) ); + + REQUIRE_THAT( nextafter( 1., 2. ), WithinULP( 1., 1 ) ); + REQUIRE_THAT( 0., WithinULP( nextafter( 0., 1. ), 1 ) ); + REQUIRE_THAT( 1., WithinULP( nextafter( 1., 0. ), 1 ) ); + REQUIRE_THAT( 1., !WithinULP( nextafter( 1., 2. ), 0 ) ); + + REQUIRE_THAT( 1., WithinULP( 1., 0 ) ); + REQUIRE_THAT( -0., WithinULP( 0., 0 ) ); + } + SECTION( "Composed" ) { + REQUIRE_THAT( 1., WithinAbs( 1., 0.5 ) || WithinULP( 2., 1 ) ); + REQUIRE_THAT( 1., WithinAbs( 2., 0.5 ) || WithinULP( 1., 0 ) ); + REQUIRE_THAT( 0.0001, WithinAbs( 0., 0.001 ) || WithinRel( 0., 0.1 ) ); + } + SECTION( "Constructor validation" ) { + REQUIRE_NOTHROW( WithinAbs( 1., 0. ) ); + REQUIRE_THROWS_AS( WithinAbs( 1., -1. ), std::domain_error ); + + REQUIRE_NOTHROW( WithinULP( 1., 0 ) ); + + REQUIRE_NOTHROW( WithinRel( 1., 0. ) ); + REQUIRE_THROWS_AS( WithinRel( 1., -0.2 ), std::domain_error ); + REQUIRE_THROWS_AS( WithinRel( 1., 1. ), std::domain_error ); + } + SECTION("IsNaN") { + REQUIRE_THAT( 1., !IsNaN() ); + } +} + +TEST_CASE( "Floating point matchers that are problematic in approvals", + "[approvals][matchers][floating-point]" ) { + REQUIRE_THAT( NAN, !WithinAbs( NAN, 0 ) ); + REQUIRE_THAT( NAN, !( WithinAbs( NAN, 100 ) || WithinULP( NAN, 123 ) ) ); + REQUIRE_THAT( NAN, !WithinULP( NAN, 123 ) ); + REQUIRE_THAT( INFINITY, WithinRel( INFINITY ) ); + REQUIRE_THAT( -INFINITY, !WithinRel( INFINITY ) ); + REQUIRE_THAT( 1., !WithinRel( INFINITY ) ); + REQUIRE_THAT( INFINITY, !WithinRel( 1. ) ); + REQUIRE_THAT( NAN, !WithinRel( NAN ) ); + REQUIRE_THAT( 1., !WithinRel( NAN ) ); + REQUIRE_THAT( NAN, !WithinRel( 1. ) ); + REQUIRE_THAT( NAN, IsNaN() ); + REQUIRE_THAT( static_cast<double>(NAN), IsNaN() ); +} + +TEST_CASE( "Arbitrary predicate matcher", "[matchers][generic]" ) { + SECTION( "Function pointer" ) { + REQUIRE_THAT( 1, Predicate<int>( alwaysTrue, "always true" ) ); + REQUIRE_THAT( 1, !Predicate<int>( alwaysFalse, "always false" ) ); + } + SECTION( "Lambdas + different type" ) { + REQUIRE_THAT( "Hello olleH", + Predicate<std::string>( + []( std::string const& str ) -> bool { + return str.front() == str.back(); + }, + "First and last character should be equal" ) ); + + REQUIRE_THAT( + "This wouldn't pass", + !Predicate<std::string>( []( std::string const& str ) -> bool { + return str.front() == str.back(); + } ) ); + } +} + +TEST_CASE( "Regression test #1", "[matchers][vector]" ) { + // At some point, UnorderedEqualsMatcher skipped + // mismatched prefixed before doing the comparison itself + std::vector<char> actual = { 'a', 'b' }; + std::vector<char> expected = { 'c', 'b' }; + + CHECK_THAT( actual, !UnorderedEquals( expected ) ); +} + +TEST_CASE( "Predicate matcher can accept const char*", + "[matchers][compilation]" ) { + REQUIRE_THAT( "foo", Predicate<const char*>( []( const char* const& ) { + return true; + } ) ); +} + +TEST_CASE( "Vector Approx matcher", "[matchers][approx][vector]" ) { + using Catch::Matchers::Approx; + SECTION( "Empty vector is roughly equal to an empty vector" ) { + std::vector<double> empty; + REQUIRE_THAT( empty, Approx( empty ) ); + } + SECTION( "Vectors with elements" ) { + std::vector<double> v1( { 1., 2., 3. } ); + SECTION( "A vector is approx equal to itself" ) { + REQUIRE_THAT( v1, Approx( v1 ) ); + REQUIRE_THAT( v1, Approx<double>( { 1., 2., 3. } ) ); + } + std::vector<double> v2( { 1.5, 2.5, 3.5 } ); + SECTION( "Different length" ) { + auto temp( v1 ); + temp.push_back( 4 ); + REQUIRE_THAT( v1, !Approx( temp ) ); + } + SECTION( "Same length, different elements" ) { + REQUIRE_THAT( v1, !Approx( v2 ) ); + REQUIRE_THAT( v1, Approx( v2 ).margin( 0.5 ) ); + REQUIRE_THAT( v1, Approx( v2 ).epsilon( 0.5 ) ); + REQUIRE_THAT( v1, Approx( v2 ).epsilon( 0.1 ).scale( 500 ) ); + } + } +} + +TEST_CASE( "Vector Approx matcher -- failing", + "[matchers][approx][vector][.failing]" ) { + using Catch::Matchers::Approx; + SECTION( "Empty and non empty vectors are not approx equal" ) { + std::vector<double> empty, t1( { 1, 2 } ); + CHECK_THAT( empty, Approx( t1 ) ); + } + SECTION( "Just different vectors" ) { + std::vector<double> v1( { 2., 4., 6. } ), v2( { 1., 3., 5. } ); + CHECK_THAT( v1, Approx( v2 ) ); + } +} + +TEST_CASE( "Exceptions matchers", "[matchers][exceptions][!throws]" ) { + REQUIRE_THROWS_MATCHES( throwsDerivedException(), + DerivedException, + Message( "DerivedException::what" ) ); + REQUIRE_THROWS_MATCHES( throwsDerivedException(), + DerivedException, + !Message( "derivedexception::what" ) ); + REQUIRE_THROWS_MATCHES( throwsSpecialException( 2 ), + SpecialException, + !Message( "DerivedException::what" ) ); + REQUIRE_THROWS_MATCHES( throwsSpecialException( 2 ), + SpecialException, + Message( "SpecialException::what" ) ); +} + +TEST_CASE( "Exception message can be matched", "[matchers][exceptions][!throws]" ) { + REQUIRE_THROWS_MATCHES( throwsDerivedException(), + DerivedException, + MessageMatches( StartsWith( "Derived" ) ) ); + REQUIRE_THROWS_MATCHES( throwsDerivedException(), + DerivedException, + MessageMatches( EndsWith( "::what" ) ) ); + REQUIRE_THROWS_MATCHES( throwsDerivedException(), + DerivedException, + MessageMatches( !StartsWith( "::what" ) ) ); + REQUIRE_THROWS_MATCHES( throwsSpecialException( 2 ), + SpecialException, + MessageMatches( StartsWith( "Special" ) ) ); +} + +struct CheckedTestingMatcher : Catch::Matchers::MatcherBase<int> { + mutable bool matchCalled = false; + bool matchSucceeds = false; + + bool match( int const& ) const override { + matchCalled = true; + return matchSucceeds; + } + std::string describe() const override { + return "CheckedTestingMatcher set to " + + ( matchSucceeds ? std::string( "succeed" ) + : std::string( "fail" ) ); + } +}; + +TEST_CASE( "Composed matchers shortcircuit", "[matchers][composed]" ) { + // Check that if first returns false, second is not touched + CheckedTestingMatcher first, second; + SECTION( "MatchAllOf" ) { + first.matchSucceeds = false; + + Detail::MatchAllOf<int> matcher = + Detail::MatchAllOf<int>{} && first && second; + CHECK_FALSE( matcher.match( 1 ) ); + + // These two assertions are the important ones + REQUIRE( first.matchCalled ); + REQUIRE( !second.matchCalled ); + } + // Check that if first returns true, second is not touched + SECTION( "MatchAnyOf" ) { + first.matchSucceeds = true; + + Detail::MatchAnyOf<int> matcher = + Detail::MatchAnyOf<int>{} || first || second; + CHECK( matcher.match( 1 ) ); + + // These two assertions are the important ones + REQUIRE( first.matchCalled ); + REQUIRE( !second.matchCalled ); + } +} + +struct CheckedTestingGenericMatcher : Catch::Matchers::MatcherGenericBase { + mutable bool matchCalled = false; + bool matchSucceeds = false; + + bool match( int const& ) const { + matchCalled = true; + return matchSucceeds; + } + std::string describe() const override { + return "CheckedTestingGenericMatcher set to " + + ( matchSucceeds ? std::string( "succeed" ) + : std::string( "fail" ) ); + } +}; + +TEST_CASE( "Composed generic matchers shortcircuit", + "[matchers][composed][generic]" ) { + // Check that if first returns false, second is not touched + CheckedTestingGenericMatcher first, second; + SECTION( "MatchAllOf" ) { + first.matchSucceeds = false; + + Detail::MatchAllOfGeneric<CheckedTestingGenericMatcher, + CheckedTestingGenericMatcher> + matcher{ first, second }; + + CHECK_FALSE( matcher.match( 1 ) ); + + // These two assertions are the important ones + REQUIRE( first.matchCalled ); + REQUIRE( !second.matchCalled ); + } + // Check that if first returns true, second is not touched + SECTION( "MatchAnyOf" ) { + first.matchSucceeds = true; + + Detail::MatchAnyOfGeneric<CheckedTestingGenericMatcher, + CheckedTestingGenericMatcher> + matcher{ first, second }; + CHECK( matcher.match( 1 ) ); + + // These two assertions are the important ones + REQUIRE( first.matchCalled ); + REQUIRE( !second.matchCalled ); + } +} + +template <typename Range> +struct EqualsRangeMatcher : Catch::Matchers::MatcherGenericBase { + + EqualsRangeMatcher( Range const& range ): m_range{ range } {} + + template <typename OtherRange> bool match( OtherRange const& other ) const { + using std::begin; + using std::end; + + return std::equal( + begin( m_range ), end( m_range ), begin( other ), end( other ) ); + } + + std::string describe() const override { + return "Equals: " + Catch::rangeToString( m_range ); + } + +private: + Range const& m_range; +}; + +template <typename Range> +auto EqualsRange( const Range& range ) -> EqualsRangeMatcher<Range> { + return EqualsRangeMatcher<Range>{ range }; +} + +TEST_CASE( "Combining templated matchers", "[matchers][templated]" ) { + std::array<int, 3> container{ { 1, 2, 3 } }; + + std::array<int, 3> a{ { 1, 2, 3 } }; + std::vector<int> b{ 0, 1, 2 }; + std::list<int> c{ 4, 5, 6 }; + + REQUIRE_THAT( container, + EqualsRange( a ) || EqualsRange( b ) || EqualsRange( c ) ); +} + +TEST_CASE( "Combining templated and concrete matchers", + "[matchers][templated]" ) { + std::vector<int> vec{ 1, 3, 5 }; + + std::array<int, 3> a{ { 5, 3, 1 } }; + + REQUIRE_THAT( vec, + Predicate<std::vector<int>>( + []( auto const& v ) { + return std::all_of( + v.begin(), v.end(), []( int elem ) { + return elem % 2 == 1; + } ); + }, + "All elements are odd" ) && + !EqualsRange( a ) ); + + const std::string str = "foobar"; + const std::array<char, 6> arr{ { 'f', 'o', 'o', 'b', 'a', 'r' } }; + const std::array<char, 6> bad_arr{ { 'o', 'o', 'f', 'b', 'a', 'r' } }; + + using Catch::Matchers::EndsWith; + using Catch::Matchers::StartsWith; + + REQUIRE_THAT( + str, StartsWith( "foo" ) && EqualsRange( arr ) && EndsWith( "bar" ) ); + REQUIRE_THAT( str, + StartsWith( "foo" ) && !EqualsRange( bad_arr ) && + EndsWith( "bar" ) ); + + REQUIRE_THAT( + str, EqualsRange( arr ) && StartsWith( "foo" ) && EndsWith( "bar" ) ); + REQUIRE_THAT( str, + !EqualsRange( bad_arr ) && StartsWith( "foo" ) && + EndsWith( "bar" ) ); + + REQUIRE_THAT( str, + EqualsRange( bad_arr ) || + ( StartsWith( "foo" ) && EndsWith( "bar" ) ) ); + REQUIRE_THAT( str, + ( StartsWith( "foo" ) && EndsWith( "bar" ) ) || + EqualsRange( bad_arr ) ); +} + +TEST_CASE( "Combining concrete matchers does not use templated matchers", + "[matchers][templated]" ) { + using Catch::Matchers::EndsWith; + using Catch::Matchers::StartsWith; + + STATIC_REQUIRE( + std::is_same<decltype( StartsWith( "foo" ) || + ( StartsWith( "bar" ) && EndsWith( "bar" ) && + !EndsWith( "foo" ) ) ), + Catch::Matchers::Detail::MatchAnyOf<std::string>>::value ); +} + +struct MatcherA : Catch::Matchers::MatcherGenericBase { + std::string describe() const override { + return "equals: (int) 1 or (string) \"1\""; + } + bool match( int i ) const { return i == 1; } + bool match( std::string const& s ) const { return s == "1"; } +}; + +struct MatcherB : Catch::Matchers::MatcherGenericBase { + std::string describe() const override { return "equals: (long long) 1"; } + bool match( long long l ) const { return l == 1ll; } +}; + +struct MatcherC : Catch::Matchers::MatcherGenericBase { + std::string describe() const override { return "equals: (T) 1"; } + template <typename T> bool match( T t ) const { return t == T{ 1 }; } +}; + +struct MatcherD : Catch::Matchers::MatcherGenericBase { + std::string describe() const override { return "equals: true"; } + bool match( bool b ) const { return b == true; } +}; + +TEST_CASE( "Combining only templated matchers", "[matchers][templated]" ) { + STATIC_REQUIRE( + std::is_same<decltype( MatcherA() || MatcherB() ), + Catch::Matchers::Detail:: + MatchAnyOfGeneric<MatcherA, MatcherB>>::value ); + + REQUIRE_THAT( 1, MatcherA() || MatcherB() ); + + STATIC_REQUIRE( + std::is_same<decltype( MatcherA() && MatcherB() ), + Catch::Matchers::Detail:: + MatchAllOfGeneric<MatcherA, MatcherB>>::value ); + + REQUIRE_THAT( 1, MatcherA() && MatcherB() ); + + STATIC_REQUIRE( + std::is_same< + decltype( MatcherA() || !MatcherB() ), + Catch::Matchers::Detail::MatchAnyOfGeneric< + MatcherA, + Catch::Matchers::Detail::MatchNotOfGeneric<MatcherB>>>::value ); + + REQUIRE_THAT( 1, MatcherA() || !MatcherB() ); +} + +TEST_CASE( "Combining MatchAnyOfGeneric does not nest", + "[matchers][templated]" ) { + // MatchAnyOfGeneric LHS + some matcher RHS + STATIC_REQUIRE( + std::is_same< + decltype( ( MatcherA() || MatcherB() ) || MatcherC() ), + Catch::Matchers::Detail:: + MatchAnyOfGeneric<MatcherA, MatcherB, MatcherC>>::value ); + + REQUIRE_THAT( 1, ( MatcherA() || MatcherB() ) || MatcherC() ); + + // some matcher LHS + MatchAnyOfGeneric RHS + STATIC_REQUIRE( + std::is_same< + decltype( MatcherA() || ( MatcherB() || MatcherC() ) ), + Catch::Matchers::Detail:: + MatchAnyOfGeneric<MatcherA, MatcherB, MatcherC>>::value ); + + REQUIRE_THAT( 1, MatcherA() || ( MatcherB() || MatcherC() ) ); + + // MatchAnyOfGeneric LHS + MatchAnyOfGeneric RHS + STATIC_REQUIRE( + std::is_same< + decltype( ( MatcherA() || MatcherB() ) || + ( MatcherC() || MatcherD() ) ), + Catch::Matchers::Detail:: + MatchAnyOfGeneric<MatcherA, MatcherB, MatcherC, MatcherD>>:: + value ); + + REQUIRE_THAT( + 1, ( MatcherA() || MatcherB() ) || ( MatcherC() || MatcherD() ) ); +} + +TEST_CASE( "Combining MatchAllOfGeneric does not nest", + "[matchers][templated]" ) { + // MatchAllOfGeneric lhs + some matcher RHS + STATIC_REQUIRE( + std::is_same< + decltype( ( MatcherA() && MatcherB() ) && MatcherC() ), + Catch::Matchers::Detail:: + MatchAllOfGeneric<MatcherA, MatcherB, MatcherC>>::value ); + + REQUIRE_THAT( 1, ( MatcherA() && MatcherB() ) && MatcherC() ); + + // some matcher LHS + MatchAllOfGeneric RSH + STATIC_REQUIRE( + std::is_same< + decltype( MatcherA() && ( MatcherB() && MatcherC() ) ), + Catch::Matchers::Detail:: + MatchAllOfGeneric<MatcherA, MatcherB, MatcherC>>::value ); + + REQUIRE_THAT( 1, MatcherA() && ( MatcherB() && MatcherC() ) ); + + // MatchAllOfGeneric LHS + MatchAllOfGeneric RHS + STATIC_REQUIRE( + std::is_same< + decltype( ( MatcherA() && MatcherB() ) && + ( MatcherC() && MatcherD() ) ), + Catch::Matchers::Detail:: + MatchAllOfGeneric<MatcherA, MatcherB, MatcherC, MatcherD>>:: + value ); + + REQUIRE_THAT( + 1, ( MatcherA() && MatcherB() ) && ( MatcherC() && MatcherD() ) ); +} + +TEST_CASE( "Combining MatchNotOfGeneric does not nest", + "[matchers][templated]" ) { + STATIC_REQUIRE( + std::is_same< + decltype( !MatcherA() ), + Catch::Matchers::Detail::MatchNotOfGeneric<MatcherA>>::value ); + + REQUIRE_THAT( 0, !MatcherA() ); + + STATIC_REQUIRE( + std::is_same<decltype( !!MatcherA() ), MatcherA const&>::value ); + + REQUIRE_THAT( 1, !!MatcherA() ); + + STATIC_REQUIRE( + std::is_same< + decltype( !!!MatcherA() ), + Catch::Matchers::Detail::MatchNotOfGeneric<MatcherA>>::value ); + + REQUIRE_THAT( 0, !!!MatcherA() ); + + STATIC_REQUIRE( + std::is_same<decltype( !!!!MatcherA() ), MatcherA const&>::value ); + + REQUIRE_THAT( 1, !!!!MatcherA() ); +} + +struct EvilAddressOfOperatorUsed : std::exception { + const char* what() const noexcept override { + return "overloaded address-of operator of matcher was used instead of " + "std::addressof"; + } +}; + +struct EvilCommaOperatorUsed : std::exception { + const char* what() const noexcept override { + return "overloaded comma operator of matcher was used"; + } +}; + +struct EvilMatcher : Catch::Matchers::MatcherGenericBase { + std::string describe() const override { return "equals: 45"; } + + bool match( int i ) const { return i == 45; } + + EvilMatcher const* operator&() const { throw EvilAddressOfOperatorUsed(); } + + int operator,( EvilMatcher const& ) const { throw EvilCommaOperatorUsed(); } +}; + +TEST_CASE( "Overloaded comma or address-of operators are not used", + "[matchers][templated]" ) { + REQUIRE_THROWS_AS( ( EvilMatcher(), EvilMatcher() ), + EvilCommaOperatorUsed ); + REQUIRE_THROWS_AS( &EvilMatcher(), EvilAddressOfOperatorUsed ); + REQUIRE_NOTHROW( EvilMatcher() || ( EvilMatcher() && !EvilMatcher() ) ); + REQUIRE_NOTHROW( ( EvilMatcher() && EvilMatcher() ) || !EvilMatcher() ); +} + +struct ImmovableMatcher : Catch::Matchers::MatcherGenericBase { + ImmovableMatcher() = default; + ImmovableMatcher( ImmovableMatcher const& ) = delete; + ImmovableMatcher( ImmovableMatcher&& ) = delete; + ImmovableMatcher& operator=( ImmovableMatcher const& ) = delete; + ImmovableMatcher& operator=( ImmovableMatcher&& ) = delete; + + std::string describe() const override { return "always false"; } + + template <typename T> bool match( T&& ) const { return false; } +}; + +struct MatcherWasMovedOrCopied : std::exception { + const char* what() const noexcept override { + return "attempted to copy or move a matcher"; + } +}; + +struct ThrowOnCopyOrMoveMatcher : Catch::Matchers::MatcherGenericBase { + ThrowOnCopyOrMoveMatcher() = default; + + [[noreturn]] ThrowOnCopyOrMoveMatcher( ThrowOnCopyOrMoveMatcher const& other ): + Catch::Matchers::MatcherGenericBase( other ) { + throw MatcherWasMovedOrCopied(); + } + // NOLINTNEXTLINE(performance-noexcept-move-constructor) + [[noreturn]] ThrowOnCopyOrMoveMatcher( ThrowOnCopyOrMoveMatcher&& other ): + Catch::Matchers::MatcherGenericBase( CATCH_MOVE(other) ) { + throw MatcherWasMovedOrCopied(); + } + ThrowOnCopyOrMoveMatcher& operator=( ThrowOnCopyOrMoveMatcher const& ) { + throw MatcherWasMovedOrCopied(); + } + // NOLINTNEXTLINE(performance-noexcept-move-constructor) + ThrowOnCopyOrMoveMatcher& operator=( ThrowOnCopyOrMoveMatcher&& ) { + throw MatcherWasMovedOrCopied(); + } + + std::string describe() const override { return "always false"; } + + template <typename T> bool match( T&& ) const { return false; } +}; + +TEST_CASE( "Matchers are not moved or copied", + "[matchers][templated][approvals]" ) { + REQUIRE_NOTHROW( + ( ThrowOnCopyOrMoveMatcher() && ThrowOnCopyOrMoveMatcher() ) || + !ThrowOnCopyOrMoveMatcher() ); +} + +TEST_CASE( "Immovable matchers can be used", + "[matchers][templated][approvals]" ) { + REQUIRE_THAT( 123, + ( ImmovableMatcher() && ImmovableMatcher() ) || + !ImmovableMatcher() ); +} + +struct ReferencingMatcher : Catch::Matchers::MatcherGenericBase { + std::string describe() const override { return "takes reference"; } + bool match( int& i ) const { return i == 22; } +}; + +TEST_CASE( "Matchers can take references", + "[matchers][templated][approvals]" ) { + REQUIRE_THAT( 22, ReferencingMatcher{} ); +} + +#ifdef __clang__ +# pragma clang diagnostic pop +#endif + +TEMPLATE_TEST_CASE( + "#2152 - ULP checks between differently signed values were wrong", + "[matchers][floating-point][ulp]", + float, + double ) { + using Catch::Matchers::WithinULP; + + static constexpr TestType smallest_non_zero = + std::numeric_limits<TestType>::denorm_min(); + + CHECK_THAT( smallest_non_zero, WithinULP( -smallest_non_zero, 2 ) ); + CHECK_THAT( smallest_non_zero, !WithinULP( -smallest_non_zero, 1 ) ); +} diff --git a/tests/SelfTest/UsageTests/MatchersRanges.tests.cpp b/tests/SelfTest/UsageTests/MatchersRanges.tests.cpp new file mode 100644 index 0000000..4f906b9 --- /dev/null +++ b/tests/SelfTest/UsageTests/MatchersRanges.tests.cpp @@ -0,0 +1,936 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 + +#include <catch2/catch_test_macros.hpp> +#include <catch2/matchers/catch_matchers_container_properties.hpp> +#include <catch2/matchers/catch_matchers_contains.hpp> +#include <catch2/matchers/catch_matchers_range_equals.hpp> +#include <catch2/matchers/catch_matchers_floating_point.hpp> +#include <catch2/matchers/catch_matchers_quantifiers.hpp> +#include <catch2/matchers/catch_matchers_predicate.hpp> +#include <catch2/matchers/catch_matchers_string.hpp> + +#include <helpers/range_test_helpers.hpp> + +#include <cmath> +#include <list> +#include <map> +#include <type_traits> +#include <vector> +#include <memory> + +struct MoveOnlyTestElement { + int num = 0; + MoveOnlyTestElement(int n) :num(n) {} + + MoveOnlyTestElement(MoveOnlyTestElement&& rhs) = default; + MoveOnlyTestElement& operator=(MoveOnlyTestElement&& rhs) = default; + + friend bool operator==(MoveOnlyTestElement const& lhs, MoveOnlyTestElement const& rhs) { + return lhs.num == rhs.num; + } + + friend std::ostream& operator<<(std::ostream& out, MoveOnlyTestElement const& elem) { + out << elem.num; + return out; + } +}; + +TEST_CASE("Basic use of the Contains range matcher", "[matchers][templated][contains]") { + using Catch::Matchers::Contains; + + SECTION("Different argument ranges, same element type, default comparison") { + std::array<int, 3> a{ { 1,2,3 } }; + std::vector<int> b{ 0,1,2 }; + std::list<int> c{ 4,5,6 }; + + // A contains 1 + REQUIRE_THAT(a, Contains(1)); + // B contains 1 + REQUIRE_THAT(b, Contains(1)); + // C does not contain 1 + REQUIRE_THAT(c, !Contains(1)); + } + + SECTION("Different argument ranges, same element type, custom comparison") { + std::array<int, 3> a{ { 1,2,3 } }; + std::vector<int> b{ 0,1,2 }; + std::list<int> c{ 4,5,6 }; + + auto close_enough = [](int lhs, int rhs) { return std::abs(lhs - rhs) <= 1; }; + + // A contains 1, which is "close enough" to 0 + REQUIRE_THAT(a, Contains(0, close_enough)); + // B contains 0 directly + REQUIRE_THAT(b, Contains(0, close_enough)); + // C does not contain anything "close enough" to 0 + REQUIRE_THAT(c, !Contains(0, close_enough)); + } + + SECTION("Different element type, custom comparisons") { + std::array<std::string, 3> a{ { "abc", "abcd" , "abcde" } }; + + REQUIRE_THAT(a, Contains(4, [](auto&& lhs, size_t sz) { + return lhs.size() == sz; + })); + } + + SECTION("Can handle type that requires ADL-found free function begin and end") { + unrelated::needs_ADL_begin<int> in{1, 2, 3, 4, 5}; + + REQUIRE_THAT(in, Contains(1)); + REQUIRE_THAT(in, !Contains(8)); + } + + SECTION("Initialization with move only types") { + std::array<MoveOnlyTestElement, 3> in{ { MoveOnlyTestElement{ 1 }, MoveOnlyTestElement{ 2 }, MoveOnlyTestElement{ 3 } } }; + + REQUIRE_THAT(in, Contains(MoveOnlyTestElement{ 2 })); + REQUIRE_THAT(in, !Contains(MoveOnlyTestElement{ 9 })); + } + + SECTION("Matching using matcher") { + std::array<double, 4> in{ {1, 2, 3} }; + + REQUIRE_THAT(in, Contains(Catch::Matchers::WithinAbs(0.5, 0.5))); + } +} + +namespace { + + struct has_empty { + bool empty() const { return false; } + }; + +} // end unnamed namespace + +TEST_CASE("Basic use of the Empty range matcher", "[matchers][templated][empty]") { + using Catch::Matchers::IsEmpty; + SECTION("Simple, std-provided containers") { + std::array<int, 0> empty_array{}; + std::array<double, 1> non_empty_array{}; + REQUIRE_THAT(empty_array, IsEmpty()); + REQUIRE_THAT(non_empty_array, !IsEmpty()); + + std::vector<std::string> empty_vec; + std::vector<char> non_empty_vec{ 'a', 'b', 'c' }; + REQUIRE_THAT(empty_vec, IsEmpty()); + REQUIRE_THAT(non_empty_vec, !IsEmpty()); + + std::list<std::list<std::list<int>>> inner_lists_are_empty; + inner_lists_are_empty.push_back({}); + REQUIRE_THAT(inner_lists_are_empty, !IsEmpty()); + REQUIRE_THAT(inner_lists_are_empty.front(), IsEmpty()); + } + SECTION("Type with empty") { + REQUIRE_THAT(has_empty{}, !IsEmpty()); + } + SECTION("Type requires ADL found empty free function") { + REQUIRE_THAT(unrelated::ADL_empty{}, IsEmpty()); + } +} + +namespace { + class LessThanMatcher final : public Catch::Matchers::MatcherBase<size_t> { + size_t m_target; + public: + explicit LessThanMatcher(size_t target): + m_target(target) + {} + + bool match(size_t const& size) const override { + return size < m_target; + } + + std::string describe() const override { + return "is less than " + std::to_string(m_target); + } + }; + + LessThanMatcher Lt(size_t sz) { + return LessThanMatcher{ sz }; + } + + struct has_size { + size_t size() const { + return 13; + } + }; + +} // end unnamed namespace + +TEST_CASE("Usage of the SizeIs range matcher", "[matchers][templated][size]") { + using Catch::Matchers::SizeIs; + SECTION("Some with stdlib containers") { + std::vector<int> empty_vec; + REQUIRE_THAT(empty_vec, SizeIs(0)); + REQUIRE_THAT(empty_vec, !SizeIs(2)); + REQUIRE_THAT(empty_vec, SizeIs(Lt(2))); + + std::array<int, 2> arr{}; + REQUIRE_THAT(arr, SizeIs(2)); + REQUIRE_THAT(arr, SizeIs( Lt(3))); + REQUIRE_THAT(arr, !SizeIs(!Lt(3))); + + std::map<int, int> map{ {1, 1}, {2, 2}, {3, 3} }; + REQUIRE_THAT(map, SizeIs(3)); + } + SECTION("Type requires ADL found size free function") { + REQUIRE_THAT(unrelated::ADL_size{}, SizeIs(12)); + } + SECTION("Type has size member") { + REQUIRE_THAT(has_size{}, SizeIs(13)); + } +} + + +TEST_CASE("Usage of AllMatch range matcher", "[matchers][templated][quantifiers]") { + using Catch::Matchers::AllMatch; + using Catch::Matchers::Predicate; + + SECTION("Basic usage") { + using Catch::Matchers::Contains; + using Catch::Matchers::SizeIs; + + std::array<std::array<int, 5>, 5> data{{ + {{ 0, 1, 2, 3, 5 }}, + {{ 4,-3,-2, 5, 0 }}, + {{ 0, 0, 0, 5, 0 }}, + {{ 0,-5, 0, 5, 0 }}, + {{ 1, 0, 0,-1, 5 }} + }}; + + REQUIRE_THAT(data, AllMatch(SizeIs(5))); + REQUIRE_THAT(data, !AllMatch(Contains(0) && Contains(1))); + } + + SECTION("Type requires ADL found begin and end") { + unrelated::needs_ADL_begin<int> needs_adl{ 1, 2, 3, 4, 5 }; + REQUIRE_THAT( needs_adl, AllMatch( Predicate<int>( []( int elem ) { + return elem < 6; + } ) ) ); + } + + SECTION("Shortcircuiting") { + with_mocked_iterator_access<int> mocked{ 1, 2, 3, 4, 5 }; + SECTION("All are read") { + auto allMatch = AllMatch(Predicate<int>([](int elem) { + return elem < 10; + })); + REQUIRE_THAT(mocked, allMatch); + REQUIRE(mocked.m_derefed[0]); + REQUIRE(mocked.m_derefed[1]); + REQUIRE(mocked.m_derefed[2]); + REQUIRE(mocked.m_derefed[3]); + REQUIRE(mocked.m_derefed[4]); + } + SECTION("Short-circuited") { + auto allMatch = AllMatch(Predicate<int>([](int elem) { + return elem < 3; + })); + REQUIRE_THAT(mocked, !allMatch); + REQUIRE(mocked.m_derefed[0]); + REQUIRE(mocked.m_derefed[1]); + REQUIRE(mocked.m_derefed[2]); + REQUIRE_FALSE(mocked.m_derefed[3]); + REQUIRE_FALSE(mocked.m_derefed[4]); + } + } +} + +TEST_CASE("Usage of AnyMatch range matcher", "[matchers][templated][quantifiers]") { + using Catch::Matchers::AnyMatch; + using Catch::Matchers::Predicate; + + SECTION("Basic usage") { + using Catch::Matchers::Contains; + using Catch::Matchers::SizeIs; + + std::array<std::array<int, 5>, 5> data{ { + {{ 0, 1, 2, 3, 5 }}, + {{ 4,-3,-2, 5, 0 }}, + {{ 0, 0, 0, 5, 0 }}, + {{ 0,-5, 0, 5, 0 }}, + {{ 1, 0, 0,-1, 5 }} + } }; + + REQUIRE_THAT(data, AnyMatch(SizeIs(5))); + REQUIRE_THAT(data, !AnyMatch(Contains(0) && Contains(10))); + } + + SECTION( "Type requires ADL found begin and end" ) { + unrelated::needs_ADL_begin<int> needs_adl{ 1, 2, 3, 4, 5 }; + REQUIRE_THAT( needs_adl, AnyMatch( Predicate<int>( []( int elem ) { + return elem < 3; + } ) ) ); + } + + SECTION("Shortcircuiting") { + with_mocked_iterator_access<int> mocked{ 1, 2, 3, 4, 5 }; + SECTION("All are read") { + auto anyMatch = AnyMatch( + Predicate<int>( []( int elem ) { return elem > 10; } ) ); + REQUIRE_THAT( mocked, !anyMatch ); + REQUIRE( mocked.m_derefed[0] ); + REQUIRE( mocked.m_derefed[1] ); + REQUIRE( mocked.m_derefed[2] ); + REQUIRE( mocked.m_derefed[3] ); + REQUIRE( mocked.m_derefed[4] ); + } + SECTION("Short-circuited") { + auto anyMatch = AnyMatch( + Predicate<int>( []( int elem ) { return elem < 3; } ) ); + REQUIRE_THAT( mocked, anyMatch ); + REQUIRE( mocked.m_derefed[0] ); + REQUIRE_FALSE( mocked.m_derefed[1] ); + REQUIRE_FALSE( mocked.m_derefed[2] ); + REQUIRE_FALSE( mocked.m_derefed[3] ); + REQUIRE_FALSE( mocked.m_derefed[4] ); + } + } +} + +TEST_CASE("Usage of NoneMatch range matcher", "[matchers][templated][quantifiers]") { + using Catch::Matchers::NoneMatch; + using Catch::Matchers::Predicate; + + SECTION("Basic usage") { + using Catch::Matchers::Contains; + using Catch::Matchers::SizeIs; + + std::array<std::array<int, 5>, 5> data{ { + {{ 0, 1, 2, 3, 5 }}, + {{ 4,-3,-2, 5, 0 }}, + {{ 0, 0, 0, 5, 0 }}, + {{ 0,-5, 0, 5, 0 }}, + {{ 1, 0, 0,-1, 5 }} + } }; + + REQUIRE_THAT(data, NoneMatch(SizeIs(6))); + REQUIRE_THAT(data, !NoneMatch(Contains(0) && Contains(1))); + } + + SECTION( "Type requires ADL found begin and end" ) { + unrelated::needs_ADL_begin<int> needs_adl{ 1, 2, 3, 4, 5 }; + REQUIRE_THAT( needs_adl, NoneMatch( Predicate<int>( []( int elem ) { + return elem > 6; + } ) ) ); + } + + SECTION("Shortcircuiting") { + with_mocked_iterator_access<int> mocked{ 1, 2, 3, 4, 5 }; + SECTION("All are read") { + auto noneMatch = NoneMatch( + Predicate<int>([](int elem) { return elem > 10; })); + REQUIRE_THAT(mocked, noneMatch); + REQUIRE(mocked.m_derefed[0]); + REQUIRE(mocked.m_derefed[1]); + REQUIRE(mocked.m_derefed[2]); + REQUIRE(mocked.m_derefed[3]); + REQUIRE(mocked.m_derefed[4]); + } + SECTION("Short-circuited") { + auto noneMatch = NoneMatch( + Predicate<int>([](int elem) { return elem < 3; })); + REQUIRE_THAT(mocked, !noneMatch); + REQUIRE(mocked.m_derefed[0]); + REQUIRE_FALSE(mocked.m_derefed[1]); + REQUIRE_FALSE(mocked.m_derefed[2]); + REQUIRE_FALSE(mocked.m_derefed[3]); + REQUIRE_FALSE(mocked.m_derefed[4]); + } + } +} + +namespace { + struct ConvertibleToBool + { + bool v; + + explicit operator bool() const + { + return v; + } + }; +} + +namespace Catch { + template <> + struct StringMaker<ConvertibleToBool> { + static std::string + convert( ConvertibleToBool const& convertible_to_bool ) { + return ::Catch::Detail::stringify( convertible_to_bool.v ); + } + }; +} // namespace Catch + +TEST_CASE("Usage of AllTrue range matcher", "[matchers][templated][quantifiers]") { + using Catch::Matchers::AllTrue; + + SECTION( "Basic usage" ) { + SECTION( "All true evaluates to true" ) { + std::array<bool, 5> const data{ { true, true, true, true, true } }; + REQUIRE_THAT( data, AllTrue() ); + } + SECTION( "Empty evaluates to true" ) { + std::array<bool, 0> const data{}; + REQUIRE_THAT( data, AllTrue() ); + } + SECTION( "One false evaluates to false" ) { + std::array<bool, 5> const data{ { true, true, false, true, true } }; + REQUIRE_THAT( data, !AllTrue() ); + } + SECTION( "All false evaluates to false" ) { + std::array<bool, 5> const data{ + { false, false, false, false, false } }; + REQUIRE_THAT( data, !AllTrue() ); + } + } + + SECTION( "Contained type is convertible to bool" ) { + SECTION( "All true evaluates to true" ) { + std::array<ConvertibleToBool, 5> const data{ + { { true }, { true }, { true }, { true }, { true } } }; + REQUIRE_THAT( data, AllTrue() ); + } + SECTION( "One false evaluates to false" ) { + std::array<ConvertibleToBool, 5> const data{ + { { true }, { true }, { false }, { true }, { true } } }; + REQUIRE_THAT( data, !AllTrue() ); + } + SECTION( "All false evaluates to false" ) { + std::array<ConvertibleToBool, 5> const data{ + { { false }, { false }, { false }, { false }, { false } } }; + REQUIRE_THAT( data, !AllTrue() ); + } + } + + SECTION( "Shortcircuiting" ) { + SECTION( "All are read" ) { + with_mocked_iterator_access<bool> const mocked{ + true, true, true, true, true }; + REQUIRE_THAT( mocked, AllTrue() ); + REQUIRE( mocked.m_derefed[0] ); + REQUIRE( mocked.m_derefed[1] ); + REQUIRE( mocked.m_derefed[2] ); + REQUIRE( mocked.m_derefed[3] ); + REQUIRE( mocked.m_derefed[4] ); + } + SECTION( "Short-circuited" ) { + with_mocked_iterator_access<bool> const mocked{ + true, true, false, true, true }; + REQUIRE_THAT( mocked, !AllTrue() ); + REQUIRE( mocked.m_derefed[0] ); + REQUIRE( mocked.m_derefed[1] ); + REQUIRE( mocked.m_derefed[2] ); + REQUIRE_FALSE( mocked.m_derefed[3] ); + REQUIRE_FALSE( mocked.m_derefed[4] ); + } + } +} + +TEST_CASE( "Usage of NoneTrue range matcher", "[matchers][templated][quantifiers]" ) { + using Catch::Matchers::NoneTrue; + + SECTION( "Basic usage" ) { + SECTION( "All true evaluates to false" ) { + std::array<bool, 5> const data{ { true, true, true, true, true } }; + REQUIRE_THAT( data, !NoneTrue() ); + } + SECTION( "Empty evaluates to true" ) { + std::array<bool, 0> const data{}; + REQUIRE_THAT( data, NoneTrue() ); + } + SECTION( "One true evaluates to false" ) { + std::array<bool, 5> const data{ + { false, false, true, false, false } }; + REQUIRE_THAT( data, !NoneTrue() ); + } + SECTION( "All false evaluates to true" ) { + std::array<bool, 5> const data{ + { false, false, false, false, false } }; + REQUIRE_THAT( data, NoneTrue() ); + } + } + + SECTION( "Contained type is convertible to bool" ) { + SECTION( "All true evaluates to false" ) { + std::array<ConvertibleToBool, 5> const data{ + { { true }, { true }, { true }, { true }, { true } } }; + REQUIRE_THAT( data, !NoneTrue() ); + } + SECTION( "One true evaluates to false" ) { + std::array<ConvertibleToBool, 5> const data{ + { { false }, { false }, { true }, { false }, { false } } }; + REQUIRE_THAT( data, !NoneTrue() ); + } + SECTION( "All false evaluates to true" ) { + std::array<ConvertibleToBool, 5> const data{ + { { false }, { false }, { false }, { false }, { false } } }; + REQUIRE_THAT( data, NoneTrue() ); + } + } + + SECTION( "Shortcircuiting" ) { + SECTION( "All are read" ) { + with_mocked_iterator_access<bool> const mocked{ + false, false, false, false, false }; + REQUIRE_THAT( mocked, NoneTrue() ); + REQUIRE( mocked.m_derefed[0] ); + REQUIRE( mocked.m_derefed[1] ); + REQUIRE( mocked.m_derefed[2] ); + REQUIRE( mocked.m_derefed[3] ); + REQUIRE( mocked.m_derefed[4] ); + } + SECTION( "Short-circuited" ) { + with_mocked_iterator_access<bool> const mocked{ + false, false, true, true, true }; + REQUIRE_THAT( mocked, !NoneTrue() ); + REQUIRE( mocked.m_derefed[0] ); + REQUIRE( mocked.m_derefed[1] ); + REQUIRE( mocked.m_derefed[2] ); + REQUIRE_FALSE( mocked.m_derefed[3] ); + REQUIRE_FALSE( mocked.m_derefed[4] ); + } + } +} + +TEST_CASE( "Usage of AnyTrue range matcher", "[matchers][templated][quantifiers]" ) { + using Catch::Matchers::AnyTrue; + + SECTION( "Basic usage" ) { + SECTION( "All true evaluates to true" ) { + std::array<bool, 5> const data{ { true, true, true, true, true } }; + REQUIRE_THAT( data, AnyTrue() ); + } + SECTION( "Empty evaluates to false" ) { + std::array<bool, 0> const data{}; + REQUIRE_THAT( data, !AnyTrue() ); + } + SECTION( "One true evaluates to true" ) { + std::array<bool, 5> const data{ + { false, false, true, false, false } }; + REQUIRE_THAT( data, AnyTrue() ); + } + SECTION( "All false evaluates to false" ) { + std::array<bool, 5> const data{ + { false, false, false, false, false } }; + REQUIRE_THAT( data, !AnyTrue() ); + } + } + + SECTION( "Contained type is convertible to bool" ) { + SECTION( "All true evaluates to true" ) { + std::array<ConvertibleToBool, 5> const data{ + { { true }, { true }, { true }, { true }, { true } } }; + REQUIRE_THAT( data, AnyTrue() ); + } + SECTION( "One true evaluates to true" ) { + std::array<ConvertibleToBool, 5> const data{ + { { false }, { false }, { true }, { false }, { false } } }; + REQUIRE_THAT( data, AnyTrue() ); + } + SECTION( "All false evaluates to false" ) { + std::array<ConvertibleToBool, 5> const data{ + { { false }, { false }, { false }, { false }, { false } } }; + REQUIRE_THAT( data, !AnyTrue() ); + } + } + + SECTION( "Shortcircuiting" ) { + SECTION( "All are read" ) { + with_mocked_iterator_access<bool> const mocked{ + false, false, false, false, true }; + REQUIRE_THAT( mocked, AnyTrue() ); + REQUIRE( mocked.m_derefed[0] ); + REQUIRE( mocked.m_derefed[1] ); + REQUIRE( mocked.m_derefed[2] ); + REQUIRE( mocked.m_derefed[3] ); + REQUIRE( mocked.m_derefed[4] ); + } + SECTION( "Short-circuited" ) { + with_mocked_iterator_access<bool> const mocked{ + false, false, true, true, true }; + REQUIRE_THAT( mocked, AnyTrue() ); + REQUIRE( mocked.m_derefed[0] ); + REQUIRE( mocked.m_derefed[1] ); + REQUIRE( mocked.m_derefed[2] ); + REQUIRE_FALSE( mocked.m_derefed[3] ); + REQUIRE_FALSE( mocked.m_derefed[4] ); + } + } +} + +TEST_CASE("All/Any/None True matchers support types with ADL begin", + "[approvals][matchers][quantifiers][templated]") { + using Catch::Matchers::AllTrue; + using Catch::Matchers::NoneTrue; + using Catch::Matchers::AnyTrue; + + + SECTION( "Type requires ADL found begin and end" ) { + unrelated::needs_ADL_begin<bool> const needs_adl{ + true, true, true, true, true }; + REQUIRE_THAT( needs_adl, AllTrue() ); + } + + SECTION( "Type requires ADL found begin and end" ) { + unrelated::needs_ADL_begin<bool> const needs_adl{ + false, false, false, false, false }; + REQUIRE_THAT( needs_adl, NoneTrue() ); + } + + SECTION( "Type requires ADL found begin and end" ) { + unrelated::needs_ADL_begin<bool> const needs_adl{ + false, false, true, false, false }; + REQUIRE_THAT( needs_adl, AnyTrue() ); + } +} + +// Range loop iterating over range with different types for begin and end is a +// C++17 feature, and GCC refuses to compile such code unless the lang mode is +// set to C++17 or later. +#if defined(CATCH_CPP17_OR_GREATER) + +TEST_CASE( "The quantifier range matchers support types with different types returned from begin and end", + "[matchers][templated][quantifiers][approvals]" ) { + using Catch::Matchers::AllMatch; + using Catch::Matchers::AllTrue; + using Catch::Matchers::AnyMatch; + using Catch::Matchers::AnyTrue; + using Catch::Matchers::NoneMatch; + using Catch::Matchers::NoneTrue; + + using Catch::Matchers::Predicate; + + SECTION( "AllAnyNoneMatch" ) { + has_different_begin_end_types<int> diff_types{ 1, 2, 3, 4, 5 }; + REQUIRE_THAT( diff_types, !AllMatch( Predicate<int>( []( int elem ) { + return elem < 3; + } ) ) ); + + REQUIRE_THAT( diff_types, AnyMatch( Predicate<int>( []( int elem ) { + return elem < 2; + } ) ) ); + + REQUIRE_THAT( diff_types, !NoneMatch( Predicate<int>( []( int elem ) { + return elem < 3; + } ) ) ); + } + SECTION( "AllAnyNoneTrue" ) { + has_different_begin_end_types<bool> diff_types{ false, false, true, false, false }; + + REQUIRE_THAT( diff_types, !AllTrue() ); + REQUIRE_THAT( diff_types, AnyTrue() ); + REQUIRE_THAT( diff_types, !NoneTrue() ); + } +} + +TEST_CASE( "RangeEquals supports ranges with different types returned from begin and end", + "[matchers][templated][range][approvals] ") { + using Catch::Matchers::RangeEquals; + using Catch::Matchers::UnorderedRangeEquals; + + has_different_begin_end_types<int> diff_types{ 1, 2, 3, 4, 5 }; + std::array<int, 5> arr1{ { 1, 2, 3, 4, 5 } }, arr2{ { 2, 3, 4, 5, 6 } }; + + REQUIRE_THAT( diff_types, RangeEquals( arr1 ) ); + REQUIRE_THAT( diff_types, RangeEquals( arr2, []( int l, int r ) { + return l + 1 == r; + } ) ); + REQUIRE_THAT( diff_types, UnorderedRangeEquals( diff_types ) ); +} + +TEST_CASE( "RangeContains supports ranges with different types returned from " + "begin and end", + "[matchers][templated][range][approvals]" ) { + using Catch::Matchers::Contains; + + has_different_begin_end_types<size_t> diff_types{ 1, 2, 3, 4, 5 }; + REQUIRE_THAT( diff_types, Contains( size_t( 3 ) ) ); + REQUIRE_THAT( diff_types, Contains( LessThanMatcher( size_t( 4 ) ) ) ); +} + +#endif + +TEST_CASE( "Usage of RangeEquals range matcher", "[matchers][templated][quantifiers]" ) { + using Catch::Matchers::RangeEquals; + + // In these tests, the types are always the same - type conversion is in the next section + SECTION( "Basic usage" ) { + SECTION( "Empty container matches empty container" ) { + const std::vector<int> empty_vector; + CHECK_THAT( empty_vector, RangeEquals( empty_vector ) ); + } + SECTION( "Empty container does not match non-empty container" ) { + const std::vector<int> empty_vector; + const std::vector<int> non_empty_vector{ 1 }; + CHECK_THAT( empty_vector, !RangeEquals( non_empty_vector ) ); + // ...and in reverse + CHECK_THAT( non_empty_vector, !RangeEquals( empty_vector ) ); + } + SECTION( "Two equal 1-length non-empty containers" ) { + const std::array<int, 1> non_empty_array{ { 1 } }; + CHECK_THAT( non_empty_array, RangeEquals( non_empty_array ) ); + } + SECTION( "Two equal-sized, equal, non-empty containers" ) { + const std::array<int, 3> array_a{ { 1, 2, 3 } }; + CHECK_THAT( array_a, RangeEquals( array_a ) ); + } + SECTION( "Two equal-sized, non-equal, non-empty containers" ) { + const std::array<int, 3> array_a{ { 1, 2, 3 } }; + const std::array<int, 3> array_b{ { 2, 2, 3 } }; + const std::array<int, 3> array_c{ { 1, 2, 2 } }; + CHECK_THAT( array_a, !RangeEquals( array_b ) ); + CHECK_THAT( array_a, !RangeEquals( array_c ) ); + } + SECTION( "Two non-equal-sized, non-empty containers (with same first " + "elements)" ) { + const std::vector<int> vector_a{ 1, 2, 3 }; + const std::vector<int> vector_b{ 1, 2, 3, 4 }; + CHECK_THAT( vector_a, !RangeEquals( vector_b ) ); + } + } + + SECTION( "Custom predicate" ) { + + auto close_enough = []( int lhs, int rhs ) { + return std::abs( lhs - rhs ) <= 1; + }; + + SECTION( "Two equal non-empty containers (close enough)" ) { + const std::vector<int> vector_a{ { 1, 2, 3 } }; + const std::vector<int> vector_a_plus_1{ { 2, 3, 4 } }; + CHECK_THAT( vector_a, RangeEquals( vector_a_plus_1, close_enough ) ); + } + SECTION( "Two non-equal non-empty containers (close enough)" ) { + const std::vector<int> vector_a{ { 1, 2, 3 } }; + const std::vector<int> vector_b{ { 3, 3, 4 } }; + CHECK_THAT( vector_a, !RangeEquals( vector_b, close_enough ) ); + } + } + + SECTION( "Ranges that need ADL begin/end" ) { + unrelated::needs_ADL_begin<int> const + needs_adl1{ 1, 2, 3, 4, 5 }, + needs_adl2{ 1, 2, 3, 4, 5 }, + needs_adl3{ 2, 3, 4, 5, 6 }; + + REQUIRE_THAT( needs_adl1, RangeEquals( needs_adl2 ) ); + REQUIRE_THAT( needs_adl1, RangeEquals( needs_adl3, []( int l, int r ) { + return l + 1 == r; + } ) ); + } + + SECTION( "Compare against std::initializer_list" ) { + const std::array<int, 3> array_a{ { 1, 2, 3 } }; + + REQUIRE_THAT( array_a, RangeEquals( { 1, 2, 3 } ) ); + REQUIRE_THAT( array_a, RangeEquals( { 2, 4, 6 }, []( int l, int r ) { + return l * 2 == r; + } ) ); + } + + SECTION("Check short-circuiting behaviour") { + with_mocked_iterator_access<int> const mocked1{ 1, 2, 3, 4 }; + + SECTION( "Check short-circuits on failure" ) { + std::array<int, 4> arr{ { 1, 2, 4, 4 } }; + + REQUIRE_THAT( mocked1, !RangeEquals( arr ) ); + REQUIRE( mocked1.m_derefed[0] ); + REQUIRE( mocked1.m_derefed[1] ); + REQUIRE( mocked1.m_derefed[2] ); + REQUIRE_FALSE( mocked1.m_derefed[3] ); + } + SECTION("All elements are checked on success") { + std::array<int, 4> arr{ { 1, 2, 3, 4 } }; + + REQUIRE_THAT( mocked1, RangeEquals( arr ) ); + REQUIRE( mocked1.m_derefed[0] ); + REQUIRE( mocked1.m_derefed[1] ); + REQUIRE( mocked1.m_derefed[2] ); + REQUIRE( mocked1.m_derefed[3] ); + } + } +} + +TEST_CASE( "Usage of UnorderedRangeEquals range matcher", + "[matchers][templated][quantifiers]" ) { + using Catch::Matchers::UnorderedRangeEquals; + + // In these tests, the types are always the same - type conversion is in the + // next section + SECTION( "Basic usage" ) { + SECTION( "Empty container matches empty container" ) { + const std::vector<int> empty_vector; + CHECK_THAT( empty_vector, UnorderedRangeEquals( empty_vector ) ); + } + SECTION( "Empty container does not match non-empty container" ) { + const std::vector<int> empty_vector; + const std::vector<int> non_empty_vector{ 1 }; + CHECK_THAT( empty_vector, + !UnorderedRangeEquals( non_empty_vector ) ); + // ...and in reverse + CHECK_THAT( non_empty_vector, + !UnorderedRangeEquals( empty_vector ) ); + } + SECTION( "Two equal 1-length non-empty containers" ) { + const std::array<int, 1> non_empty_array{ { 1 } }; + CHECK_THAT( non_empty_array, + UnorderedRangeEquals( non_empty_array ) ); + } + SECTION( "Two equal-sized, equal, non-empty containers" ) { + const std::array<int, 3> array_a{ { 1, 2, 3 } }; + CHECK_THAT( array_a, UnorderedRangeEquals( array_a ) ); + } + SECTION( "Two equal-sized, non-equal, non-empty containers" ) { + const std::array<int, 3> array_a{ { 1, 2, 3 } }; + const std::array<int, 3> array_b{ { 2, 2, 3 } }; + CHECK_THAT( array_a, !UnorderedRangeEquals( array_b ) ); + } + SECTION( "Two non-equal-sized, non-empty containers" ) { + const std::vector<int> vector_a{ 1, 2, 3 }; + const std::vector<int> vector_b{ 1, 2, 3, 4 }; + CHECK_THAT( vector_a, !UnorderedRangeEquals( vector_b ) ); + } + } + + SECTION( "Custom predicate" ) { + + auto close_enough = []( int lhs, int rhs ) { + return std::abs( lhs - rhs ) <= 1; + }; + + SECTION( "Two equal non-empty containers (close enough)" ) { + const std::vector<int> vector_a{ { 1, 10, 20 } }; + const std::vector<int> vector_a_plus_1{ { 11, 21, 2 } }; + CHECK_THAT( vector_a, + UnorderedRangeEquals( vector_a_plus_1, close_enough ) ); + } + SECTION( "Two non-equal non-empty containers (close enough)" ) { + const std::vector<int> vector_a{ { 1, 10, 21 } }; + const std::vector<int> vector_b{ { 11, 21, 3 } }; + CHECK_THAT( vector_a, + !UnorderedRangeEquals( vector_b, close_enough ) ); + } + } + + + SECTION( "Ranges that need ADL begin/end" ) { + unrelated::needs_ADL_begin<int> const + needs_adl1{ 1, 2, 3, 4, 5 }, + needs_adl2{ 1, 2, 3, 4, 5 }; + + REQUIRE_THAT( needs_adl1, UnorderedRangeEquals( needs_adl2 ) ); + } + + SECTION( "Compare against std::initializer_list" ) { + const std::array<int, 3> array_a{ { 1, 10, 20 } }; + + REQUIRE_THAT( array_a, UnorderedRangeEquals( { 10, 20, 1 } ) ); + REQUIRE_THAT( array_a, + UnorderedRangeEquals( { 11, 21, 2 }, []( int l, int r ) { + return std::abs( l - r ) <= 1; + } ) ); + } +} + +/** + * Return true if the type given has a random access iterator type. + */ +template <typename Container> +static constexpr bool ContainerIsRandomAccess( const Container& ) { + using array_iter_category = typename std::iterator_traits< + typename Container::iterator>::iterator_category; + + return std::is_base_of<std::random_access_iterator_tag, + array_iter_category>::value; +} + +TEST_CASE( "Type conversions of RangeEquals and similar", + "[matchers][templated][quantifiers]" ) { + using Catch::Matchers::RangeEquals; + using Catch::Matchers::UnorderedRangeEquals; + + // In these test, we can always test RangeEquals and + // UnorderedRangeEquals in the same way, since we're mostly + // testing the template type deductions (and RangeEquals + // implies UnorderedRangeEquals) + + SECTION( "Container conversions" ) { + SECTION( "Two equal containers of different container types" ) { + const std::array<int, 3> array_int_a{ { 1, 2, 3 } }; + const int c_array[3] = { 1, 2, 3 }; + CHECK_THAT( array_int_a, RangeEquals( c_array ) ); + CHECK_THAT( array_int_a, UnorderedRangeEquals( c_array ) ); + } + SECTION( "Two equal containers of different container types " + "(differ in array N)" ) { + const std::array<int, 3> array_int_3{ { 1, 2, 3 } }; + const std::array<int, 4> array_int_4{ { 1, 2, 3, 4 } }; + CHECK_THAT( array_int_3, !RangeEquals( array_int_4 ) ); + CHECK_THAT( array_int_3, !UnorderedRangeEquals( array_int_4 ) ); + } + SECTION( "Two equal containers of different container types and value " + "types" ) { + const std::array<int, 3> array_int_a{ { 1, 2, 3 } }; + const std::vector<int> vector_char_a{ 1, 2, 3 }; + CHECK_THAT( array_int_a, RangeEquals( vector_char_a ) ); + CHECK_THAT( array_int_a, UnorderedRangeEquals( vector_char_a ) ); + } + SECTION( "Two equal containers, one random access, one not" ) { + const std::array<int, 3> array_int_a{ { 1, 2, 3 } }; + const std::list<int> list_char_a{ 1, 2, 3 }; + + // Verify these types really are different in random access nature + STATIC_REQUIRE( ContainerIsRandomAccess( array_int_a ) != + ContainerIsRandomAccess( list_char_a ) ); + + CHECK_THAT( array_int_a, RangeEquals( list_char_a ) ); + CHECK_THAT( array_int_a, UnorderedRangeEquals( list_char_a ) ); + } + } + + SECTION( "Value type" ) { + SECTION( "Two equal containers of different value types" ) { + const std::vector<int> vector_int_a{ 1, 2, 3 }; + const std::vector<char> vector_char_a{ 1, 2, 3 }; + CHECK_THAT( vector_int_a, RangeEquals( vector_char_a ) ); + CHECK_THAT( vector_int_a, UnorderedRangeEquals( vector_char_a ) ); + } + SECTION( "Two non-equal containers of different value types" ) { + const std::vector<int> vector_int_a{ 1, 2, 3 }; + const std::vector<char> vector_char_b{ 1, 2, 2 }; + CHECK_THAT( vector_int_a, !RangeEquals( vector_char_b ) ); + CHECK_THAT( vector_int_a, !UnorderedRangeEquals( vector_char_b ) ); + } + } + + SECTION( "Ranges with begin that needs ADL" ) { + unrelated::needs_ADL_begin<int> a{ 1, 2, 3 }, b{ 3, 2, 1 }; + REQUIRE_THAT( a, !RangeEquals( b ) ); + REQUIRE_THAT( a, UnorderedRangeEquals( b ) ); + } + + SECTION( "Custom predicate" ) { + + auto close_enough = []( int lhs, int rhs ) { + return std::abs( lhs - rhs ) <= 1; + }; + + SECTION( "Two equal non-empty containers (close enough)" ) { + const std::vector<int> vector_a{ { 1, 2, 3 } }; + const std::array<char, 3> array_a_plus_1{ { 2, 3, 4 } }; + CHECK_THAT( vector_a, + RangeEquals( array_a_plus_1, close_enough ) ); + CHECK_THAT( vector_a, + UnorderedRangeEquals( array_a_plus_1, close_enough ) ); + } + } +}
\ No newline at end of file diff --git a/tests/SelfTest/UsageTests/Message.tests.cpp b/tests/SelfTest/UsageTests/Message.tests.cpp new file mode 100644 index 0000000..7626e00 --- /dev/null +++ b/tests/SelfTest/UsageTests/Message.tests.cpp @@ -0,0 +1,312 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 + +#include <catch2/catch_test_macros.hpp> +#include <iostream> + +TEST_CASE( "INFO and WARN do not abort tests", "[messages][.]" ) { + INFO( "this is a " << "message" ); // This should output the message if a failure occurs + WARN( "this is a " << "warning" ); // This should always output the message but then continue +} + +TEST_CASE( "#1455 - INFO and WARN can start with a linebreak", "[messages][.]" ) { + // Previously these would be hidden from the console reporter output, + // because it would fail at properly reflowing the text + INFO( "\nThis info message starts with a linebreak" ); + WARN( "\nThis warning message starts with a linebreak" ); +} + +TEST_CASE( "SUCCEED counts as a test pass", "[messages]" ) { + SUCCEED( "this is a " << "success" ); +} + +TEST_CASE( "INFO gets logged on failure", "[failing][messages][.]" ) { + INFO( "this message should be logged" ); + INFO( "so should this" ); + int a = 2; + REQUIRE( a == 1 ); +} + +TEST_CASE( "INFO gets logged on failure, even if captured before successful assertions", "[failing][messages][.]" ) { + INFO( "this message may be logged later" ); + int a = 2; + CHECK( a == 2 ); + + INFO( "this message should be logged" ); + + CHECK( a == 1 ); + + INFO( "and this, but later" ); + + CHECK( a == 0 ); + + INFO( "but not this" ); + + CHECK( a == 2 ); +} + +TEST_CASE( "FAIL aborts the test", "[failing][messages][.]" ) { + FAIL( "This is a " << "failure" ); // This should output the message and abort + WARN( "We should never see this"); +} + +TEST_CASE( "FAIL_CHECK does not abort the test", "[failing][messages][.]" ) { + FAIL_CHECK( "This is a " << "failure" ); // This should output the message then continue + WARN( "This message appears in the output"); +} + +TEST_CASE( "FAIL does not require an argument", "[failing][messages][.]" ) { + FAIL(); +} + +TEST_CASE( "SUCCEED does not require an argument", "[messages][.]" ) { + SUCCEED(); +} + +TEST_CASE( "Output from all sections is reported", "[failing][messages][.]" ) { + SECTION( "one" ) { + FAIL( "Message from section one" ); + } + + SECTION( "two" ) { + FAIL( "Message from section two" ); + } +} + +TEST_CASE( "Standard output from all sections is reported", "[messages][.]" ) { + SECTION( "one" ) { + std::cout << "Message from section one\n"; + } + + SECTION( "two" ) { + std::cout << "Message from section two\n"; + } +} + +TEST_CASE( "Standard error is reported and redirected", "[messages][.][approvals]" ) { + SECTION( "std::cerr" ) { + std::cerr << "Write to std::cerr\n"; + } + SECTION( "std::clog" ) { + std::clog << "Write to std::clog\n"; + } + SECTION( "Interleaved writes to cerr and clog" ) { + std::cerr << "Inter"; + std::clog << "leaved"; + std::cerr << ' '; + std::clog << "writes"; + std::cerr << " to error"; + std::clog << " streams\n" << std::flush; + } +} + +TEST_CASE( "INFO is reset for each loop", "[messages][failing][.]" ) { + for( int i=0; i<100; i++ ) + { + INFO( "current counter " << i ); + CAPTURE( i ); + REQUIRE( i < 10 ); + } +} + +TEST_CASE( "The NO_FAIL macro reports a failure but does not fail the test", "[messages]" ) { + CHECK_NOFAIL( 1 == 2 ); +} + +TEST_CASE( "just info", "[info][isolated info][messages]" ) { + INFO( "this should never be seen" ); +} +TEST_CASE( "just failure", "[fail][isolated info][.][messages]" ) { + FAIL( "Previous info should not be seen" ); +} + + +TEST_CASE( "sends information to INFO", "[.][failing]" ) { + INFO( "hi" ); + int i = 7; + CAPTURE( i ); + REQUIRE( false ); +} + +TEST_CASE( "Pointers can be converted to strings", "[messages][.][approvals]" ) { + int p; + WARN( "actual address of p: " << &p ); + WARN( "toString(p): " << ::Catch::Detail::stringify( &p ) ); +} + +template <typename T> +static void unscoped_info( T msg ) { + UNSCOPED_INFO( msg ); +} + +TEST_CASE( "just unscoped info", "[unscoped][info]" ) { + unscoped_info( "this should NOT be seen" ); + unscoped_info( "this also should NOT be seen" ); +} + +TEST_CASE( "just failure after unscoped info", "[failing][.][unscoped][info]" ) { + FAIL( "previous unscoped info SHOULD not be seen" ); +} + +TEST_CASE( "print unscoped info if passing unscoped info is printed", "[unscoped][info]" ) { + unscoped_info( "this MAY be seen IF info is printed for passing assertions" ); + REQUIRE( true ); +} + +TEST_CASE( "prints unscoped info on failure", "[failing][.][unscoped][info]" ) { + unscoped_info( "this SHOULD be seen" ); + unscoped_info( "this SHOULD also be seen" ); + REQUIRE( false ); + unscoped_info( "but this should NOT be seen" ); +} + +TEST_CASE( "not prints unscoped info from previous failures", "[failing][.][unscoped][info]" ) { + unscoped_info( "this MAY be seen only for the FIRST assertion IF info is printed for passing assertions" ); + REQUIRE( true ); + unscoped_info( "this MAY be seen only for the SECOND assertion IF info is printed for passing assertions" ); + REQUIRE( true ); + unscoped_info( "this SHOULD be seen" ); + REQUIRE( false ); +} + +TEST_CASE( "prints unscoped info only for the first assertion", "[failing][.][unscoped][info]" ) { + unscoped_info( "this SHOULD be seen only ONCE" ); + CHECK( false ); + CHECK( true ); + unscoped_info( "this MAY also be seen only ONCE IF info is printed for passing assertions" ); + CHECK( true ); + CHECK( true ); +} + +TEST_CASE( "stacks unscoped info in loops", "[failing][.][unscoped][info]" ) { + UNSCOPED_INFO("Count 1 to 3..."); + for (int i = 1; i <= 3; i++) { + unscoped_info(i); + } + CHECK( false ); + + UNSCOPED_INFO("Count 4 to 6..."); + for (int i = 4; i <= 6; i++) { + unscoped_info(i); + } + CHECK( false ); +} + +TEST_CASE( "mix info, unscoped info and warning", "[unscoped][info]" ) { + INFO("info"); + unscoped_info("unscoped info"); + WARN("and warn may mix"); + WARN("they are not cleared after warnings"); +} + +TEST_CASE( "CAPTURE can deal with complex expressions", "[messages][capture]" ) { + int a = 1; + int b = 2; + int c = 3; + CAPTURE( a, b, c, a + b, a+b, c > b, a == 1 ); + SUCCEED(); +} + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-value" // In (1, 2), the "1" is unused ... +#endif +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-value" // All the comma operators are side-effect free +#endif +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4709) // comma in indexing operator +#endif + +template <typename T1, typename T2> +struct helper_1436 { + helper_1436(T1 t1_, T2 t2_): + t1{ t1_ }, + t2{ t2_ } + {} + T1 t1; + T2 t2; +}; + +template <typename T1, typename T2> +std::ostream& operator<<(std::ostream& out, helper_1436<T1, T2> const& helper) { + out << "{ " << helper.t1 << ", " << helper.t2 << " }"; + return out; +} + +// Clang and gcc have different names for this warning, and clang also +// warns about an unused value. This warning must be disabled for C++20. +#if defined(__GNUG__) && !defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpragmas" +#pragma GCC diagnostic ignored "-Wcomma-subscript" +#elif defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-pragmas" +#pragma clang diagnostic ignored "-Wunknown-warning-option" +#pragma clang diagnostic ignored "-Wdeprecated-comma-subscript" +#pragma clang diagnostic ignored "-Wunused-value" +#endif + +namespace { + template <typename T> + struct custom_index_op { + constexpr custom_index_op( std::initializer_list<T> ) {} + constexpr T operator[]( size_t ) { return T{}; } +#if defined( __cpp_multidimensional_subscript ) && \ + __cpp_multidimensional_subscript >= 202110L + constexpr T operator[]( size_t, size_t, size_t ) const noexcept { + return T{}; + } +#endif + }; +} + +TEST_CASE("CAPTURE can deal with complex expressions involving commas", "[messages][capture]") { + CAPTURE(custom_index_op<int>{1, 2, 3}[0, 1, 2], + custom_index_op<int>{1, 2, 3}[(0, 1)], + custom_index_op<int>{1, 2, 3}[0]); + CAPTURE((helper_1436<int, int>{12, -12}), + (helper_1436<int, int>(-12, 12))); + CAPTURE( (1, 2), (2, 3) ); + SUCCEED(); +} + +#ifdef __GNUG__ +#pragma GCC diagnostic pop +#endif + +TEST_CASE("CAPTURE parses string and character constants", "[messages][capture]") { + CAPTURE(("comma, in string", "escaped, \", "), "single quote in string,',", "some escapes, \\,\\\\"); + CAPTURE("some, ), unmatched, } prenheses {[<"); + CAPTURE('"', '\'', ',', '}', ')', '(', '{'); + SUCCEED(); +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +TEST_CASE( "INFO and UNSCOPED_INFO can stream multiple arguments", + "[messages][info][.failing]" ) { + INFO( "This info" + << " has multiple" + << " parts." ); + UNSCOPED_INFO( "This unscoped info" + << " has multiple" + << " parts." ); + FAIL( "Show infos!" ); +} diff --git a/tests/SelfTest/UsageTests/Misc.tests.cpp b/tests/SelfTest/UsageTests/Misc.tests.cpp new file mode 100644 index 0000000..3697f06 --- /dev/null +++ b/tests/SelfTest/UsageTests/Misc.tests.cpp @@ -0,0 +1,560 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 + +#include <catch2/catch_test_macros.hpp> +#include <catch2/catch_template_test_macros.hpp> +#include <catch2/internal/catch_config_wchar.hpp> +#include <catch2/internal/catch_windows_h_proxy.hpp> + + +#include <iostream> +#include <cerrno> +#include <limits> +#include <array> +#include <tuple> + +namespace { + + static const char* makeString(bool makeNull) { + return makeNull ? nullptr : "valid string"; + } + static bool testCheckedIf(bool flag) { + CHECKED_IF(flag) + return true; + else + return false; + } + static bool testCheckedElse(bool flag) { + CHECKED_ELSE(flag) + return false; + + return true; + } + + static unsigned int Factorial(unsigned int number) { + return number > 1 ? Factorial(number - 1) * number : 1; + } + + static int f() { + return 1; + } + + static void manuallyRegisteredTestFunction() { + SUCCEED("was called"); + } + + struct AutoTestReg { + AutoTestReg() { + REGISTER_TEST_CASE(manuallyRegisteredTestFunction, "ManuallyRegistered"); + } + }; + + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS + static AutoTestReg autoTestReg; + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + + template<typename T> + struct Foo { + size_t size() { return 0; } + }; + + template<typename T, size_t S> + struct Bar { + size_t size() { return S; } + }; + +} + +TEST_CASE( "random SECTION tests", "[.][sections][failing]" ) { + int a = 1; + int b = 2; + + SECTION( "doesn't equal" ) { + REQUIRE( a != b ); + REQUIRE( b != a ); + } + + SECTION( "not equal" ) { + REQUIRE( a != b); + } +} + +TEST_CASE( "nested SECTION tests", "[.][sections][failing]" ) { + int a = 1; + int b = 2; + + SECTION( "doesn't equal" ) { + REQUIRE( a != b ); + REQUIRE( b != a ); + + SECTION( "not equal" ) { + REQUIRE( a != b); + } + } +} + +TEST_CASE( "more nested SECTION tests", "[sections][failing][.]" ) { + int a = 1; + int b = 2; + + SECTION( "doesn't equal" ) { + SECTION( "equal" ) { + REQUIRE( a == b ); + } + + SECTION( "not equal" ) { + REQUIRE( a != b ); + } + SECTION( "less than" ) { + REQUIRE( a < b ); + } + } +} + +TEST_CASE( "even more nested SECTION tests", "[sections]" ) { + SECTION( "c" ) { + SECTION( "d (leaf)" ) { + SUCCEED(); // avoid failing due to no tests + } + + SECTION( "e (leaf)" ) { + SUCCEED(); // avoid failing due to no tests + } + } + + SECTION( "f (leaf)" ) { + SUCCEED(); // avoid failing due to no tests + } +} + +TEST_CASE( "looped SECTION tests", "[.][failing][sections]" ) { + int a = 1; + + for( int b = 0; b < 10; ++b ) { + DYNAMIC_SECTION( "b is currently: " << b ) { + CHECK( b > a ); + } + } +} + +TEST_CASE( "looped tests", "[.][failing]" ) { + static const int fib[] = { 1, 1, 2, 3, 5, 8, 13, 21 }; + + for( std::size_t i=0; i < sizeof(fib)/sizeof(int); ++i ) { + INFO( "Testing if fib[" << i << "] (" << fib[i] << ") is even" ); + CHECK( ( fib[i] % 2 ) == 0 ); + } +} + +TEST_CASE( "Sends stuff to stdout and stderr", "[.]" ) { + std::cout << "A string sent directly to stdout\n" << std::flush; + std::cerr << "A string sent directly to stderr\n" << std::flush; + std::clog << "A string sent to stderr via clog\n" << std::flush; +} + +TEST_CASE( "null strings" ) { + REQUIRE( makeString( false ) != static_cast<char*>(nullptr)); + REQUIRE( makeString( true ) == static_cast<char*>(nullptr)); +} + +TEST_CASE( "checkedIf" ) { + REQUIRE( testCheckedIf( true ) ); +} + +TEST_CASE( "checkedIf, failing", "[failing][.]" ) { + REQUIRE( testCheckedIf( false ) ); +} + +TEST_CASE( "checkedElse" ) { + REQUIRE( testCheckedElse( true ) ); +} + +TEST_CASE( "checkedElse, failing", "[failing][.]" ) { + REQUIRE( testCheckedElse( false ) ); +} + +TEST_CASE("Testing checked-if", "[checked-if]") { + CHECKED_IF(true) { + SUCCEED(); + } + CHECKED_IF(false) { + FAIL(); + } + CHECKED_ELSE(true) { + FAIL(); + } + CHECKED_ELSE(false) { + SUCCEED(); + } +} + +TEST_CASE("Testing checked-if 2", "[checked-if][!shouldfail]") { + CHECKED_IF(true) { + FAIL(); + } + // If the checked if is not entered, this passes and the test + // fails, because of the [!shouldfail] tag. + SUCCEED(); +} + +TEST_CASE("Testing checked-if 3", "[checked-if][!shouldfail]") { + CHECKED_ELSE(false) { + FAIL(); + } + // If the checked false is not entered, this passes and the test + // fails, because of the [!shouldfail] tag. + SUCCEED(); +} + +[[noreturn]] +TEST_CASE("Testing checked-if 4", "[checked-if][!shouldfail]") { + CHECKED_ELSE(true) {} + throw std::runtime_error("Uncaught exception should fail!"); +} + +[[noreturn]] +TEST_CASE("Testing checked-if 5", "[checked-if][!shouldfail]") { + CHECKED_ELSE(false) {} + throw std::runtime_error("Uncaught exception should fail!"); +} + +TEST_CASE( "xmlentitycheck" ) { + SECTION( "embedded xml: <test>it should be possible to embed xml characters, such as <, \" or &, or even whole <xml>documents</xml> within an attribute</test>" ) { + SUCCEED(); // We need this here to stop it failing due to no tests + } + SECTION( "encoded chars: these should all be encoded: &&&\"\"\"<<<&\"<<&\"" ) { + SUCCEED(); // We need this here to stop it failing due to no tests + } +} + +TEST_CASE( "send a single char to INFO", "[failing][.]" ) { + INFO(3); + REQUIRE(false); +} + +TEST_CASE( "Factorials are computed", "[factorial]" ) { + REQUIRE( Factorial(0) == 1 ); + REQUIRE( Factorial(1) == 1 ); + REQUIRE( Factorial(2) == 2 ); + REQUIRE( Factorial(3) == 6 ); + REQUIRE( Factorial(10) == 3628800 ); +} + +TEST_CASE( "An empty test with no assertions", "[empty]" ) {} + +TEST_CASE( "Nice descriptive name", "[tag1][tag2][tag3][.]" ) { + WARN( "This one ran" ); +} +TEST_CASE( "first tag", "[tag1]" ) {} +TEST_CASE( "second tag", "[tag2]" ) {} + +TEST_CASE( "vectors can be sized and resized", "[vector]" ) { + + std::vector<int> v( 5 ); + + REQUIRE( v.size() == 5 ); + REQUIRE( v.capacity() >= 5 ); + + SECTION( "resizing bigger changes size and capacity" ) { + v.resize( 10 ); + + REQUIRE( v.size() == 10 ); + REQUIRE( v.capacity() >= 10 ); + } + SECTION( "resizing smaller changes size but not capacity" ) { + v.resize( 0 ); + + REQUIRE( v.size() == 0 ); + REQUIRE( v.capacity() >= 5 ); + + SECTION( "We can use the 'swap trick' to reset the capacity" ) { + std::vector<int> empty; + empty.swap( v ); + + REQUIRE( v.capacity() == 0 ); + } + } + SECTION( "reserving bigger changes capacity but not size" ) { + v.reserve( 10 ); + + REQUIRE( v.size() == 5 ); + REQUIRE( v.capacity() >= 10 ); + } + SECTION( "reserving smaller does not change size or capacity" ) { + v.reserve( 0 ); + + REQUIRE( v.size() == 5 ); + REQUIRE( v.capacity() >= 5 ); + } +} + +TEMPLATE_TEST_CASE( "TemplateTest: vectors can be sized and resized", "[vector][template]", int, float, std::string, (std::tuple<int,float>) ) { + + std::vector<TestType> v( 5 ); + + REQUIRE( v.size() == 5 ); + REQUIRE( v.capacity() >= 5 ); + + SECTION( "resizing bigger changes size and capacity" ) { + v.resize( 10 ); + + REQUIRE( v.size() == 10 ); + REQUIRE( v.capacity() >= 10 ); + } + SECTION( "resizing smaller changes size but not capacity" ) { + v.resize( 0 ); + + REQUIRE( v.size() == 0 ); + REQUIRE( v.capacity() >= 5 ); + + SECTION( "We can use the 'swap trick' to reset the capacity" ) { + std::vector<TestType> empty; + empty.swap( v ); + + REQUIRE( v.capacity() == 0 ); + } + } + SECTION( "reserving bigger changes capacity but not size" ) { + v.reserve( 10 ); + + REQUIRE( v.size() == 5 ); + REQUIRE( v.capacity() >= 10 ); + } + SECTION( "reserving smaller does not change size or capacity" ) { + v.reserve( 0 ); + + REQUIRE( v.size() == 5 ); + REQUIRE( v.capacity() >= 5 ); + } +} + +TEMPLATE_TEST_CASE_SIG("TemplateTestSig: vectors can be sized and resized", "[vector][template][nttp]", ((typename TestType, int V), TestType, V), (int,5), (float,4), (std::string,15), ((std::tuple<int, float>), 6)) { + + std::vector<TestType> v(V); + + REQUIRE(v.size() == V); + REQUIRE(v.capacity() >= V); + + SECTION("resizing bigger changes size and capacity") { + v.resize(2 * V); + + REQUIRE(v.size() == 2 * V); + REQUIRE(v.capacity() >= 2 * V); + } + SECTION("resizing smaller changes size but not capacity") { + v.resize(0); + + REQUIRE(v.size() == 0); + REQUIRE(v.capacity() >= V); + + SECTION("We can use the 'swap trick' to reset the capacity") { + std::vector<TestType> empty; + empty.swap(v); + + REQUIRE(v.capacity() == 0); + } + } + SECTION("reserving bigger changes capacity but not size") { + v.reserve(2 * V); + + REQUIRE(v.size() == V); + REQUIRE(v.capacity() >= 2 * V); + } + SECTION("reserving smaller does not change size or capacity") { + v.reserve(0); + + REQUIRE(v.size() == V); + REQUIRE(v.capacity() >= V); + } +} + +TEMPLATE_PRODUCT_TEST_CASE("A Template product test case", "[template][product]", (std::vector, Foo), (int, float)) { + TestType x; + REQUIRE(x.size() == 0); +} + +TEMPLATE_PRODUCT_TEST_CASE_SIG("A Template product test case with array signature", "[template][product][nttp]", ((typename T, size_t S), T, S), (std::array, Bar), ((int, 9), (float, 42))) { + TestType x; + REQUIRE(x.size() > 0); +} + +TEMPLATE_PRODUCT_TEST_CASE("Product with differing arities", "[template][product]", std::tuple, (int, (int, double), (int, double, float))) { + REQUIRE(std::tuple_size<TestType>::value >= 1); +} + +using MyTypes = std::tuple<int, char, float>; +TEMPLATE_LIST_TEST_CASE("Template test case with test types specified inside std::tuple", "[template][list]", MyTypes) +{ + REQUIRE(std::is_arithmetic<TestType>::value); +} + +struct NonDefaultConstructibleType { + NonDefaultConstructibleType() = delete; +}; + +using MyNonDefaultConstructibleTypes = std::tuple<NonDefaultConstructibleType, float>; +TEMPLATE_LIST_TEST_CASE("Template test case with test types specified inside non-default-constructible std::tuple", "[template][list]", MyNonDefaultConstructibleTypes) +{ + REQUIRE(std::is_trivially_copyable<TestType>::value); +} + +struct NonCopyableAndNonMovableType { + NonCopyableAndNonMovableType() = default; + + NonCopyableAndNonMovableType(NonCopyableAndNonMovableType const &) = delete; + NonCopyableAndNonMovableType(NonCopyableAndNonMovableType &&) = delete; + auto operator=(NonCopyableAndNonMovableType const &) -> NonCopyableAndNonMovableType & = delete; + auto operator=(NonCopyableAndNonMovableType &&) -> NonCopyableAndNonMovableType & = delete; +}; + +using NonCopyableAndNonMovableTypes = std::tuple<NonCopyableAndNonMovableType, float>; +TEMPLATE_LIST_TEST_CASE("Template test case with test types specified inside non-copyable and non-movable std::tuple", "[template][list]", NonCopyableAndNonMovableTypes) +{ + REQUIRE(std::is_default_constructible<TestType>::value); +} + +// https://github.com/philsquared/Catch/issues/166 +TEST_CASE("A couple of nested sections followed by a failure", "[failing][.]") { + SECTION("Outer") + SECTION("Inner") + SUCCEED("that's not flying - that's failing in style"); + + FAIL("to infinity and beyond"); +} + +TEST_CASE("not allowed", "[!throws]") { + // This test case should not be included if you run with -e on the command line + SUCCEED(); +} + +TEST_CASE( "Tabs and newlines show in output", "[.][whitespace][failing]" ) { + + // Based on issue #242 + std::string s1 = "if ($b == 10) {\n\t\t$a\t= 20;\n}"; + std::string s2 = "if ($b == 10) {\n\t$a = 20;\n}\n"; + CHECK( s1 == s2 ); +} + + +#if defined(CATCH_CONFIG_WCHAR) +TEST_CASE( "toString on const wchar_t const pointer returns the string contents", "[toString]" ) { + const wchar_t * const s = L"wide load"; + std::string result = ::Catch::Detail::stringify( s ); + CHECK( result == "\"wide load\"" ); +} + +TEST_CASE( "toString on const wchar_t pointer returns the string contents", "[toString]" ) { + const wchar_t * s = L"wide load"; + std::string result = ::Catch::Detail::stringify( s ); + CHECK( result == "\"wide load\"" ); +} + +TEST_CASE( "toString on wchar_t const pointer returns the string contents", "[toString]" ) { + auto const s = const_cast<wchar_t*>( L"wide load" ); + std::string result = ::Catch::Detail::stringify( s ); + CHECK( result == "\"wide load\"" ); +} + +TEST_CASE( "toString on wchar_t returns the string contents", "[toString]" ) { + auto s = const_cast<wchar_t*>( L"wide load" ); + std::string result = ::Catch::Detail::stringify( s ); + CHECK( result == "\"wide load\"" ); +} +#endif // CATCH_CONFIG_WCHAR + +TEST_CASE( "long long" ) { + constexpr long long l = std::numeric_limits<long long>::max(); + + REQUIRE( l == std::numeric_limits<long long>::max() ); +} + +TEST_CASE( "This test 'should' fail but doesn't", "[.][failing][!shouldfail]" ) { + SUCCEED( "oops!" ); +} + +TEST_CASE( "# A test name that starts with a #" ) { + SUCCEED( "yay" ); +} + +TEST_CASE( "#835 -- errno should not be touched by Catch2", "[.][failing][!shouldfail]" ) { + errno = 1; + // Check that reporting failed test doesn't change errno. + CHECK(f() == 0); + // We want to avoid expanding `errno` macro in assertion, because + // we capture the expression after macro expansion, and would have + // to normalize the ways different platforms spell `errno`. + const auto errno_after = errno; + REQUIRE(errno_after == 1); +} + +TEST_CASE( "#961 -- Dynamically created sections should all be reported", "[.]" ) { + for (char i = '0'; i < '5'; ++i) { + SECTION(std::string("Looped section ") + i) { + SUCCEED( "Everything is OK" ); + } + } +} + +TEST_CASE( "#1175 - Hidden Test", "[.]" ) { + // Just for checking that hidden test is not listed by default + SUCCEED(); +} + +TEMPLATE_TEST_CASE_SIG("#1954 - 7 arg template test case sig compiles", "[regression][.compilation]", + ((int Tnx, int Tnu, int Tny, int Tph, int Tch, int Tineq, int Teq), Tnx, Tnu, Tny, Tph, Tch, Tineq, Teq), + (1, 1, 1, 1, 1, 0, 0), (5, 1, 1, 1, 1, 0, 0), (5, 3, 1, 1, 1, 0, 0)) { + SUCCEED(); +} + +TEST_CASE("Same test name but with different tags is fine", "[.approvals][some-tag]") {} +TEST_CASE("Same test name but with different tags is fine", "[.approvals][other-tag]") {} + +// MinGW doesn't support __try, and Clang has only very partial support +#if defined(_MSC_VER) +void throw_and_catch() +{ + __try { + RaiseException(0xC0000005, 0, 0, NULL); + } + __except (1) + { + + } +} + + +TEST_CASE("Validate SEH behavior - handled", "[approvals][FatalConditionHandler][CATCH_PLATFORM_WINDOWS]") +{ + // Validate that Catch2 framework correctly handles tests raising and handling SEH exceptions. + throw_and_catch(); +} + +void throw_no_catch() +{ + RaiseException(0xC0000005, 0, 0, NULL); +} + +TEST_CASE("Validate SEH behavior - unhandled", "[.approvals][FatalConditionHandler][CATCH_PLATFORM_WINDOWS]") +{ + // Validate that Catch2 framework correctly handles tests raising and not handling SEH exceptions. + throw_no_catch(); +} + +static LONG CALLBACK dummyExceptionFilter(PEXCEPTION_POINTERS ExceptionInfo) { + return EXCEPTION_CONTINUE_SEARCH; +} + +TEST_CASE("Validate SEH behavior - no crash for stack unwinding", "[approvals][!throws][!shouldfail][FatalConditionHandler][CATCH_PLATFORM_WINDOWS]") +{ + // Trigger stack unwinding with SEH top-level filter changed and validate the test fails expectedly with no application crash + SetUnhandledExceptionFilter(dummyExceptionFilter); + throw 1; +} + +#endif // _MSC_VER diff --git a/tests/SelfTest/UsageTests/Skip.tests.cpp b/tests/SelfTest/UsageTests/Skip.tests.cpp new file mode 100644 index 0000000..661795e --- /dev/null +++ b/tests/SelfTest/UsageTests/Skip.tests.cpp @@ -0,0 +1,100 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 + +#include <catch2/catch_test_macros.hpp> +#include <catch2/generators/catch_generators_range.hpp> + +#include <iostream> + +TEST_CASE( "tests can be skipped dynamically at runtime", "[skipping]" ) { + SKIP(); + FAIL( "this is not reached" ); +} + +TEST_CASE( "skipped tests can optionally provide a reason", "[skipping]" ) { + const int answer = 43; + SKIP( "skipping because answer = " << answer ); + FAIL( "this is not reached" ); +} + +TEST_CASE( "sections can be skipped dynamically at runtime", "[skipping]" ) { + SECTION( "not skipped" ) { SUCCEED(); } + SECTION( "skipped" ) { SKIP(); } + SECTION( "also not skipped" ) { SUCCEED(); } +} + +TEST_CASE( "nested sections can be skipped dynamically at runtime", + "[skipping]" ) { + SECTION( "A" ) { std::cout << "a"; } + SECTION( "B" ) { + SECTION( "B1" ) { std::cout << "b1"; } + SECTION( "B2" ) { SKIP(); } + } + std::cout << "!\n"; +} + +TEST_CASE( "dynamic skipping works with generators", "[skipping]" ) { + const int answer = GENERATE( 41, 42, 43 ); + if ( answer != 42 ) { SKIP( "skipping because answer = " << answer ); } + SUCCEED(); +} + +TEST_CASE( "failed assertions before SKIP cause test case to fail", + "[skipping][!shouldfail]" ) { + CHECK( 3 == 4 ); + SKIP(); +} + +TEST_CASE( "a succeeding test can still be skipped", + "[skipping][!shouldfail]" ) { + SUCCEED(); + SKIP(); +} + +TEST_CASE( "failing in some unskipped sections causes entire test case to fail", + "[skipping][!shouldfail]" ) { + SECTION( "skipped" ) { SKIP(); } + SECTION( "not skipped" ) { FAIL(); } +} + +TEST_CASE( "failing for some generator values causes entire test case to fail", + "[skipping][!shouldfail]" ) { + int i = GENERATE( 1, 2, 3, 4 ); + if ( i % 2 == 0 ) { + SKIP(); + } else { + FAIL(); + } +} + +namespace { + class test_skip_generator : public Catch::Generators::IGenerator<int> { + public: + explicit test_skip_generator() { SKIP( "This generator is empty" ); } + + auto get() const -> int const& override { + static constexpr int value = 1; + return value; + } + + auto next() -> bool override { return false; } + }; + + static auto make_test_skip_generator() + -> Catch::Generators::GeneratorWrapper<int> { + return { new test_skip_generator() }; + } + +} // namespace + +TEST_CASE( "Empty generators can SKIP in constructor", "[skipping]" ) { + // The generator signals emptiness with `SKIP` + auto sample = GENERATE( make_test_skip_generator() ); + // This assertion would fail, but shouldn't trigger + REQUIRE( sample == 0 ); +} diff --git a/tests/SelfTest/UsageTests/ToStringByte.tests.cpp b/tests/SelfTest/UsageTests/ToStringByte.tests.cpp new file mode 100644 index 0000000..624abbf --- /dev/null +++ b/tests/SelfTest/UsageTests/ToStringByte.tests.cpp @@ -0,0 +1,23 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 + +#include <catch2/catch_test_macros.hpp> + +#if defined(CATCH_CONFIG_CPP17_BYTE) + +TEST_CASE( "std::byte -> toString", "[toString][byte][approvals]" ) { + using type = std::byte; + REQUIRE( "0" == ::Catch::Detail::stringify( type{ 0 } ) ); +} + +TEST_CASE( "std::vector<std::byte> -> toString", "[toString][byte][approvals]" ) { + using type = std::vector<std::byte>; + REQUIRE( "{ 0, 1, 2 }" == ::Catch::Detail::stringify( type{ std::byte{0}, std::byte{1}, std::byte{2} } ) ); +} + +#endif // CATCH_INTERNAL_CONFIG_CPP17_BYTE diff --git a/tests/SelfTest/UsageTests/ToStringChrono.tests.cpp b/tests/SelfTest/UsageTests/ToStringChrono.tests.cpp new file mode 100644 index 0000000..744b899 --- /dev/null +++ b/tests/SelfTest/UsageTests/ToStringChrono.tests.cpp @@ -0,0 +1,51 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 + +#include <catch2/catch_test_macros.hpp> + +#include <chrono> +#include <cstdint> + +TEST_CASE("Stringifying std::chrono::duration helpers", "[toString][chrono]") { + // No literals because we still support c++11 + auto hour = std::chrono::hours(1); + auto minute = std::chrono::minutes(1); + auto seconds = std::chrono::seconds(60); + auto micro = std::chrono::microseconds(1); + auto milli = std::chrono::milliseconds(1); + auto nano = std::chrono::nanoseconds(1); + REQUIRE(minute == seconds); + REQUIRE(hour != seconds); + REQUIRE(micro != milli); + REQUIRE(nano != micro); +} + +TEST_CASE("Stringifying std::chrono::duration with weird ratios", "[toString][chrono]") { + std::chrono::duration<int64_t, std::ratio<30>> half_minute(1); + std::chrono::duration<int64_t, std::ratio<1, 1000000000000>> pico_second(1); + std::chrono::duration<int64_t, std::ratio<1, 1000000000000000>> femto_second(1); + std::chrono::duration<int64_t, std::ratio<1, 1000000000000000000>> atto_second(1); + REQUIRE(half_minute != femto_second); + REQUIRE(pico_second != atto_second); +} + +TEST_CASE("Stringifying std::chrono::time_point<system_clock>", "[toString][chrono]") { + auto now = std::chrono::system_clock::now(); + auto later = now + std::chrono::minutes(2); + REQUIRE(now != later); +} + +TEST_CASE("Stringifying std::chrono::time_point<Clock>", "[toString][chrono][!nonportable]") { + auto now = std::chrono::high_resolution_clock::now(); + auto later = now + std::chrono::minutes(2); + REQUIRE(now != later); + + auto now2 = std::chrono::steady_clock::now(); + auto later2 = now2 + std::chrono::minutes(2); + REQUIRE(now2 != later2); +} diff --git a/tests/SelfTest/UsageTests/ToStringGeneral.tests.cpp b/tests/SelfTest/UsageTests/ToStringGeneral.tests.cpp new file mode 100644 index 0000000..78c0c80 --- /dev/null +++ b/tests/SelfTest/UsageTests/ToStringGeneral.tests.cpp @@ -0,0 +1,200 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 + +#define CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER +#include <catch2/catch_test_macros.hpp> + +#include <map> +#include <set> + +TEST_CASE( "Character pretty printing" ){ + SECTION("Specifically escaped"){ + CHECK(::Catch::Detail::stringify('\t') == "'\\t'"); + CHECK(::Catch::Detail::stringify('\n') == "'\\n'"); + CHECK(::Catch::Detail::stringify('\r') == "'\\r'"); + CHECK(::Catch::Detail::stringify('\f') == "'\\f'"); + } + SECTION("General chars"){ + CHECK(::Catch::Detail::stringify( ' ' ) == "' '" ); + CHECK(::Catch::Detail::stringify( 'A' ) == "'A'" ); + CHECK(::Catch::Detail::stringify( 'z' ) == "'z'" ); + } + SECTION("Low ASCII"){ + CHECK(::Catch::Detail::stringify( '\0' ) == "0" ); + CHECK(::Catch::Detail::stringify( static_cast<char>(2) ) == "2" ); + CHECK(::Catch::Detail::stringify( static_cast<char>(5) ) == "5" ); + } +} + + +TEST_CASE( "Capture and info messages" ) { + SECTION("Capture should stringify like assertions") { + int i = 2; + CAPTURE(i); + REQUIRE(true); + } + SECTION("Info should NOT stringify the way assertions do") { + int i = 3; + INFO(i); + REQUIRE(true); + } +} + +TEST_CASE( "std::map is convertible string", "[toString]" ) { + + SECTION( "empty" ) { + std::map<std::string, int> emptyMap; + + REQUIRE( Catch::Detail::stringify( emptyMap ) == "{ }" ); + } + + SECTION( "single item" ) { + std::map<std::string, int> map = { { "one", 1 } }; + + REQUIRE( Catch::Detail::stringify( map ) == "{ { \"one\", 1 } }" ); + } + + SECTION( "several items" ) { + std::map<std::string, int> map = { + { "abc", 1 }, + { "def", 2 }, + { "ghi", 3 } + }; + + REQUIRE( Catch::Detail::stringify( map ) == "{ { \"abc\", 1 }, { \"def\", 2 }, { \"ghi\", 3 } }" ); + } +} + +TEST_CASE( "std::set is convertible string", "[toString]" ) { + + SECTION( "empty" ) { + std::set<std::string> emptySet; + + REQUIRE( Catch::Detail::stringify( emptySet ) == "{ }" ); + } + + SECTION( "single item" ) { + std::set<std::string> set = { "one" }; + + REQUIRE( Catch::Detail::stringify( set ) == "{ \"one\" }" ); + } + + SECTION( "several items" ) { + std::set<std::string> set = { "abc", "def", "ghi" }; + + REQUIRE( Catch::Detail::stringify( set ) == "{ \"abc\", \"def\", \"ghi\" }" ); + } +} + +TEST_CASE("Static arrays are convertible to string", "[toString]") { + SECTION("Single item") { + int singular[1] = { 1 }; + REQUIRE(Catch::Detail::stringify(singular) == "{ 1 }"); + } + SECTION("Multiple") { + int arr[3] = { 3, 2, 1 }; + REQUIRE(Catch::Detail::stringify(arr) == "{ 3, 2, 1 }"); + } + SECTION("Non-trivial inner items") { + std::vector<std::string> arr[2] = { {"1:1", "1:2", "1:3"}, {"2:1", "2:2"} }; + REQUIRE(Catch::Detail::stringify(arr) == R"({ { "1:1", "1:2", "1:3" }, { "2:1", "2:2" } })"); + } +} + +#ifdef CATCH_CONFIG_CPP17_STRING_VIEW + +TEST_CASE("String views are stringified like other strings", "[toString][approvals]") { + std::string_view view{"abc"}; + CHECK(Catch::Detail::stringify(view) == R"("abc")"); + + std::string_view arr[] { view }; + CHECK(Catch::Detail::stringify(arr) == R"({ "abc" })"); +} + +#endif + +TEST_CASE("Precision of floating point stringification can be set", "[toString][floatingPoint]") { + SECTION("Floats") { + using sm = Catch::StringMaker<float>; + const auto oldPrecision = sm::precision; + + const float testFloat = 1.12345678901234567899f; + sm::precision = 5; + auto str1 = sm::convert( testFloat ); + // "1." prefix = 2 chars, f suffix is another char + CHECK(str1.size() == 3 + 5); + + sm::precision = 10; + auto str2 = sm::convert(testFloat); + REQUIRE(str2.size() == 3 + 10); + sm::precision = oldPrecision; + } + SECTION("Double") { + using sm = Catch::StringMaker<double>; + const auto oldPrecision = sm::precision; + + const double testDouble = 1.123456789012345678901234567899; + sm::precision = 5; + auto str1 = sm::convert(testDouble); + // "1." prefix = 2 chars + CHECK(str1.size() == 2 + 5); + + sm::precision = 15; + auto str2 = sm::convert(testDouble); + REQUIRE(str2.size() == 2 + 15); + + sm::precision = oldPrecision; + } +} + +namespace { + +struct WhatException : std::exception { + char const* what() const noexcept override { + return "This exception has overridden what() method"; + } + ~WhatException() override; +}; + +struct OperatorException : std::exception { + ~OperatorException() override; +}; + +std::ostream& operator<<(std::ostream& out, OperatorException const&) { + out << "OperatorException"; + return out; +} + +struct StringMakerException : std::exception { + ~StringMakerException() override; +}; + +} // end anonymous namespace + +namespace Catch { +template <> +struct StringMaker<StringMakerException> { + static std::string convert(StringMakerException const&) { + return "StringMakerException"; + } +}; +} + +// Avoid -Wweak-tables +WhatException::~WhatException() = default; +OperatorException::~OperatorException() = default; +StringMakerException::~StringMakerException() = default; + + + + +TEST_CASE("Exception as a value (e.g. in REQUIRE_THROWS_MATCHES) can be stringified", "[toString][exception]") { + REQUIRE(::Catch::Detail::stringify(WhatException{}) == "This exception has overridden what() method"); + REQUIRE(::Catch::Detail::stringify(OperatorException{}) == "OperatorException"); + REQUIRE(::Catch::Detail::stringify(StringMakerException{}) == "StringMakerException"); +} diff --git a/tests/SelfTest/UsageTests/ToStringOptional.tests.cpp b/tests/SelfTest/UsageTests/ToStringOptional.tests.cpp new file mode 100644 index 0000000..3671771 --- /dev/null +++ b/tests/SelfTest/UsageTests/ToStringOptional.tests.cpp @@ -0,0 +1,35 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 + +#define CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER +#include <catch2/catch_test_macros.hpp> + +#if defined(CATCH_CONFIG_CPP17_OPTIONAL) + +TEST_CASE( "std::optional<int> -> toString", "[toString][optional][approvals]" ) { + using type = std::optional<int>; + REQUIRE( "{ }" == ::Catch::Detail::stringify( type{} ) ); + REQUIRE( "0" == ::Catch::Detail::stringify( type{ 0 } ) ); +} + +TEST_CASE( "std::optional<std::string> -> toString", "[toString][optional][approvals]" ) { + using type = std::optional<std::string>; + REQUIRE( "{ }" == ::Catch::Detail::stringify( type{} ) ); + REQUIRE( "\"abc\"" == ::Catch::Detail::stringify( type{ "abc" } ) ); +} + +TEST_CASE( "std::vector<std::optional<int> > -> toString", "[toString][optional][approvals]" ) { + using type = std::vector<std::optional<int> >; + REQUIRE( "{ 0, { }, 2 }" == ::Catch::Detail::stringify( type{ 0, {}, 2 } ) ); +} + +TEST_CASE( "std::nullopt -> toString", "[toString][optional][approvals]" ) { + REQUIRE( "{ }" == ::Catch::Detail::stringify( std::nullopt ) ); +} + +#endif // CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL diff --git a/tests/SelfTest/UsageTests/ToStringPair.tests.cpp b/tests/SelfTest/UsageTests/ToStringPair.tests.cpp new file mode 100644 index 0000000..f5cb239 --- /dev/null +++ b/tests/SelfTest/UsageTests/ToStringPair.tests.cpp @@ -0,0 +1,38 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 + +#define CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER +#include <catch2/catch_test_macros.hpp> + +TEST_CASE( "std::pair<int,std::string> -> toString", "[toString][pair]" ) { + std::pair<int,std::string> value( 34, "xyzzy" ); + REQUIRE( ::Catch::Detail::stringify( value ) == "{ 34, \"xyzzy\" }" ); +} + +TEST_CASE( "std::pair<int,const std::string> -> toString", "[toString][pair]" ) { + std::pair<int,const std::string> value( 34, "xyzzy" ); + REQUIRE( ::Catch::Detail::stringify(value) == "{ 34, \"xyzzy\" }" ); +} + +TEST_CASE( "std::vector<std::pair<std::string,int> > -> toString", "[toString][pair]" ) { + std::vector<std::pair<std::string,int> > pr; + pr.push_back( std::make_pair("green", 55 ) ); + REQUIRE( ::Catch::Detail::stringify( pr ) == "{ { \"green\", 55 } }" ); +} + +// This is pretty contrived - I figure if this works, anything will... +TEST_CASE( "pair<pair<int,const char *,pair<std::string,int> > -> toString", "[toString][pair]" ) { + typedef std::pair<int,const char *> left_t; + typedef std::pair<std::string,int> right_t; + + left_t left( 42, "Arthur" ); + right_t right( "Ford", 24 ); + + std::pair<left_t,right_t> pair( left, right ); + REQUIRE( ::Catch::Detail::stringify( pair ) == "{ { 42, \"Arthur\" }, { \"Ford\", 24 } }" ); +} diff --git a/tests/SelfTest/UsageTests/ToStringTuple.tests.cpp b/tests/SelfTest/UsageTests/ToStringTuple.tests.cpp new file mode 100644 index 0000000..9d1d2c4 --- /dev/null +++ b/tests/SelfTest/UsageTests/ToStringTuple.tests.cpp @@ -0,0 +1,54 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 + +#define CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER +#include <catch2/catch_test_macros.hpp> + +#include <tuple> + +TEST_CASE( "tuple<>", "[toString][tuple]" ) +{ + typedef std::tuple<> type; + CHECK( "{ }" == ::Catch::Detail::stringify(type{}) ); + type value {}; + CHECK( "{ }" == ::Catch::Detail::stringify(value) ); +} + +TEST_CASE( "tuple<int>", "[toString][tuple]" ) +{ + typedef std::tuple<int> type; + CHECK( "{ 0 }" == ::Catch::Detail::stringify(type{0}) ); +} + + +TEST_CASE( "tuple<float,int>", "[toString][tuple]" ) +{ + typedef std::tuple<float,int> type; + CHECK( "1.5f" == ::Catch::Detail::stringify(float(1.5)) ); + CHECK( "{ 1.5f, 0 }" == ::Catch::Detail::stringify(type{1.5f,0}) ); +} + +TEST_CASE( "tuple<string,string>", "[toString][tuple]" ) +{ + typedef std::tuple<std::string,std::string> type; + CHECK( "{ \"hello\", \"world\" }" == ::Catch::Detail::stringify(type{"hello","world"}) ); +} + +TEST_CASE( "tuple<tuple<int>,tuple<>,float>", "[toString][tuple]" ) +{ + typedef std::tuple<std::tuple<int>,std::tuple<>,float> type; + type value { std::tuple<int>{42}, {}, 1.5f }; + CHECK( "{ { 42 }, { }, 1.5f }" == ::Catch::Detail::stringify(value) ); +} + +TEST_CASE( "tuple<nullptr,int,const char *>", "[approvals][toString][tuple]" ) { + typedef std::tuple<std::nullptr_t,int,const char *> type; + type value { nullptr, 42, "Catch me" }; + CHECK( "{ nullptr, 42, \"Catch me\" }" == ::Catch::Detail::stringify(value) ); +} + diff --git a/tests/SelfTest/UsageTests/ToStringVariant.tests.cpp b/tests/SelfTest/UsageTests/ToStringVariant.tests.cpp new file mode 100644 index 0000000..197ba55 --- /dev/null +++ b/tests/SelfTest/UsageTests/ToStringVariant.tests.cpp @@ -0,0 +1,99 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 + +#define CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER +#include <catch2/catch_test_macros.hpp> + +#if defined(CATCH_CONFIG_CPP17_VARIANT) + +#include <string> +#include <variant> + +// We need 2 types with non-trivial copies/moves +struct MyType1 { + MyType1() = default; + [[noreturn]] MyType1(MyType1 const&) { throw 1; } + MyType1& operator=(MyType1 const&) { throw 3; } +}; +struct MyType2 { + MyType2() = default; + [[noreturn]] MyType2(MyType2 const&) { throw 2; } + MyType2& operator=(MyType2 const&) { throw 4; } +}; + +TEST_CASE( "variant<std::monostate>", "[toString][variant][approvals]") +{ + using type = std::variant<std::monostate>; + CHECK( "{ }" == ::Catch::Detail::stringify(type{}) ); + type value {}; + CHECK( "{ }" == ::Catch::Detail::stringify(value) ); + CHECK( "{ }" == ::Catch::Detail::stringify(std::get<0>(value)) ); +} + +TEST_CASE( "variant<int>", "[toString][variant][approvals]") +{ + using type = std::variant<int>; + CHECK( "0" == ::Catch::Detail::stringify(type{0}) ); +} + +TEST_CASE( "variant<float, int>", "[toString][variant][approvals]") +{ + using type = std::variant<float, int>; + CHECK( "0.5f" == ::Catch::Detail::stringify(type{0.5f}) ); + CHECK( "0" == ::Catch::Detail::stringify(type{0}) ); +} + +TEST_CASE( "variant -- valueless-by-exception", "[toString][variant][approvals]" ) { + using type = std::variant<MyType1, MyType2>; + + type value; + REQUIRE_THROWS_AS(value.emplace<MyType2>(MyType2{}), int); + REQUIRE(value.valueless_by_exception()); + CHECK("{valueless variant}" == ::Catch::Detail::stringify(value)); +} + + +TEST_CASE( "variant<string, int>", "[toString][variant][approvals]") +{ + using type = std::variant<std::string, int>; + CHECK( "\"foo\"" == ::Catch::Detail::stringify(type{"foo"}) ); + CHECK( "0" == ::Catch::Detail::stringify(type{0}) ); +} + +TEST_CASE( "variant<variant<float, int>, string>", "[toString][variant][approvals]") +{ + using inner = std::variant<MyType1, float, int>; + using type = std::variant<inner, std::string>; + CHECK( "0.5f" == ::Catch::Detail::stringify(type{0.5f}) ); + CHECK( "0" == ::Catch::Detail::stringify(type{0}) ); + CHECK( "\"foo\"" == ::Catch::Detail::stringify(type{"foo"}) ); + + SECTION("valueless nested variant") { + type value = inner{0.5f}; + REQUIRE( std::holds_alternative<inner>(value) ); + REQUIRE( std::holds_alternative<float>(std::get<inner>(value)) ); + + REQUIRE_THROWS_AS( std::get<0>(value).emplace<MyType1>(MyType1{}), int ); + + // outer variant is still valid and contains inner + REQUIRE( std::holds_alternative<inner>(value) ); + // inner variant is valueless + REQUIRE( std::get<inner>(value).valueless_by_exception() ); + CHECK( "{valueless variant}" == ::Catch::Detail::stringify(value) ); + } +} + +TEST_CASE( "variant<nullptr,int,const char *>", "[toString][variant][approvals]" ) +{ + using type = std::variant<std::nullptr_t,int,const char *>; + CHECK( "nullptr" == ::Catch::Detail::stringify(type{nullptr}) ); + CHECK( "42" == ::Catch::Detail::stringify(type{42}) ); + CHECK( "\"Catch me\"" == ::Catch::Detail::stringify(type{"Catch me"}) ); +} + +#endif // CATCH_INTERNAL_CONFIG_CPP17_VARIANT diff --git a/tests/SelfTest/UsageTests/ToStringVector.tests.cpp b/tests/SelfTest/UsageTests/ToStringVector.tests.cpp new file mode 100644 index 0000000..c042744 --- /dev/null +++ b/tests/SelfTest/UsageTests/ToStringVector.tests.cpp @@ -0,0 +1,94 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 + +#include <catch2/catch_test_macros.hpp> +#include <vector> +#include <array> + +// vector +TEST_CASE( "vector<int> -> toString", "[toString][vector]" ) +{ + std::vector<int> vv; + REQUIRE( ::Catch::Detail::stringify(vv) == "{ }" ); + vv.push_back( 42 ); + REQUIRE( ::Catch::Detail::stringify(vv) == "{ 42 }" ); + vv.push_back( 250 ); + REQUIRE( ::Catch::Detail::stringify(vv) == "{ 42, 250 }" ); +} + +TEST_CASE( "vector<string> -> toString", "[toString][vector]" ) +{ + std::vector<std::string> vv; + REQUIRE( ::Catch::Detail::stringify(vv) == "{ }" ); + vv.emplace_back( "hello" ); + REQUIRE( ::Catch::Detail::stringify(vv) == "{ \"hello\" }" ); + vv.emplace_back( "world" ); + REQUIRE( ::Catch::Detail::stringify(vv) == "{ \"hello\", \"world\" }" ); +} + +namespace { + /* Minimal Allocator */ + template<typename T> + struct minimal_allocator { + using value_type = T; + using size_type = std::size_t; + + minimal_allocator() = default; + template <typename U> + minimal_allocator(const minimal_allocator<U>&) {} + + + T *allocate( size_type n ) { + return static_cast<T *>( ::operator new( n * sizeof(T) ) ); + } + void deallocate( T *p, size_type /*n*/ ) { + ::operator delete( static_cast<void *>(p) ); + } + template<typename U> + bool operator==( const minimal_allocator<U>& ) const { return true; } + template<typename U> + bool operator!=( const minimal_allocator<U>& ) const { return false; } + }; +} + +TEST_CASE( "vector<int,allocator> -> toString", "[toString][vector,allocator]" ) { + std::vector<int,minimal_allocator<int> > vv; + REQUIRE( ::Catch::Detail::stringify(vv) == "{ }" ); + vv.push_back( 42 ); + REQUIRE( ::Catch::Detail::stringify(vv) == "{ 42 }" ); + vv.push_back( 250 ); + REQUIRE( ::Catch::Detail::stringify(vv) == "{ 42, 250 }" ); +} + +TEST_CASE( "vec<vec<string,alloc>> -> toString", "[toString][vector,allocator]" ) { + using inner = std::vector<std::string, minimal_allocator<std::string>>; + using vector = std::vector<inner>; + vector v; + REQUIRE( ::Catch::Detail::stringify(v) == "{ }" ); + v.push_back( inner { "hello" } ); + v.push_back( inner { "world" } ); + REQUIRE( ::Catch::Detail::stringify(v) == "{ { \"hello\" }, { \"world\" } }" ); +} + +// Based on PR by mat-so: https://github.com/catchorg/Catch2/pull/606/files#diff-43562f40f8c6dcfe2c54557316e0f852 +TEST_CASE( "vector<bool> -> toString", "[toString][containers][vector]" ) { + std::vector<bool> bools; + REQUIRE( ::Catch::Detail::stringify(bools) == "{ }"); + bools.push_back(true); + REQUIRE( ::Catch::Detail::stringify(bools) == "{ true }"); + bools.push_back(false); + REQUIRE( ::Catch::Detail::stringify(bools) == "{ true, false }"); +} +TEST_CASE( "array<int, N> -> toString", "[toString][containers][array]" ) { + std::array<int, 0> empty; + REQUIRE( Catch::Detail::stringify( empty ) == "{ }" ); + std::array<int, 1> oneValue = {{ 42 }}; + REQUIRE( Catch::Detail::stringify( oneValue ) == "{ 42 }" ); + std::array<int, 2> twoValues = {{ 42, 250 }}; + REQUIRE( Catch::Detail::stringify( twoValues ) == "{ 42, 250 }" ); +} diff --git a/tests/SelfTest/UsageTests/ToStringWhich.tests.cpp b/tests/SelfTest/UsageTests/ToStringWhich.tests.cpp new file mode 100644 index 0000000..ec7a49e --- /dev/null +++ b/tests/SelfTest/UsageTests/ToStringWhich.tests.cpp @@ -0,0 +1,186 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 + +#include <catch2/catch_test_macros.hpp> + + + +#if defined(__GNUC__) +// This has to be left enabled until end of the TU, because the GCC +// frontend reports operator<<(std::ostream& os, const has_maker_and_operator&) +// as unused anyway +# pragma GCC diagnostic ignored "-Wunused-function" +#endif + +namespace { + +struct has_operator { }; +struct has_maker {}; +struct has_maker_and_operator {}; +struct has_neither {}; +struct has_template_operator {}; + +std::ostream& operator<<(std::ostream& os, const has_operator&) { + os << "operator<<( has_operator )"; + return os; +} + +std::ostream& operator<<(std::ostream& os, const has_maker_and_operator&) { + os << "operator<<( has_maker_and_operator )"; + return os; +} + +template <typename StreamT> +StreamT& operator<<(StreamT& os, const has_template_operator&) { + os << "operator<<( has_template_operator )"; + return os; +} + +} // end anonymous namespace + +namespace Catch { + template<> + struct StringMaker<has_maker> { + static std::string convert( const has_maker& ) { + return "StringMaker<has_maker>"; + } + }; + template<> + struct StringMaker<has_maker_and_operator> { + static std::string convert( const has_maker_and_operator& ) { + return "StringMaker<has_maker_and_operator>"; + } + }; +} + +// Call the operator +TEST_CASE( "stringify( has_operator )", "[toString]" ) { + has_operator item; + REQUIRE( ::Catch::Detail::stringify( item ) == "operator<<( has_operator )" ); +} + +// Call the stringmaker +TEST_CASE( "stringify( has_maker )", "[toString]" ) { + has_maker item; + REQUIRE( ::Catch::Detail::stringify( item ) == "StringMaker<has_maker>" ); +} + +// Call the stringmaker +TEST_CASE( "stringify( has_maker_and_operator )", "[toString]" ) { + has_maker_and_operator item; + REQUIRE( ::Catch::Detail::stringify( item ) == "StringMaker<has_maker_and_operator>" ); +} + +TEST_CASE("stringify( has_neither )", "[toString]") { + has_neither item; + REQUIRE( ::Catch::Detail::stringify(item) == "{?}" ); +} + +// Call the templated operator +TEST_CASE( "stringify( has_template_operator )", "[toString]" ) { + has_template_operator item; + REQUIRE( ::Catch::Detail::stringify( item ) == "operator<<( has_template_operator )" ); +} + + +// Vectors... + +TEST_CASE( "stringify( vectors<has_operator> )", "[toString]" ) { + std::vector<has_operator> v(1); + REQUIRE( ::Catch::Detail::stringify( v ) == "{ operator<<( has_operator ) }" ); +} + +TEST_CASE( "stringify( vectors<has_maker> )", "[toString]" ) { + std::vector<has_maker> v(1); + REQUIRE( ::Catch::Detail::stringify( v ) == "{ StringMaker<has_maker> }" ); +} + +TEST_CASE( "stringify( vectors<has_maker_and_operator> )", "[toString]" ) { + std::vector<has_maker_and_operator> v(1); + REQUIRE( ::Catch::Detail::stringify( v ) == "{ StringMaker<has_maker_and_operator> }" ); +} + +namespace { + +// Range-based conversion should only be used if other possibilities fail +struct int_iterator { + using iterator_category = std::input_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = int; + using reference = int&; + using pointer = int*; + + int_iterator() = default; + int_iterator(int i) :val(i) {} + + value_type operator*() const { return val; } + bool operator==(int_iterator rhs) const { return val == rhs.val; } + bool operator!=(int_iterator rhs) const { return val != rhs.val; } + int_iterator operator++() { ++val; return *this; } + int_iterator operator++(int) { + auto temp(*this); + ++val; + return temp; + } +private: + int val = 5; +}; + +struct streamable_range { + int_iterator begin() const { return int_iterator{ 1 }; } + int_iterator end() const { return {}; } +}; + +std::ostream& operator<<(std::ostream& os, const streamable_range&) { + os << "op<<(streamable_range)"; + return os; +} + +struct stringmaker_range { + int_iterator begin() const { return int_iterator{ 1 }; } + int_iterator end() const { return {}; } +}; + +} // end anonymous namespace + +namespace Catch { +template <> +struct StringMaker<stringmaker_range> { + static std::string convert(stringmaker_range const&) { + return "stringmaker(streamable_range)"; + } +}; +} + +namespace { + +struct just_range { + int_iterator begin() const { return int_iterator{ 1 }; } + int_iterator end() const { return {}; } +}; + +struct disabled_range { + int_iterator begin() const { return int_iterator{ 1 }; } + int_iterator end() const { return {}; } +}; + +} // end anonymous namespace + +namespace Catch { +template <> +struct is_range<disabled_range> { + static const bool value = false; +}; +} + +TEST_CASE("stringify ranges", "[toString]") { + REQUIRE(::Catch::Detail::stringify(streamable_range{}) == "op<<(streamable_range)"); + REQUIRE(::Catch::Detail::stringify(stringmaker_range{}) == "stringmaker(streamable_range)"); + REQUIRE(::Catch::Detail::stringify(just_range{}) == "{ 1, 2, 3, 4 }"); + REQUIRE(::Catch::Detail::stringify(disabled_range{}) == "{?}"); +} diff --git a/tests/SelfTest/UsageTests/Tricky.tests.cpp b/tests/SelfTest/UsageTests/Tricky.tests.cpp new file mode 100644 index 0000000..041d786 --- /dev/null +++ b/tests/SelfTest/UsageTests/Tricky.tests.cpp @@ -0,0 +1,380 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 + +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wpadded" +#endif + +#ifdef _MSC_VER +#pragma warning (disable : 4702) // Disable unreachable code warning for the last test + // that is triggered when compiling as Win32|Release +#endif + +#include <catch2/catch_test_macros.hpp> +#include <catch2/generators/catch_generators.hpp> +#include <catch2/generators/catch_generators_range.hpp> + +#include <cstdio> +#include <sstream> +#include <iostream> + +struct Opaque +{ + int val; + bool operator ==( const Opaque& o ) const + { + return val == o.val; + } +}; + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE +( + "A failing expression with a non streamable type is still captured", + "[Tricky][failing][.]" +) +{ + + Opaque o1, o2; + o1.val = 7; + o2.val = 8; + + CHECK( &o1 == &o2 ); + CHECK( o1 == o2 ); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_CASE +( + "An expression with side-effects should only be evaluated once", + "[Tricky]" +) +{ + int i = 7; + + REQUIRE( i++ == 7 ); + REQUIRE( i++ == 8 ); + +} + +namespace A { + struct X + { + X() : a(4), b(2), c(7) {} + X(int v) : a(v), b(2), c(7) {} + int a; + int b; + int c; + }; +} + +namespace B { + struct Y + { + Y() : a(4), b(2), c(7) {} + Y(int v) : a(v), b(2), c(7) {} + int a; + int b; + int c; + }; +} + +inline bool operator==(const A::X& lhs, const B::Y& rhs) +{ + return (lhs.a == rhs.a); +} + +inline bool operator==(const B::Y& lhs, const A::X& rhs) +{ + return (lhs.a == rhs.a); +} + + +/////////////////////////////////////////////////////////////////////////////// +/* This, currently, does not compile with LLVM +TEST_CASE +( + "Operators at different namespace levels not hijacked by Koenig lookup" + "[Tricky]" +) +{ + A::X x; + B::Y y; + REQUIRE( x == y ); +} +*/ + +namespace ObjectWithConversions +{ + struct Object + { + operator unsigned int() const {return 0xc0000000;} + }; + + /////////////////////////////////////////////////////////////////////////////// + TEST_CASE + ( + "Implicit conversions are supported inside assertion macros", + "[Tricky][approvals]" + ) + { + Object o; + REQUIRE(0xc0000000 == o ); + } +} + +namespace EnumBitFieldTests +{ + enum Bits : uint32_t { + bit0 = 0x0001, + bit1 = 0x0002, + bit2 = 0x0004, + bit3 = 0x0008, + bit1and2 = bit1 | bit2, + bit30 = 0x40000000, + bit31 = 0x80000000, + bit30and31 = bit30 | bit31 + }; + + TEST_CASE( "Test enum bit values", "[Tricky]" ) + { + REQUIRE( 0xc0000000 == bit30and31 ); + } +} + +struct Obj +{ + Obj():prop(&p){} + + int p = 0; + int* prop; +}; + +TEST_CASE("boolean member", "[Tricky]") +{ + Obj obj; + REQUIRE( obj.prop != nullptr ); +} + +// Tests for a problem submitted by Ralph McArdell +// +// The static bool value should not need to be defined outside the +// struct it is declared in - but when evaluating it in a deduced +// context it appears to require the extra definition. +// The issue was fixed by adding bool overloads to bypass the +// templates that were there to deduce it. +template <bool B> +struct is_true +{ + static const bool value = B; +}; + +TEST_CASE( "(unimplemented) static bools can be evaluated", "[Tricky]" ) +{ + SECTION("compare to true") + { + REQUIRE( is_true<true>::value == true ); + REQUIRE( true == is_true<true>::value ); + } + SECTION("compare to false") + { + REQUIRE( is_true<false>::value == false ); + REQUIRE( false == is_true<false>::value ); + } + + SECTION("negation") + { + REQUIRE( !is_true<false>::value ); + } + + SECTION("double negation") + { + REQUIRE( !!is_true<true>::value ); + } + + SECTION("direct") + { + REQUIRE( is_true<true>::value ); + REQUIRE_FALSE( is_true<false>::value ); + } +} + +struct Boolable +{ + explicit Boolable( bool value ) : m_value( value ) {} + + explicit operator bool() const { + return m_value; + } + + bool m_value; +}; + +TEST_CASE( "Objects that evaluated in boolean contexts can be checked", "[Tricky][SafeBool]" ) +{ + Boolable True( true ); + Boolable False( false ); + + CHECK( True ); + CHECK( !False ); + CHECK_FALSE( False ); +} + +TEST_CASE( "Assertions then sections", "[Tricky]" ) +{ + // This was causing a failure due to the way the console reporter was handling + // the current section + + REQUIRE( true ); + + SECTION( "A section" ) + { + REQUIRE( true ); + + SECTION( "Another section" ) + { + REQUIRE( true ); + } + SECTION( "Another other section" ) + { + REQUIRE( true ); + } + } +} + +struct Awkward +{ + operator int() const { return 7; } +}; + +TEST_CASE( "non streamable - with conv. op", "[Tricky]" ) +{ + Awkward awkward; + std::string s = ::Catch::Detail::stringify( awkward ); + REQUIRE( s == "7" ); +} + +inline void foo() {} + +using fooptr_t = void (*)(); + +TEST_CASE( "Comparing function pointers", "[Tricky][function pointer]" ) +{ + // This was giving a warning in VS2010 + // #179 + fooptr_t a = foo; + + REQUIRE( a ); + REQUIRE( a == &foo ); +} + +struct S +{ + void f() {} +}; + + +TEST_CASE( "Comparing member function pointers", "[Tricky][member function pointer][approvals]" ) +{ + using MF = void (S::*)(); + MF m = &S::f; + + CHECK( m == &S::f ); +} + +class ClassName {}; + +TEST_CASE( "pointer to class", "[Tricky]" ) +{ + ClassName *p = 0; + REQUIRE( p == 0 ); +} + +#include <memory> + +TEST_CASE( "null_ptr", "[Tricky]" ) +{ + std::unique_ptr<int> ptr; + REQUIRE(ptr.get() == nullptr); +} + +TEST_CASE( "X/level/0/a", "[Tricky]" ) { SUCCEED(""); } +TEST_CASE( "X/level/0/b", "[Tricky][fizz]" ){ SUCCEED(""); } +TEST_CASE( "X/level/1/a", "[Tricky]" ) { SUCCEED(""); } +TEST_CASE( "X/level/1/b", "[Tricky]" ) { SUCCEED(""); } + +TEST_CASE( "has printf" ) { + + // This can cause problems as, currently, stdout itself is not redirected - only the cout (and cerr) buffer + printf( "loose text artifact\n" ); +} + +namespace { + struct constructor_throws { + [[noreturn]] constructor_throws() { + throw 1; + } + }; +} + +TEST_CASE("Commas in various macros are allowed") { + REQUIRE_THROWS( std::vector<constructor_throws>{constructor_throws{}, constructor_throws{}} ); + CHECK_THROWS( std::vector<constructor_throws>{constructor_throws{}, constructor_throws{}} ); + REQUIRE_NOTHROW( std::vector<int>{1, 2, 3} == std::vector<int>{1, 2, 3} ); + CHECK_NOTHROW( std::vector<int>{1, 2, 3} == std::vector<int>{1, 2, 3} ); + + REQUIRE(std::vector<int>{1, 2} == std::vector<int>{1, 2}); + CHECK( std::vector<int>{1, 2} == std::vector<int>{1, 2} ); + REQUIRE_FALSE(std::vector<int>{1, 2} == std::vector<int>{1, 2, 3}); + CHECK_FALSE( std::vector<int>{1, 2} == std::vector<int>{1, 2, 3} ); + + CHECK_NOFAIL( std::vector<int>{1, 2} == std::vector<int>{1, 2} ); + CHECKED_IF( std::vector<int>{1, 2} == std::vector<int>{1, 2} ) { + REQUIRE(true); + } CHECKED_ELSE( std::vector<int>{1, 2} == std::vector<int>{1, 2} ) { + CHECK(true); + } +} + +TEST_CASE( "non-copyable objects", "[.][failing]" ) { + // Thanks to Agustin Bergé (@k-ballo on the cpplang Slack) for raising this + std::type_info const& ti = typeid(int); + CHECK( ti == typeid(int) ); +} + +TEST_CASE("#1514: stderr/stdout is not captured in tests aborted by an exception", "[output-capture][regression][.]") { + std::cout << "This would not be caught previously\n" << std::flush; + std::clog << "Nor would this\n" << std::flush; + // FAIL aborts the test by throwing a Catch exception + FAIL("1514"); +} + + +TEST_CASE( "#2025: -c shouldn't cause infinite loop", "[sections][generators][regression][.approvals]" ) { + SECTION( "Check cursor from buffer offset" ) { + auto bufPos = GENERATE_REF( range( 0, 44 ) ); + WHEN( "Buffer position is " << bufPos ) { REQUIRE( 1 == 1 ); } + } +} + +TEST_CASE("#2025: original repro", "[sections][generators][regression][.approvals]") { + auto fov = GENERATE(true, false); + DYNAMIC_SECTION("fov_" << fov) { + std::cout << "inside with fov: " << fov << '\n'; + } +} + +TEST_CASE("#2025: same-level sections", "[sections][generators][regression][.approvals]") { + SECTION("A") { + SUCCEED(); + } + auto i = GENERATE(1, 2, 3); + SECTION("B") { + REQUIRE(i < 4); + } +} diff --git a/tests/SelfTest/UsageTests/VariadicMacros.tests.cpp b/tests/SelfTest/UsageTests/VariadicMacros.tests.cpp new file mode 100644 index 0000000..92048a0 --- /dev/null +++ b/tests/SelfTest/UsageTests/VariadicMacros.tests.cpp @@ -0,0 +1,29 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 + +#include <catch2/catch_test_macros.hpp> + + +TEST_CASE() +{ + SUCCEED( "anonymous test case" ); +} + +TEST_CASE( "Test case with one argument" ) +{ + SUCCEED( "no assertions" ); +} + +TEST_CASE( "Variadic macros", "[variadic][sections]" ) +{ + SECTION( "Section with one argument" ) + { + SUCCEED( "no assertions" ); + } +} + |
