diff options
author | Abseil Team <absl-team@google.com> | 2018-05-04T16·58-0700 |
---|---|---|
committer | vslashg <gfalcon@google.com> | 2018-05-04T17·30-0400 |
commit | 26b789f9a53d086c8b8c9c2668efb251e37861cd (patch) | |
tree | 9fdcdfa6f42993dd9abce0498d18810911765211 /absl/types/variant_exception_safety_test.cc | |
parent | 9613678332c976568272c8f4a78631a29159271d (diff) |
- 07191b0f52301e1e4a790e236f7b7c2fd90561ae Disambiguates computed return type of absl::optional logi... by Abseil Team <absl-team@google.com>
- acd95f8ec4e6ec1587cb198c7f40af3c81094d92 Release container benchmarks. by Alex Strelnikov <strel@google.com> - 80f596b6b7c5e06453e778c16527d5a0e85f8413 Allow absl::base_internal::AtomicHook to have a default v... by Derek Mauro <dmauro@google.com> - 8402631546af8bcbd4acdf897d0cdfb805ad544a Release thread_identity benchmark. by Alex Strelnikov <strel@google.com> - 6dcb1e90fefb8556ce4654983d3a73c7585b4b99 Fix spelling error in variant.h by Abseil Team <absl-team@google.com> - faa8a81e1442018c0d400b09a595a5be55074715 Run tests from CMake. The CI is currently Linux only, fo... by Jon Cohen <cohenjon@google.com> - 745ed6db574f931f2ec3a88e964fb03a5f22f816 Internal change. by Derek Mauro <dmauro@google.com> - 23facd7d1c5f43ac8181b016ee4acc5955f048c1 absl::variant exception safety test. by Xiaoyi Zhang <zhangxy@google.com> - c18e21e7cf8f6e83ae9d90e536e886409dd6cf68 Reinstate the syntax check on time-zone abbreviations now... by Abseil Team <absl-team@google.com> - da469f4314f0c820665a2b5b9477af9462b23e42 Import CCTZ changes to internal copy. by Shaindel Schwartz <shaindel@google.com> - 44ea35843517be03ab256b69449ccfea64352621 Import CCTZ changes to internal copy. by Abseil Team <absl-team@google.com> - 55d1105312687c6093950fac831c7540f49045b5 Import CCTZ changes to internal copy. by Greg Falcon <gfalcon@google.com> - 58d7965ad274406410b6d833213eca04d41c6867 Add zoneinfo as a data dependency to the //absl/time tests. by Shaindel Schwartz <shaindel@google.com> - 6acc50146f9ff29015bfaaa5bf9900691f839da5 Change benchmark target type from cc_test to cc_binary. by Alex Strelnikov <strel@google.com> - db3fbdae8f9f285a466f7a070326b1ce43b6a0dd Update WORKSPACE for C++ microbenchmarks and release algo... by Alex Strelnikov <strel@google.com> - 0869ae168255242af651853ed01719166d8cebf6 Update to Bazel version 0.13.0. by Abseil Team <absl-team@google.com> - e507dd53ab788964207fdf27d31b72a33c296fab Add missing include of cstdio by Abseil Team <absl-team@google.com> GitOrigin-RevId: 07191b0f52301e1e4a790e236f7b7c2fd90561ae Change-Id: I90994cf2b438fbec894724dcd9b90882281eef56
Diffstat (limited to 'absl/types/variant_exception_safety_test.cc')
-rw-r--r-- | absl/types/variant_exception_safety_test.cc | 519 |
1 files changed, 519 insertions, 0 deletions
diff --git a/absl/types/variant_exception_safety_test.cc b/absl/types/variant_exception_safety_test.cc new file mode 100644 index 000000000000..377e4afac572 --- /dev/null +++ b/absl/types/variant_exception_safety_test.cc @@ -0,0 +1,519 @@ +// Copyright 2017 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 +// +// 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. +#include "absl/types/variant.h" + +#include <iostream> +#include <memory> +#include <utility> +#include <vector> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/base/internal/exception_safety_testing.h" +#include "absl/memory/memory.h" + +namespace absl { +namespace { + +using ::testing::MakeExceptionSafetyTester; +using ::testing::nothrow_guarantee; +using ::testing::strong_guarantee; +using ::testing::TestThrowingCtor; + +using Thrower = testing::ThrowingValue<>; +using CopyNothrow = testing::ThrowingValue<testing::TypeSpec::kNoThrowCopy>; +using MoveNothrow = testing::ThrowingValue<testing::TypeSpec::kNoThrowMove>; +using ThrowingAlloc = testing::ThrowingAllocator<Thrower>; +using ThrowerVec = std::vector<Thrower, ThrowingAlloc>; +using ThrowingVariant = + absl::variant<Thrower, CopyNothrow, MoveNothrow, ThrowerVec>; + +struct ConversionException {}; + +template <class T> +struct ExceptionOnConversion { + operator T() const { // NOLINT + throw ConversionException(); + } +}; + +// Forces a variant into the valueless by exception state. +void ToValuelessByException(ThrowingVariant& v) { // NOLINT + try { + v.emplace<Thrower>(); + v.emplace<Thrower>(ExceptionOnConversion<Thrower>()); + } catch (ConversionException& /*e*/) { + // This space intentionally left blank. + } +} + +// Check that variant is still in a usable state after an exception is thrown. +testing::AssertionResult CheckInvariants(ThrowingVariant* v) { + using testing::AssertionFailure; + using testing::AssertionSuccess; + + // Try using the active alternative + if (absl::holds_alternative<Thrower>(*v)) { + auto& t = absl::get<Thrower>(*v); + t = Thrower{-100}; + if (t.Get() != -100) { + return AssertionFailure() << "Thrower should be assigned -100"; + } + } else if (absl::holds_alternative<ThrowerVec>(*v)) { + auto& tv = absl::get<ThrowerVec>(*v); + tv.clear(); + tv.emplace_back(-100); + if (tv.size() != 1 || tv[0].Get() != -100) { + return AssertionFailure() << "ThrowerVec should be {Thrower{-100}}"; + } + } else if (absl::holds_alternative<CopyNothrow>(*v)) { + auto& t = absl::get<CopyNothrow>(*v); + t = CopyNothrow{-100}; + if (t.Get() != -100) { + return AssertionFailure() << "CopyNothrow should be assigned -100"; + } + } else if (absl::holds_alternative<MoveNothrow>(*v)) { + auto& t = absl::get<MoveNothrow>(*v); + t = MoveNothrow{-100}; + if (t.Get() != -100) { + return AssertionFailure() << "MoveNothrow should be assigned -100"; + } + } + + // Try making variant valueless_by_exception + if (!v->valueless_by_exception()) ToValuelessByException(*v); + if (!v->valueless_by_exception()) { + return AssertionFailure() << "Variant should be valueless_by_exception"; + } + try { + auto unused = absl::get<Thrower>(*v); + static_cast<void>(unused); + return AssertionFailure() << "Variant should not contain Thrower"; + } catch (absl::bad_variant_access) { + } catch (...) { + return AssertionFailure() << "Unexpected exception throw from absl::get"; + } + + // Try using the variant + v->emplace<Thrower>(100); + if (!absl::holds_alternative<Thrower>(*v) || + absl::get<Thrower>(*v) != Thrower(100)) { + return AssertionFailure() << "Variant should contain Thrower(100)"; + } + v->emplace<ThrowerVec>({Thrower(100)}); + if (!absl::holds_alternative<ThrowerVec>(*v) || + absl::get<ThrowerVec>(*v)[0] != Thrower(100)) { + return AssertionFailure() + << "Variant should contain ThrowerVec{Thrower(100)}"; + } + return AssertionSuccess(); +} + +Thrower ExpectedThrower() { return Thrower(42); } +ThrowerVec ExpectedThrowerVec() { return {Thrower(100), Thrower(200)}; } +ThrowingVariant ValuelessByException() { + ThrowingVariant v; + ToValuelessByException(v); + return v; +} +ThrowingVariant WithThrower() { return Thrower(39); } +ThrowingVariant WithThrowerVec() { + return ThrowerVec{Thrower(1), Thrower(2), Thrower(3)}; +} +ThrowingVariant WithCopyNoThrow() { return CopyNothrow(39); } +ThrowingVariant WithMoveNoThrow() { return MoveNothrow(39); } + +TEST(VariantExceptionSafetyTest, DefaultConstructor) { + TestThrowingCtor<ThrowingVariant>(); +} + +TEST(VariantExceptionSafetyTest, CopyConstructor) { + { + ThrowingVariant v(ExpectedThrower()); + TestThrowingCtor<ThrowingVariant>(v); + } + { + ThrowingVariant v(ExpectedThrowerVec()); + TestThrowingCtor<ThrowingVariant>(v); + } + { + ThrowingVariant v(ValuelessByException()); + TestThrowingCtor<ThrowingVariant>(v); + } +} + +TEST(VariantExceptionSafetyTest, MoveConstructor) { + { + ThrowingVariant v(ExpectedThrower()); + TestThrowingCtor<ThrowingVariant>(std::move(v)); + } + { + ThrowingVariant v(ExpectedThrowerVec()); + TestThrowingCtor<ThrowingVariant>(std::move(v)); + } + { + ThrowingVariant v(ValuelessByException()); + TestThrowingCtor<ThrowingVariant>(std::move(v)); + } +} + +TEST(VariantExceptionSafetyTest, ValueConstructor) { + TestThrowingCtor<ThrowingVariant>(ExpectedThrower()); + TestThrowingCtor<ThrowingVariant>(ExpectedThrowerVec()); +} + +TEST(VariantExceptionSafetyTest, InPlaceTypeConstructor) { + TestThrowingCtor<ThrowingVariant>(absl::in_place_type_t<Thrower>{}, + ExpectedThrower()); + TestThrowingCtor<ThrowingVariant>(absl::in_place_type_t<ThrowerVec>{}, + ExpectedThrowerVec()); +} + +TEST(VariantExceptionSafetyTest, InPlaceIndexConstructor) { + TestThrowingCtor<ThrowingVariant>(absl::in_place_index_t<0>{}, + ExpectedThrower()); + TestThrowingCtor<ThrowingVariant>(absl::in_place_index_t<3>{}, + ExpectedThrowerVec()); +} + +TEST(VariantExceptionSafetyTest, CopyAssign) { + // variant& operator=(const variant& rhs); + // Let j be rhs.index() + { + // - neither *this nor rhs holds a value + const ThrowingVariant rhs = ValuelessByException(); + EXPECT_TRUE(MakeExceptionSafetyTester() + .WithInitialValue(ValuelessByException()) + .WithInvariants(nothrow_guarantee) + .Test([&rhs](ThrowingVariant* lhs) { *lhs = rhs; })); + } + { + // - *this holds a value but rhs does not + const ThrowingVariant rhs = ValuelessByException(); + EXPECT_TRUE(MakeExceptionSafetyTester() + .WithInitialValue(WithThrower()) + .WithInvariants(nothrow_guarantee) + .Test([&rhs](ThrowingVariant* lhs) { *lhs = rhs; })); + } + // - index() == j + { + const ThrowingVariant rhs(ExpectedThrower()); + auto tester = + MakeExceptionSafetyTester() + .WithInitialValue(WithThrower()) + .WithOperation([&rhs](ThrowingVariant* lhs) { *lhs = rhs; }); + EXPECT_TRUE(tester.WithInvariants(CheckInvariants).Test()); + EXPECT_FALSE(tester.WithInvariants(strong_guarantee).Test()); + } + { + const ThrowingVariant rhs(ExpectedThrowerVec()); + auto tester = + MakeExceptionSafetyTester() + .WithInitialValue(WithThrowerVec()) + .WithOperation([&rhs](ThrowingVariant* lhs) { *lhs = rhs; }); + EXPECT_TRUE(tester.WithInvariants(CheckInvariants).Test()); + EXPECT_FALSE(tester.WithInvariants(strong_guarantee).Test()); + } + // libstdc++ std::variant has bugs on copy assignment regarding exception + // safety. +#if !(defined(ABSL_HAVE_STD_VARIANT) && defined(__GLIBCXX__)) + // index() != j + // if is_nothrow_copy_constructible_v<Tj> or + // !is_nothrow_move_constructible<Tj> is true, equivalent to + // emplace<j>(get<j>(rhs)) + { + // is_nothrow_copy_constructible_v<Tj> == true + // should not throw because emplace() invokes Tj's copy ctor + // which should not throw. + const ThrowingVariant rhs(CopyNothrow{}); + EXPECT_TRUE(MakeExceptionSafetyTester() + .WithInitialValue(WithThrower()) + .WithInvariants(nothrow_guarantee) + .Test([&rhs](ThrowingVariant* lhs) { *lhs = rhs; })); + } + { + // is_nothrow_copy_constructible<Tj> == false && + // is_nothrow_move_constructible<Tj> == false + // should provide basic guarantee because emplace() invokes Tj's copy ctor + // which may throw. + const ThrowingVariant rhs(ExpectedThrower()); + auto tester = + MakeExceptionSafetyTester() + .WithInitialValue(WithCopyNoThrow()) + .WithOperation([&rhs](ThrowingVariant* lhs) { *lhs = rhs; }); + EXPECT_TRUE(tester + .WithInvariants(CheckInvariants, + [](ThrowingVariant* lhs) { + return lhs->valueless_by_exception(); + }) + .Test()); + EXPECT_FALSE(tester.WithInvariants(strong_guarantee).Test()); + } +#endif // !(defined(ABSL_HAVE_STD_VARIANT) && defined(__GLIBCXX__)) + { + // is_nothrow_copy_constructible_v<Tj> == false && + // is_nothrow_move_constructible_v<Tj> == true + // should provide strong guarantee because it is equivalent to + // operator=(variant(rhs)) which creates a temporary then invoke the move + // ctor which shouldn't throw. + const ThrowingVariant rhs(MoveNothrow{}); + EXPECT_TRUE(MakeExceptionSafetyTester() + .WithInitialValue(WithThrower()) + .WithInvariants(CheckInvariants, strong_guarantee) + .Test([&rhs](ThrowingVariant* lhs) { *lhs = rhs; })); + } +} + +TEST(VariantExceptionSafetyTest, MoveAssign) { + // variant& operator=(variant&& rhs); + // Let j be rhs.index() + { + // - neither *this nor rhs holds a value + ThrowingVariant rhs = ValuelessByException(); + + EXPECT_TRUE(MakeExceptionSafetyTester() + .WithInitialValue(ValuelessByException()) + .WithInvariants(nothrow_guarantee) + .Test([rhs](ThrowingVariant* lhs) mutable { + *lhs = std::move(rhs); + })); + } + { + // - *this holds a value but rhs does not + ThrowingVariant rhs = ValuelessByException(); + EXPECT_TRUE(MakeExceptionSafetyTester() + .WithInitialValue(WithThrower()) + .WithInvariants(nothrow_guarantee) + .Test([rhs](ThrowingVariant* lhs) mutable { + *lhs = std::move(rhs); + })); + } + { + // - index() == j + // assign get<j>(std::move(rhs)) to the value contained in *this. + // If an exception is thrown during call to Tj's move assignment, the state + // of the contained value is as defined by the exception safety guarantee of + // Tj's move assignment; index() will be j. + ThrowingVariant rhs(ExpectedThrower()); + size_t j = rhs.index(); + // Since Thrower's move assignment has basic guarantee, so should variant's. + auto tester = MakeExceptionSafetyTester() + .WithInitialValue(WithThrower()) + .WithOperation([rhs](ThrowingVariant* lhs) mutable { + *lhs = std::move(rhs); + }); + EXPECT_TRUE(tester + .WithInvariants( + CheckInvariants, + [j](ThrowingVariant* lhs) { return lhs->index() == j; }) + .Test()); + EXPECT_FALSE(tester.WithInvariants(strong_guarantee).Test()); + } + { + // - otherwise (index() != j), equivalent to + // emplace<j>(get<j>(std::move(rhs))) + // - If an exception is thrown during the call to Tj's move construction + // (with j being rhs.index()), the variant will hold no value. + ThrowingVariant rhs(CopyNothrow{}); + EXPECT_TRUE(MakeExceptionSafetyTester() + .WithInitialValue(WithThrower()) + .WithInvariants(CheckInvariants, + [](ThrowingVariant* lhs) { + return lhs->valueless_by_exception(); + }) + .Test([rhs](ThrowingVariant* lhs) mutable { + *lhs = std::move(rhs); + })); + } +} + +TEST(VariantExceptionSafetyTest, ValueAssign) { + // template<class T> variant& operator=(T&& t); + // Let Tj be the type that is selected by overload resolution to be assigned. + { + // If *this holds a Tj, assigns std::forward<T>(t) to the value contained in + // *this. If an exception is thrown during the assignment of + // std::forward<T>(t) to the value contained in *this, the state of the + // contained value and t are as defined by the exception safety guarantee of + // the assignment expression; valueless_by_exception() will be false. + // Since Thrower's copy/move assignment has basic guarantee, so should + // variant's. + Thrower rhs = ExpectedThrower(); + // copy assign + auto copy_tester = + MakeExceptionSafetyTester() + .WithInitialValue(WithThrower()) + .WithOperation([rhs](ThrowingVariant* lhs) { *lhs = rhs; }); + EXPECT_TRUE(copy_tester + .WithInvariants(CheckInvariants, + [](ThrowingVariant* lhs) { + return !lhs->valueless_by_exception(); + }) + .Test()); + EXPECT_FALSE(copy_tester.WithInvariants(strong_guarantee).Test()); + // move assign + auto move_tester = MakeExceptionSafetyTester() + .WithInitialValue(WithThrower()) + .WithOperation([rhs](ThrowingVariant* lhs) mutable { + *lhs = std::move(rhs); + }); + EXPECT_TRUE(move_tester + .WithInvariants(CheckInvariants, + [](ThrowingVariant* lhs) { + return !lhs->valueless_by_exception(); + }) + .Test()); + + EXPECT_FALSE(move_tester.WithInvariants(strong_guarantee).Test()); + } + // Otherwise (*this holds something else), if is_nothrow_constructible_v<Tj, + // T> || !is_nothrow_move_constructible_v<Tj> is true, equivalent to + // emplace<j>(std::forward<T>(t)). + // We simplify the test by letting T = `const Tj&` or `Tj&&`, so we can reuse + // the CopyNothrow and MoveNothrow types. + + // if is_nothrow_constructible_v<Tj, T> + // (i.e. is_nothrow_copy/move_constructible_v<Tj>) is true, emplace() just + // invokes the copy/move constructor and it should not throw. + { + const CopyNothrow rhs; + EXPECT_TRUE(MakeExceptionSafetyTester() + .WithInitialValue(WithThrower()) + .WithInvariants(nothrow_guarantee) + .Test([&rhs](ThrowingVariant* lhs) { *lhs = rhs; })); + } + { + MoveNothrow rhs; + EXPECT_TRUE(MakeExceptionSafetyTester() + .WithInitialValue(WithThrower()) + .WithInvariants(nothrow_guarantee) + .Test([rhs](ThrowingVariant* lhs) mutable { + *lhs = std::move(rhs); + })); + } + // if is_nothrow_constructible_v<Tj, T> == false && + // is_nothrow_move_constructible<Tj> == false + // emplace() invokes the copy/move constructor which may throw so it should + // provide basic guarantee and variant object might not hold a value. + { + Thrower rhs = ExpectedThrower(); + // copy + auto copy_tester = + MakeExceptionSafetyTester() + .WithInitialValue(WithCopyNoThrow()) + .WithOperation([&rhs](ThrowingVariant* lhs) { *lhs = rhs; }); + EXPECT_TRUE(copy_tester + .WithInvariants(CheckInvariants, + [](ThrowingVariant* lhs) { + return lhs->valueless_by_exception(); + }) + .Test()); + EXPECT_FALSE(copy_tester.WithInvariants(strong_guarantee).Test()); + // move + auto move_tester = MakeExceptionSafetyTester() + .WithInitialValue(WithCopyNoThrow()) + .WithOperation([rhs](ThrowingVariant* lhs) mutable { + *lhs = std::move(rhs); + }); + EXPECT_TRUE(move_tester + .WithInvariants(CheckInvariants, + [](ThrowingVariant* lhs) { + return lhs->valueless_by_exception(); + }) + .Test()); + EXPECT_FALSE(move_tester.WithInvariants(strong_guarantee).Test()); + } + // Otherwise (if is_nothrow_constructible_v<Tj, T> == false && + // is_nothrow_move_constructible<Tj> == true), + // equivalent to operator=(variant(std::forward<T>(t))) + // This should have strong guarantee because it creates a temporary variant + // and operator=(variant&&) invokes Tj's move ctor which doesn't throw. + // libstdc++ std::variant has bugs on conversion assignment regarding + // exception safety. +#if !(defined(ABSL_HAVE_STD_VARIANT) && defined(__GLIBCXX__)) + { + MoveNothrow rhs; + EXPECT_TRUE(MakeExceptionSafetyTester() + .WithInitialValue(WithThrower()) + .WithInvariants(CheckInvariants, strong_guarantee) + .Test([&rhs](ThrowingVariant* lhs) { *lhs = rhs; })); + } +#endif // !(defined(ABSL_HAVE_STD_VARIANT) && defined(__GLIBCXX__)) +} + +TEST(VariantExceptionSafetyTest, Emplace) { + // If an exception during the initialization of the contained value, the + // variant might not hold a value. The standard requires emplace() to provide + // only basic guarantee. + { + Thrower args = ExpectedThrower(); + auto tester = MakeExceptionSafetyTester() + .WithInitialValue(WithThrower()) + .WithOperation([&args](ThrowingVariant* v) { + v->emplace<Thrower>(args); + }); + EXPECT_TRUE(tester + .WithInvariants(CheckInvariants, + [](ThrowingVariant* v) { + return v->valueless_by_exception(); + }) + .Test()); + EXPECT_FALSE(tester.WithInvariants(strong_guarantee).Test()); + } +} + +TEST(VariantExceptionSafetyTest, Swap) { + // if both are valueless_by_exception(), no effect + { + ThrowingVariant rhs = ValuelessByException(); + EXPECT_TRUE( + MakeExceptionSafetyTester() + .WithInitialValue(ValuelessByException()) + .WithInvariants(nothrow_guarantee) + .Test([rhs](ThrowingVariant* lhs) mutable { lhs->swap(rhs); })); + } + // if index() == rhs.index(), calls swap(get<i>(*this), get<i>(rhs)) + // where i is index(). + { + ThrowingVariant rhs = ExpectedThrower(); + EXPECT_TRUE( + MakeExceptionSafetyTester() + .WithInitialValue(WithThrower()) + .WithInvariants(CheckInvariants) + .Test([rhs](ThrowingVariant* lhs) mutable { lhs->swap(rhs); })); + } + // Otherwise, exchanges the value of rhs and *this. The exception safety + // involves variant in moved-from state which is not specified in the + // standard, and since swap is 3-step it's impossible for it to provide a + // overall strong guarantee. So, we are only checking basic guarantee here. + { + ThrowingVariant rhs = ExpectedThrower(); + EXPECT_TRUE( + MakeExceptionSafetyTester() + .WithInitialValue(WithCopyNoThrow()) + .WithInvariants(CheckInvariants) + .Test([rhs](ThrowingVariant* lhs) mutable { lhs->swap(rhs); })); + } + { + ThrowingVariant rhs = ExpectedThrower(); + EXPECT_TRUE( + MakeExceptionSafetyTester() + .WithInitialValue(WithCopyNoThrow()) + .WithInvariants(CheckInvariants) + .Test([rhs](ThrowingVariant* lhs) mutable { rhs.swap(*lhs); })); + } +} + +} // namespace +} // namespace absl |