X-Git-Url: http://plrg.eecs.uci.edu/git/?a=blobdiff_plain;f=folly%2Ftest%2FOptionalTest.cpp;h=b7484dfa978b6488050eab02470ad15982ea9f67;hb=19e3e9fe724a363e342e92a5b08378900d6ab539;hp=1268ff662d74c9ca9444bfd9fc25f2d540bdf893;hpb=a8991a6b6fa1cc08adc57f300376aeccc279cee6;p=folly.git diff --git a/folly/test/OptionalTest.cpp b/folly/test/OptionalTest.cpp index 1268ff66..b7484dfa 100644 --- a/folly/test/OptionalTest.cpp +++ b/folly/test/OptionalTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2012 Facebook, Inc. + * Copyright 2017 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,22 +14,34 @@ * limitations under the License. */ -#include "folly/Optional.h" +#include +#include -#include -#include #include #include +#include #include +#include +#include +#include -#include -#include #include -using namespace folly; using std::unique_ptr; using std::shared_ptr; +namespace folly { + +template +std::ostream& operator<<(std::ostream& os, const Optional& v) { + if (v) { + os << "Optional(" << v.value() << ')'; + } else { + os << "None"; + } + return os; +} + struct NoDefault { NoDefault(int, int) {} char a, b, c; @@ -47,7 +59,7 @@ TEST(Optional, NoDefault) { Optional x; EXPECT_FALSE(x); x.emplace(4, 5); - EXPECT_TRUE(x); + EXPECT_TRUE(bool(x)); x.clear(); EXPECT_FALSE(x); } @@ -56,53 +68,140 @@ TEST(Optional, String) { Optional maybeString; EXPECT_FALSE(maybeString); maybeString = "hello"; - EXPECT_TRUE(maybeString); + EXPECT_TRUE(bool(maybeString)); } TEST(Optional, Const) { { // default construct Optional opt; - EXPECT_FALSE(opt); + EXPECT_FALSE(bool(opt)); opt.emplace(4); - EXPECT_EQ(opt, 4); + EXPECT_EQ(*opt, 4); opt.emplace(5); - EXPECT_EQ(opt, 5); + EXPECT_EQ(*opt, 5); opt.clear(); - EXPECT_FALSE(opt); + EXPECT_FALSE(bool(opt)); } { // copy-constructed const int x = 6; Optional opt(x); - EXPECT_EQ(opt, 6); + EXPECT_EQ(*opt, 6); } { // move-constructed const int x = 7; Optional opt(std::move(x)); - EXPECT_EQ(opt, 7); + EXPECT_EQ(*opt, 7); } // no assignment allowed } TEST(Optional, Simple) { Optional opt; - EXPECT_FALSE(opt); + EXPECT_FALSE(bool(opt)); + EXPECT_EQ(42, opt.value_or(42)); opt = 4; - EXPECT_TRUE(opt); + EXPECT_TRUE(bool(opt)); EXPECT_EQ(4, *opt); + EXPECT_EQ(4, opt.value_or(42)); opt = 5; EXPECT_EQ(5, *opt); opt.clear(); - EXPECT_FALSE(opt); + EXPECT_FALSE(bool(opt)); +} + +class MoveTester { +public: + /* implicit */ MoveTester(const char* s) : s_(s) {} + MoveTester(const MoveTester&) = default; + MoveTester(MoveTester&& other) noexcept { + s_ = std::move(other.s_); + other.s_ = ""; + } + MoveTester& operator=(const MoveTester&) = default; + MoveTester& operator=(MoveTester&& other) noexcept { + s_ = std::move(other.s_); + other.s_ = ""; + return *this; + } +private: + friend bool operator==(const MoveTester& o1, const MoveTester& o2); + std::string s_; +}; + +bool operator==(const MoveTester& o1, const MoveTester& o2) { + return o1.s_ == o2.s_; +} + +TEST(Optional, value_or_rvalue_arg) { + Optional opt; + MoveTester dflt = "hello"; + EXPECT_EQ("hello", opt.value_or(dflt)); + EXPECT_EQ("hello", dflt); + EXPECT_EQ("hello", opt.value_or(std::move(dflt))); + EXPECT_EQ("", dflt); + EXPECT_EQ("world", opt.value_or("world")); + + dflt = "hello"; + // Make sure that the const overload works on const objects + const auto& optc = opt; + EXPECT_EQ("hello", optc.value_or(dflt)); + EXPECT_EQ("hello", dflt); + EXPECT_EQ("hello", optc.value_or(std::move(dflt))); + EXPECT_EQ("", dflt); + EXPECT_EQ("world", optc.value_or("world")); + + dflt = "hello"; + opt = "meow"; + EXPECT_EQ("meow", opt.value_or(dflt)); + EXPECT_EQ("hello", dflt); + EXPECT_EQ("meow", opt.value_or(std::move(dflt))); + EXPECT_EQ("hello", dflt); // only moved if used +} + +TEST(Optional, value_or_noncopyable) { + Optional> opt; + std::unique_ptr dflt(new int(42)); + EXPECT_EQ(42, *std::move(opt).value_or(std::move(dflt))); +} + +struct ExpectingDeleter { + explicit ExpectingDeleter(int expected) : expected(expected) { } + int expected; + void operator()(const int* ptr) { + EXPECT_EQ(*ptr, expected); + delete ptr; + } +}; + +TEST(Optional, value_move) { + auto ptr = Optional>( + {new int(42), ExpectingDeleter{1337}}).value(); + *ptr = 1337; +} + +TEST(Optional, dereference_move) { + auto ptr = *Optional>( + {new int(42), ExpectingDeleter{1337}}); + *ptr = 1337; +} + +TEST(Optional, EmptyConstruct) { + Optional opt; + EXPECT_FALSE(bool(opt)); + Optional test1(opt); + EXPECT_FALSE(bool(test1)); + Optional test2(std::move(opt)); + EXPECT_FALSE(bool(test2)); } TEST(Optional, Unique) { Optional> opt; opt.clear(); - EXPECT_FALSE(opt); + EXPECT_FALSE(bool(opt)); // empty->emplaced opt.emplace(new int(5)); - EXPECT_TRUE(opt); + EXPECT_TRUE(bool(opt)); EXPECT_EQ(5, **opt); opt.clear(); @@ -115,24 +214,24 @@ TEST(Optional, Unique) { // move it out by move construct Optional> moved(std::move(opt)); - EXPECT_TRUE(moved); - EXPECT_FALSE(opt); + EXPECT_TRUE(bool(moved)); + EXPECT_FALSE(bool(opt)); EXPECT_EQ(7, **moved); - EXPECT_TRUE(moved); + EXPECT_TRUE(bool(moved)); opt = std::move(moved); // move it back by move assign - EXPECT_FALSE(moved); - EXPECT_TRUE(opt); + EXPECT_FALSE(bool(moved)); + EXPECT_TRUE(bool(opt)); EXPECT_EQ(7, **opt); } TEST(Optional, Shared) { shared_ptr ptr; Optional> opt; - EXPECT_FALSE(opt); + EXPECT_FALSE(bool(opt)); // empty->emplaced opt.emplace(new int(5)); - EXPECT_TRUE(opt); + EXPECT_TRUE(bool(opt)); ptr = opt.value(); EXPECT_EQ(ptr.get(), opt->get()); EXPECT_EQ(2, ptr.use_count()); @@ -176,7 +275,7 @@ TEST(Optional, Order) { { 3 }, }; std::sort(vect.begin(), vect.end()); - EXPECT_TRUE(vect == expected); + EXPECT_EQ(vect, expected); } TEST(Optional, Swap) { @@ -209,21 +308,16 @@ TEST(Optional, Comparisons) { Optional o1(1); Optional o2(2); - EXPECT_TRUE(o_ < 1); - EXPECT_TRUE(o_ <= 1); - EXPECT_TRUE(o_ <= o_); - EXPECT_TRUE(o_ == o_); - EXPECT_TRUE(o_ != 1); - EXPECT_TRUE(o_ >= o_); - EXPECT_TRUE(1 >= o_); - EXPECT_TRUE(1 > o_); + EXPECT_TRUE(o_ <= (o_)); + EXPECT_TRUE(o_ == (o_)); + EXPECT_TRUE(o_ >= (o_)); EXPECT_TRUE(o1 < o2); EXPECT_TRUE(o1 <= o2); - EXPECT_TRUE(o1 <= o1); - EXPECT_TRUE(o1 == o1); + EXPECT_TRUE(o1 <= (o1)); + EXPECT_TRUE(o1 == (o1)); EXPECT_TRUE(o1 != o2); - EXPECT_TRUE(o1 >= o1); + EXPECT_TRUE(o1 >= (o1)); EXPECT_TRUE(o2 >= o1); EXPECT_TRUE(o2 > o1); @@ -231,11 +325,12 @@ TEST(Optional, Comparisons) { EXPECT_FALSE(o2 <= o1); EXPECT_FALSE(o2 <= o1); EXPECT_FALSE(o2 == o1); - EXPECT_FALSE(o1 != o1); + EXPECT_FALSE(o1 != (o1)); EXPECT_FALSE(o1 >= o2); EXPECT_FALSE(o1 >= o2); EXPECT_FALSE(o1 > o2); + /* folly::Optional explicitly doesn't support comparisons with contained value EXPECT_TRUE(1 < o2); EXPECT_TRUE(1 <= o2); EXPECT_TRUE(1 <= o1); @@ -253,6 +348,69 @@ TEST(Optional, Comparisons) { EXPECT_FALSE(o1 >= 2); EXPECT_FALSE(o1 >= 2); EXPECT_FALSE(o1 > 2); + */ + + // boost::optional does support comparison with contained value, which can + // lead to confusion when a bool is contained + boost::optional boi(3); + EXPECT_TRUE(boi < 5); + EXPECT_TRUE(boi <= 4); + EXPECT_TRUE(boi == 3); + EXPECT_TRUE(boi != 2); + EXPECT_TRUE(boi >= 1); + EXPECT_TRUE(boi > 0); + EXPECT_TRUE(1 < boi); + EXPECT_TRUE(2 <= boi); + EXPECT_TRUE(3 == boi); + EXPECT_TRUE(4 != boi); + EXPECT_TRUE(5 >= boi); + EXPECT_TRUE(6 > boi); + + boost::optional bob(false); + EXPECT_TRUE((bool)bob); + EXPECT_TRUE(bob == false); // well that was confusing + EXPECT_FALSE(bob != false); +} + +TEST(Optional, Conversions) { + Optional mbool; + Optional mshort; + Optional mstr; + Optional mint; + + //These don't compile + //bool b = mbool; + //short s = mshort; + //char* c = mstr; + //int x = mint; + //char* c(mstr); + //short s(mshort); + //int x(mint); + + // intended explicit operator bool, for if (opt). + bool b(mbool); + EXPECT_FALSE(b); + + // Truthy tests work and are not ambiguous + if (mbool && mshort && mstr && mint) { // only checks not-empty + if (*mbool && *mshort && *mstr && *mint) { // only checks value + ; + } + } + + mbool = false; + EXPECT_TRUE(bool(mbool)); + EXPECT_FALSE(*mbool); + + mbool = true; + EXPECT_TRUE(bool(mbool)); + EXPECT_TRUE(*mbool); + + mbool = none; + EXPECT_FALSE(bool(mbool)); + + // No conversion allowed; does not compile + // EXPECT_TRUE(mbool == false); } TEST(Optional, Pointee) { @@ -261,7 +419,137 @@ TEST(Optional, Pointee) { x = 1; EXPECT_TRUE(get_pointer(x)); *get_pointer(x) = 2; - EXPECT_TRUE(x == 2); + EXPECT_TRUE(*x == 2); x = none; EXPECT_FALSE(get_pointer(x)); } + +TEST(Optional, MakeOptional) { + // const L-value version + const std::string s("abc"); + auto optStr = make_optional(s); + ASSERT_TRUE(optStr.hasValue()); + EXPECT_EQ(*optStr, "abc"); + *optStr = "cde"; + EXPECT_EQ(s, "abc"); + EXPECT_EQ(*optStr, "cde"); + + // L-value version + std::string s2("abc"); + auto optStr2 = make_optional(s2); + ASSERT_TRUE(optStr2.hasValue()); + EXPECT_EQ(*optStr2, "abc"); + *optStr2 = "cde"; + // it's vital to check that s2 wasn't clobbered + EXPECT_EQ(s2, "abc"); + + // L-value reference version + std::string& s3(s2); + auto optStr3 = make_optional(s3); + ASSERT_TRUE(optStr3.hasValue()); + EXPECT_EQ(*optStr3, "abc"); + *optStr3 = "cde"; + EXPECT_EQ(s3, "abc"); + + // R-value ref version + unique_ptr pInt(new int(3)); + auto optIntPtr = make_optional(std::move(pInt)); + EXPECT_TRUE(pInt.get() == nullptr); + ASSERT_TRUE(optIntPtr.hasValue()); + EXPECT_EQ(**optIntPtr, 3); +} + +#if __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wself-move" +#endif + +TEST(Optional, SelfAssignment) { + Optional a = 42; + a = a; + ASSERT_TRUE(a.hasValue() && a.value() == 42); + + Optional b = 23333333; + b = std::move(b); + ASSERT_TRUE(b.hasValue() && b.value() == 23333333); +} + +#if __clang__ +# pragma clang diagnostic pop +#endif + +class ContainsOptional { + public: + ContainsOptional() { } + explicit ContainsOptional(int x) : opt_(x) { } + bool hasValue() const { return opt_.hasValue(); } + int value() const { return opt_.value(); } + + ContainsOptional(const ContainsOptional &other) = default; + ContainsOptional& operator=(const ContainsOptional &other) = default; + ContainsOptional(ContainsOptional &&other) = default; + ContainsOptional& operator=(ContainsOptional &&other) = default; + + private: + Optional opt_; +}; + +/** + * Test that a class containing an Optional can be copy and move assigned. + * This was broken under gcc 4.7 until assignment operators were explicitly + * defined. + */ +TEST(Optional, AssignmentContained) { + { + ContainsOptional source(5), target; + target = source; + EXPECT_TRUE(target.hasValue()); + EXPECT_EQ(5, target.value()); + } + + { + ContainsOptional source(5), target; + target = std::move(source); + EXPECT_TRUE(target.hasValue()); + EXPECT_EQ(5, target.value()); + EXPECT_FALSE(source.hasValue()); + } + + { + ContainsOptional opt_uninit, target(10); + target = opt_uninit; + EXPECT_FALSE(target.hasValue()); + } +} + +TEST(Optional, Exceptions) { + Optional empty; + EXPECT_THROW(empty.value(), OptionalEmptyException); +} + +TEST(Optional, NoThrowDefaultConstructible) { + EXPECT_TRUE(std::is_nothrow_default_constructible>::value); +} + +struct NoDestructor {}; + +struct WithDestructor { + ~WithDestructor(); +}; + +TEST(Optional, TriviallyDestructible) { + // These could all be static_asserts but EXPECT_* give much nicer output on + // failure. + EXPECT_TRUE(std::is_trivially_destructible>::value); + EXPECT_TRUE(std::is_trivially_destructible>::value); + EXPECT_FALSE(std::is_trivially_destructible>::value); +} + +TEST(Optional, Hash) { + // Test it's usable in std::unordered map (compile time check) + std::unordered_map, Optional> obj; + // Also check the std::hash template can be instantiated by the compiler + std::hash>()(none); + std::hash>()(3); +} +}