Ensure curly-braces around control-flow
[folly.git] / folly / gen / test / BaseTest.cpp
index b7c71377a71a5ac49c8edc9939d7c4bd3082de90..0158b93674eb702820dbfae24a3d51480ee1ab9b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2014 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.
  */
 
 #include <glog/logging.h>
-#include <gtest/gtest.h>
+
 #include <iosfwd>
 #include <random>
 #include <set>
 #include <vector>
 
-#include "folly/FBVector.h"
-#include "folly/MapUtil.h"
-#include "folly/Memory.h"
-#include "folly/dynamic.h"
-#include "folly/gen/Base.h"
-#include "folly/experimental/TestUtil.h"
+#include <folly/FBVector.h>
+#include <folly/MapUtil.h>
+#include <folly/Memory.h>
+#include <folly/String.h>
+#include <folly/dynamic.h>
+#include <folly/experimental/TestUtil.h>
+#include <folly/gen/Base.h>
+#include <folly/portability/GTest.h>
 
 using namespace folly::gen;
 using namespace folly;
@@ -46,12 +48,12 @@ EXPECT_SAME(int&, typename ArgumentReference<int&>::type);
 EXPECT_SAME(const int&, typename ArgumentReference<const int&>::type);
 EXPECT_SAME(const int&, typename ArgumentReference<const int>::type);
 
-template<typename T>
+template <typename T>
 ostream& operator<<(ostream& os, const set<T>& values) {
   return os << from(values);
 }
 
-template<typename T>
+template <typename T>
 ostream& operator<<(ostream& os, const vector<T>& values) {
   os << "[";
   for (auto& value : values) {
@@ -69,7 +71,7 @@ auto multiply = [](int a, int b) { return a * b; };
 
 auto product = foldl(1, multiply);
 
-template<typename A, typename B>
+template <typename A, typename B>
 ostream& operator<<(ostream& os, const pair<A, B>& pair) {
   return os << "(" << pair.first << ", " << pair.second << ")";
 }
@@ -124,7 +126,7 @@ TEST(Gen, Member) {
           | sum);
   EXPECT_EQ(10 * (1 + 10) / 2,
             from(counters)
-          | mapped([](const Counter& c) { return &c; })
+          | indirect
           | member(&Counter::count)
           | sum);
   EXPECT_EQ(10 * (2 + 11) / 2,
@@ -133,7 +135,7 @@ TEST(Gen, Member) {
           | sum);
   EXPECT_EQ(10 * (3 + 12) / 2,
             from(counters)
-          | mapped([](Counter& c) { return &c; })
+          | indirect
           | member(&Counter::incr)
           | sum);
   EXPECT_EQ(10 * (3 + 12) / 2,
@@ -165,16 +167,16 @@ TEST(Gen, Field) {
   std::vector<X> xs(1);
   EXPECT_EQ(2, from(xs)
              | field(&X::a)
-             | first);
+             | sum);
   EXPECT_EQ(3, from(xs)
              | field(&X::b)
-             | first);
+             | sum);
   EXPECT_EQ(4, from(xs)
              | field(&X::c)
-             | first);
+             | sum);
   EXPECT_EQ(2, seq(&xs[0], &xs[0])
              | field(&X::a)
-             | first);
+             | sum);
   // type-verification
   empty<X&>() | field(&X::a) | assert_type<const int&>();
   empty<X*>() | field(&X::a) | assert_type<const int&>();
@@ -255,6 +257,49 @@ TEST(Gen, Filter) {
   EXPECT_EQ(expected, actual);
 }
 
+TEST(Gen, FilterDefault) {
+  {
+    // Default filter should remove 0s
+    const auto expected = vector<int>{1, 1, 2, 3};
+    auto actual =
+        from({0, 1, 1, 0, 2, 3, 0})
+      | filter()
+      | as<vector>();
+    EXPECT_EQ(expected, actual);
+  }
+  {
+    // Default filter should remove nullptrs
+    int a = 5;
+    int b = 3;
+    int c = 0;
+    const auto expected = vector<int*>{&a, &b, &c};
+    auto actual =
+        from({(int*)nullptr, &a, &b, &c, (int*)nullptr})
+      | filter()
+      | as<vector>();
+    EXPECT_EQ(expected, actual);
+  }
+  {
+    // Default filter on Optionals should remove folly::null
+    const auto expected =
+        vector<Optional<int>>{Optional<int>(5), Optional<int>(0)};
+    const auto actual =
+        from({Optional<int>(5), Optional<int>(), Optional<int>(0)})
+      | filter()
+      | as<vector>();
+    EXPECT_EQ(expected, actual);
+  }
+}
+
+TEST(Gen, FilterSink) {
+  auto actual
+    = seq(1, 2)
+    | map([](int x) { return vector<int>{x}; })
+    | filter([](vector<int> v) { return !v.empty(); })
+    | as<vector>();
+  EXPECT_FALSE(from(actual) | rconcat | isEmpty);
+}
+
 TEST(Gen, Contains) {
   {
     auto gen =
@@ -306,6 +351,52 @@ TEST(Gen, Take) {
       | as<vector>();
     EXPECT_EQ(expected, actual);
   }
+  {
+    int64_t limit = 5;
+    take(limit - 5);
+    EXPECT_THROW(take(limit - 6), std::invalid_argument);
+  }
+}
+
+
+TEST(Gen, Stride) {
+  {
+    EXPECT_THROW(stride(0), std::invalid_argument);
+  }
+  {
+    auto expected = vector<int>{1, 2, 3, 4};
+    auto actual
+      = seq(1, 4)
+      | stride(1)
+      | as<vector<int>>();
+    EXPECT_EQ(expected, actual);
+  }
+  {
+    auto expected = vector<int>{1, 3, 5, 7};
+    auto actual
+      = seq(1, 8)
+      | stride(2)
+      | as<vector<int>>();
+    EXPECT_EQ(expected, actual);
+  }
+  {
+    auto expected = vector<int>{1, 4, 7, 10};
+    auto actual
+      = seq(1, 12)
+      | stride(3)
+      | as<vector<int>>();
+    EXPECT_EQ(expected, actual);
+  }
+  {
+    auto expected = vector<int>{1, 3, 5, 7, 9, 1, 4, 7, 10};
+    auto actual
+      = ((seq(1, 10) | stride(2)) +
+         (seq(1, 10) | stride(3)))
+      | as<vector<int>>();
+    EXPECT_EQ(expected, actual);
+  }
+  EXPECT_EQ(500, seq(1) | take(1000) | stride(2) | count);
+  EXPECT_EQ(10, seq(1) | take(1000) | stride(2) | take(10) | count);
 }
 
 TEST(Gen, Sample) {
@@ -384,7 +475,35 @@ TEST(Gen, Until) {
       | as<vector<int>>();
     EXPECT_EQ(expected, actual);
   }
-  */
+    */
+}
+
+TEST(Gen, Visit) {
+  auto increment = [](int& i) { ++i; };
+  auto clone = map([](int i) { return i; });
+  { // apply()
+    auto expected = 10;
+    auto actual = seq(0) | clone | visit(increment) | take(4) | sum;
+    EXPECT_EQ(expected, actual);
+  }
+  { // foreach()
+    auto expected = 10;
+    auto actual = seq(0, 3) | clone | visit(increment) | sum;
+    EXPECT_EQ(expected, actual);
+  }
+  { // tee-like
+    std::vector<int> x2, x4;
+    std::vector<int> expected2{0, 1, 4, 9};
+    std::vector<int> expected4{0, 1, 16, 81};
+
+    auto tee = [](std::vector<int>& container) {
+      return visit([&](int value) { container.push_back(value); });
+    };
+    EXPECT_EQ(
+        98, seq(0, 3) | map(square) | tee(x2) | map(square) | tee(x4) | sum);
+    EXPECT_EQ(expected2, x2);
+    EXPECT_EQ(expected4, x4);
+  }
 }
 
 TEST(Gen, Composed) {
@@ -502,18 +621,62 @@ TEST(Gen, DistinctMove) {   //  0  1  4  9  6  5  6  9  4  1  0
   EXPECT_EQ(expected, actual);
 }
 
+TEST(Gen, DistinctInfinite) {
+  // distinct should be able to handle an infinite sequence, provided that, of
+  // of cource, is it eventually made finite before returning the result.
+  auto expected = seq(0) | take(5) | as<vector>(); // 0 1 2 3 4
+
+  auto actual =
+      seq(0)                              // 0 1 2 3 4 5 6 7 ...
+    | mapped([](int i) { return i / 2; }) // 0 0 1 1 2 2 3 3 ...
+    | distinct                            // 0 1 2 3 4 5 6 7 ...
+    | take(5)                             // 0 1 2 3 4
+    | as<vector>();
+
+  EXPECT_EQ(expected, actual);
+}
+
+TEST(Gen, DistinctByInfinite) {
+  // Similarly to the DistinctInfinite test case, distinct by should be able to
+  // handle infinite sequences. Note that depending on how many values we take()
+  // at the end, the sequence may infinite loop. This is fine becasue we cannot
+  // solve the halting problem.
+  auto expected = vector<int>{1, 2};
+  auto actual =
+      seq(1)                                    // 1 2 3 4 5 6 7 8 ...
+    | distinctBy([](int i) { return i % 2; })   // 1 2 (but might by infinite)
+    | take(2)                                   // 1 2
+    | as<vector>();
+  // Note that if we had take(3), this would infinite loop
+
+  EXPECT_EQ(expected, actual);
+}
+
 TEST(Gen, MinBy) {
   EXPECT_EQ(7, seq(1, 10)
              | minBy([](int i) -> double {
                  double d = i - 6.8;
                  return d * d;
-               }));
+               })
+             | unwrap);
 }
 
 TEST(Gen, MaxBy) {
   auto gen = from({"three", "eleven", "four"});
 
-  EXPECT_EQ("eleven", gen | maxBy(&strlen));
+  EXPECT_EQ("eleven", gen | maxBy(&strlen) | unwrap);
+}
+
+TEST(Gen, Min) {
+  auto odds = seq(2,10) | filter([](int i){ return i % 2; });
+
+  EXPECT_EQ(3, odds | min);
+}
+
+TEST(Gen, Max) {
+  auto odds = seq(2,10) | filter([](int i){ return i % 2; });
+
+  EXPECT_EQ(9, odds | max);
 }
 
 TEST(Gen, Append) {
@@ -529,7 +692,7 @@ TEST(Gen, FromRValue) {
     // reference of a std::vector when it is used as the 'other' for an rvalue
     // constructor.  Use fbvector because we're sure its size will be zero in
     // this case.
-    fbvector<int> v({1,2,3,4});
+    fbvector<int> v({1, 2, 3, 4});
     auto q1 = from(v);
     EXPECT_EQ(v.size(), 4);  // ensure that the lvalue version was called!
     auto expected = 1 * 2 * 3 * 4;
@@ -541,11 +704,11 @@ TEST(Gen, FromRValue) {
   }
   {
     auto expected = 7;
-    auto q = from([] {return vector<int>({3,7,5}); }());
+    auto q = from([] { return vector<int>({3, 7, 5}); }());
     EXPECT_EQ(expected, q | max);
   }
   {
-    for (auto size: {5, 1024, 16384, 1<<20}) {
+    for (auto size : {5, 1024, 16384, 1 << 20}) {
       auto q1 = from(vector<int>(size, 2));
       auto q2 = from(vector<int>(size, 3));
       // If the rvalue specialization is broken/gone, then the compiler will
@@ -569,6 +732,14 @@ TEST(Gen, OrderBy) {
     | orderBy([](int x) { return (5.1 - x) * (5.1 - x); })
     | as<vector>();
   EXPECT_EQ(expected, actual);
+
+  expected = seq(1, 10) | as<vector>();
+  actual =
+      from(expected)
+    | map([] (int x) { return 11 - x; })
+    | orderBy()
+    | as<vector>();
+  EXPECT_EQ(expected, actual);
 }
 
 TEST(Gen, Foldl) {
@@ -582,17 +753,13 @@ TEST(Gen, Foldl) {
 TEST(Gen, Reduce) {
   int expected = 2 + 3 + 4 + 5;
   auto actual = seq(2, 5) | reduce(add);
-  EXPECT_EQ(expected, actual);
+  EXPECT_EQ(expected, actual | unwrap);
 }
 
 TEST(Gen, ReduceBad) {
   auto gen = seq(1) | take(0);
-  try {
-    EXPECT_TRUE(true);
-    gen | reduce(add);
-    EXPECT_TRUE(false);
-  } catch (...) {
-  }
+  auto actual = gen | reduce(add);
+  EXPECT_FALSE(actual); // Empty sequences are okay, they just yeild 'none'
 }
 
 TEST(Gen, Moves) {
@@ -605,10 +772,8 @@ TEST(Gen, Moves) {
 }
 
 TEST(Gen, First) {
-  auto gen =
-      seq(0)
-    | filter([](int x) { return x > 3; });
-  EXPECT_EQ(4, gen | first);
+  auto gen = seq(0) | filter([](int x) { return x > 3; });
+  EXPECT_EQ(4, gen | first | unwrap);
 }
 
 TEST(Gen, FromCopy) {
@@ -647,15 +812,23 @@ TEST(Gen, Get) {
   EXPECT_EQ(36, from(tuples) | get<2>() | sum);
 }
 
+TEST(Gen, notEmpty) {
+  EXPECT_TRUE(seq(0, 1) | notEmpty);
+  EXPECT_TRUE(just(1) | notEmpty);
+  EXPECT_FALSE(gen::range(0, 0) | notEmpty);
+  EXPECT_FALSE(from({1}) | take(0) | notEmpty);
+}
+
+TEST(Gen, isEmpty) {
+  EXPECT_FALSE(seq(0, 1) | isEmpty);
+  EXPECT_FALSE(just(1) | isEmpty);
+  EXPECT_TRUE(gen::range(0, 0) | isEmpty);
+  EXPECT_TRUE(from({1}) | take(0) | isEmpty);
+}
+
 TEST(Gen, Any) {
-  EXPECT_TRUE(seq(0) | any);
-  EXPECT_TRUE(seq(0, 1) | any);
   EXPECT_TRUE(seq(0, 10) | any([](int i) { return i == 7; }));
   EXPECT_FALSE(seq(0, 10) | any([](int i) { return i == 11; }));
-
-  EXPECT_TRUE(from({1}) | any);
-  EXPECT_FALSE(gen::range(0, 0) | any);
-  EXPECT_FALSE(from({1}) | take(0) | any);
 }
 
 TEST(Gen, All) {
@@ -704,10 +877,12 @@ TEST(Gen, MapYielders) {
            | map([](int n) {
                return GENERATOR(int) {
                  int i;
-                 for (i = 1; i < n; ++i)
+                 for (i = 1; i < n; ++i) {
                    yield(i);
-                 for (; i >= 1; --i)
+                 }
+                 for (; i >= 1; --i) {
                    yield(i);
+                 }
                };
              })
            | concat;
@@ -767,13 +942,13 @@ class TestIntSeq : public GenImpl<int, TestIntSeq> {
     return true;
   }
 
-  TestIntSeq(TestIntSeq&&) = default;
-  TestIntSeq& operator=(TestIntSeq&&) = default;
+  TestIntSeq(TestIntSeq&&) noexcept = default;
+  TestIntSeq& operator=(TestIntSeq&&) noexcept = default;
   TestIntSeq(const TestIntSeq&) = delete;
   TestIntSeq& operator=(const TestIntSeq&) = delete;
 };
 
-}  // namespace
+} // namespace
 
 TEST(Gen, NoGeneratorCopies) {
   EXPECT_EQ(15, TestIntSeq() | sum);
@@ -809,7 +984,7 @@ struct CopyCounter {
     ++alive;
   }
 
-  CopyCounter(CopyCounter&& source) {
+  CopyCounter(CopyCounter&& source) noexcept {
     *this = std::move(source);
     ++alive;
   }
@@ -855,11 +1030,15 @@ TEST(Gen, CopyCount) {
 
 // test dynamics with various layers of nested arrays.
 TEST(Gen, Dynamic) {
-  dynamic array1 = {1, 2};
+  dynamic array1 = dynamic::array(1, 2);
   EXPECT_EQ(dynamic(3), from(array1) | sum);
-  dynamic array2 = {{1}, {1, 2}};
+  dynamic array2 = folly::dynamic::array(
+      folly::dynamic::array(1), folly::dynamic::array(1, 2));
   EXPECT_EQ(dynamic(4), from(array2) | rconcat | sum);
-  dynamic array3 = {{{1}}, {{1}, {1, 2}}};
+  dynamic array3 = folly::dynamic::array(
+      folly::dynamic::array(folly::dynamic::array(1)),
+      folly::dynamic::array(
+          folly::dynamic::array(1), folly::dynamic::array(1, 2)));
   EXPECT_EQ(dynamic(5), from(array3) | rconcat | rconcat | sum);
 }
 
@@ -911,7 +1090,8 @@ TEST(Gen, Cycle) {
     };
     auto s = countdown;
     EXPECT_EQ((vector<int> { 1, 2, 3, 1, 2, 1}),
-              s | cycle | as<vector>());
+              s | cycle | take(7) | as<vector>());
+    // take necessary as cycle returns an infinite generator
   }
 }
 
@@ -969,6 +1149,50 @@ TEST(Gen, Dereference) {
   }
 }
 
+namespace {
+struct DereferenceWrapper {
+  string data;
+  string& operator*() & {
+    return data;
+  }
+  string&& operator*() && {
+    return std::move(data);
+  }
+  explicit operator bool() {
+    return true;
+  }
+};
+bool operator==(const DereferenceWrapper& a, const DereferenceWrapper& b) {
+  return a.data == b.data;
+}
+void PrintTo(const DereferenceWrapper& a, std::ostream* o) {
+  *o << "Wrapper{\"" << cEscape<string>(a.data) << "\"}";
+}
+}
+
+TEST(Gen, DereferenceWithLValueRef) {
+  auto original = vector<DereferenceWrapper>{{"foo"}, {"bar"}};
+  auto copy = original;
+  auto expected = vector<string>{"foo", "bar"};
+  auto actual = from(original) | dereference | as<vector>();
+  EXPECT_EQ(expected, actual);
+  EXPECT_EQ(copy, original);
+}
+
+TEST(Gen, DereferenceWithRValueRef) {
+  auto original = vector<DereferenceWrapper>{{"foo"}, {"bar"}};
+  auto empty = vector<DereferenceWrapper>{{}, {}};
+  auto expected = vector<string>{"foo", "bar"};
+  auto actual = from(original) | move | dereference | as<vector>();
+  EXPECT_EQ(expected, actual);
+  EXPECT_EQ(empty, original);
+}
+
+TEST(Gen, Indirect) {
+  vector<int> vs{1};
+  EXPECT_EQ(&vs[0], from(vs) | indirect | first | unwrap);
+}
+
 TEST(Gen, Guard) {
   using std::runtime_error;
   EXPECT_THROW(from({"1", "a", "3"})
@@ -1027,8 +1251,179 @@ TEST(Gen, BatchMove) {
   EXPECT_EQ(expected, actual);
 }
 
+TEST(Gen, Window) {
+  auto expected = seq(0, 10) | as<std::vector>();
+  for (size_t windowSize = 1; windowSize <= 20; ++windowSize) {
+    // no early stop
+    auto actual = seq(0, 10) |
+        mapped([](int i) { return std::unique_ptr<int>(new int(i)); }) |
+        window(4) | dereference | as<std::vector>();
+    EXPECT_EQ(expected, actual) << windowSize;
+  }
+  for (size_t windowSize = 1; windowSize <= 20; ++windowSize) {
+    // pre-window take
+    auto actual = seq(0) |
+        mapped([](int i) { return std::unique_ptr<int>(new int(i)); }) |
+        take(11) | window(4) | dereference | as<std::vector>();
+    EXPECT_EQ(expected, actual) << windowSize;
+  }
+  for (size_t windowSize = 1; windowSize <= 20; ++windowSize) {
+    // post-window take
+    auto actual = seq(0) |
+        mapped([](int i) { return std::unique_ptr<int>(new int(i)); }) |
+        window(4) | take(11) | dereference | as<std::vector>();
+    EXPECT_EQ(expected, actual) << windowSize;
+  }
+}
+
+TEST(Gen, Just) {
+  {
+    int x = 3;
+    auto j = just(x);
+    EXPECT_EQ(&x, j | indirect | first | unwrap);
+    x = 4;
+    EXPECT_EQ(4, j | sum);
+  }
+  {
+    int x = 3;
+    const int& cx = x;
+    auto j = just(cx);
+    EXPECT_EQ(&x, j | indirect | first | unwrap);
+    x = 5;
+    EXPECT_EQ(5, j | sum);
+  }
+  {
+    int x = 3;
+    auto j = just(std::move(x));
+    EXPECT_NE(&x, j | indirect | first | unwrap);
+    x = 5;
+    EXPECT_EQ(3, j | sum);
+  }
+}
+
+TEST(Gen, GroupBy) {
+  vector<string> strs{"zero", "one", "two",   "three", "four",
+                      "five", "six", "seven", "eight", "nine"};
+
+  auto gb = from(strs) | groupBy([](const string& str) { return str.size(); });
+
+  EXPECT_EQ(10, gb | mapOp(count) | sum);
+  EXPECT_EQ(3, gb | count);
+
+  vector<string> mode{"zero", "four", "five", "nine"};
+  EXPECT_EQ(mode,
+            gb | maxBy([](const Group<size_t, string>& g) { return g.size(); })
+               | unwrap
+               | as<vector>());
+
+  vector<string> largest{"three", "seven", "eight"};
+  EXPECT_EQ(largest,
+            gb | maxBy([](const Group<size_t, string>& g) { return g.key(); })
+               | unwrap
+               | as<vector>());
+}
+
+TEST(Gen, Unwrap) {
+  Optional<int> o(4);
+  Optional<int> e;
+  EXPECT_EQ(4, o | unwrap);
+  EXPECT_THROW(e | unwrap, OptionalEmptyException);
+
+  auto oup = folly::make_optional(std::make_unique<int>(5));
+  // optional has a value, and that value is non-null
+  EXPECT_TRUE(bool(oup | unwrap));
+  EXPECT_EQ(5, *(oup | unwrap));
+  EXPECT_TRUE(oup.hasValue()); // still has a pointer (null or not)
+  EXPECT_TRUE(bool(oup.value())); // that value isn't null
+
+  auto moved1 = std::move(oup) | unwrapOr(std::make_unique<int>(6));
+  // oup still has a value, but now it's now nullptr since the pointer was moved
+  // into moved1
+  EXPECT_TRUE(oup.hasValue());
+  EXPECT_FALSE(oup.value());
+  EXPECT_TRUE(bool(moved1));
+  EXPECT_EQ(5, *moved1);
+
+  auto moved2 = std::move(oup) | unwrapOr(std::make_unique<int>(7));
+  // oup's still-valid nullptr value wins here, the pointer to 7 doesn't apply
+  EXPECT_FALSE(moved2);
+
+  oup.clear();
+  auto moved3 = std::move(oup) | unwrapOr(std::make_unique<int>(8));
+  // oup is empty now, so the unwrapOr comes into play.
+  EXPECT_TRUE(bool(moved3));
+  EXPECT_EQ(8, *moved3);
+
+  {
+  // mixed types, with common type matching optional
+    Optional<double> full(3.3);
+    decltype(full) empty;
+    auto fallback = unwrapOr(4);
+    EXPECT_EQ(3.3, full | fallback);
+    EXPECT_EQ(3.3, std::move(full) | fallback);
+    EXPECT_EQ(3.3, full | std::move(fallback));
+    EXPECT_EQ(3.3, std::move(full) | std::move(fallback));
+    EXPECT_EQ(4.0, empty | fallback);
+    EXPECT_EQ(4.0, std::move(empty) | fallback);
+    EXPECT_EQ(4.0, empty | std::move(fallback));
+    EXPECT_EQ(4.0, std::move(empty) | std::move(fallback));
+  }
+
+  {
+  // mixed types, with common type matching fallback
+    Optional<int> full(3);
+    decltype(full) empty;
+    auto fallback = unwrapOr(5.0); // type: double
+    // if we chose 'int' as the common type, we'd see truncation here
+    EXPECT_EQ(1.5, (full | fallback) / 2);
+    EXPECT_EQ(1.5, (std::move(full) | fallback) / 2);
+    EXPECT_EQ(1.5, (full | std::move(fallback)) / 2);
+    EXPECT_EQ(1.5, (std::move(full) | std::move(fallback)) / 2);
+    EXPECT_EQ(2.5, (empty | fallback) / 2);
+    EXPECT_EQ(2.5, (std::move(empty) | fallback) / 2);
+    EXPECT_EQ(2.5, (empty | std::move(fallback)) / 2);
+    EXPECT_EQ(2.5, (std::move(empty) | std::move(fallback)) / 2);
+  }
+
+  {
+    auto opt = folly::make_optional(std::make_shared<int>(8));
+    auto fallback = unwrapOr(std::make_unique<int>(9));
+    // fallback must be std::move'd to be used
+    EXPECT_EQ(8, *(opt | std::move(fallback)));
+    EXPECT_TRUE(bool(opt.value())); // shared_ptr copied out, not moved
+    EXPECT_TRUE(bool(opt)); // value still present
+    EXPECT_TRUE(bool(fallback.value())); // fallback value not needed
+
+    EXPECT_EQ(8, *(std::move(opt) | std::move(fallback)));
+    EXPECT_FALSE(opt.value()); // shared_ptr moved out
+    EXPECT_TRUE(bool(opt)); // gutted value still present
+    EXPECT_TRUE(bool(fallback.value())); // fallback value not needed
+
+    opt.clear();
+
+    EXPECT_FALSE(opt); // opt is empty now
+    EXPECT_EQ(9, *(std::move(opt) | std::move(fallback)));
+    EXPECT_FALSE(fallback.value()); // fallback moved out!
+  }
+
+  {
+    // test with nullptr
+    vector<int> v{1, 2};
+    EXPECT_EQ(&v[1], from(v) | indirect | max | unwrap);
+    v.clear();
+    EXPECT_FALSE(from(v) | indirect | max | unwrapOr(nullptr));
+  }
+
+  {
+    // mixed type determined by fallback
+    Optional<std::nullptr_t> empty;
+    int x = 3;
+    EXPECT_EQ(&x, empty | unwrapOr(&x));
+  }
+}
+
 int main(int argc, char *argv[]) {
   testing::InitGoogleTest(&argc, argv);
-  google::ParseCommandLineFlags(&argc, &argv, true);
+  gflags::ParseCommandLineFlags(&argc, &argv, true);
   return RUN_ALL_TESTS();
 }