| // Copyright 2019 The Abseil Authors. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // |
| // ----------------------------------------------------------------------------- |
| // conformance_testing.h |
| // ----------------------------------------------------------------------------- |
| // |
| |
| #ifndef ABSL_TYPES_INTERNAL_CONFORMANCE_TESTING_H_ |
| #define ABSL_TYPES_INTERNAL_CONFORMANCE_TESTING_H_ |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // // |
| // Many templates in this file take a `T` and a `Prof` type as explicit // |
| // template arguments. These are a type to be checked and a // |
| // "Regularity Profile" that describes what operations that type `T` is // |
| // expected to support. See "regularity_profiles.h" for more details // |
| // regarding Regularity Profiles. // |
| // // |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| #include <cstddef> |
| #include <set> |
| #include <tuple> |
| #include <type_traits> |
| #include <utility> |
| |
| #include "gtest/gtest.h" |
| #include "absl/meta/type_traits.h" |
| #include "absl/strings/ascii.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/string_view.h" |
| #include "absl/types/internal/conformance_aliases.h" |
| #include "absl/types/internal/conformance_archetype.h" |
| #include "absl/types/internal/conformance_profile.h" |
| #include "absl/types/internal/conformance_testing_helpers.h" |
| #include "absl/types/internal/parentheses.h" |
| #include "absl/types/internal/transform_args.h" |
| #include "absl/utility/utility.h" |
| |
| namespace absl { |
| ABSL_NAMESPACE_BEGIN |
| namespace types_internal { |
| |
| // Returns true if the compiler incorrectly greedily instantiates constexpr |
| // templates in any unevaluated context. |
| constexpr bool constexpr_instantiation_when_unevaluated() { |
| #if defined(__apple_build_version__) // TODO(calabrese) Make more specific |
| return true; |
| #elif defined(__clang__) |
| return __clang_major__ < 4; |
| #elif defined(__GNUC__) |
| // TODO(calabrese) Figure out why gcc 7 fails (seems like a different bug) |
| return __GNUC__ < 5 || (__GNUC__ == 5 && __GNUC_MINOR__ < 2) || __GNUC__ >= 7; |
| #else |
| return false; |
| #endif |
| } |
| |
| // Returns true if the standard library being used incorrectly produces an error |
| // when instantiating the definition of a poisoned std::hash specialization. |
| constexpr bool poisoned_hash_fails_instantiation() { |
| #if defined(_MSC_VER) && !defined(_LIBCPP_VERSION) |
| return _MSC_VER < 1914; |
| #else |
| return false; |
| #endif |
| } |
| |
| template <class Fun> |
| struct GeneratorType { |
| decltype(std::declval<const Fun&>()()) operator()() const |
| noexcept(noexcept(std::declval<const Fun&>()())) { |
| return fun(); |
| } |
| |
| Fun fun; |
| const char* description; |
| }; |
| |
| // A "make" function for the GeneratorType template that deduces the function |
| // object type. |
| template <class Fun, |
| absl::enable_if_t<IsNullaryCallable<Fun>::value>** = nullptr> |
| GeneratorType<Fun> Generator(Fun fun, const char* description) { |
| return GeneratorType<Fun>{absl::move(fun), description}; |
| } |
| |
| // A type that contains a set of nullary function objects that each return an |
| // instance of the same type and value (though possibly different |
| // representations, such as +0 and -0 or two vectors with the same elements but |
| // with different capacities). |
| template <class... Funs> |
| struct EquivalenceClassType { |
| std::tuple<GeneratorType<Funs>...> generators; |
| }; |
| |
| // A "make" function for the EquivalenceClassType template that deduces the |
| // function object types and is constrained such that a user can only pass in |
| // function objects that all have the same return type. |
| template <class... Funs, absl::enable_if_t<AreGeneratorsWithTheSameReturnType< |
| Funs...>::value>** = nullptr> |
| EquivalenceClassType<Funs...> EquivalenceClass(GeneratorType<Funs>... funs) { |
| return {std::make_tuple(absl::move(funs)...)}; |
| } |
| |
| // A type that contains an ordered series of EquivalenceClassTypes, from |
| // smallest value to largest value. |
| template <class... EqClasses> |
| struct OrderedEquivalenceClasses { |
| std::tuple<EqClasses...> eq_classes; |
| }; |
| |
| // An object containing the parts of a given (name, initialization expression), |
| // and is capable of generating a string that describes the given. |
| struct GivenDeclaration { |
| std::string outputDeclaration(std::size_t width) const { |
| const std::size_t indent_size = 2; |
| std::string result = absl::StrCat(" ", name); |
| |
| if (!expression.empty()) { |
| // Indent |
| result.resize(indent_size + width, ' '); |
| absl::StrAppend(&result, " = ", expression, ";\n"); |
| } else { |
| absl::StrAppend(&result, ";\n"); |
| } |
| |
| return result; |
| } |
| |
| std::string name; |
| std::string expression; |
| }; |
| |
| // Produce a string that contains all of the givens of an error report. |
| template <class... Decls> |
| std::string PrepareGivenContext(const Decls&... decls) { |
| const std::size_t width = (std::max)({decls.name.size()...}); |
| return absl::StrCat("Given:\n", decls.outputDeclaration(width)..., "\n"); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // Function objects that perform a check for each comparison operator // |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| #define ABSL_INTERNAL_EXPECT_OP(name, op) \ |
| struct Expect##name { \ |
| template <class T> \ |
| void operator()(absl::string_view test_name, absl::string_view context, \ |
| const T& lhs, const T& rhs, absl::string_view lhs_name, \ |
| absl::string_view rhs_name) const { \ |
| if (!static_cast<bool>(lhs op rhs)) { \ |
| errors->addTestFailure( \ |
| test_name, absl::StrCat(context, \ |
| "**Unexpected comparison result**\n" \ |
| "\n" \ |
| "Expression:\n" \ |
| " ", \ |
| lhs_name, " " #op " ", rhs_name, \ |
| "\n" \ |
| "\n" \ |
| "Expected: true\n" \ |
| " Actual: false")); \ |
| } else { \ |
| errors->addTestSuccess(test_name); \ |
| } \ |
| } \ |
| \ |
| ConformanceErrors* errors; \ |
| }; \ |
| \ |
| struct ExpectNot##name { \ |
| template <class T> \ |
| void operator()(absl::string_view test_name, absl::string_view context, \ |
| const T& lhs, const T& rhs, absl::string_view lhs_name, \ |
| absl::string_view rhs_name) const { \ |
| if (lhs op rhs) { \ |
| errors->addTestFailure( \ |
| test_name, absl::StrCat(context, \ |
| "**Unexpected comparison result**\n" \ |
| "\n" \ |
| "Expression:\n" \ |
| " ", \ |
| lhs_name, " " #op " ", rhs_name, \ |
| "\n" \ |
| "\n" \ |
| "Expected: false\n" \ |
| " Actual: true")); \ |
| } else { \ |
| errors->addTestSuccess(test_name); \ |
| } \ |
| } \ |
| \ |
| ConformanceErrors* errors; \ |
| } |
| |
| ABSL_INTERNAL_EXPECT_OP(Eq, ==); |
| ABSL_INTERNAL_EXPECT_OP(Ne, !=); |
| ABSL_INTERNAL_EXPECT_OP(Lt, <); |
| ABSL_INTERNAL_EXPECT_OP(Le, <=); |
| ABSL_INTERNAL_EXPECT_OP(Ge, >=); |
| ABSL_INTERNAL_EXPECT_OP(Gt, >); |
| |
| #undef ABSL_INTERNAL_EXPECT_OP |
| |
| // A function object that verifies that two objects hash to the same value by |
| // way of the std::hash specialization. |
| struct ExpectSameHash { |
| template <class T> |
| void operator()(absl::string_view test_name, absl::string_view context, |
| const T& lhs, const T& rhs, absl::string_view lhs_name, |
| absl::string_view rhs_name) const { |
| if (std::hash<T>()(lhs) != std::hash<T>()(rhs)) { |
| errors->addTestFailure( |
| test_name, absl::StrCat(context, |
| "**Unexpected hash result**\n" |
| "\n" |
| "Expression:\n" |
| " std::hash<T>()(", |
| lhs_name, ") == std::hash<T>()(", rhs_name, |
| ")\n" |
| "\n" |
| "Expected: true\n" |
| " Actual: false")); |
| } else { |
| errors->addTestSuccess(test_name); |
| } |
| } |
| |
| ConformanceErrors* errors; |
| }; |
| |
| // A function template that takes two objects and verifies that each comparison |
| // operator behaves in a way that is consistent with equality. It has "OneWay" |
| // in the name because the first argument will always be the left-hand operand |
| // of the corresponding comparison operator and the second argument will |
| // always be the right-hand operand. It will never switch that order. |
| // At a higher level in the test suite, the one-way form is called once for each |
| // of the two possible orders whenever lhs and rhs are not the same initializer. |
| template <class T, class Prof> |
| void ExpectOneWayEquality(ConformanceErrors* errors, |
| absl::string_view test_name, |
| absl::string_view context, const T& lhs, const T& rhs, |
| absl::string_view lhs_name, |
| absl::string_view rhs_name) { |
| If<PropertiesOfT<Prof>::is_equality_comparable>::Invoke( |
| ExpectEq{errors}, test_name, context, lhs, rhs, lhs_name, rhs_name); |
| |
| If<PropertiesOfT<Prof>::is_inequality_comparable>::Invoke( |
| ExpectNotNe{errors}, test_name, context, lhs, rhs, lhs_name, rhs_name); |
| |
| If<PropertiesOfT<Prof>::is_less_than_comparable>::Invoke( |
| ExpectNotLt{errors}, test_name, context, lhs, rhs, lhs_name, rhs_name); |
| |
| If<PropertiesOfT<Prof>::is_less_equal_comparable>::Invoke( |
| ExpectLe{errors}, test_name, context, lhs, rhs, lhs_name, rhs_name); |
| |
| If<PropertiesOfT<Prof>::is_greater_equal_comparable>::Invoke( |
| ExpectGe{errors}, test_name, context, lhs, rhs, lhs_name, rhs_name); |
| |
| If<PropertiesOfT<Prof>::is_greater_than_comparable>::Invoke( |
| ExpectNotGt{errors}, test_name, context, lhs, rhs, lhs_name, rhs_name); |
| |
| If<PropertiesOfT<Prof>::is_hashable>::Invoke( |
| ExpectSameHash{errors}, test_name, context, lhs, rhs, lhs_name, rhs_name); |
| } |
| |
| // A function template that takes two objects and verifies that each comparison |
| // operator behaves in a way that is consistent with equality. This function |
| // differs from ExpectOneWayEquality in that this will do checks with argument |
| // order reversed in addition to in-order. |
| template <class T, class Prof> |
| void ExpectEquality(ConformanceErrors* errors, absl::string_view test_name, |
| absl::string_view context, const T& lhs, const T& rhs, |
| absl::string_view lhs_name, absl::string_view rhs_name) { |
| (ExpectOneWayEquality<T, Prof>)(errors, test_name, context, lhs, rhs, |
| lhs_name, rhs_name); |
| (ExpectOneWayEquality<T, Prof>)(errors, test_name, context, rhs, lhs, |
| rhs_name, lhs_name); |
| } |
| |
| // Given a generator, makes sure that a generated value and a moved-from |
| // generated value are equal. |
| template <class T, class Prof> |
| struct ExpectMoveConstructOneGenerator { |
| template <class Fun> |
| void operator()(const Fun& generator) const { |
| const T object = generator(); |
| const T moved_object = absl::move(generator()); // Force no elision. |
| |
| (ExpectEquality<T, Prof>)(errors, "Move construction", |
| PrepareGivenContext( |
| GivenDeclaration{"const _T object", |
| generator.description}, |
| GivenDeclaration{"const _T moved_object", |
| std::string("std::move(") + |
| generator.description + |
| ")"}), |
| object, moved_object, "object", "moved_object"); |
| } |
| |
| ConformanceErrors* errors; |
| }; |
| |
| // Given a generator, makes sure that a generated value and a copied-from |
| // generated value are equal. |
| template <class T, class Prof> |
| struct ExpectCopyConstructOneGenerator { |
| template <class Fun> |
| void operator()(const Fun& generator) const { |
| const T object = generator(); |
| const T copied_object = static_cast<const T&>(generator()); |
| |
| (ExpectEquality<T, Prof>)(errors, "Copy construction", |
| PrepareGivenContext( |
| GivenDeclaration{"const _T object", |
| generator.description}, |
| GivenDeclaration{ |
| "const _T copied_object", |
| std::string("static_cast<const _T&>(") + |
| generator.description + ")"}), |
| object, copied_object, "object", "copied_object"); |
| } |
| |
| ConformanceErrors* errors; |
| }; |
| |
| // Default-construct and do nothing before destruction. |
| // |
| // This is useful in exercising the codepath of default construction followed by |
| // destruction, but does not explicitly test anything. An example of where this |
| // might fail is a default destructor that default-initializes a scalar and a |
| // destructor reads the value of that member. Sanitizers can catch this as long |
| // as our test attempts to execute such a case. |
| template <class T> |
| struct ExpectDefaultConstructWithDestruct { |
| void operator()() const { |
| // Scoped so that destructor gets called before reporting success. |
| { |
| T object; |
| static_cast<void>(object); |
| } |
| |
| errors->addTestSuccess("Default construction"); |
| } |
| |
| ConformanceErrors* errors; |
| }; |
| |
| // Check move-assign into a default-constructed object. |
| template <class T, class Prof> |
| struct ExpectDefaultConstructWithMoveAssign { |
| template <class Fun> |
| void operator()(const Fun& generator) const { |
| const T source_of_truth = generator(); |
| T object; |
| object = generator(); |
| |
| (ExpectEquality<T, Prof>)(errors, "Move assignment", |
| PrepareGivenContext( |
| GivenDeclaration{"const _T object", |
| generator.description}, |
| GivenDeclaration{"_T object", ""}, |
| GivenDeclaration{"object", |
| generator.description}), |
| object, source_of_truth, "std::as_const(object)", |
| "source_of_truth"); |
| } |
| |
| ConformanceErrors* errors; |
| }; |
| |
| // Check copy-assign into a default-constructed object. |
| template <class T, class Prof> |
| struct ExpectDefaultConstructWithCopyAssign { |
| template <class Fun> |
| void operator()(const Fun& generator) const { |
| const T source_of_truth = generator(); |
| T object; |
| object = static_cast<const T&>(generator()); |
| |
| (ExpectEquality<T, Prof>)(errors, "Copy assignment", |
| PrepareGivenContext( |
| GivenDeclaration{"const _T source_of_truth", |
| generator.description}, |
| GivenDeclaration{"_T object", ""}, |
| GivenDeclaration{ |
| "object", |
| std::string("static_cast<const _T&>(") + |
| generator.description + ")"}), |
| object, source_of_truth, "std::as_const(object)", |
| "source_of_truth"); |
| } |
| |
| ConformanceErrors* errors; |
| }; |
| |
| // Perform a self move-assign. |
| template <class T, class Prof> |
| struct ExpectSelfMoveAssign { |
| template <class Fun> |
| void operator()(const Fun& generator) const { |
| T object = generator(); |
| object = absl::move(object); |
| |
| // NOTE: Self move-assign results in a valid-but-unspecified state. |
| |
| (ExpectEquality<T, Prof>)(errors, "Move assignment", |
| PrepareGivenContext( |
| GivenDeclaration{"_T object", |
| generator.description}, |
| GivenDeclaration{"object", |
| "std::move(object)"}), |
| object, object, "object", "object"); |
| } |
| |
| ConformanceErrors* errors; |
| }; |
| |
| // Perform a self copy-assign. |
| template <class T, class Prof> |
| struct ExpectSelfCopyAssign { |
| template <class Fun> |
| void operator()(const Fun& generator) const { |
| const T source_of_truth = generator(); |
| T object = generator(); |
| const T& const_object = object; |
| object = const_object; |
| |
| (ExpectEquality<T, Prof>)(errors, "Copy assignment", |
| PrepareGivenContext( |
| GivenDeclaration{"const _T source_of_truth", |
| generator.description}, |
| GivenDeclaration{"_T object", |
| generator.description}, |
| GivenDeclaration{"object", |
| "std::as_const(object)"}), |
| const_object, source_of_truth, |
| "std::as_const(object)", "source_of_truth"); |
| } |
| |
| ConformanceErrors* errors; |
| }; |
| |
| // Perform a self-swap. |
| template <class T, class Prof> |
| struct ExpectSelfSwap { |
| template <class Fun> |
| void operator()(const Fun& generator) const { |
| const T source_of_truth = generator(); |
| T object = generator(); |
| |
| type_traits_internal::Swap(object, object); |
| |
| std::string preliminary_info = absl::StrCat( |
| PrepareGivenContext( |
| GivenDeclaration{"const _T source_of_truth", generator.description}, |
| GivenDeclaration{"_T object", generator.description}), |
| "After performing a self-swap:\n" |
| " using std::swap;\n" |
| " swap(object, object);\n" |
| "\n"); |
| |
| (ExpectEquality<T, Prof>)(errors, "Swap", std::move(preliminary_info), |
| object, source_of_truth, "std::as_const(object)", |
| "source_of_truth"); |
| } |
| |
| ConformanceErrors* errors; |
| }; |
| |
| // Perform each of the single-generator checks when necessary operations are |
| // supported. |
| template <class T, class Prof> |
| struct ExpectSelfComparison { |
| template <class Fun> |
| void operator()(const Fun& generator) const { |
| const T object = generator(); |
| (ExpectOneWayEquality<T, Prof>)(errors, "Comparison", |
| PrepareGivenContext(GivenDeclaration{ |
| "const _T object", |
| generator.description}), |
| object, object, "object", "object"); |
| } |
| |
| ConformanceErrors* errors; |
| }; |
| |
| // Perform each of the single-generator checks when necessary operations are |
| // supported. |
| template <class T, class Prof> |
| struct ExpectConsistency { |
| template <class Fun> |
| void operator()(const Fun& generator) const { |
| If<PropertiesOfT<Prof>::is_move_constructible>::Invoke( |
| ExpectMoveConstructOneGenerator<T, Prof>{errors}, generator); |
| |
| If<PropertiesOfT<Prof>::is_copy_constructible>::Invoke( |
| ExpectCopyConstructOneGenerator<T, Prof>{errors}, generator); |
| |
| If<PropertiesOfT<Prof>::is_default_constructible && |
| PropertiesOfT<Prof>::is_move_assignable>:: |
| Invoke(ExpectDefaultConstructWithMoveAssign<T, Prof>{errors}, |
| generator); |
| |
| If<PropertiesOfT<Prof>::is_default_constructible && |
| PropertiesOfT<Prof>::is_copy_assignable>:: |
| Invoke(ExpectDefaultConstructWithCopyAssign<T, Prof>{errors}, |
| generator); |
| |
| If<PropertiesOfT<Prof>::is_move_assignable>::Invoke( |
| ExpectSelfMoveAssign<T, Prof>{errors}, generator); |
| |
| If<PropertiesOfT<Prof>::is_copy_assignable>::Invoke( |
| ExpectSelfCopyAssign<T, Prof>{errors}, generator); |
| |
| If<PropertiesOfT<Prof>::is_swappable>::Invoke( |
| ExpectSelfSwap<T, Prof>{errors}, generator); |
| } |
| |
| ConformanceErrors* errors; |
| }; |
| |
| // Check move-assign with two different values. |
| template <class T, class Prof> |
| struct ExpectMoveAssign { |
| template <class Fun0, class Fun1> |
| void operator()(const Fun0& generator0, const Fun1& generator1) const { |
| const T source_of_truth1 = generator1(); |
| T object = generator0(); |
| object = generator1(); |
| |
| (ExpectEquality<T, Prof>)(errors, "Move assignment", |
| PrepareGivenContext( |
| GivenDeclaration{"const _T source_of_truth1", |
| generator1.description}, |
| GivenDeclaration{"_T object", |
| generator0.description}, |
| GivenDeclaration{"object", |
| generator1.description}), |
| object, source_of_truth1, "std::as_const(object)", |
| "source_of_truth1"); |
| } |
| |
| ConformanceErrors* errors; |
| }; |
| |
| // Check copy-assign with two different values. |
| template <class T, class Prof> |
| struct ExpectCopyAssign { |
| template <class Fun0, class Fun1> |
| void operator()(const Fun0& generator0, const Fun1& generator1) const { |
| const T source_of_truth1 = generator1(); |
| T object = generator0(); |
| object = static_cast<const T&>(generator1()); |
| |
| (ExpectEquality<T, Prof>)(errors, "Copy assignment", |
| PrepareGivenContext( |
| GivenDeclaration{"const _T source_of_truth1", |
| generator1.description}, |
| GivenDeclaration{"_T object", |
| generator0.description}, |
| GivenDeclaration{ |
| "object", |
| std::string("static_cast<const _T&>(") + |
| generator1.description + ")"}), |
| object, source_of_truth1, "std::as_const(object)", |
| "source_of_truth1"); |
| } |
| |
| ConformanceErrors* errors; |
| }; |
| |
| // Check swap with two different values. |
| template <class T, class Prof> |
| struct ExpectSwap { |
| template <class Fun0, class Fun1> |
| void operator()(const Fun0& generator0, const Fun1& generator1) const { |
| const T source_of_truth0 = generator0(); |
| const T source_of_truth1 = generator1(); |
| T object0 = generator0(); |
| T object1 = generator1(); |
| |
| type_traits_internal::Swap(object0, object1); |
| |
| const std::string context = |
| PrepareGivenContext( |
| GivenDeclaration{"const _T source_of_truth0", |
| generator0.description}, |
| GivenDeclaration{"const _T source_of_truth1", |
| generator1.description}, |
| GivenDeclaration{"_T object0", generator0.description}, |
| GivenDeclaration{"_T object1", generator1.description}) + |
| "After performing a swap:\n" |
| " using std::swap;\n" |
| " swap(object0, object1);\n" |
| "\n"; |
| |
| (ExpectEquality<T, Prof>)(errors, "Swap", context, object0, |
| source_of_truth1, "std::as_const(object0)", |
| "source_of_truth1"); |
| (ExpectEquality<T, Prof>)(errors, "Swap", context, object1, |
| source_of_truth0, "std::as_const(object1)", |
| "source_of_truth0"); |
| } |
| |
| ConformanceErrors* errors; |
| }; |
| |
| // Validate that `generator0` and `generator1` produce values that are equal. |
| template <class T, class Prof> |
| struct ExpectEquivalenceClassComparison { |
| template <class Fun0, class Fun1> |
| void operator()(const Fun0& generator0, const Fun1& generator1) const { |
| const T object0 = generator0(); |
| const T object1 = generator1(); |
| |
| (ExpectEquality<T, Prof>)(errors, "Comparison", |
| PrepareGivenContext( |
| GivenDeclaration{"const _T object0", |
| generator0.description}, |
| GivenDeclaration{"const _T object1", |
| generator1.description}), |
| object0, object1, "object0", "object1"); |
| } |
| |
| ConformanceErrors* errors; |
| }; |
| |
| // Validate that all objects in the same equivalence-class have the same value. |
| template <class T, class Prof> |
| struct ExpectEquivalenceClassConsistency { |
| template <class Fun0, class Fun1> |
| void operator()(const Fun0& generator0, const Fun1& generator1) const { |
| If<PropertiesOfT<Prof>::is_move_assignable>::Invoke( |
| ExpectMoveAssign<T, Prof>{errors}, generator0, generator1); |
| |
| If<PropertiesOfT<Prof>::is_copy_assignable>::Invoke( |
| ExpectCopyAssign<T, Prof>{errors}, generator0, generator1); |
| |
| If<PropertiesOfT<Prof>::is_swappable>::Invoke(ExpectSwap<T, Prof>{errors}, |
| generator0, generator1); |
| } |
| |
| ConformanceErrors* errors; |
| }; |
| |
| // Given a "lesser" object and a "greater" object, perform every combination of |
| // comparison operators supported for the type, expecting consistent results. |
| template <class T, class Prof> |
| void ExpectOrdered(ConformanceErrors* errors, absl::string_view context, |
| const T& small, const T& big, absl::string_view small_name, |
| absl::string_view big_name) { |
| const absl::string_view test_name = "Comparison"; |
| |
| If<PropertiesOfT<Prof>::is_equality_comparable>::Invoke( |
| ExpectNotEq{errors}, test_name, context, small, big, small_name, |
| big_name); |
| If<PropertiesOfT<Prof>::is_equality_comparable>::Invoke( |
| ExpectNotEq{errors}, test_name, context, big, small, big_name, |
| small_name); |
| |
| If<PropertiesOfT<Prof>::is_inequality_comparable>::Invoke( |
| ExpectNe{errors}, test_name, context, small, big, small_name, big_name); |
| If<PropertiesOfT<Prof>::is_inequality_comparable>::Invoke( |
| ExpectNe{errors}, test_name, context, big, small, big_name, small_name); |
| |
| If<PropertiesOfT<Prof>::is_less_than_comparable>::Invoke( |
| ExpectLt{errors}, test_name, context, small, big, small_name, big_name); |
| If<PropertiesOfT<Prof>::is_less_than_comparable>::Invoke( |
| ExpectNotLt{errors}, test_name, context, big, small, big_name, |
| small_name); |
| |
| If<PropertiesOfT<Prof>::is_less_equal_comparable>::Invoke( |
| ExpectLe{errors}, test_name, context, small, big, small_name, big_name); |
| If<PropertiesOfT<Prof>::is_less_equal_comparable>::Invoke( |
| ExpectNotLe{errors}, test_name, context, big, small, big_name, |
| small_name); |
| |
| If<PropertiesOfT<Prof>::is_greater_equal_comparable>::Invoke( |
| ExpectNotGe{errors}, test_name, context, small, big, small_name, |
| big_name); |
| If<PropertiesOfT<Prof>::is_greater_equal_comparable>::Invoke( |
| ExpectGe{errors}, test_name, context, big, small, big_name, small_name); |
| |
| If<PropertiesOfT<Prof>::is_greater_than_comparable>::Invoke( |
| ExpectNotGt{errors}, test_name, context, small, big, small_name, |
| big_name); |
| If<PropertiesOfT<Prof>::is_greater_than_comparable>::Invoke( |
| ExpectGt{errors}, test_name, context, big, small, big_name, small_name); |
| } |
| |
| // For every two elements of an equivalence class, makes sure that those two |
| // elements compare equal, including checks with the same argument passed as |
| // both operands. |
| template <class T, class Prof> |
| struct ExpectEquivalenceClassComparisons { |
| template <class... Funs> |
| void operator()(EquivalenceClassType<Funs...> eq_class) const { |
| (ForEachTupleElement)(ExpectSelfComparison<T, Prof>{errors}, |
| eq_class.generators); |
| |
| (ForEveryTwo)(ExpectEquivalenceClassComparison<T, Prof>{errors}, |
| eq_class.generators); |
| } |
| |
| ConformanceErrors* errors; |
| }; |
| |
| // For every element of an equivalence class, makes sure that the element is |
| // self-consistent (in other words, if any of move/copy/swap are defined, |
| // perform those operations and make such that results and operands still |
| // compare equal to known values whenever it is required for that operation. |
| template <class T, class Prof> |
| struct ExpectEquivalenceClass { |
| template <class... Funs> |
| void operator()(EquivalenceClassType<Funs...> eq_class) const { |
| (ForEachTupleElement)(ExpectConsistency<T, Prof>{errors}, |
| eq_class.generators); |
| |
| (ForEveryTwo)(ExpectEquivalenceClassConsistency<T, Prof>{errors}, |
| eq_class.generators); |
| } |
| |
| ConformanceErrors* errors; |
| }; |
| |
| // Validate that the passed-in argument is a generator of a greater value than |
| // the one produced by the "small_gen" datamember with respect to all of the |
| // comparison operators that Prof requires, with both argument orders to test. |
| template <class T, class Prof, class SmallGenerator> |
| struct ExpectBiggerGeneratorThanComparisons { |
| template <class BigGenerator> |
| void operator()(BigGenerator big_gen) const { |
| const T small = small_gen(); |
| const T big = big_gen(); |
| |
| (ExpectOrdered<T, Prof>)(errors, |
| PrepareGivenContext( |
| GivenDeclaration{"const _T small", |
| small_gen.description}, |
| GivenDeclaration{"const _T big", |
| big_gen.description}), |
| small, big, "small", "big"); |
| } |
| |
| SmallGenerator small_gen; |
| ConformanceErrors* errors; |
| }; |
| |
| // Perform all of the move, copy, and swap checks on the value generated by |
| // `small_gen` and the value generated by `big_gen`. |
| template <class T, class Prof, class SmallGenerator> |
| struct ExpectBiggerGeneratorThan { |
| template <class BigGenerator> |
| void operator()(BigGenerator big_gen) const { |
| If<PropertiesOfT<Prof>::is_move_assignable>::Invoke( |
| ExpectMoveAssign<T, Prof>{errors}, small_gen, big_gen); |
| If<PropertiesOfT<Prof>::is_move_assignable>::Invoke( |
| ExpectMoveAssign<T, Prof>{errors}, big_gen, small_gen); |
| |
| If<PropertiesOfT<Prof>::is_copy_assignable>::Invoke( |
| ExpectCopyAssign<T, Prof>{errors}, small_gen, big_gen); |
| If<PropertiesOfT<Prof>::is_copy_assignable>::Invoke( |
| ExpectCopyAssign<T, Prof>{errors}, big_gen, small_gen); |
| |
| If<PropertiesOfT<Prof>::is_swappable>::Invoke(ExpectSwap<T, Prof>{errors}, |
| small_gen, big_gen); |
| } |
| |
| SmallGenerator small_gen; |
| ConformanceErrors* errors; |
| }; |
| |
| // Validate that the result of a generator is greater than the results of all |
| // generators in an equivalence class with respect to comparisons. |
| template <class T, class Prof, class SmallGenerator> |
| struct ExpectBiggerGeneratorThanEqClassesComparisons { |
| template <class BigEqClass> |
| void operator()(BigEqClass big_eq_class) const { |
| (ForEachTupleElement)( |
| ExpectBiggerGeneratorThanComparisons<T, Prof, SmallGenerator>{small_gen, |
| errors}, |
| big_eq_class.generators); |
| } |
| |
| SmallGenerator small_gen; |
| ConformanceErrors* errors; |
| }; |
| |
| // Validate that the non-comparison binary operations required by Prof are |
| // correct for the result of each generator of big_eq_class and a generator of |
| // the logically smaller value returned by small_gen. |
| template <class T, class Prof, class SmallGenerator> |
| struct ExpectBiggerGeneratorThanEqClasses { |
| template <class BigEqClass> |
| void operator()(BigEqClass big_eq_class) const { |
| (ForEachTupleElement)( |
| ExpectBiggerGeneratorThan<T, Prof, SmallGenerator>{small_gen, errors}, |
| big_eq_class.generators); |
| } |
| |
| SmallGenerator small_gen; |
| ConformanceErrors* errors; |
| }; |
| |
| // Validate that each equivalence class that is passed is logically less than |
| // the equivalence classes that comes later on in the argument list. |
| template <class T, class Prof> |
| struct ExpectOrderedEquivalenceClassesComparisons { |
| template <class... BigEqClasses> |
| struct Impl { |
| // Validate that the value produced by `small_gen` is less than all of the |
| // values generated by those of the logically larger equivalence classes. |
| template <class SmallGenerator> |
| void operator()(SmallGenerator small_gen) const { |
| (ForEachTupleElement)(ExpectBiggerGeneratorThanEqClassesComparisons< |
| T, Prof, SmallGenerator>{small_gen, errors}, |
| big_eq_classes); |
| } |
| |
| std::tuple<BigEqClasses...> big_eq_classes; |
| ConformanceErrors* errors; |
| }; |
| |
| // When given no equivalence classes, no validation is necessary. |
| void operator()() const {} |
| |
| template <class SmallEqClass, class... BigEqClasses> |
| void operator()(SmallEqClass small_eq_class, |
| BigEqClasses... big_eq_classes) const { |
| // For each generator in the first equivalence class, make sure that it is |
| // less than each of those in the logically greater equivalence classes. |
| (ForEachTupleElement)( |
| Impl<BigEqClasses...>{std::make_tuple(absl::move(big_eq_classes)...), |
| errors}, |
| small_eq_class.generators); |
| |
| // Recurse so that all equivalence class combinations are checked. |
| (*this)(absl::move(big_eq_classes)...); |
| } |
| |
| ConformanceErrors* errors; |
| }; |
| |
| // Validate that the non-comparison binary operations required by Prof are |
| // correct for the result of each generator of big_eq_classes and a generator of |
| // the logically smaller value returned by small_gen. |
| template <class T, class Prof> |
| struct ExpectOrderedEquivalenceClasses { |
| template <class... BigEqClasses> |
| struct Impl { |
| template <class SmallGenerator> |
| void operator()(SmallGenerator small_gen) const { |
| (ForEachTupleElement)( |
| ExpectBiggerGeneratorThanEqClasses<T, Prof, SmallGenerator>{small_gen, |
| errors}, |
| big_eq_classes); |
| } |
| |
| std::tuple<BigEqClasses...> big_eq_classes; |
| ConformanceErrors* errors; |
| }; |
| |
| // Check that small_eq_class is logically consistent and also is logically |
| // less than all values in big_eq_classes. |
| template <class SmallEqClass, class... BigEqClasses> |
| void operator()(SmallEqClass small_eq_class, |
| BigEqClasses... big_eq_classes) const { |
| (ForEachTupleElement)( |
| Impl<BigEqClasses...>{std::make_tuple(absl::move(big_eq_classes)...), |
| errors}, |
| small_eq_class.generators); |
| |
| (*this)(absl::move(big_eq_classes)...); |
| } |
| |
| // Terminating case of operator(). |
| void operator()() const {} |
| |
| ConformanceErrors* errors; |
| }; |
| |
| // Validate that a type meets the syntactic requirements of std::hash if the |
| // range of profiles requires it. |
| template <class T, class MinProf, class MaxProf> |
| struct ExpectHashable { |
| void operator()() const { |
| ExpectModelOfHashable<T, MinProf, MaxProf>(errors); |
| } |
| |
| ConformanceErrors* errors; |
| }; |
| |
| // Validate that the type `T` meets all of the requirements associated with |
| // `MinProf` and without going beyond the syntactic properties of `MaxProf`. |
| template <class T, class MinProf, class MaxProf> |
| struct ExpectModels { |
| void operator()(ConformanceErrors* errors) const { |
| ExpectModelOfDefaultConstructible<T, MinProf, MaxProf>(errors); |
| ExpectModelOfMoveConstructible<T, MinProf, MaxProf>(errors); |
| ExpectModelOfCopyConstructible<T, MinProf, MaxProf>(errors); |
| ExpectModelOfMoveAssignable<T, MinProf, MaxProf>(errors); |
| ExpectModelOfCopyAssignable<T, MinProf, MaxProf>(errors); |
| ExpectModelOfDestructible<T, MinProf, MaxProf>(errors); |
| ExpectModelOfEqualityComparable<T, MinProf, MaxProf>(errors); |
| ExpectModelOfInequalityComparable<T, MinProf, MaxProf>(errors); |
| ExpectModelOfLessThanComparable<T, MinProf, MaxProf>(errors); |
| ExpectModelOfLessEqualComparable<T, MinProf, MaxProf>(errors); |
| ExpectModelOfGreaterEqualComparable<T, MinProf, MaxProf>(errors); |
| ExpectModelOfGreaterThanComparable<T, MinProf, MaxProf>(errors); |
| ExpectModelOfSwappable<T, MinProf, MaxProf>(errors); |
| |
| // Only check hashability on compilers that have a compliant default-hash. |
| If<!poisoned_hash_fails_instantiation()>::Invoke( |
| ExpectHashable<T, MinProf, MaxProf>{errors}); |
| } |
| }; |
| |
| // A metafunction that yields a Profile matching the set of properties that are |
| // safe to be checked (lack-of-hashability is only checked on standard library |
| // implementations that are standards compliant in that they provide a std::hash |
| // primary template that is SFINAE-friendly) |
| template <class LogicalProf, class T> |
| struct MinimalCheckableProfile { |
| using type = |
| MinimalProfiles<PropertiesOfT<LogicalProf>, |
| PropertiesOfT<SyntacticConformanceProfileOf< |
| T, !PropertiesOfT<LogicalProf>::is_hashable && |
| poisoned_hash_fails_instantiation() |
| ? CheckHashability::no |
| : CheckHashability::yes>>>; |
| }; |
| |
| // An identity metafunction |
| template <class T> |
| struct Always { |
| using type = T; |
| }; |
| |
| // Validate the T meets all of the necessary requirements of LogicalProf, with |
| // syntactic requirements defined by the profile range [MinProf, MaxProf]. |
| template <class T, class LogicalProf, class MinProf, class MaxProf, |
| class... EqClasses> |
| ConformanceErrors ExpectRegularityImpl( |
| OrderedEquivalenceClasses<EqClasses...> vals) { |
| ConformanceErrors errors((NameOf<T>())); |
| |
| If<!constexpr_instantiation_when_unevaluated()>::Invoke( |
| ExpectModels<T, MinProf, MaxProf>(), &errors); |
| |
| using minimal_profile = typename absl::conditional_t< |
| constexpr_instantiation_when_unevaluated(), Always<LogicalProf>, |
| MinimalCheckableProfile<LogicalProf, T>>::type; |
| |
| If<PropertiesOfT<minimal_profile>::is_default_constructible>::Invoke( |
| ExpectDefaultConstructWithDestruct<T>{&errors}); |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // Perform all comparison checks first, since later checks depend on their |
| // correctness. |
| // |
| // Check all of the comparisons for all values in the same equivalence |
| // class (equal with respect to comparison operators and hash the same). |
| (ForEachTupleElement)( |
| ExpectEquivalenceClassComparisons<T, minimal_profile>{&errors}, |
| vals.eq_classes); |
| |
| // Check all of the comparisons for each combination of values that are in |
| // different equivalence classes (not equal with respect to comparison |
| // operators). |
| absl::apply( |
| ExpectOrderedEquivalenceClassesComparisons<T, minimal_profile>{&errors}, |
| vals.eq_classes); |
| // |
| ////////////////////////////////////////////////////////////////////////////// |
| |
| // Perform remaining checks, relying on comparisons. |
| // TODO(calabrese) short circuit if any comparisons above failed. |
| (ForEachTupleElement)(ExpectEquivalenceClass<T, minimal_profile>{&errors}, |
| vals.eq_classes); |
| |
| absl::apply(ExpectOrderedEquivalenceClasses<T, minimal_profile>{&errors}, |
| vals.eq_classes); |
| |
| return errors; |
| } |
| |
| // A type that represents a range of profiles that are acceptable to be matched. |
| // |
| // `MinProf` is the minimum set of syntactic requirements that must be met. |
| // |
| // `MaxProf` is the maximum set of syntactic requirements that must be met. |
| // This maximum is particularly useful for certain "strictness" checking. Some |
| // examples for when this is useful: |
| // |
| // * Making sure that a type is move-only (rather than simply movable) |
| // |
| // * Making sure that a member function is *not* noexcept in cases where it |
| // cannot be noexcept, such as if a dependent datamember has certain |
| // operations that are not noexcept. |
| // |
| // * Making sure that a type tightly matches a spec, such as the standard. |
| // |
| // `LogicalProf` is the Profile for which run-time testing is to take place. |
| // |
| // Note: The reason for `LogicalProf` is because it is often the case, when |
| // dealing with templates, that a declaration of a given operation is specified, |
| // but whose body would fail to instantiate. Examples include the |
| // copy-constructor of a standard container when the element-type is move-only, |
| // or the comparison operators of a standard container when the element-type |
| // does not have the necessary comparison operations defined. The `LogicalProf` |
| // parameter allows us to capture the intent of what should be tested at |
| // run-time, even in the cases where syntactically it might otherwise appear as |
| // though the type undergoing testing supports more than it actually does. |
| template <class LogicalProf, class MinProf = LogicalProf, |
| class MaxProf = MinProf> |
| struct ProfileRange { |
| using logical_profile = LogicalProf; |
| using min_profile = MinProf; |
| using max_profile = MaxProf; |
| }; |
| |
| // Similar to ProfileRange except that it creates a profile range that is |
| // coupled with a Domain and is used when testing that a type matches exactly |
| // the "minimum" requirements of LogicalProf. |
| template <class StrictnessDomain, class LogicalProf, |
| class MinProf = LogicalProf, class MaxProf = MinProf> |
| struct StrictProfileRange { |
| // We do not yet support extension. |
| static_assert( |
| std::is_same<StrictnessDomain, RegularityDomain>::value, |
| "Currently, the only valid StrictnessDomain is RegularityDomain."); |
| using strictness_domain = StrictnessDomain; |
| using logical_profile = LogicalProf; |
| using min_profile = MinProf; |
| using max_profile = MaxProf; |
| }; |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // A metafunction that creates a StrictProfileRange from a Domain and either a |
| // Profile or ProfileRange. |
| template <class StrictnessDomain, class ProfOrRange> |
| struct MakeStrictProfileRange; |
| |
| template <class StrictnessDomain, class LogicalProf> |
| struct MakeStrictProfileRange { |
| using type = StrictProfileRange<StrictnessDomain, LogicalProf>; |
| }; |
| |
| template <class StrictnessDomain, class LogicalProf, class MinProf, |
| class MaxProf> |
| struct MakeStrictProfileRange<StrictnessDomain, |
| ProfileRange<LogicalProf, MinProf, MaxProf>> { |
| using type = |
| StrictProfileRange<StrictnessDomain, LogicalProf, MinProf, MaxProf>; |
| }; |
| |
| template <class StrictnessDomain, class ProfOrRange> |
| using MakeStrictProfileRangeT = |
| typename MakeStrictProfileRange<StrictnessDomain, ProfOrRange>::type; |
| // |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| // A profile in the RegularityDomain with the strongest possible requirements. |
| using MostStrictProfile = |
| CombineProfiles<TriviallyCompleteProfile, NothrowComparableProfile>; |
| |
| // Forms a ProfileRange that treats the Profile as the bare minimum requirements |
| // of a type. |
| template <class LogicalProf, class MinProf = LogicalProf> |
| using LooseProfileRange = StrictProfileRange<RegularityDomain, LogicalProf, |
| MinProf, MostStrictProfile>; |
| |
| template <class Prof> |
| using MakeLooseProfileRangeT = Prof; |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // The following classes implement the metafunction ProfileRangeOfT<T> that |
| // takes either a Profile or ProfileRange and yields the ProfileRange to be |
| // used during testing. |
| // |
| template <class T, class /*Enabler*/ = void> |
| struct ProfileRangeOfImpl; |
| |
| template <class T> |
| struct ProfileRangeOfImpl<T, absl::void_t<PropertiesOfT<T>>> { |
| using type = LooseProfileRange<T>; |
| }; |
| |
| template <class T> |
| struct ProfileRangeOf : ProfileRangeOfImpl<T> {}; |
| |
| template <class StrictnessDomain, class LogicalProf, class MinProf, |
| class MaxProf> |
| struct ProfileRangeOf< |
| StrictProfileRange<StrictnessDomain, LogicalProf, MinProf, MaxProf>> { |
| using type = |
| StrictProfileRange<StrictnessDomain, LogicalProf, MinProf, MaxProf>; |
| }; |
| |
| template <class T> |
| using ProfileRangeOfT = typename ProfileRangeOf<T>::type; |
| // |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| // Extract the logical profile of a range (what will be runtime tested). |
| template <class T> |
| using LogicalProfileOfT = typename ProfileRangeOfT<T>::logical_profile; |
| |
| // Extract the minimal syntactic profile of a range (error if not at least). |
| template <class T> |
| using MinProfileOfT = typename ProfileRangeOfT<T>::min_profile; |
| |
| // Extract the maximum syntactic profile of a range (error if more than). |
| template <class T> |
| using MaxProfileOfT = typename ProfileRangeOfT<T>::max_profile; |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // |
| template <class T> |
| struct IsProfileOrProfileRange : IsProfile<T>::type {}; |
| |
| template <class StrictnessDomain, class LogicalProf, class MinProf, |
| class MaxProf> |
| struct IsProfileOrProfileRange< |
| StrictProfileRange<StrictnessDomain, LogicalProf, MinProf, MaxProf>> |
| : std::true_type {}; |
| // |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| // TODO(calabrese): Consider naming the functions in this class the same as |
| // the macros (defined later on) so that auto-complete leads to the correct name |
| // and so that a user cannot accidentally call a function rather than the macro |
| // form. |
| template <bool ExpectSuccess, class T, class... EqClasses> |
| struct ExpectConformanceOf { |
| // Add a value to be tested. Subsequent calls to this function on the same |
| // object must specify logically "larger" values with respect to the |
| // comparison operators of the type, if any. |
| // |
| // NOTE: This function should not be called directly. A stateless lambda is |
| // implicitly formed and passed when using the INITIALIZER macro at the bottom |
| // of this file. |
| template <class Fun, |
| absl::enable_if_t<std::is_same< |
| ResultOfGeneratorT<GeneratorType<Fun>>, T>::value>** = nullptr> |
| ABSL_MUST_USE_RESULT ExpectConformanceOf<ExpectSuccess, T, EqClasses..., |
| EquivalenceClassType<Fun>> |
| initializer(GeneratorType<Fun> fun) && { |
| return { |
| {std::tuple_cat(absl::move(ordered_vals.eq_classes), |
| std::make_tuple((EquivalenceClass)(absl::move(fun))))}, |
| std::move(expected_failed_tests)}; |
| } |
| |
| template <class... TestNames, |
| absl::enable_if_t<!ExpectSuccess && sizeof...(EqClasses) == 0 && |
| absl::conjunction<std::is_convertible< |
| TestNames, absl::string_view>...>::value>** = |
| nullptr> |
| ABSL_MUST_USE_RESULT ExpectConformanceOf<ExpectSuccess, T, EqClasses...> |
| due_to(TestNames&&... test_names) && { |
| (InsertEach)(&expected_failed_tests, |
| absl::AsciiStrToLower(absl::string_view(test_names))...); |
| |
| return {absl::move(ordered_vals), std::move(expected_failed_tests)}; |
| } |
| |
| template <class... TestNames, int = 0, // MSVC disambiguator |
| absl::enable_if_t<ExpectSuccess && sizeof...(EqClasses) == 0 && |
| absl::conjunction<std::is_convertible< |
| TestNames, absl::string_view>...>::value>** = |
| nullptr> |
| ABSL_MUST_USE_RESULT ExpectConformanceOf<ExpectSuccess, T, EqClasses...> |
| due_to(TestNames&&... test_names) && { |
| // TODO(calabrese) Instead have DUE_TO only exist via a CRTP base. |
| // This would produce better errors messages than the static_assert. |
| static_assert(!ExpectSuccess, |
| "DUE_TO cannot be called when conformance is expected -- did " |
| "you mean to use ASSERT_NONCONFORMANCE_OF?"); |
| } |
| |
| // Add a value to be tested. Subsequent calls to this function on the same |
| // object must specify logically "larger" values with respect to the |
| // comparison operators of the type, if any. |
| // |
| // NOTE: This function should not be called directly. A stateful lambda is |
| // implicitly formed and passed when using the INITIALIZER macro at the bottom |
| // of this file. |
| template <class Fun, |
| absl::enable_if_t<std::is_same< |
| ResultOfGeneratorT<GeneratorType<Fun>>, T>::value>** = nullptr> |
| ABSL_MUST_USE_RESULT ExpectConformanceOf<ExpectSuccess, T, EqClasses..., |
| EquivalenceClassType<Fun>> |
| dont_class_directly_stateful_initializer(GeneratorType<Fun> fun) && { |
| return { |
| {std::tuple_cat(absl::move(ordered_vals.eq_classes), |
| std::make_tuple((EquivalenceClass)(absl::move(fun))))}, |
| std::move(expected_failed_tests)}; |
| } |
| |
| // Add a set of value to be tested, where each value is equal with respect to |
| // the comparison operators and std::hash specialization, if defined. |
| template < |
| class... Funs, |
| absl::void_t<absl::enable_if_t<std::is_same< |
| ResultOfGeneratorT<GeneratorType<Funs>>, T>::value>...>** = nullptr> |
| ABSL_MUST_USE_RESULT ExpectConformanceOf<ExpectSuccess, T, EqClasses..., |
| EquivalenceClassType<Funs...>> |
| equivalence_class(GeneratorType<Funs>... funs) && { |
| return {{std::tuple_cat( |
| absl::move(ordered_vals.eq_classes), |
| std::make_tuple((EquivalenceClass)(absl::move(funs)...)))}, |
| std::move(expected_failed_tests)}; |
| } |
| |
| // Execute the tests for the captured set of values, strictly matching a range |
| // of expected profiles in a given domain. |
| template < |
| class ProfRange, |
| absl::enable_if_t<IsProfileOrProfileRange<ProfRange>::value>** = nullptr> |
| ABSL_MUST_USE_RESULT ::testing::AssertionResult with_strict_profile( |
| ProfRange /*profile*/) { |
| ConformanceErrors test_result = |
| (ExpectRegularityImpl< |
| T, LogicalProfileOfT<ProfRange>, MinProfileOfT<ProfRange>, |
| MaxProfileOfT<ProfRange>>)(absl::move(ordered_vals)); |
| |
| return ExpectSuccess ? test_result.assertionResult() |
| : test_result.expectFailedTests(expected_failed_tests); |
| } |
| |
| // Execute the tests for the captured set of values, loosely matching a range |
| // of expected profiles (loose in that an interface is allowed to be more |
| // refined that a profile suggests, such as a type having a noexcept copy |
| // constructor when all that is required is that the copy constructor exists). |
| template <class Prof, absl::enable_if_t<IsProfile<Prof>::value>** = nullptr> |
| ABSL_MUST_USE_RESULT ::testing::AssertionResult with_loose_profile( |
| Prof /*profile*/) { |
| ConformanceErrors test_result = |
| (ExpectRegularityImpl< |
| T, Prof, Prof, |
| CombineProfiles<TriviallyCompleteProfile, |
| NothrowComparableProfile>>)(absl:: |
| move(ordered_vals)); |
| |
| return ExpectSuccess ? test_result.assertionResult() |
| : test_result.expectFailedTests(expected_failed_tests); |
| } |
| |
| OrderedEquivalenceClasses<EqClasses...> ordered_vals; |
| std::set<std::string> expected_failed_tests; |
| }; |
| |
| template <class T> |
| using ExpectConformanceOfType = ExpectConformanceOf</*ExpectSuccess=*/true, T>; |
| |
| template <class T> |
| using ExpectNonconformanceOfType = |
| ExpectConformanceOf</*ExpectSuccess=*/false, T>; |
| |
| struct EquivalenceClassMaker { |
| // TODO(calabrese) Constrain to callable |
| template <class Fun> |
| static GeneratorType<Fun> initializer(GeneratorType<Fun> fun) { |
| return fun; |
| } |
| }; |
| |
| // A top-level macro that begins the builder pattern. |
| // |
| // The argument here takes the datatype to be tested. |
| #define ABSL_INTERNAL_ASSERT_CONFORMANCE_OF(...) \ |
| GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ |
| if ABSL_INTERNAL_LPAREN \ |
| const ::testing::AssertionResult gtest_ar = \ |
| ABSL_INTERNAL_LPAREN ::absl::types_internal::ExpectConformanceOfType< \ |
| __VA_ARGS__>() |
| |
| // Akin to ASSERT_CONFORMANCE_OF except that it expects failure and tries to |
| // match text. |
| #define ABSL_INTERNAL_ASSERT_NONCONFORMANCE_OF(...) \ |
| GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ |
| if ABSL_INTERNAL_LPAREN \ |
| const ::testing::AssertionResult gtest_ar = \ |
| ABSL_INTERNAL_LPAREN ::absl::types_internal::ExpectNonconformanceOfType< \ |
| __VA_ARGS__>() |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // NOTE: The following macros look like they are recursive, but are not (macros |
| // cannot recurse). These actually refer to member functions of the same name. |
| // This is done intentionally so that a user cannot accidentally invoke a |
| // member function of the conformance-testing suite without going through the |
| // macro. |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| // Specify expected test failures as comma-separated strings. |
| #define DUE_TO(...) due_to(__VA_ARGS__) |
| |
| // Specify a value to be tested. |
| // |
| // Note: Internally, this takes an expression and turns it into the return value |
| // of lambda that captures no data. The expression is stringized during |
| // preprocessing so that it can be used in error reports. |
| #define INITIALIZER(...) \ |
| initializer(::absl::types_internal::Generator( \ |
| [] { return __VA_ARGS__; }, ABSL_INTERNAL_STRINGIZE(__VA_ARGS__))) |
| |
| // Specify a value to be tested. |
| // |
| // Note: Internally, this takes an expression and turns it into the return value |
| // of lambda that captures data by reference. The expression is stringized |
| // during preprocessing so that it can be used in error reports. |
| #define STATEFUL_INITIALIZER(...) \ |
| stateful_initializer(::absl::types_internal::Generator( \ |
| [&] { return __VA_ARGS__; }, ABSL_INTERNAL_STRINGIZE(__VA_ARGS__))) |
| |
| // Used in the builder-pattern. |
| // |
| // Takes a series of INITIALIZER and/or STATEFUL_INITIALIZER invocations and |
| // forwards them along to be tested, grouping them such that the testing suite |
| // knows that they are supposed to represent the same logical value (the values |
| // compare the same, hash the same, etc.). |
| #define EQUIVALENCE_CLASS(...) \ |
| equivalence_class(ABSL_INTERNAL_TRANSFORM_ARGS( \ |
| ABSL_INTERNAL_PREPEND_EQ_MAKER, __VA_ARGS__)) |
| |
| // An invocation of this or WITH_STRICT_PROFILE must end the builder-pattern. |
| // It takes a Profile as its argument. |
| // |
| // This executes the tests and allows types that are "more referined" than the |
| // profile specifies, but not less. For instance, if the Profile specifies |
| // noexcept copy-constructiblity, the test will fail if the copy-constructor is |
| // not noexcept, however, it will succeed if the copy constructor is trivial. |
| // |
| // This is useful for testing that a type meets some minimum set of |
| // requirements. |
| #define WITH_LOOSE_PROFILE(...) \ |
| with_loose_profile( \ |
| ::absl::types_internal::MakeLooseProfileRangeT<__VA_ARGS__>()) \ |
| ABSL_INTERNAL_RPAREN ABSL_INTERNAL_RPAREN; \ |
| else GTEST_FATAL_FAILURE_(gtest_ar.failure_message()) // NOLINT |
| |
| // An invocation of this or WITH_STRICT_PROFILE must end the builder-pattern. |
| // It takes a Domain and a Profile as its arguments. |
| // |
| // This executes the tests and disallows types that differ at all from the |
| // properties of the Profile. For instance, if the Profile specifies noexcept |
| // copy-constructiblity, the test will fail if the copy constructor is trivial. |
| // |
| // This is useful for testing that a type does not do anything more than a |
| // specification requires, such as to minimize things like Hyrum's Law, or more |
| // commonly, to prevent a type from being "accidentally" copy-constructible in |
| // a way that may produce incorrect results, simply because the user forget to |
| // delete that operation. |
| #define WITH_STRICT_PROFILE(...) \ |
| with_strict_profile( \ |
| ::absl::types_internal::MakeStrictProfileRangeT<__VA_ARGS__>()) \ |
| ABSL_INTERNAL_RPAREN ABSL_INTERNAL_RPAREN; \ |
| else GTEST_FATAL_FAILURE_(gtest_ar.failure_message()) // NOLINT |
| |
| // Internal macro that is used in the internals of the EDSL when forming |
| // equivalence classes. |
| #define ABSL_INTERNAL_PREPEND_EQ_MAKER(arg) \ |
| ::absl::types_internal::EquivalenceClassMaker().arg |
| |
| } // namespace types_internal |
| ABSL_NAMESPACE_END |
| } // namespace absl |
| |
| #endif // ABSL_TYPES_INTERNAL_CONFORMANCE_TESTING_H_ |