| // Copyright 2026 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. | |
| #include "absl/status/status_builder.h" | |
| #include <stddef.h> | |
| #include <memory> | |
| #include <string> | |
| #include <type_traits> | |
| #include <utility> | |
| #include <vector> | |
| #include "gmock/gmock.h" | |
| #include "gtest/gtest.h" | |
| #include "absl/base/config.h" | |
| #include "absl/log/log.h" | |
| #include "absl/log/log_entry.h" | |
| #include "absl/log/log_sink.h" | |
| #include "absl/status/status.h" | |
| #include "absl/status/status_matchers.h" | |
| #include "absl/status/statusor.h" | |
| #include "absl/strings/string_view.h" | |
| #include "absl/types/source_location.h" | |
| namespace absl { | |
| ABSL_NAMESPACE_BEGIN | |
| namespace { | |
| using ::absl_testing::StatusIs; | |
| using ::testing::AllOf; | |
| using ::testing::AnyOf; | |
| using ::testing::ElementsAre; | |
| using ::testing::Eq; | |
| using ::testing::IsEmpty; | |
| using ::testing::Pointee; | |
| using ::testing::Property; | |
| // Converts a StatusBuilder to a Status. | |
| absl::Status ToStatus(const StatusBuilder& s) { return s; } | |
| // Converts a StatusBuilder to a StatusOr<T>. | |
| template <typename T> | |
| absl::StatusOr<T> ToStatusOr(const StatusBuilder& s) { | |
| return s; | |
| } | |
| void CheckSourceLocation( | |
| const absl::Status& status, std::vector<int> lines = {}, | |
| absl::SourceLocation loc = absl::SourceLocation::current()) { | |
| ASSERT_EQ(status.GetSourceLocations().size(), lines.size()) | |
| << "Size check failed at " << loc.line(); | |
| for (size_t i = 0; i < lines.size(); ++i) { | |
| EXPECT_EQ(absl::string_view(status.GetSourceLocations()[i].file_name()), | |
| absl::string_view(loc.file_name())) | |
| << "File name check failed at " << loc.line(); | |
| EXPECT_EQ(status.GetSourceLocations()[i].line(), lines[i]) | |
| << "Line check failed at " << loc.line(); | |
| } | |
| } | |
| class StatusBuilderTest : public ::testing::Test {}; | |
| TEST_F(StatusBuilderTest, Size) { | |
| EXPECT_LE(sizeof(StatusBuilder), 40) | |
| << "Relax this test with caution and thorough testing. If StatusBuilder " | |
| "is too large it can potentially blow stacks, especially in debug " | |
| "builds. See the comments for StatusBuilder::Rep."; | |
| } | |
| TEST_F(StatusBuilderTest, ExplicitSourceLocation) { | |
| const absl::SourceLocation kLocation = absl::SourceLocation::current(); | |
| { | |
| const StatusBuilder builder(absl::OkStatus(), kLocation); | |
| EXPECT_THAT(builder.source_location().file_name(), | |
| Eq(kLocation.file_name())); | |
| EXPECT_THAT(builder.source_location().line(), Eq(kLocation.line())); | |
| } | |
| } | |
| TEST_F(StatusBuilderTest, ImplicitSourceLocation) { | |
| const StatusBuilder builder(absl::OkStatus()); | |
| auto loc = absl::SourceLocation::current(); | |
| EXPECT_THAT(builder.source_location().file_name(), | |
| AnyOf(Eq(absl::string_view(loc.file_name())), | |
| Eq(absl::string_view("<source_location>")))); | |
| EXPECT_THAT(builder.source_location().line(), | |
| AnyOf(Eq(1), Eq(loc.line() - 1))); | |
| } | |
| testing::Matcher<absl::SourceLocation> SourceLocationIs( | |
| absl::SourceLocation loc) { | |
| return AnyOf( | |
| AllOf(Property(&absl::SourceLocation::file_name, Eq(loc.file_name())), | |
| Property(&absl::SourceLocation::line, Eq(loc.line()))), | |
| // Fallback for platforms that don't support source locations. | |
| AllOf(Property(&absl::SourceLocation::file_name, Eq("<source_location>")), | |
| Property(&absl::SourceLocation::line, Eq(1)))); | |
| } | |
| TEST_F(StatusBuilderTest, GetPreviousSourceLocations) { | |
| const absl::SourceLocation loc0 = absl::SourceLocation::current(); | |
| absl::Status status = absl::InvalidArgumentError("hi", loc0); | |
| const absl::SourceLocation loc1 = absl::SourceLocation::current(); | |
| status.AddSourceLocation(loc1); | |
| const absl::SourceLocation loc2 = absl::SourceLocation::current(); | |
| status.AddSourceLocation(loc2); | |
| // The builder's location is not included. | |
| const StatusBuilder builder(status); | |
| EXPECT_THAT(builder.GetPreviousSourceLocations(), | |
| ElementsAre(SourceLocationIs(loc0), SourceLocationIs(loc1), | |
| SourceLocationIs(loc2))); | |
| } | |
| TEST_F(StatusBuilderTest, EmptyGetPreviousSourceLocationsForNewFromStatusCode) { | |
| const StatusBuilder builder(absl::StatusCode::kInvalidArgument); | |
| EXPECT_THAT(builder.GetPreviousSourceLocations(), IsEmpty()); | |
| } | |
| TEST_F(StatusBuilderTest, StatusCode) { | |
| // OK | |
| { | |
| const StatusBuilder builder(absl::StatusCode::kOk); | |
| EXPECT_TRUE(builder.ok()); | |
| EXPECT_THAT(builder.code(), Eq(absl::StatusCode::kOk)); | |
| } | |
| // Non-OK code | |
| { | |
| const StatusBuilder builder(absl::StatusCode::kInvalidArgument); | |
| EXPECT_FALSE(builder.ok()); | |
| EXPECT_THAT(builder.code(), Eq(absl::StatusCode::kInvalidArgument)); | |
| } | |
| } | |
| TEST_F(StatusBuilderTest, OkIgnoresStuff) { | |
| EXPECT_THAT(ToStatus(StatusBuilder(absl::OkStatus(), absl::SourceLocation()) | |
| << "booyah"), | |
| Eq(absl::OkStatus())); | |
| } | |
| TEST_F(StatusBuilderTest, Streaming) { | |
| EXPECT_THAT( | |
| ToStatus(StatusBuilder(absl::CancelledError(), absl::SourceLocation()) | |
| << "booyah"), | |
| Eq(absl::CancelledError("booyah"))); | |
| EXPECT_THAT( | |
| ToStatus( | |
| StatusBuilder(absl::AbortedError("hello"), absl::SourceLocation()) | |
| << "world"), | |
| Eq(absl::AbortedError("hello; world"))); | |
| } | |
| TEST_F(StatusBuilderTest, PrependLvalue) { | |
| { | |
| StatusBuilder builder(absl::CancelledError(), absl::SourceLocation()); | |
| EXPECT_THAT(ToStatus(builder.SetPrepend() << "booyah"), | |
| Eq(absl::CancelledError("booyah"))); | |
| } | |
| { | |
| StatusBuilder builder(absl::AbortedError(" hello"), absl::SourceLocation()); | |
| EXPECT_THAT(ToStatus(builder.SetPrepend() << "world"), | |
| Eq(absl::AbortedError("world hello"))); | |
| } | |
| } | |
| TEST_F(StatusBuilderTest, PrependRvalue) { | |
| EXPECT_THAT( | |
| ToStatus(StatusBuilder(absl::CancelledError(), absl::SourceLocation()) | |
| .SetPrepend() | |
| << "booyah"), | |
| Eq(absl::CancelledError("booyah"))); | |
| EXPECT_THAT(ToStatus(StatusBuilder(absl::AbortedError(" hello"), | |
| absl::SourceLocation()) | |
| .SetPrepend() | |
| << "world"), | |
| Eq(absl::AbortedError("world hello"))); | |
| } | |
| TEST_F(StatusBuilderTest, AppendLvalue) { | |
| { | |
| StatusBuilder builder(absl::CancelledError(), absl::SourceLocation()); | |
| EXPECT_THAT(ToStatus(builder.SetAppend() << "booyah"), | |
| Eq(absl::CancelledError("booyah"))); | |
| } | |
| { | |
| StatusBuilder builder(absl::AbortedError("hello"), absl::SourceLocation()); | |
| EXPECT_THAT(ToStatus(builder.SetAppend() << " world"), | |
| Eq(absl::AbortedError("hello world"))); | |
| } | |
| } | |
| TEST_F(StatusBuilderTest, AppendRvalue) { | |
| EXPECT_THAT( | |
| ToStatus(StatusBuilder(absl::CancelledError(), absl::SourceLocation()) | |
| .SetAppend() | |
| << "booyah"), | |
| Eq(absl::CancelledError("booyah"))); | |
| EXPECT_THAT(ToStatus(StatusBuilder(absl::AbortedError("hello"), | |
| absl::SourceLocation()) | |
| .SetAppend() | |
| << " world"), | |
| Eq(absl::AbortedError("hello world"))); | |
| } | |
| TEST_F(StatusBuilderTest, WithRvalueRef) { | |
| auto policy = [](StatusBuilder sb) { return sb << "policy"; }; | |
| EXPECT_THAT(ToStatus(StatusBuilder(absl::AbortedError("hello"), | |
| absl::SourceLocation()) | |
| .With(policy)), | |
| Eq(absl::AbortedError("hello; policy"))); | |
| } | |
| TEST_F(StatusBuilderTest, WithRef) { | |
| auto policy = [](StatusBuilder sb) { return sb << "policy"; }; | |
| StatusBuilder sb(absl::AbortedError("zomg"), absl::SourceLocation()); | |
| EXPECT_THAT(ToStatus(sb.With(policy)), | |
| Eq(absl::AbortedError("zomg; policy"))); | |
| } | |
| TEST_F(StatusBuilderTest, WithTypeChange) { | |
| auto policy = [](StatusBuilder sb) -> std::string { | |
| return sb.ok() ? "true" : "false"; | |
| }; | |
| EXPECT_EQ(StatusBuilder(absl::CancelledError(), absl::SourceLocation()) | |
| .With(policy), | |
| "false"); | |
| EXPECT_EQ( | |
| StatusBuilder(absl::OkStatus(), absl::SourceLocation()).With(policy), | |
| "true"); | |
| } | |
| struct MoveOnlyAdaptor { | |
| std::unique_ptr<int> value; | |
| std::unique_ptr<int> operator()(const absl::Status&) && { | |
| return std::move(value); | |
| } | |
| }; | |
| TEST_F(StatusBuilderTest, WithMoveOnlyAdaptor) { | |
| StatusBuilder sb(absl::AbortedError("zomg"), absl::SourceLocation()); | |
| EXPECT_THAT(sb.With(MoveOnlyAdaptor{std::make_unique<int>(100)}), | |
| Pointee(100)); | |
| EXPECT_THAT(StatusBuilder(absl::AbortedError("zomg"), absl::SourceLocation()) | |
| .With(MoveOnlyAdaptor{std::make_unique<int>(100)}), | |
| Pointee(100)); | |
| } | |
| struct StringifiableType { | |
| absl::string_view message; | |
| template <typename Sink> | |
| friend void AbslStringify(Sink& sink, const StringifiableType& o) { | |
| sink.Append(o.message); | |
| } | |
| }; | |
| class MockLogSink : public absl::LogSink { | |
| public: | |
| MOCK_METHOD(void, Send, (const absl::LogEntry&), (override)); | |
| }; | |
| TEST(WithExtraMessagePolicyTest, AppendsToExtraMessage) { | |
| // The policy simply calls operator<< on the builder; the following examples | |
| // demonstrate that, without duplicating all of the above tests. | |
| EXPECT_THAT(ToStatus(StatusBuilder(absl::AbortedError("hello"), | |
| absl::SourceLocation()) | |
| .With(ExtraMessage("world"))), | |
| Eq(absl::AbortedError("hello; world"))); | |
| EXPECT_THAT(ToStatus(StatusBuilder(absl::AbortedError("hello"), | |
| absl::SourceLocation()) | |
| .With(ExtraMessage() << "world")), | |
| Eq(absl::AbortedError("hello; world"))); | |
| EXPECT_THAT(ToStatus(StatusBuilder(absl::AbortedError("hello"), | |
| absl::SourceLocation()) | |
| .With(ExtraMessage("world")) | |
| .With(ExtraMessage("!"))), | |
| Eq(absl::AbortedError("hello; world!"))); | |
| EXPECT_THAT(ToStatus(StatusBuilder(absl::AbortedError("hello"), | |
| absl::SourceLocation()) | |
| .With(ExtraMessage("world, ")) | |
| .SetPrepend()), | |
| Eq(absl::AbortedError("world, hello"))); | |
| EXPECT_THAT(ToStatus(StatusBuilder(absl::AbortedError("hello"), | |
| absl::SourceLocation()) | |
| .With(ExtraMessage() << StringifiableType{"world"})), | |
| Eq(absl::AbortedError("hello; world"))); | |
| // The above examples use temporary StatusBuilder rvalues; verify things also | |
| // work fine when StatusBuilder is an lvalue. | |
| StatusBuilder builder(absl::AbortedError("hello"), absl::SourceLocation()); | |
| EXPECT_THAT( | |
| ToStatus(builder.With(ExtraMessage("world")).With(ExtraMessage("!"))), | |
| Eq(absl::AbortedError("hello; world!"))); | |
| } | |
| TEST(WithExtraMessagePolicyTest, | |
| ExtraMessageStreamOperatorPreservesRvalueness) { | |
| static_assert( | |
| std::is_same_v<ExtraMessage&&, decltype(ExtraMessage() << "foo")>); | |
| } | |
| TEST_F(StatusBuilderTest, StatusSourceLocationChaining) { | |
| { | |
| absl::Status src = absl::OkStatus(); | |
| CheckSourceLocation(src); | |
| CheckSourceLocation(ToStatus(StatusBuilder(src, absl::SourceLocation()))); | |
| CheckSourceLocation( | |
| ToStatus(StatusBuilder(src, absl::SourceLocation::current()))); | |
| CheckSourceLocation( | |
| ToStatus(StatusBuilder(src, absl::SourceLocation::current()) << "hmm")); | |
| } | |
| { | |
| absl::Status src = absl::Status(absl::StatusCode::kCancelled, ""); | |
| CheckSourceLocation(src); | |
| CheckSourceLocation(ToStatus(StatusBuilder(src, absl::SourceLocation()))); | |
| CheckSourceLocation( | |
| ToStatus(StatusBuilder(src, absl::SourceLocation::current()))); | |
| CheckSourceLocation( | |
| ToStatus(StatusBuilder(src, absl::SourceLocation::current()) << "")); | |
| CheckSourceLocation( | |
| ToStatus(StatusBuilder(src, absl::SourceLocation::current()) << "hmm"), | |
| {__builtin_LINE() - 1}); | |
| } | |
| { | |
| absl::Status src = absl::Status(absl::StatusCode::kCancelled, "msg", | |
| absl::SourceLocation()); | |
| CheckSourceLocation(src); | |
| CheckSourceLocation(ToStatus(StatusBuilder(src, absl::SourceLocation()))); | |
| CheckSourceLocation( | |
| ToStatus(StatusBuilder(src, absl::SourceLocation::current())), | |
| {__builtin_LINE() - 1}); | |
| CheckSourceLocation( | |
| ToStatus(StatusBuilder(src, absl::SourceLocation::current()) << "hmm"), | |
| {__builtin_LINE() - 1}); | |
| } | |
| { | |
| absl::Status src = absl::Status(absl::StatusCode::kCancelled, "msg"); | |
| int src_line = __builtin_LINE() - 1; | |
| CheckSourceLocation(src, {src_line}); | |
| CheckSourceLocation(ToStatus(StatusBuilder(src, absl::SourceLocation())), | |
| {src_line}); | |
| CheckSourceLocation( | |
| ToStatus(StatusBuilder(src, absl::SourceLocation::current())), | |
| {src_line, __builtin_LINE() - 1}); | |
| CheckSourceLocation( | |
| ToStatus(StatusBuilder(src, absl::SourceLocation::current()) << "hmm"), | |
| {src_line, __builtin_LINE() - 1}); | |
| } | |
| } | |
| TEST_F(StatusBuilderTest, SetErrorCode) { | |
| StatusBuilder builder; | |
| builder.SetCode(absl::StatusCode::kResourceExhausted); | |
| LOG(INFO) << "Builder code: " << builder; | |
| EXPECT_FALSE(builder.ok()); | |
| EXPECT_EQ(builder.code(), absl::StatusCode::kResourceExhausted); | |
| } | |
| TEST_F(StatusBuilderTest, BuilderToStatusOrStatusShouldGiveErrorStatusOr) { | |
| absl::StatusOr<absl::Status> value = StatusBuilder(absl::CancelledError()); | |
| ASSERT_FALSE(value.ok()); | |
| EXPECT_THAT(value.status(), StatusIs(absl::StatusCode::kCancelled)); | |
| } | |
| } // namespace | |
| ABSL_NAMESPACE_END | |
| } // namespace absl |