// Copyright 2023 The Pigweed 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. #include "pw_bluetooth_sapphire/internal/host/common/error.h" #include #include "pw_bluetooth_sapphire/internal/host/common/uuid.h" #include "pw_unit_test/framework.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>); static_assert(!detail::IsErrorV); } // namespace // This template specialization must be in ::bt for name lookup reasons template <> struct ProtocolErrorTraits { 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 { 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 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(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::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 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 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(), "host_error"); } TEST(ErrorDeathTest, ReadingProtocolErrorThatIsNotPresentIsFatal) { const Error 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(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(HostError::kCanceled)); EXPECT_FALSE(result == ToResult(HostError::kCanceled)); EXPECT_FALSE(result == fit::ok()); EXPECT_FALSE(result == fit::result>(fit::ok())); // Use operator!= through GTest EXPECT_NE(ToResult(HostError::kCanceled), result); EXPECT_NE(ToResult(TestError::kFail2), result); // Compare to a general result EXPECT_NE(ToResult(HostError::kCanceled), result); EXPECT_NE(fit::result>(fit::ok()), result); // Compare results to fix::success EXPECT_NE(fit::ok(), result); EXPECT_EQ(fit::ok(), ToResult(TestError::kSuccess)); const fit::result, int> success_with_value = fit::ok(1); const fit::result, int> error_with_value = fit::error(MakeError(TestError::kFail1)); const fit::result, 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, int> error_with_value_holding_host_error = fit::error(Error(HostError::kFailed)); // ToResult(HostError) constructs a bt::Error 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 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::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> success_result = fit::ok(); EXPECT_EQ("[result: ok()]", internal::ToString(success_result)); constexpr fit::result, int> success_result_with_value = fit::ok(1); EXPECT_EQ("[result: ok(?)]", internal::ToString(success_result_with_value)); constexpr fit::result, 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, int> success_with_value = fit::ok(1); EXPECT_FALSE( bt_is_error(success_with_value, ERROR, "ErrorTest", "error message")); const fit::result, 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