// 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_profiles.h // ----------------------------------------------------------------------------- // // This file contains templates for representing "Regularity Profiles" and // concisely-named versions of commonly used Regularity Profiles. // // A Regularity Profile is a compile-time description of the types of operations // that a given type supports, along with properties of those operations when // they do exist. For instance, a Regularity Profile may describe a type that // has a move-constructor that is noexcept and a copy constructor that is not // noexcept. This description can then be examined and passed around to other // templates for the purposes of asserting expectations on user-defined types // via a series trait checks, or for determining what kinds of run-time tests // are able to be performed. // // Regularity Profiles are also used when creating "archetypes," which are // minimum-conforming types that meet all of the requirements of a given // Regularity Profile. For more information regarding archetypes, see // "conformance_archetypes.h". #ifndef ABSL_TYPES_INTERNAL_CONFORMANCE_PROFILE_H_ #define ABSL_TYPES_INTERNAL_CONFORMANCE_PROFILE_H_ #include #include #include #include #include "gtest/gtest.h" #include "absl/algorithm/container.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_testing_helpers.h" #include "absl/utility/utility.h" // TODO(calabrese) Add support for extending profiles. namespace absl { ABSL_NAMESPACE_BEGIN namespace types_internal { // Converts an enum to its underlying integral value. template constexpr absl::underlying_type_t UnderlyingValue(Enum value) { return static_cast>(value); } // A tag type used in place of a matcher when checking that an assertion result // does not actually contain any errors. struct NoError {}; // ----------------------------------------------------------------------------- // ConformanceErrors // ----------------------------------------------------------------------------- class ConformanceErrors { public: // Setup the error reporting mechanism by seeding it with the name of the type // that is being tested. explicit ConformanceErrors(std::string type_name) : assertion_result_(false), type_name_(std::move(type_name)) { assertion_result_ << "\n\n" "Assuming the following type alias:\n" "\n" " using _T = " << type_name_ << ";\n\n"; outputDivider(); } // Adds the test name to the list of successfully run tests iff it was not // previously reported as failing. This behavior is useful for tests that // have multiple parts, where failures and successes are reported individually // with the same test name. void addTestSuccess(absl::string_view test_name) { auto normalized_test_name = absl::AsciiStrToLower(test_name); // If the test is already reported as failing, do not add it to the list of // successes. if (test_failures_.find(normalized_test_name) == test_failures_.end()) { test_successes_.insert(std::move(normalized_test_name)); } } // Streams a single error description into the internal buffer (a visual // divider is automatically inserted after the error so that multiple errors // are visibly distinct). // // This function increases the error count by 1. // // TODO(calabrese) Determine desired behavior when if this function throws. template void addTestFailure(absl::string_view test_name, const P&... args) { // Output a message related to the test failure. assertion_result_ << "\n\n" "Failed test: " << test_name << "\n\n"; addTestFailureImpl(args...); assertion_result_ << "\n\n"; outputDivider(); auto normalized_test_name = absl::AsciiStrToLower(test_name); // If previous parts of this test succeeded, remove it from that set. test_successes_.erase(normalized_test_name); // Add the test name to the list of failed tests. test_failures_.insert(std::move(normalized_test_name)); has_error_ = true; } // Convert this object into a testing::AssertionResult instance such that it // can be used with gtest. ::testing::AssertionResult assertionResult() const { return has_error_ ? assertion_result_ : ::testing::AssertionSuccess(); } // Convert this object into a testing::AssertionResult instance such that it // can be used with gtest. This overload expects errors, using the specified // matcher. ::testing::AssertionResult expectFailedTests( const std::set& test_names) const { // Since we are expecting nonconformance, output an error message when the // type actually conformed to the specified profile. if (!has_error_) { return ::testing::AssertionFailure() << "Unexpected conformance of type:\n" " " << type_name_ << "\n\n"; } // Get a list of all expected failures that did not actually fail // (or that were not run). std::vector nonfailing_tests; absl::c_set_difference(test_names, test_failures_, std::back_inserter(nonfailing_tests)); // Get a list of all "expected failures" that were never actually run. std::vector unrun_tests; absl::c_set_difference(nonfailing_tests, test_successes_, std::back_inserter(unrun_tests)); // Report when the user specified tests that were not run. if (!unrun_tests.empty()) { const bool tests_were_run = !(test_failures_.empty() && test_successes_.empty()); // Prepare an assertion result used in the case that tests pass that were // expected to fail. ::testing::AssertionResult result = ::testing::AssertionFailure(); result << "When testing type:\n " << type_name_ << "\n\nThe following tests were expected to fail but were not " "run"; if (tests_were_run) result << " (was the test name spelled correctly?)"; result << ":\n\n"; // List all of the tests that unexpectedly passed. for (const auto& test_name : unrun_tests) { result << " " << test_name << "\n"; } if (!tests_were_run) result << "\nNo tests were run."; if (!test_failures_.empty()) { // List test failures result << "\nThe tests that were run and failed are:\n\n"; for (const auto& test_name : test_failures_) { result << " " << test_name << "\n"; } } if (!test_successes_.empty()) { // List test successes result << "\nThe tests that were run and succeeded are:\n\n"; for (const auto& test_name : test_successes_) { result << " " << test_name << "\n"; } } return result; } // If some tests passed when they were expected to fail, alert the caller. if (nonfailing_tests.empty()) return ::testing::AssertionSuccess(); // Prepare an assertion result used in the case that tests pass that were // expected to fail. ::testing::AssertionResult unexpected_successes = ::testing::AssertionFailure(); unexpected_successes << "When testing type:\n " << type_name_ << "\n\nThe following tests passed when they were " "expected to fail:\n\n"; // List all of the tests that unexpectedly passed. for (const auto& test_name : nonfailing_tests) { unexpected_successes << " " << test_name << "\n"; } return unexpected_successes; } private: void outputDivider() { assertion_result_ << "========================================"; } void addTestFailureImpl() {} template void addTestFailureImpl(const H& head, const T&... tail) { assertion_result_ << head; addTestFailureImpl(tail...); } ::testing::AssertionResult assertion_result_; std::set test_failures_; std::set test_successes_; std::string type_name_; bool has_error_ = false; }; template struct PropertiesOfImpl {}; template struct PropertiesOfImpl> { using type = typename T::properties; }; template struct PropertiesOfImpl> { using type = typename PropertiesOfImpl::type; }; template struct PropertiesOf : PropertiesOfImpl {}; template using PropertiesOfT = typename PropertiesOf::type; // NOTE: These enums use this naming convention to be consistent with the // standard trait names, which is useful since it allows us to match up each // enum name with a corresponding trait name in macro definitions. // An enum that describes the various expectations on an operations existence. enum class function_support { maybe, yes, nothrow, trivial }; constexpr const char* PessimisticPropertyDescription(function_support v) { return v == function_support::maybe ? "no" : v == function_support::yes ? "yes, potentially throwing" : v == function_support::nothrow ? "yes, nothrow" : "yes, trivial"; } // Return a string that describes the kind of property support that was // expected. inline std::string ExpectedFunctionKindList(function_support min, function_support max) { if (min == max) { std::string result = absl::StrCat("Expected:\n ", PessimisticPropertyDescription( static_cast(UnderlyingValue(min))), "\n"); return result; } std::string result = "Expected one of:\n"; for (auto curr_support = UnderlyingValue(min); curr_support <= UnderlyingValue(max); ++curr_support) { absl::StrAppend(&result, " ", PessimisticPropertyDescription( static_cast(curr_support)), "\n"); } return result; } template void ExpectModelOfImpl(ConformanceErrors* errors, Enum min_support, Enum max_support, Enum kind) { const auto kind_value = UnderlyingValue(kind); const auto min_support_value = UnderlyingValue(min_support); const auto max_support_value = UnderlyingValue(max_support); if (!(kind_value >= min_support_value && kind_value <= max_support_value)) { errors->addTestFailure( PropertyName(kind), "**Failed property expectation**\n\n", ExpectedFunctionKindList( static_cast(min_support_value), static_cast(max_support_value)), '\n', "Actual:\n ", PessimisticPropertyDescription( static_cast(kind_value))); } else { errors->addTestSuccess(PropertyName(kind)); } } #define ABSL_INTERNAL_SPECIAL_MEMBER_FUNCTION_ENUM(description, name) \ enum class name { maybe, yes, nothrow, trivial }; \ \ constexpr const char* PropertyName(name v) { return description; } \ static_assert(true, "") // Force a semicolon when using this macro. ABSL_INTERNAL_SPECIAL_MEMBER_FUNCTION_ENUM("support for default construction", default_constructible); ABSL_INTERNAL_SPECIAL_MEMBER_FUNCTION_ENUM("support for move construction", move_constructible); ABSL_INTERNAL_SPECIAL_MEMBER_FUNCTION_ENUM("support for copy construction", copy_constructible); ABSL_INTERNAL_SPECIAL_MEMBER_FUNCTION_ENUM("support for move assignment", move_assignable); ABSL_INTERNAL_SPECIAL_MEMBER_FUNCTION_ENUM("support for copy assignment", copy_assignable); ABSL_INTERNAL_SPECIAL_MEMBER_FUNCTION_ENUM("support for destruction", destructible); #undef ABSL_INTERNAL_SPECIAL_MEMBER_FUNCTION_ENUM #define ABSL_INTERNAL_INTRINSIC_FUNCTION_ENUM(description, name) \ enum class name { maybe, yes, nothrow }; \ \ constexpr const char* PropertyName(name v) { return description; } \ static_assert(true, "") // Force a semicolon when using this macro. ABSL_INTERNAL_INTRINSIC_FUNCTION_ENUM("support for ==", equality_comparable); ABSL_INTERNAL_INTRINSIC_FUNCTION_ENUM("support for !=", inequality_comparable); ABSL_INTERNAL_INTRINSIC_FUNCTION_ENUM("support for <", less_than_comparable); ABSL_INTERNAL_INTRINSIC_FUNCTION_ENUM("support for <=", less_equal_comparable); ABSL_INTERNAL_INTRINSIC_FUNCTION_ENUM("support for >=", greater_equal_comparable); ABSL_INTERNAL_INTRINSIC_FUNCTION_ENUM("support for >", greater_than_comparable); ABSL_INTERNAL_INTRINSIC_FUNCTION_ENUM("support for swap", swappable); #undef ABSL_INTERNAL_INTRINSIC_FUNCTION_ENUM enum class hashable { maybe, yes }; constexpr const char* PropertyName(hashable v) { return "support for std::hash"; } template using AlwaysFalse = std::false_type; #define ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_SPECIAL_MEMBER(name, property) \ template \ constexpr property property##_support_of() { \ return std::is_##property::value \ ? std::is_nothrow_##property::value \ ? absl::is_trivially_##property::value \ ? property::trivial \ : property::nothrow \ : property::yes \ : property::maybe; \ } \ \ template \ void ExpectModelOf##name(ConformanceErrors* errors) { \ (ExpectModelOfImpl)(errors, PropertiesOfT::property##_support, \ PropertiesOfT::property##_support, \ property##_support_of()); \ } ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_SPECIAL_MEMBER(DefaultConstructible, default_constructible); ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_SPECIAL_MEMBER(MoveConstructible, move_constructible); ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_SPECIAL_MEMBER(CopyConstructible, copy_constructible); ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_SPECIAL_MEMBER(MoveAssignable, move_assignable); ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_SPECIAL_MEMBER(CopyAssignable, copy_assignable); ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_SPECIAL_MEMBER(Destructible, destructible); #undef ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_SPECIAL_MEMBER void BoolFunction(bool) noexcept; //////////////////////////////////////////////////////////////////////////////// // // A metafunction for checking if an operation exists through SFINAE. // // `T` is the type to test and Op is an alias containing the expression to test. template class Op, class = void> struct IsOpableImpl : std::false_type {}; template class Op> struct IsOpableImpl>> : std::true_type {}; template