/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ #include "AzCore/Outcome/Outcome.h" #include #include #include namespace UnitTest { //! Class that tracks how it was build and modified struct KnowThyself { enum class State { NormalConstructor, CopyConstructor, MoveConstructor, InitializerListConstructor, CopyAssignment, MoveAssignment, StolenFrom, Destroyed, }; KnowThyself(int id) : m_state(State::NormalConstructor) , m_id(id) {} KnowThyself(const KnowThyself& rhs) : m_state(State::CopyConstructor) , m_id(rhs.m_id) , m_originalState(rhs.m_state) , m_lineage(rhs.m_lineage + 1) { } KnowThyself(KnowThyself&& rhs) : m_state(State::MoveConstructor) , m_id(rhs.m_id) , m_originalState(rhs.m_state) , m_lineage(rhs.m_lineage + 1) { rhs.m_state = State::StolenFrom; } KnowThyself(std::initializer_list lastBecomesId) : m_state(State::InitializerListConstructor) , m_id(*(lastBecomesId.end() - 1)) {} KnowThyself& operator=(const KnowThyself& rhs) { m_state = State::CopyAssignment; m_id = rhs.m_id; m_originalState = rhs.m_state; m_lineage = rhs.m_lineage + 1; return *this; } KnowThyself& operator=(KnowThyself&& rhs) { m_state = State::MoveAssignment; m_id = rhs.m_id; m_originalState = rhs.m_state; m_lineage = rhs.m_lineage + 1; rhs.m_state = State::StolenFrom; return *this; } ~KnowThyself() { m_state = State::Destroyed; } State m_state = State::Destroyed; int m_id = -1; State m_originalState = State::Destroyed; int m_lineage = 0; }; struct Aligned16 { AZ_ALIGN(char m_data, 16); }; //! Class that changes a value when it's created and destroyed. //! +1 on creation. -1 on destruction. struct RefCounter { RefCounter(int* ref) { intPtr = ref; (*intPtr)++; } RefCounter(const RefCounter& other) { intPtr = other.intPtr; (*intPtr)++; } RefCounter(RefCounter&& other) { intPtr = other.intPtr; other.intPtr = nullptr; } ~RefCounter() { if (intPtr) { (*intPtr)--; } } RefCounter& operator=(const RefCounter& other) { intPtr = other.intPtr; (*intPtr)++; return *this; } int* intPtr; }; //! A class for testing that the appropriate constructor and operator= //! functions are used when copying Outcome's contained value. //! A raw copy of this class or its members will not suffice. struct ClassWithComplexMembers { //! unordered_set contains a list, //! The list contains a head node with next and prev pointers. //! In an empty list, the head node points at itself. //! A raw copy of this list results in a head node that //! points to the head node from the other list. AZStd::unordered_set s1; AZStd::unordered_set s2; }; class OutcomeTest : public ::testing::Test { public: enum class Error { InvalidInput, KnuckleHeads, }; AZ::Outcome TestReturnsError() { return AZ::Failure(Error::InvalidInput); } AZ::Outcome TestReturnsValue(int id) { return AZ::Success(KnowThyself(id)); } void run() { { // test function returning error auto outcome = TestReturnsError(); AZ_TEST_ASSERT(!outcome.IsSuccess()); AZ_TEST_ASSERT(outcome.GetError() == Error::InvalidInput); if (outcome) { AZ_TEST_ASSERT(false && "bool operator not working for errors"); } // test const and non-const GetError() const auto& constOutcome = outcome; AZ_TEST_ASSERT(constOutcome.GetError() == Error::InvalidInput); outcome.GetError() = Error::KnuckleHeads; AZ_TEST_ASSERT(outcome.GetError() == Error::KnuckleHeads); } { // test function returning success auto outcome = TestReturnsValue(5); AZ_TEST_ASSERT(outcome.IsSuccess()); AZ_TEST_ASSERT(outcome.GetValue().m_id == 5); if (!outcome) { AZ_TEST_ASSERT(false && "bool operator not working for successes"); } // test const and non-const GetValue() const auto& constOutcome = outcome; AZ_TEST_ASSERT(constOutcome.GetValue().m_id == 5); outcome.GetValue().m_id = 7; AZ_TEST_ASSERT(outcome.GetValue().m_id == 7); } { // test value move constructor AZ::Outcome outcome = AZ::Success(KnowThyself(5)); AZ_TEST_ASSERT(outcome.IsSuccess()); AZ_TEST_ASSERT(outcome.GetValue().m_id == 5); AZ_TEST_ASSERT(outcome.GetValue().m_state == KnowThyself::State::MoveConstructor); } { // test value copy constructor KnowThyself lvalue(5); AZ::Outcome outcome = AZ::Success(lvalue); AZ_TEST_ASSERT(outcome.IsSuccess()); AZ_TEST_ASSERT(outcome.GetValue().m_id == 5); AZ_TEST_ASSERT(outcome.GetValue().m_originalState == KnowThyself::State::CopyConstructor); AZ_TEST_ASSERT(lvalue.m_state != KnowThyself::State::StolenFrom); } { // test error copy constructor KnowThyself lvalue(5); AZ::Outcome outcome = AZ::Failure(lvalue); AZ_TEST_ASSERT(!outcome.IsSuccess()); AZ_TEST_ASSERT(outcome.GetError().m_id == 5); AZ_TEST_ASSERT(outcome.GetError().m_originalState == KnowThyself::State::CopyConstructor); } { // test error move constructor AZ::Outcome outcome = AZ::Failure(KnowThyself(5)); AZ_TEST_ASSERT(!outcome.IsSuccess()); AZ_TEST_ASSERT(outcome.GetError().m_id == 5); AZ_TEST_ASSERT(outcome.GetError().m_state == KnowThyself::State::MoveConstructor); } { // test copy constructor on successful outcome AZ::Outcome outcomeSrc = AZ::Success(KnowThyself(5)); AZ::Outcome outcomeDst = outcomeSrc; AZ_TEST_ASSERT(outcomeDst.IsSuccess()); AZ_TEST_ASSERT(outcomeDst.GetValue().m_id == 5); AZ_TEST_ASSERT(outcomeDst.GetValue().m_state == KnowThyself::State::CopyConstructor); AZ_TEST_ASSERT(outcomeSrc.GetValue().m_state != KnowThyself::State::StolenFrom); } { // test copy constructor on failed outcome AZ::Outcome outcomeSrc = AZ::Failure(KnowThyself(5)); AZ::Outcome outcomeDst = outcomeSrc; AZ_TEST_ASSERT(!outcomeDst.IsSuccess()); AZ_TEST_ASSERT(outcomeDst.GetError().m_id == 5); AZ_TEST_ASSERT(outcomeDst.GetError().m_state == KnowThyself::State::CopyConstructor); AZ_TEST_ASSERT(outcomeSrc.GetError().m_state != KnowThyself::State::StolenFrom); } { // test move constructor on successful outcome AZ::Outcome outcome(AZ::Outcome(AZ::Success(KnowThyself(5)))); AZ_TEST_ASSERT(outcome.IsSuccess()); AZ_TEST_ASSERT(outcome.GetValue().m_id == 5); AZ_TEST_ASSERT(outcome.GetValue().m_state == KnowThyself::State::MoveConstructor); } { // test move constructor on failed outcome AZ::Outcome outcome(AZ::Outcome(AZ::Failure(KnowThyself(5)))); AZ_TEST_ASSERT(!outcome.IsSuccess()); AZ_TEST_ASSERT(outcome.GetError().m_id == 5); AZ_TEST_ASSERT(outcome.GetError().m_state == KnowThyself::State::MoveConstructor); } { // test assignment between successful outcomes AZ::Outcome outcomeSrc(AZ::Success(KnowThyself(5))); AZ::Outcome outcomeDst(AZ::Success(KnowThyself(99))); outcomeDst = outcomeSrc; // copy from src AZ_TEST_ASSERT(outcomeDst.GetValue().m_id == 5); AZ_TEST_ASSERT(outcomeDst.GetValue().m_state == KnowThyself::State::CopyAssignment); AZ_TEST_ASSERT(outcomeSrc.GetValue().m_state != KnowThyself::State::StolenFrom); outcomeDst = AZStd::move(outcomeSrc); // move from src AZ_TEST_ASSERT(outcomeDst.GetValue().m_id == 5); AZ_TEST_ASSERT(outcomeDst.GetValue().m_state == KnowThyself::State::MoveAssignment); AZ_TEST_ASSERT(outcomeSrc.GetValue().m_state == KnowThyself::State::StolenFrom); } { // test assignment between failed outcomes AZ::Outcome outcomeSrc(AZ::Failure(KnowThyself(5))); AZ::Outcome outcomeDst(AZ::Failure(KnowThyself(99))); outcomeDst = outcomeSrc; // copy from src AZ_TEST_ASSERT(outcomeDst.GetError().m_id == 5); AZ_TEST_ASSERT(outcomeDst.GetError().m_state == KnowThyself::State::CopyAssignment); AZ_TEST_ASSERT(outcomeSrc.GetError().m_state != KnowThyself::State::StolenFrom); outcomeDst = AZStd::move(outcomeSrc); // move from src AZ_TEST_ASSERT(outcomeDst.GetError().m_id == 5); AZ_TEST_ASSERT(outcomeDst.GetError().m_state == KnowThyself::State::MoveAssignment); AZ_TEST_ASSERT(outcomeSrc.GetError().m_state == KnowThyself::State::StolenFrom); } { // test assignment operator going from successful to failed outcome int counter = 0; AZ::Outcome successToFailure = AZ::Success(RefCounter(&counter)); AZ::Outcome failedOutcome = AZ::Failure(KnowThyself(5)); AZ_TEST_ASSERT(counter == 1); successToFailure = failedOutcome; AZ_TEST_ASSERT(counter == 0); // ensure destructor ran for successToFailure.m_success AZ_TEST_ASSERT(!successToFailure.IsSuccess()); AZ_TEST_ASSERT(successToFailure.GetError().m_id == 5); AZ_TEST_ASSERT(failedOutcome.GetError().m_state != KnowThyself::State::StolenFrom); successToFailure = AZ::Success(RefCounter(&counter)); // reset back to success AZ_TEST_ASSERT(counter == 1); successToFailure = AZStd::move(failedOutcome); AZ_TEST_ASSERT(counter == 0); // ensure destructor ran for successToFailure.m_success AZ_TEST_ASSERT(!successToFailure.IsSuccess()); AZ_TEST_ASSERT(successToFailure.GetError().m_id == 5); AZ_TEST_ASSERT(failedOutcome.GetError().m_state == KnowThyself::State::StolenFrom); } { // test assignment operator going from failed to successful outcome int counter = 0; AZ::Outcome failureToSuccess = AZ::Failure(RefCounter(&counter)); AZ::Outcome successfulOutcome = AZ::Success(KnowThyself(5)); AZ_TEST_ASSERT(counter == 1); failureToSuccess = successfulOutcome; AZ_TEST_ASSERT(counter == 0); // ensure destructor ran for failureToSuccess.m_failure AZ_TEST_ASSERT(failureToSuccess.IsSuccess()); AZ_TEST_ASSERT(failureToSuccess.GetValue().m_id == 5); AZ_TEST_ASSERT(successfulOutcome.GetValue().m_state != KnowThyself::State::StolenFrom); failureToSuccess = AZ::Failure(RefCounter(&counter)); // reset back to failure AZ_TEST_ASSERT(counter == 1); failureToSuccess = AZStd::move(successfulOutcome); AZ_TEST_ASSERT(counter == 0); // ensure destructor ran for failureToSuccess.m_failure AZ_TEST_ASSERT(failureToSuccess.IsSuccess()); AZ_TEST_ASSERT(failureToSuccess.GetValue().m_id == 5); AZ_TEST_ASSERT(successfulOutcome.GetValue().m_state == KnowThyself::State::StolenFrom); } { // test value type with trivial destructor AZ::Outcome outcome = AZ::Success(5); AZ_TEST_ASSERT(outcome.IsSuccess()); } { // test error type with trivial destructor AZ::Outcome outcome = AZ::Failure(5); AZ_TEST_ASSERT(!outcome.IsSuccess()); } { // test TakeValue and TakeError AZ::Outcome goodOutcome = AZ::Success(KnowThyself(5)); KnowThyself fromGoodOutcome = goodOutcome.TakeValue(); AZ_TEST_ASSERT(fromGoodOutcome.m_id == 5); AZ_TEST_ASSERT(fromGoodOutcome.m_state == KnowThyself::State::MoveConstructor); AZ_TEST_ASSERT(goodOutcome.GetValue().m_state == KnowThyself::State::StolenFrom); AZ::Outcome failedOutcome = AZ::Failure(KnowThyself(5)); KnowThyself fromFailedOutcome = failedOutcome.TakeError(); AZ_TEST_ASSERT(fromFailedOutcome.m_id == 5); AZ_TEST_ASSERT(fromFailedOutcome.m_state == KnowThyself::State::MoveConstructor); AZ_TEST_ASSERT(failedOutcome.GetError().m_state == KnowThyself::State::StolenFrom); } { // test GetValueOr() AZ::Outcome goodOutcome = AZ::Success(KnowThyself(5)); AZ::Outcome failedOutcome = AZ::Failure(Error::InvalidInput); KnowThyself shouldBeFive = goodOutcome.GetValueOr(KnowThyself(7)); AZ_TEST_ASSERT(shouldBeFive.m_id == 5); KnowThyself shouldBeSeven = failedOutcome.GetValueOr(KnowThyself(7)); AZ_TEST_ASSERT(shouldBeSeven.m_id == 7); // test that GetValueOr properly handles rvalue parameters KnowThyself shouldBeStolenFrom(9); KnowThyself shouldBeNine = failedOutcome.GetValueOr(AZStd::move(shouldBeStolenFrom)); AZ_TEST_ASSERT(shouldBeNine.m_id == 9); AZ_TEST_ASSERT(shouldBeStolenFrom.m_state == KnowThyself::State::StolenFrom); // test that GetValueOr properly handles lvalue parameters KnowThyself shouldEqualGood = failedOutcome.GetValueOr(goodOutcome.GetValue()); AZ_TEST_ASSERT(shouldEqualGood.m_id == 5); AZ_TEST_ASSERT(goodOutcome.GetValue().m_state != KnowThyself::State::StolenFrom); } { // test alignment AZ_TEST_STATIC_ASSERT((AZStd::alignment_of >::value == 16)); AZ_TEST_STATIC_ASSERT((AZStd::alignment_of >::value == 16)); AZ::Outcome outcomeA = AZ::Success(Aligned16()); AZ::Outcome outcomeB = AZ::Failure(Aligned16()); AZ_TEST_ASSERT(reinterpret_cast(&outcomeA.GetValue().m_data) % 16 == 0); AZ_TEST_ASSERT(reinterpret_cast(&outcomeB.GetError().m_data) % 16 == 0); } { // test with void types AZ::Outcome outcome1 = AZ::Success(); AZ_TEST_ASSERT(outcome1.IsSuccess()); AZ::Outcome outcome2 = AZ::Failure(); AZ_TEST_ASSERT(!outcome2.IsSuccess()); AZ::Outcome outcome3 = AZ::Success(); AZ_TEST_ASSERT(outcome3.IsSuccess()); AZ::Outcome outcome4 = AZ::Failure(Error::KnuckleHeads); AZ_TEST_ASSERT(!outcome4.IsSuccess()); AZ_TEST_ASSERT(outcome4.GetError() == Error::KnuckleHeads); AZ::Outcome outcome5 = AZ::Success(Error::KnuckleHeads); AZ_TEST_ASSERT(outcome5.IsSuccess()); AZ_TEST_ASSERT(outcome5.GetValue() == Error::KnuckleHeads); AZ::Outcome outcome6 = AZ::Failure(); AZ_TEST_ASSERT(!outcome6.IsSuccess()); } { // test that not too many copies/moves are performed // Please edit these test cases when number of moves/copies changes AZ::Outcome outcome1 = AZ::Success(KnowThyself(2)); AZ_TEST_ASSERT(outcome1.GetValue().m_state == KnowThyself::State::MoveConstructor); AZ_TEST_ASSERT(outcome1.GetValue().m_lineage == 2); KnowThyself val1 = outcome1.GetValue(); AZ_TEST_ASSERT(val1.m_lineage == 3); AZ::Outcome outcome2 = AZStd::move(outcome1); AZ_TEST_ASSERT(outcome2.GetValue().m_state == KnowThyself::State::MoveConstructor); AZ::Outcome outcome3 = outcome2; AZ_TEST_ASSERT(outcome3.GetValue().m_state == KnowThyself::State::CopyConstructor); } { // A previous iteration of Outcome would fail due to // the SuccessValue being copied improperly. ClassWithComplexMembers complex; AZ::Outcome outcome = AZ::Success(AZStd::move(complex)); AZ_TEST_ASSERT(outcome.GetValue().s1.empty()); } } }; TEST_F(OutcomeTest, Test) { run(); } class OutcomeSerializationTest : public AllocatorsFixture { public: // We must expose the class for serialization first. void SetUp() override { AllocatorsFixture::SetUp(); AZ::AllocatorInstance::Create(); AZ::AllocatorInstance::Create(); m_serializeContext = AZStd::make_unique(); } void TearDown() override { m_serializeContext.reset(); AZ::AllocatorInstance::Destroy(); AZ::AllocatorInstance::Destroy(); AllocatorsFixture::TearDown(); } protected: AZStd::unique_ptr m_serializeContext; }; TEST_F(OutcomeSerializationTest, ReflectingOutcomeTwiceDoesNotLogError) { using TestOutcome = AZ::Outcome; AZ::GenericClassInfo* outcomeGenericInfo = AZ::SerializeGenericTypeInfo::GetGenericInfo(); ASSERT_NE(nullptr, outcomeGenericInfo); outcomeGenericInfo->Reflect(m_serializeContext.get()); AZ_TEST_START_TRACE_SUPPRESSION; outcomeGenericInfo->Reflect(m_serializeContext.get()); AZ_TEST_STOP_TRACE_SUPPRESSION(0); } } // namespace UnitTest