| // Copyright 2021 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/error.h" |
| |
| #include <sstream> |
| |
| #include <gtest/gtest.h> |
| |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/uuid.h" |
| |
| namespace bt { |
| namespace { |
| |
| enum class TestError : uint8_t { |
| kSuccess = 0, |
| kFail1 = 1, |
| kFail2 = 2, |
| }; |
| |
| enum class TestErrorWithoutSuccess { |
| kFail0 = 0, |
| kFail1 = 1, |
| }; |
| |
| // Test detail::IsErrorV |
| static_assert(detail::IsErrorV<Error<TestError>>); |
| static_assert(!detail::IsErrorV<TestError>); |
| |
| } // namespace |
| |
| // This template specialization must be in ::bt for name lookup reasons |
| template <> |
| struct ProtocolErrorTraits<TestError> { |
| static std::string ToString(TestError code) { |
| switch (code) { |
| case TestError::kSuccess: |
| return "success (TestError 0)"; |
| case TestError::kFail1: |
| return "fail 1 (TestError 1)"; |
| case TestError::kFail2: |
| return "fail 2 (TestError 2)"; |
| default: |
| return "unknown (TestError)"; |
| } |
| } |
| |
| static constexpr bool is_success(TestError proto_code) { |
| return proto_code == TestError::kSuccess; |
| } |
| }; |
| |
| template <> |
| struct ProtocolErrorTraits<TestErrorWithoutSuccess> { |
| static std::string ToString(TestErrorWithoutSuccess code) { |
| switch (code) { |
| case TestErrorWithoutSuccess::kFail0: |
| return "fail 0 (TestErrorWithoutSuccess 0)"; |
| case TestErrorWithoutSuccess::kFail1: |
| return "fail 1 (TestErrorWithoutSuccess 1)"; |
| default: |
| return "unknown (TestError)"; |
| } |
| } |
| |
| // is_success() is omitted |
| }; |
| |
| namespace { |
| |
| // Build an Error when |proto_code| is guaranteed to be an error. This could be |
| // consteval in C++20 so that if the code isn't an error, it doesn't compile. |
| constexpr Error<TestError> MakeError(TestError proto_code) { |
| return ToResult(proto_code).error_value(); |
| } |
| |
| TEST(ErrorTest, ResultFromNonSuccessHostError) { |
| // Create a result that can hold TestError but which holds a HostError |
| constexpr fit::result result = ToResult<TestError>(HostError::kFailed); |
| ASSERT_TRUE(result.is_error()); |
| |
| // Unwrap the result then access Error::is(…) |
| constexpr Error error = result.error_value(); |
| ASSERT_TRUE(error.is_host_error()); |
| EXPECT_FALSE(error.is_protocol_error()); |
| EXPECT_EQ(HostError::kFailed, error.host_error()); |
| EXPECT_TRUE(error.is(HostError::kFailed)); |
| EXPECT_FALSE(error.is(HostError::kTimedOut)); |
| |
| // Compare to protocol error |
| EXPECT_FALSE(error.is(TestError::kFail1)); |
| EXPECT_FALSE(error.is(TestError::kSuccess)); |
| |
| // Compare result to error |
| EXPECT_EQ(error, result); |
| EXPECT_EQ(result, error); |
| } |
| |
| TEST(ErrorTest, ResultFromSuccessHostError) { |
| constexpr fit::result result = ToResult<TestError>(TestError::kSuccess); |
| ASSERT_EQ(fit::ok(), result); |
| |
| // Compare result to error |
| const Error error = MakeError(TestError::kFail1); |
| EXPECT_NE(error, result); |
| EXPECT_NE(result, error); |
| } |
| |
| TEST(ErrorTest, ResultFromNonSuccessGeneralHostError) { |
| // Create a result that can hold and only holds a HostError |
| constexpr fit::result result = ToResult(HostError::kFailed); |
| ASSERT_TRUE(result.is_error()); |
| |
| // Unwrap the result then access Error::is(…) |
| constexpr Error general_error = result.error_value(); |
| ASSERT_TRUE(general_error.is_host_error()); |
| EXPECT_FALSE(general_error.is_protocol_error()); |
| EXPECT_EQ(HostError::kFailed, general_error.host_error()); |
| EXPECT_TRUE(general_error.is(HostError::kFailed)); |
| EXPECT_FALSE(general_error.is(HostError::kTimedOut)); |
| |
| // Compare result to error |
| EXPECT_EQ(general_error, result); |
| EXPECT_EQ(result, general_error); |
| |
| // Create a specific kind of Error from the only-HostError-holding Error |
| constexpr Error<TestError> specific_error = general_error; |
| EXPECT_TRUE(specific_error.is(HostError::kFailed)); |
| EXPECT_EQ(general_error, specific_error); |
| EXPECT_EQ(specific_error, general_error); |
| |
| // Test operator!= |
| constexpr Error different_specific_error = MakeError(TestError::kFail1); |
| EXPECT_NE(general_error, different_specific_error); |
| EXPECT_NE(different_specific_error, general_error); |
| } |
| |
| TEST(ErrorTest, ResultFromNonSuccessProtocolError) { |
| constexpr fit::result result = ToResult(TestError::kFail1); |
| ASSERT_TRUE(result.is_error()); |
| |
| // Unwrap the result then access Error::is(…) |
| constexpr Error error = result.error_value(); |
| ASSERT_TRUE(error.is_protocol_error()); |
| EXPECT_FALSE(error.is_host_error()); |
| EXPECT_EQ(TestError::kFail1, error.protocol_error()); |
| EXPECT_TRUE(error.is(TestError::kFail1)); |
| EXPECT_FALSE(error.is(TestError::kSuccess)); |
| EXPECT_FALSE(error.is(TestError::kFail2)); |
| |
| // Compare to HostError |
| EXPECT_FALSE(error.is(HostError::kFailed)); |
| |
| // Compare result to error |
| EXPECT_EQ(error, result); |
| EXPECT_EQ(result, error); |
| } |
| |
| TEST(ErrorTest, ResultFromSuccessProtocolError) { |
| constexpr fit::result result = ToResult(TestError::kSuccess); |
| ASSERT_EQ(fit::ok(), result); |
| |
| // Compare result to error |
| const Error error = MakeError(TestError::kFail1); |
| EXPECT_NE(error, result); |
| EXPECT_NE(result, error); |
| } |
| |
| TEST(ErrorTest, ResultFromNonSuccessProtocolErrorThatOnlyHoldsErrors) { |
| // Use public ctor to construct the error directly. |
| constexpr Error<TestErrorWithoutSuccess> error( |
| TestErrorWithoutSuccess::kFail0); |
| EXPECT_TRUE(error.is(TestErrorWithoutSuccess::kFail0)); |
| } |
| |
| TEST(ErrorDeathTest, ReadingHostErrorThatIsNotPresentIsFatal) { |
| const Error error = MakeError(TestError::kFail1); |
| ASSERT_DEATH_IF_SUPPORTED([[maybe_unused]] auto _ = error.host_error(), |
| "HostError"); |
| } |
| |
| TEST(ErrorDeathTest, ReadingProtocolErrorThatIsNotPresentIsFatal) { |
| const Error<TestError> error(HostError::kFailed); |
| ASSERT_DEATH_IF_SUPPORTED([[maybe_unused]] auto _ = error.protocol_error(), |
| "protocol error"); |
| } |
| |
| TEST(ErrorTest, ResultIsAnyOf) { |
| constexpr Error error = MakeError(TestError::kFail1); |
| |
| // None of the arguments compare equal to error's contents |
| EXPECT_FALSE(error.is_any_of( |
| HostError::kFailed, TestError::kFail2, TestError::kSuccess)); |
| |
| // One argument matches |
| EXPECT_TRUE(error.is_any_of( |
| HostError::kFailed, TestError::kFail2, TestError::kFail1)); |
| EXPECT_TRUE(error.is_any_of( |
| HostError::kFailed, TestError::kFail1, TestError::kFail2)); |
| EXPECT_TRUE(error.is_any_of(TestError::kFail1)); |
| } |
| |
| TEST(ErrorTest, ErrorCanBeComparedInTests) { |
| const Error error = MakeError(TestError::kFail1); |
| |
| // Compare to HostError |
| EXPECT_FALSE(error.is(HostError::kFailed)); |
| |
| // Use operator== through GTest |
| EXPECT_EQ(error, error); |
| |
| // Use operator!= through GTest |
| EXPECT_NE(MakeError(TestError::kFail2), error); |
| EXPECT_NE(Error<>(HostError::kFailed), error); |
| EXPECT_NE(Error<TestError>(HostError::kFailed), error); |
| } |
| |
| TEST(ErrorTest, ResultCanBeComparedInTests) { |
| constexpr fit::result result = ToResult(TestError::kFail1); |
| |
| // Use operator== through GTest |
| EXPECT_EQ(result, result); |
| |
| // And explicitly |
| EXPECT_FALSE(result == ToResult<TestError>(HostError::kCanceled)); |
| EXPECT_FALSE(result == ToResult(HostError::kCanceled)); |
| EXPECT_FALSE(result == fit::ok()); |
| EXPECT_FALSE(result == fit::result<Error<TestError>>(fit::ok())); |
| |
| // Use operator!= through GTest |
| EXPECT_NE(ToResult<TestError>(HostError::kCanceled), result); |
| EXPECT_NE(ToResult(TestError::kFail2), result); |
| |
| // Compare to a general result |
| EXPECT_NE(ToResult(HostError::kCanceled), result); |
| EXPECT_NE(fit::result<Error<NoProtocolError>>(fit::ok()), result); |
| |
| // Compare results to fix::success |
| EXPECT_NE(fit::ok(), result); |
| EXPECT_EQ(fit::ok(), ToResult(TestError::kSuccess)); |
| |
| const fit::result<Error<TestError>, int> success_with_value = fit::ok(1); |
| const fit::result<Error<TestError>, int> error_with_value = |
| fit::error(MakeError(TestError::kFail1)); |
| const fit::result<Error<TestError>, int> different_error_with_value = |
| fit::error(MakeError(TestError::kFail2)); |
| EXPECT_EQ(success_with_value, success_with_value); |
| EXPECT_NE(success_with_value, error_with_value); |
| EXPECT_FALSE(success_with_value == error_with_value); |
| EXPECT_NE(error_with_value, different_error_with_value); |
| |
| EXPECT_EQ(ToResult(TestError::kFail1).error_value(), error_with_value); |
| EXPECT_NE(ToResult(TestError::kFail2).error_value(), error_with_value); |
| |
| const fit::result<Error<TestError>, int> error_with_value_holding_host_error = |
| fit::error(Error<TestError>(HostError::kFailed)); |
| |
| // ToResult(HostError) constructs a bt::Error<NoProtocolError> so comparisons |
| // must take this into account. |
| EXPECT_EQ(Error(HostError::kFailed), error_with_value_holding_host_error); |
| EXPECT_NE(Error(HostError::kFailed), error_with_value); |
| } |
| |
| TEST(ErrorTest, VisitOnHostError) { |
| constexpr Error<TestError> error(HostError::kFailed); |
| ASSERT_TRUE(error.is_host_error()); |
| |
| bool host_visited = false; |
| bool proto_visited = false; |
| error.Visit([&host_visited](HostError) { host_visited = true; }, |
| [&proto_visited](TestError) { proto_visited = true; }); |
| EXPECT_TRUE(host_visited); |
| EXPECT_FALSE(proto_visited); |
| } |
| |
| TEST(ErrorTest, VisitOnProtoError) { |
| constexpr Error error = MakeError(TestError::kFail1); |
| ASSERT_TRUE(error.is_protocol_error()); |
| |
| bool host_visited = false; |
| bool proto_visited = false; |
| error.Visit([&host_visited](HostError) { host_visited = true; }, |
| [&proto_visited](TestError) { proto_visited = true; }); |
| EXPECT_FALSE(host_visited); |
| EXPECT_TRUE(proto_visited); |
| } |
| |
| TEST(ErrorTest, HostErrorToString) { |
| constexpr Error error(HostError::kFailed); |
| EXPECT_EQ(HostErrorToString(error.host_error()), error.ToString()); |
| } |
| |
| TEST(ErrorTest, GeneralHostErrorToString) { |
| constexpr Error error(HostError::kFailed); |
| EXPECT_EQ(HostErrorToString(error.host_error()), error.ToString()); |
| } |
| |
| TEST(ErrorTest, ProtocolErrorToString) { |
| constexpr Error error = MakeError(TestError::kFail2); |
| EXPECT_EQ(ProtocolErrorTraits<TestError>::ToString(TestError::kFail2), |
| error.ToString()); |
| |
| // Test that GoogleTest's value printer converts to the same string |
| EXPECT_EQ(internal::ToString(error), ::testing::PrintToString(error)); |
| |
| // ostringstream::operator<< returns a ostream&, so test that our operator is |
| // compatible |
| std::ostringstream oss; |
| oss << error; |
| } |
| |
| TEST(ErrorTest, ToStringOnResult) { |
| constexpr fit::result proto_error_result = ToResult(TestError::kFail2); |
| EXPECT_EQ("[result: error(fail 2 (TestError 2))]", |
| internal::ToString(proto_error_result)); |
| constexpr fit::result<Error<TestError>> success_result = fit::ok(); |
| EXPECT_EQ("[result: ok()]", internal::ToString(success_result)); |
| constexpr fit::result<Error<TestError>, int> success_result_with_value = |
| fit::ok(1); |
| EXPECT_EQ("[result: ok(?)]", internal::ToString(success_result_with_value)); |
| constexpr fit::result<Error<TestError>, UUID> |
| success_result_with_printable_value = fit::ok(UUID(uint16_t{})); |
| EXPECT_EQ("[result: ok(00000000-0000-1000-8000-00805f9b34fb)]", |
| internal::ToString(success_result_with_printable_value)); |
| |
| // Test that GoogleTest's value printer converts to the same string |
| EXPECT_EQ(internal::ToString(proto_error_result), |
| ::testing::PrintToString(proto_error_result)); |
| EXPECT_EQ(internal::ToString(success_result), |
| ::testing::PrintToString(success_result)); |
| EXPECT_EQ(internal::ToString(success_result_with_printable_value), |
| ::testing::PrintToString(success_result_with_printable_value)); |
| |
| // The value printer will try to stream types to the GoogleTest ostream if |
| // possible, so it may not always match bt::internal::ToString's output. |
| EXPECT_EQ("[result: ok(1)]", |
| ::testing::PrintToString(success_result_with_value)); |
| } |
| |
| TEST(ErrorTest, BtIsErrorMacroCompiles) { |
| const fit::result general_error = ToResult(HostError::kFailed); |
| EXPECT_TRUE(bt_is_error(general_error, ERROR, "ErrorTest", "error message")); |
| const fit::result<Error<TestError>, int> success_with_value = fit::ok(1); |
| EXPECT_FALSE( |
| bt_is_error(success_with_value, ERROR, "ErrorTest", "error message")); |
| const fit::result<Error<TestError>, int> error_with_value = |
| fit::error(MakeError(TestError::kFail1)); |
| EXPECT_TRUE( |
| bt_is_error(error_with_value, ERROR, "ErrorTest", "error message")); |
| } |
| |
| TEST(ErrorTest, BtIsErrorOnlyEvaluatesResultOnce) { |
| int result_count = 0; |
| auto result_func = [&]() { |
| result_count++; |
| return ToResult(TestError::kFail1); |
| }; |
| bt_is_error(result_func(), ERROR, "ErrorTest", "error message"); |
| EXPECT_EQ(result_count, 1); |
| } |
| |
| TEST(ErrorTest, BtStrMacroOnResult) { |
| constexpr fit::result proto_error_result = ToResult(TestError::kFail2); |
| EXPECT_EQ(internal::ToString(proto_error_result), bt_str(proto_error_result)); |
| } |
| |
| } // namespace |
| } // namespace bt |