// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab /* * Ceph - scalable distributed file system * * Copyright (C) 2018 Adam C. Emerson * * This is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License version 2, as published by the Free Software * Foundation. See file COPYING. * */ #include #include #include "gtest/gtest.h" #include "include/any.h" using std::optional; using std::bad_any_cast; using ceph::immobile_any; using ceph::unique_any; using ceph::shared_any; using ceph::make_immobile_any; using ceph::make_unique_any; using ceph::make_shared_any; using ceph::any_cast; using std::swap; template static void test_empty() { A a; EXPECT_FALSE(a.has_value()); EXPECT_EQ(typeid(void), a.type()); a.reset(); EXPECT_FALSE(a.has_value()); EXPECT_EQ(typeid(void), a.type()); } TEST(Empty, Immobile) { static_assert(std::is_nothrow_default_constructible_v>); test_empty>(); } TEST(Empty, Unique) { static_assert(std::is_nothrow_default_constructible_v); test_empty(); } TEST(Empty, Shared) { static_assert(std::is_nothrow_default_constructible_v); test_empty(); } struct cmd_tattler { static thread_local bool copied; static thread_local bool moved; static thread_local bool destructed; static void reset() { copied = false; moved = false; destructed = false; } cmd_tattler() noexcept = default; ~cmd_tattler() noexcept { if (destructed) { std::terminate(); } destructed = true; } cmd_tattler(const cmd_tattler&) noexcept { if (copied) { std::terminate(); } copied = true; } cmd_tattler& operator =(const cmd_tattler&) noexcept { if (copied) { std::terminate(); } copied = true; return *this; } cmd_tattler(cmd_tattler&&) noexcept { if (moved) { std::terminate(); } moved = true; } cmd_tattler& operator =(cmd_tattler&&) noexcept { if (moved) { std::terminate(); } moved = true; return *this; } }; thread_local bool cmd_tattler::copied = false; thread_local bool cmd_tattler::moved = false; thread_local bool cmd_tattler::destructed = false; struct not_noexcept { not_noexcept() = default; not_noexcept(const not_noexcept&) noexcept(false) { } not_noexcept& operator =(const not_noexcept&) noexcept(false) { return *this; } not_noexcept(not_noexcept&&) noexcept(false) { } not_noexcept& operator =(not_noexcept&&) noexcept(false) { return *this; } template explicit not_noexcept(Args&& ...) noexcept(false) { } template not_noexcept(std::initializer_list, Args&& ...) noexcept(false) { } }; template static void test_value_CMD() { { cmd_tattler::reset(); cmd_tattler c; A a(c); EXPECT_TRUE(cmd_tattler::copied); EXPECT_TRUE(a.has_value()); EXPECT_EQ(typeid(cmd_tattler), a.type()); a.reset(); EXPECT_TRUE(cmd_tattler::destructed); EXPECT_FALSE(a.has_value()); EXPECT_EQ(typeid(void), a.type()); cmd_tattler::reset(); a = c; EXPECT_TRUE(cmd_tattler::copied); EXPECT_TRUE(a.has_value()); EXPECT_EQ(typeid(cmd_tattler), a.type()); cmd_tattler::reset(); a = c; EXPECT_TRUE(cmd_tattler::copied); EXPECT_TRUE(cmd_tattler::destructed); EXPECT_TRUE(a.has_value()); EXPECT_EQ(typeid(cmd_tattler), a.type()); cmd_tattler::reset(); a.reset(); EXPECT_TRUE(cmd_tattler::destructed); cmd_tattler::reset(); } { cmd_tattler::reset(); cmd_tattler c; A a(std::move(c)); EXPECT_TRUE(cmd_tattler::moved); EXPECT_TRUE(a.has_value()); EXPECT_EQ(typeid(cmd_tattler), a.type()); a.reset(); EXPECT_TRUE(cmd_tattler::destructed); EXPECT_FALSE(a.has_value()); EXPECT_EQ(typeid(void), a.type()); cmd_tattler::reset(); a = std::move(c); EXPECT_TRUE(cmd_tattler::moved); EXPECT_TRUE(a.has_value()); EXPECT_EQ(typeid(cmd_tattler), a.type()); cmd_tattler::reset(); a = std::move(c); EXPECT_TRUE(cmd_tattler::moved); EXPECT_TRUE(cmd_tattler::destructed); EXPECT_TRUE(a.has_value()); EXPECT_EQ(typeid(cmd_tattler), a.type()); cmd_tattler::reset(); a.reset(); EXPECT_TRUE(cmd_tattler::destructed); cmd_tattler::reset(); } { cmd_tattler::reset(); A a(cmd_tattler{}); EXPECT_TRUE(cmd_tattler::moved); EXPECT_TRUE(cmd_tattler::destructed); EXPECT_TRUE(a.has_value()); EXPECT_EQ(typeid(cmd_tattler), a.type()); cmd_tattler::reset(); a.reset(); EXPECT_TRUE(cmd_tattler::destructed); EXPECT_FALSE(a.has_value()); EXPECT_EQ(typeid(void), a.type()); cmd_tattler::reset(); a = cmd_tattler{}; EXPECT_TRUE(cmd_tattler::moved); EXPECT_TRUE(cmd_tattler::destructed); EXPECT_TRUE(a.has_value()); EXPECT_EQ(typeid(cmd_tattler), a.type()); cmd_tattler::reset(); a.reset(); EXPECT_TRUE(cmd_tattler::destructed); cmd_tattler::reset(); } } TEST(Value_CMD, Immobile) { static_assert(std::is_nothrow_constructible_v< immobile_any<1024>, const cmd_tattler&>); static_assert(std::is_nothrow_assignable_v< immobile_any<1024>, const cmd_tattler&>); static_assert(std::is_nothrow_constructible_v< immobile_any<1024>, cmd_tattler&&>); static_assert(std::is_nothrow_assignable_v< immobile_any<1024>, cmd_tattler&&>); static_assert(!std::is_nothrow_constructible_v< immobile_any<1024>, const not_noexcept&>); static_assert(!std::is_nothrow_assignable_v< immobile_any<1024>, const not_noexcept&>); static_assert(!std::is_nothrow_constructible_v< immobile_any<1024>, not_noexcept&&>); static_assert(!std::is_nothrow_assignable_v< immobile_any<1024>, not_noexcept&&>); test_value_CMD>(); } TEST(Value_CMD, Unique) { static_assert(!std::is_nothrow_constructible_v< unique_any, const cmd_tattler&>); static_assert(!std::is_nothrow_assignable_v< unique_any, const cmd_tattler&>); static_assert(!std::is_nothrow_constructible_v< unique_any, cmd_tattler&&>); static_assert(!std::is_nothrow_assignable_v< unique_any, cmd_tattler&&>); static_assert(!std::is_nothrow_constructible_v< unique_any, const not_noexcept&>); static_assert(!std::is_nothrow_assignable_v< unique_any, const not_noexcept&>); static_assert(!std::is_nothrow_constructible_v< unique_any, not_noexcept&&>); static_assert(!std::is_nothrow_assignable_v< unique_any, not_noexcept&&>); test_value_CMD(); } TEST(Value_CMD, Shared) { static_assert(!std::is_nothrow_constructible_v< shared_any, const cmd_tattler&>); static_assert(!std::is_nothrow_assignable_v< shared_any, const cmd_tattler&>); static_assert(!std::is_nothrow_constructible_v< shared_any, cmd_tattler&&>); static_assert(!std::is_nothrow_assignable_v< shared_any, cmd_tattler&&>); static_assert(!std::is_nothrow_constructible_v< shared_any, const not_noexcept&>); static_assert(!std::is_nothrow_assignable_v< shared_any, const not_noexcept&>); static_assert(!std::is_nothrow_constructible_v< shared_any, not_noexcept&&>); static_assert(!std::is_nothrow_assignable_v< shared_any, not_noexcept&&>); test_value_CMD(); } template static void test_move() { { A a(5); EXPECT_TRUE(a.has_value()); EXPECT_EQ(typeid(int), a.type()); A b(std::move(a)); EXPECT_TRUE(b.has_value()); EXPECT_EQ(typeid(int), b.type()); EXPECT_FALSE(a.has_value()); EXPECT_EQ(typeid(void), a.type()); } { cmd_tattler::reset(); A a(cmd_tattler{}); A b(5); EXPECT_TRUE(b.has_value()); EXPECT_EQ(typeid(int), b.type()); cmd_tattler::reset(); a = std::move(b); EXPECT_TRUE(cmd_tattler::destructed); EXPECT_FALSE(b.has_value()); EXPECT_EQ(typeid(void), b.type()); EXPECT_TRUE(a.has_value()); EXPECT_EQ(typeid(int), a.type()); } } static_assert(!std::is_move_constructible_v>); static_assert(!std::is_move_assignable_v>); TEST(Move, Unique) { static_assert(std::is_nothrow_move_constructible_v); static_assert(std::is_nothrow_move_assignable_v); test_move(); } TEST(Move, Shared) { static_assert(std::is_nothrow_move_constructible_v); static_assert(std::is_nothrow_move_assignable_v); test_move(); } template static void test_copy() { { const A a(5); EXPECT_TRUE(a.has_value()); EXPECT_EQ(typeid(int), a.type()); A b(a); EXPECT_TRUE(b.has_value()); EXPECT_EQ(typeid(int), b.type()); EXPECT_TRUE(a.has_value()); EXPECT_EQ(typeid(int), a.type()); EXPECT_EQ(any_cast(a), any_cast(b)); } { cmd_tattler::reset(); A a(cmd_tattler{}); const A b(5); EXPECT_TRUE(b.has_value()); EXPECT_EQ(typeid(int), b.type()); cmd_tattler::reset(); a = b; EXPECT_TRUE(cmd_tattler::destructed); EXPECT_TRUE(a.has_value()); EXPECT_EQ(typeid(int), a.type()); EXPECT_TRUE(b.has_value()); EXPECT_EQ(typeid(int), b.type()); EXPECT_EQ(any_cast(a), any_cast(b)); } } static_assert(!std::is_copy_constructible_v>); static_assert(!std::is_copy_assignable_v>); static_assert(!std::is_copy_constructible_v); static_assert(!std::is_copy_assignable_v); TEST(Copy, Shared) { static_assert(std::is_nothrow_copy_constructible_v); test_copy(); } struct unmoving { optional a; unmoving() noexcept {} template explicit unmoving(Args&& ...args) noexcept : a(sizeof...(Args)) {} template explicit unmoving(std::initializer_list l) noexcept : a(-l.size()) {} template unmoving(std::initializer_list l, Args&& ...args) noexcept : a(-l.size() * sizeof...(Args)) {} unmoving(const unmoving&) = delete; unmoving& operator =(const unmoving&) = delete; unmoving(unmoving&&) = delete; unmoving& operator =(unmoving&&) = delete; }; template static void test_unmoving_pack_il() { // Nothing! { const A a(std::in_place_type); EXPECT_TRUE(a.has_value()); EXPECT_EQ(typeid(unmoving), a.type()); EXPECT_FALSE(any_cast(a).a); } { cmd_tattler::reset(); A a(cmd_tattler{}); cmd_tattler::reset(); a.template emplace(); EXPECT_TRUE(cmd_tattler::destructed); EXPECT_TRUE(a.has_value()); EXPECT_EQ(typeid(unmoving), a.type()); EXPECT_FALSE(any_cast(a).a); } // Pack! { const A a(std::in_place_type, nullptr, 5, 3.1); EXPECT_TRUE(a.has_value()); EXPECT_EQ(typeid(unmoving), a.type()); EXPECT_TRUE(any_cast(a).a); EXPECT_EQ(3, *any_cast(a).a); } { cmd_tattler::reset(); A a(cmd_tattler{}); cmd_tattler::reset(); a.template emplace(nullptr, 5, 3.1); EXPECT_TRUE(cmd_tattler::destructed); EXPECT_TRUE(a.has_value()); EXPECT_EQ(typeid(unmoving), a.type()); EXPECT_EQ(3, *any_cast(a).a); } // List! { const A a(std::in_place_type, {true, true, true, true}); EXPECT_TRUE(a.has_value()); EXPECT_EQ(typeid(unmoving), a.type()); EXPECT_TRUE(any_cast(a).a); EXPECT_EQ(-4, *any_cast(a).a); } { cmd_tattler::reset(); A a(cmd_tattler{}); cmd_tattler::reset(); a.template emplace({true, true, true, true}); EXPECT_TRUE(cmd_tattler::destructed); EXPECT_TRUE(a.has_value()); EXPECT_EQ(typeid(unmoving), a.type()); EXPECT_EQ(-4, *any_cast(a).a); } // List + pack!! { const A a(std::in_place_type, {true, true, true, true}, nullptr, 5, 3.1); EXPECT_TRUE(a.has_value()); EXPECT_EQ(typeid(unmoving), a.type()); EXPECT_TRUE(any_cast(a).a); EXPECT_EQ(-12, *any_cast(a).a); } { cmd_tattler::reset(); A a(cmd_tattler{}); cmd_tattler::reset(); a.template emplace({true, true, true, true}, nullptr, 5, 3.1); EXPECT_TRUE(cmd_tattler::destructed); EXPECT_TRUE(a.has_value()); EXPECT_EQ(typeid(unmoving), a.type()); EXPECT_EQ(-12, *any_cast(a).a); } } TEST(UmovingPackIl, Immobile) { static_assert(std::is_nothrow_constructible_v, std::in_place_type_t>); static_assert(noexcept(immobile_any<1024>{}.emplace())); static_assert(std::is_nothrow_constructible_v, std::in_place_type_t, std::nullptr_t, int, double>); static_assert(noexcept(immobile_any<1024>{}.emplace( nullptr, 5, 3.1))); static_assert(std::is_nothrow_constructible_v, std::in_place_type_t, std::initializer_list>); static_assert(noexcept(immobile_any<1024>{}.emplace( {true, true, true, true}))); static_assert(std::is_nothrow_constructible_v, std::in_place_type_t, std::initializer_list, std::nullptr_t, int, double>); static_assert(noexcept(immobile_any<1024>{}.emplace( {true, true, true, true}, nullptr, 5, 3.1))); test_unmoving_pack_il>(); } TEST(UmovingPackIl, Unique) { static_assert(!std::is_nothrow_constructible_v>); static_assert(!noexcept(unique_any{}.emplace())); static_assert(!std::is_nothrow_constructible_v, std::nullptr_t, int, double>); static_assert(!noexcept(unique_any{}.emplace( nullptr, 5, 3.1))); static_assert(!std::is_nothrow_constructible_v, std::initializer_list>); static_assert(!noexcept(unique_any{}.emplace( {true, true, true, true}))); static_assert(!std::is_nothrow_constructible_v, std::initializer_list, std::nullptr_t, int, double>); static_assert(!noexcept(unique_any{}.emplace( {true, true, true, true}, nullptr, 5, 3.1))); test_unmoving_pack_il(); } TEST(UmovingPackIl, Shared) { static_assert(!std::is_nothrow_constructible_v>); static_assert(!noexcept(shared_any{}.emplace())); static_assert(!std::is_nothrow_constructible_v, std::nullptr_t, int, double>); static_assert(!noexcept(shared_any{}.emplace( nullptr, 5, 3.1))); static_assert(!std::is_nothrow_constructible_v, std::initializer_list>); static_assert(!noexcept(shared_any{}.emplace( {true, true, true, true}))); static_assert(!std::is_nothrow_constructible_v, std::initializer_list, std::nullptr_t, int, double>); static_assert(!noexcept(shared_any{}.emplace( {true, true, true, true}, nullptr, 5, 3.1))); test_unmoving_pack_il(); } template static void test_swap() { A a(true); ASSERT_TRUE(a.has_value()); ASSERT_EQ(typeid(bool), a.type()); ASSERT_EQ(true, any_cast(a)); A b(5); ASSERT_TRUE(b.has_value()); ASSERT_EQ(typeid(int), b.type()); ASSERT_EQ(5, any_cast(b)); a.swap(b); EXPECT_TRUE(a.has_value()); EXPECT_EQ(typeid(int), a.type()); EXPECT_EQ(5, any_cast(a)); EXPECT_TRUE(b.has_value()); ASSERT_EQ(typeid(bool), b.type()); ASSERT_EQ(true, any_cast(b)); swap(a,b); EXPECT_TRUE(a.has_value()); EXPECT_EQ(typeid(bool), a.type()); EXPECT_EQ(true, any_cast(a)); EXPECT_TRUE(b.has_value()); EXPECT_EQ(typeid(int), b.type()); EXPECT_EQ(5, any_cast(b)); } static_assert(!std::is_swappable_v>); TEST(Swap, Unique) { static_assert(std::is_nothrow_swappable_v); test_swap(); } TEST(Swap, Shared) { static_assert(std::is_nothrow_swappable_v); test_swap(); } template static void test_cast() { // Empty { A a; EXPECT_EQ(nullptr, any_cast(&a)); EXPECT_THROW({any_cast(a);}, bad_any_cast); EXPECT_THROW({any_cast(a);}, bad_any_cast); EXPECT_THROW({any_cast(std::move(a));}, bad_any_cast); EXPECT_THROW({any_cast(std::move(a));}, bad_any_cast); } // Constant Empty { const A a{}; EXPECT_EQ(nullptr, any_cast(const_cast(&a))); EXPECT_THROW({any_cast(a);}, bad_any_cast); EXPECT_THROW({any_cast(a);}, bad_any_cast); } // Filled! { A a(true); EXPECT_TRUE(*any_cast(&a)); EXPECT_EQ(nullptr, any_cast(&a)); EXPECT_TRUE(any_cast(a)); EXPECT_THROW({any_cast(a);}, bad_any_cast); EXPECT_TRUE(any_cast(a)); EXPECT_THROW({any_cast(a);}, bad_any_cast); EXPECT_TRUE(any_cast(std::move(a))); EXPECT_THROW({any_cast(std::move(a));}, bad_any_cast); EXPECT_TRUE(any_cast(std::move(a))); EXPECT_THROW({any_cast(std::move(a));}, bad_any_cast); } // Constant filled { const A a(true); EXPECT_TRUE(*any_cast(&a)); EXPECT_EQ(nullptr, any_cast(&a)); EXPECT_TRUE(any_cast(a)); EXPECT_THROW({any_cast(a);}, bad_any_cast); EXPECT_TRUE(any_cast(a)); EXPECT_THROW({any_cast(a);}, bad_any_cast); } // Move! { cmd_tattler::reset(); A a(cmd_tattler{}); cmd_tattler::reset(); auto q = any_cast(std::move(a)); EXPECT_TRUE(cmd_tattler::moved); cmd_tattler::reset(); a.reset(); cmd_tattler::reset(); } // Move! Again! { cmd_tattler::reset(); auto q = any_cast(A(std::in_place_type)); EXPECT_TRUE(cmd_tattler::moved); cmd_tattler::reset(); } } TEST(Cast, Immobile) { test_cast>(); } TEST(Cast, Unique) { test_cast(); } TEST(Cast, Shared) { test_cast(); } TEST(Make, Immobile) { // Nothing! { auto a{make_immobile_any()}; EXPECT_TRUE(a.has_value()); EXPECT_EQ(typeid(unmoving), a.type()); EXPECT_FALSE(any_cast(a).a); } // Pack! { auto a(make_immobile_any(nullptr, 5, 3.1)); EXPECT_TRUE(a.has_value()); EXPECT_EQ(typeid(unmoving), a.type()); EXPECT_TRUE(any_cast(a).a); EXPECT_EQ(3, *any_cast(a).a); } // List! { auto a(make_immobile_any({true, true, true, true})); EXPECT_TRUE(a.has_value()); EXPECT_EQ(typeid(unmoving), a.type()); EXPECT_TRUE(any_cast(a).a); EXPECT_EQ(-4, *any_cast(a).a); } // List + pack!! { auto a{make_immobile_any({true, true, true, true}, nullptr, 5, 3.1)}; EXPECT_TRUE(a.has_value()); EXPECT_EQ(typeid(unmoving), a.type()); EXPECT_TRUE(any_cast(a).a); EXPECT_EQ(-12, *any_cast(a).a); } } TEST(Make, Unique) { // Nothing! { auto a{make_unique_any()}; EXPECT_TRUE(a.has_value()); EXPECT_EQ(typeid(unmoving), a.type()); EXPECT_FALSE(any_cast(a).a); } // Pack! { auto a(make_unique_any(nullptr, 5, 3.1)); EXPECT_TRUE(a.has_value()); EXPECT_EQ(typeid(unmoving), a.type()); EXPECT_TRUE(any_cast(a).a); EXPECT_EQ(3, *any_cast(a).a); } // List! { auto a(make_unique_any({true, true, true, true})); EXPECT_TRUE(a.has_value()); EXPECT_EQ(typeid(unmoving), a.type()); EXPECT_TRUE(any_cast(a).a); EXPECT_EQ(-4, *any_cast(a).a); } // List + pack!! { auto a{make_unique_any({true, true, true, true}, nullptr, 5, 3.1)}; EXPECT_TRUE(a.has_value()); EXPECT_EQ(typeid(unmoving), a.type()); EXPECT_TRUE(any_cast(a).a); EXPECT_EQ(-12, *any_cast(a).a); } } TEST(Make, Shared) { // Nothing! { auto a{make_shared_any()}; EXPECT_TRUE(a.has_value()); EXPECT_EQ(typeid(unmoving), a.type()); EXPECT_FALSE(any_cast(a).a); } // Pack! { auto a(make_shared_any(nullptr, 5, 3.1)); EXPECT_TRUE(a.has_value()); EXPECT_EQ(typeid(unmoving), a.type()); EXPECT_TRUE(any_cast(a).a); EXPECT_EQ(3, *any_cast(a).a); } // List! { auto a(make_shared_any({true, true, true, true})); EXPECT_TRUE(a.has_value()); EXPECT_EQ(typeid(unmoving), a.type()); EXPECT_TRUE(any_cast(a).a); EXPECT_EQ(-4, *any_cast(a).a); } // List + pack!! { auto a{make_shared_any({true, true, true, true}, nullptr, 5, 3.1)}; EXPECT_TRUE(a.has_value()); EXPECT_EQ(typeid(unmoving), a.type()); EXPECT_TRUE(any_cast(a).a); EXPECT_EQ(-12, *any_cast(a).a); } }