// Copyright 2019 Google LLC // // 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 // // http://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. // This file is a custom fork of the version in Asylo. This will become obsolete // and will be replaced once Abseil releases absl::Status. #include "sandboxed_api/util/statusor.h" #include #include #include #include "gmock/gmock.h" #include "gtest/gtest.h" #include "sandboxed_api/util/status_matchers.h" using ::testing::Eq; using ::testing::IsFalse; using ::testing::Not; using ::testing::Pointee; namespace sapi { namespace { constexpr auto kErrorCode = absl::StatusCode::kInvalidArgument; constexpr char kErrorMessage[] = "Invalid argument"; const int kIntElement = 47; constexpr char kStringElement[] = "47 is 42, corrected for inflation"; // A data type without a default constructor. struct Foo { int bar; std::string baz; explicit Foo(int value) : bar(value), baz(kStringElement) {} }; // A data type with dynamically-allocated data. struct HeapAllocatedObject { int* value; HeapAllocatedObject() { value = new int; *value = kIntElement; } HeapAllocatedObject(const HeapAllocatedObject& other) { *this = other; } HeapAllocatedObject& operator=(const HeapAllocatedObject& other) { value = new int; *value = *other.value; return *this; } HeapAllocatedObject(HeapAllocatedObject&& other) { *this = std::move(other); } HeapAllocatedObject& operator=(HeapAllocatedObject&& other) { value = other.value; other.value = nullptr; return *this; } ~HeapAllocatedObject() { delete value; } }; // Constructs a Foo. struct FooCtor { using value_type = Foo; Foo operator()() { return Foo(kIntElement); } }; // Constructs a HeapAllocatedObject. struct HeapAllocatedObjectCtor { using value_type = HeapAllocatedObject; HeapAllocatedObject operator()() { return HeapAllocatedObject(); } }; // Constructs an integer. struct IntCtor { using value_type = int; int operator()() { return kIntElement; } }; // Constructs a string. struct StringCtor { using value_type = std::string; std::string operator()() { return std::string(kStringElement); } }; // Constructs a vector of strings. struct StringVectorCtor { using value_type = std::vector; std::vector operator()() { return {kStringElement, kErrorMessage}; } }; bool operator==(const Foo& lhs, const Foo& rhs) { return (lhs.bar == rhs.bar) && (lhs.baz == rhs.baz); } bool operator==(const HeapAllocatedObject& lhs, const HeapAllocatedObject& rhs) { return *lhs.value == *rhs.value; } // Returns an rvalue reference to the StatusOr object pointed to by // |statusor|. template StatusOr&& MoveStatusOr(StatusOr* statusor) { return std::move(*statusor); } // A test fixture is required for typed tests. template class StatusOrTest : public ::testing::Test {}; using TestTypes = ::testing::Types; TYPED_TEST_SUITE(StatusOrTest, TestTypes); // Verify that the default constructor for StatusOr constructs an object with a // non-ok status. TYPED_TEST(StatusOrTest, ConstructorDefault) { StatusOr statusor; EXPECT_THAT(statusor.ok(), IsFalse()); EXPECT_THAT(statusor.status().code(), Eq(absl::StatusCode::kUnknown)); } // Verify that StatusOr can be constructed from a Status object. TYPED_TEST(StatusOrTest, ConstructorStatus) { StatusOr statusor( absl::Status(kErrorCode, kErrorMessage)); EXPECT_THAT(statusor.ok(), IsFalse()); EXPECT_THAT(statusor.status().ok(), IsFalse()); EXPECT_THAT(statusor.status(), Eq(absl::Status(kErrorCode, kErrorMessage))); } // Verify that StatusOr can be constructed from an object of its element type. TYPED_TEST(StatusOrTest, ConstructorElementConstReference) { auto value = TypeParam()(); StatusOr statusor{value}; ASSERT_THAT(statusor, IsOk()); ASSERT_THAT(statusor.status(), IsOk()); EXPECT_THAT(statusor.ValueOrDie(), Eq(value)); } // Verify that StatusOr can be constructed from an rvalue reference of an object // of its element type. TYPED_TEST(StatusOrTest, ConstructorElementRValue) { auto value = TypeParam()(); auto value_copy(value); StatusOr statusor(std::move(value)); ASSERT_THAT(statusor, IsOk()); ASSERT_THAT(statusor.status(), IsOk()); // Compare to a copy of the original value, since the original was moved. EXPECT_THAT(statusor.ValueOrDie(), Eq(value_copy)); } // Verify that StatusOr can be copy-constructed from a StatusOr with a non-ok // status. TYPED_TEST(StatusOrTest, CopyConstructorNonOkStatus) { StatusOr statusor1 = absl::Status(kErrorCode, kErrorMessage); StatusOr statusor2(statusor1); EXPECT_THAT(statusor1.ok(), Eq(statusor2.ok())); EXPECT_THAT(statusor1.status(), Eq(statusor2.status())); } // Verify that StatusOr can be copy-constructed from a StatusOr with an ok // status. TYPED_TEST(StatusOrTest, CopyConstructorOkStatus) { StatusOr statusor1{TypeParam()()}; StatusOr statusor2{statusor1}; EXPECT_THAT(statusor1.ok(), Eq(statusor2.ok())); ASSERT_THAT(statusor2, IsOk()); EXPECT_THAT(statusor1.ValueOrDie(), Eq(statusor2.ValueOrDie())); } // Verify that copy-assignment of a StatusOr with a non-ok is working as // expected. TYPED_TEST(StatusOrTest, CopyAssignmentNonOkStatus) { StatusOr statusor1{ absl::Status(kErrorCode, kErrorMessage)}; StatusOr statusor2{TypeParam()()}; // Invoke the copy-assignment operator. statusor2 = statusor1; EXPECT_THAT(statusor1.ok(), Eq(statusor2.ok())); EXPECT_THAT(statusor1.status(), Eq(statusor2.status())); } // Verify that copy-assignment of a StatusOr with an ok status is working as // expected. TYPED_TEST(StatusOrTest, CopyAssignmentOkStatus) { StatusOr statusor1{TypeParam()()}; StatusOr statusor2{ absl::Status(kErrorCode, kErrorMessage)}; // Invoke the copy-assignment operator. statusor2 = statusor1; EXPECT_THAT(statusor1.ok(), Eq(statusor2.ok())); ASSERT_THAT(statusor2, IsOk()); EXPECT_THAT(statusor1.ValueOrDie(), Eq(statusor2.ValueOrDie())); } // Verify that StatusOr can be move-constructed from a StatusOr with a non-ok // status. TYPED_TEST(StatusOrTest, MoveConstructorNonOkStatus) { absl::Status status(kErrorCode, kErrorMessage); StatusOr statusor1(status); StatusOr statusor2(std::move(statusor1)); // Verify that the status of the donor object was updated. EXPECT_THAT(statusor1.ok(), IsFalse()); // NOLINT EXPECT_THAT(statusor1.status(), StatusIs(absl::StatusCode::kInternal)); // Verify that the destination object contains the status previously held by // the donor. EXPECT_THAT(statusor2.ok(), IsFalse()); EXPECT_THAT(statusor2.status(), Eq(status)); } // Verify that StatusOr can be move-constructed from a StatusOr with an ok // status. TYPED_TEST(StatusOrTest, MoveConstructorOkStatus) { auto value = TypeParam()(); StatusOr statusor1{value}; StatusOr statusor2{std::move(statusor1)}; // The destination object should possess the value previously held by the // donor. ASSERT_THAT(statusor2, IsOk()); EXPECT_THAT(statusor2.ValueOrDie(), Eq(value)); } // Verify that move-assignment from a StatusOr with a non-ok status is working // as expected. TYPED_TEST(StatusOrTest, MoveAssignmentOperatorNonOkStatus) { absl::Status status(kErrorCode, kErrorMessage); StatusOr statusor1{status}; StatusOr statusor2{TypeParam()()}; // Invoke the move-assignment operator. statusor2 = std::move(statusor1); // Verify that the status of the donor object was updated. EXPECT_THAT(statusor1.ok(), IsFalse()); // NOLINT EXPECT_THAT(statusor1.status(), StatusIs(absl::StatusCode::kInternal)); // Verify that the destination object contains the status previously held by // the donor. EXPECT_THAT(statusor2.ok(), IsFalse()); EXPECT_THAT(statusor2.status(), Eq(status)); } // Verify that move-assignment from a StatusOr with an ok status is working as // expected. TYPED_TEST(StatusOrTest, MoveAssignmentOperatorOkStatus) { auto value = TypeParam()(); StatusOr statusor1(value); StatusOr statusor2( absl::Status(kErrorCode, kErrorMessage)); // Invoke the move-assignment operator. statusor2 = std::move(statusor1); // The destination object should possess the value previously held by the // donor. ASSERT_THAT(statusor2, IsOk()); EXPECT_THAT(statusor2.ValueOrDie(), Eq(value)); } // Verify that the sapi::IsOk() gMock matcher works with StatusOr. TYPED_TEST(StatusOrTest, IsOkMatcher) { auto value = TypeParam()(); StatusOr statusor{value}; EXPECT_THAT(statusor, IsOk()); statusor = StatusOr( absl::Status(kErrorCode, kErrorMessage)); EXPECT_THAT(statusor, Not(IsOk())); } // Tests for move-only types. These tests use std::unique_ptr<> as the // test type, since it is valuable to support this type in the Asylo infra. // These tests are not part of the typed test suite for the following reasons: // * std::unique_ptr<> cannot be used as a type in tests that expect // the test type to support copy operations. // * std::unique_ptr<> provides an equality operator that checks equality of // the underlying ptr. Consequently, it is difficult to generalize existing // tests that verify ValueOrDie() functionality using equality comparisons. // Verify that a StatusOr object can be constructed from a move-only type. TEST(StatusOrTest, InitializationMoveOnlyType) { auto* str = new std::string(kStringElement); std::unique_ptr value(str); StatusOr> statusor(std::move(value)); ASSERT_THAT(statusor, IsOk()); EXPECT_THAT(statusor.ValueOrDie().get(), Eq(str)); } // Verify that a StatusOr object can be move-constructed from a move-only type. TEST(StatusOrTest, MoveConstructorMoveOnlyType) { auto* str = new std::string(kStringElement); std::unique_ptr value{str}; StatusOr> statusor1(std::move(value)); StatusOr> statusor2(std::move(statusor1)); // The destination object should possess the value previously held by the // donor. ASSERT_THAT(statusor2, IsOk()); EXPECT_THAT(statusor2.ValueOrDie().get(), Eq(str)); } // Verify that a StatusOr object can be move-assigned to from a StatusOr object // containing a move-only type. TEST(StatusOrTest, MoveAssignmentMoveOnlyType) { auto* str = new std::string(kStringElement); std::unique_ptr value(str); StatusOr> statusor1(std::move(value)); StatusOr> statusor2( absl::Status(kErrorCode, kErrorMessage)); // Invoke the move-assignment operator. statusor2 = std::move(statusor1); // The destination object should possess the value previously held by the // donor. ASSERT_THAT(statusor2, IsOk()); EXPECT_THAT(statusor2.ValueOrDie().get(), Eq(str)); } // Verify that a value can be moved out of a StatusOr object via ValueOrDie(). TEST(StatusOrTest, ValueOrDieMovedValue) { auto* str = new std::string(kStringElement); std::unique_ptr value(str); StatusOr> statusor(std::move(value)); std::unique_ptr moved_value = std::move(statusor).ValueOrDie(); EXPECT_THAT(moved_value.get(), Eq(str)); EXPECT_THAT(*moved_value, Eq(kStringElement)); } TEST(StatusOrTest, MapToStatusOrUniquePtr) { // A reduced version of a problematic type found in the wild. All of the // operations below should compile. using MapType = std::map>>; MapType a; // Move-construction MapType b(std::move(a)); // Move-assignment a = std::move(b); } TEST(StatusOrTest, ValueOrOk) { const StatusOr status_or = 0; EXPECT_EQ(status_or.value_or(-1), 0); } TEST(StatusOrTest, ValueOrDefault) { const StatusOr status_or = absl::CancelledError(); EXPECT_EQ(status_or.value_or(-1), -1); } TEST(StatusOrTest, MoveOnlyValueOrOk) { EXPECT_THAT(StatusOr>(absl::make_unique(0)) .value_or(absl::make_unique(-1)), Pointee(0)); } TEST(StatusOr, MoveOnlyValueOrDefault) { EXPECT_THAT(StatusOr>(absl::CancelledError()) .value_or(absl::make_unique(-1)), Pointee(-1)); } } // namespace } // namespace sapi