folly/folly/futures/test/PromiseTest.cpp

/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * 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 <memory>

#include <folly/futures/Future.h>
#include <folly/portability/GTest.h>

using namespace folly;
using std::string;

using std::unique_ptr;
typedef FutureException eggs_t;
static eggs_t eggs("eggs");

TEST(Promise, makeEmpty) {
  auto p = Promise<int>::makeEmpty();
  EXPECT_TRUE(p.isFulfilled());
}

TEST(Promise, special) {
  EXPECT_FALSE(std::is_copy_constructible<Promise<int>>::value);
  EXPECT_FALSE(std::is_copy_assignable<Promise<int>>::value);
  EXPECT_TRUE(std::is_move_constructible<Promise<int>>::value);
  EXPECT_TRUE(std::is_move_assignable<Promise<int>>::value);
}

TEST(Promise, getSemiFuture) {
  Promise<int> p;
  SemiFuture<int> f = p.getSemiFuture();
  EXPECT_FALSE(f.isReady());
}

TEST(Promise, getFuture) {
  Promise<int> p;
  Future<int> f = p.getFuture();
  EXPECT_FALSE(f.isReady());
}

TEST(Promise, setValueUnit) {
  Promise<Unit> p;
  p.setValue();
}

namespace {
auto makeValid() {
  auto valid = Promise<int>();
  EXPECT_TRUE(valid.valid());
  return valid;
}
auto makeInvalid() {
  auto invalid = Promise<int>::makeEmpty();
  EXPECT_FALSE(invalid.valid());
  return invalid;
}
} // namespace

TEST(Promise, ctorPostconditionValid) {
  // Ctors/factories that promise valid -- postcondition: valid()

#define DOIT(CREATION_EXPR)    \
  do {                         \
    auto p1 = (CREATION_EXPR); \
    EXPECT_TRUE(p1.valid());   \
    auto p2 = std::move(p1);   \
    EXPECT_FALSE(p1.valid());  \
    EXPECT_TRUE(p2.valid());   \
  } while (false)

  DOIT(makeValid());
  DOIT(Promise<int>());
  DOIT(Promise<int>{});
  DOIT(Promise<Unit>());
  DOIT(Promise<Unit>{});

#undef DOIT
}

TEST(Promise, ctorPostconditionInvalid) {
  // Ctors/factories that promise invalid -- postcondition: !valid()

#define DOIT(CREATION_EXPR)    \
  do {                         \
    auto p1 = (CREATION_EXPR); \
    EXPECT_FALSE(p1.valid());  \
    auto p2 = std::move(p1);   \
    EXPECT_FALSE(p1.valid());  \
    EXPECT_FALSE(p2.valid());  \
  } while (false)

  DOIT(makeInvalid());
  DOIT(Promise<int>::makeEmpty());

#undef DOIT
}

TEST(Promise, lacksPreconditionValid) {
  // Ops that don't throw PromiseInvalid if !valid() --
  // without precondition: valid()

#define DOIT(STMT)         \
  do {                     \
    auto p = makeValid();  \
    { STMT; }              \
    copy(std::move(p));    \
    EXPECT_NO_THROW(STMT); \
  } while (false)

  // misc methods that don't require isValid()
  DOIT(p.valid());
  DOIT(p.isFulfilled());

  // move-ctor - move-copy to local, copy(), pass-by-move-value
  DOIT(auto other = std::move(p));
  DOIT(copy(std::move(p)));
  DOIT(([](auto) {})(std::move(p)));

  // move-assignment into either {valid | invalid}
  DOIT({
    auto other = makeValid();
    other = std::move(p);
  });
  DOIT({
    auto other = makeInvalid();
    other = std::move(p);
  });

#undef DOIT
}

TEST(Promise, hasPreconditionValid) {
  // Ops that require validity; precondition: valid();
  // throw PromiseInvalid if !valid()

#define DOIT(STMT)                      \
  do {                                  \
    auto p = makeValid();               \
    EXPECT_NO_THROW(STMT);              \
    copy(std::move(p));                 \
    EXPECT_THROW(STMT, PromiseInvalid); \
  } while (false)

  auto const except = std::logic_error("foo");
  auto const ewrap = folly::exception_wrapper(except);

  DOIT(p.getSemiFuture());
  DOIT(p.getFuture());
  DOIT(p.setException(except));
  DOIT(p.setException(ewrap));
  DOIT(p.setInterruptHandler([](auto&) {}));
  DOIT(p.setValue(42));
  DOIT(p.setTry(Try<int>(42)));
  DOIT(p.setTry(Try<int>(ewrap)));
  DOIT(p.setWith([] { return 42; }));

#undef DOIT
}

TEST(Promise, hasPostconditionValid) {
  // Ops that preserve validity -- postcondition: valid()

#define DOIT(STMT)          \
  do {                      \
    auto p = makeValid();   \
    EXPECT_NO_THROW(STMT);  \
    EXPECT_TRUE(p.valid()); \
  } while (false)

  auto const swallow = [](auto) {};

  DOIT(swallow(p.valid())); // p.valid() itself preserves validity
  DOIT(swallow(p.isFulfilled()));

#undef DOIT
}

TEST(Promise, hasPostconditionInvalid) {
  // Ops that consume *this -- postcondition: !valid()

#define DOIT(CTOR, STMT)     \
  do {                       \
    auto p = (CTOR);         \
    EXPECT_NO_THROW(STMT);   \
    EXPECT_FALSE(p.valid()); \
  } while (false)

  // move-ctor of {valid|invalid}
  DOIT(makeValid(), { auto other{std::move(p)}; });
  DOIT(makeInvalid(), { auto other{std::move(p)}; });

  // move-assignment of {valid|invalid} into {valid|invalid}
  DOIT(makeValid(), {
    auto other = makeValid();
    other = std::move(p);
  });
  DOIT(makeValid(), {
    auto other = makeInvalid();
    other = std::move(p);
  });
  DOIT(makeInvalid(), {
    auto other = makeValid();
    other = std::move(p);
  });
  DOIT(makeInvalid(), {
    auto other = makeInvalid();
    other = std::move(p);
  });

  // pass-by-value of {valid|invalid}
  DOIT(makeValid(), {
    auto const byval = [](auto) {};
    byval(std::move(p));
  });
  DOIT(makeInvalid(), {
    auto const byval = [](auto) {};
    byval(std::move(p));
  });

#undef DOIT
}

TEST(Promise, setValueSemiFuture) {
  Promise<int> fund;
  auto ffund = fund.getSemiFuture();
  fund.setValue(42);
  EXPECT_EQ(42, ffund.value());

  struct Foo {
    string name;
    int value;
  };

  Promise<Foo> pod;
  auto fpod = pod.getSemiFuture();
  Foo f = {"the answer", 42};
  pod.setValue(f);
  Foo f2 = fpod.value();
  EXPECT_EQ(f.name, f2.name);
  EXPECT_EQ(f.value, f2.value);

  pod = Promise<Foo>();
  fpod = pod.getSemiFuture();
  pod.setValue(std::move(f2));
  Foo f3 = fpod.value();
  EXPECT_EQ(f.name, f3.name);
  EXPECT_EQ(f.value, f3.value);

  Promise<unique_ptr<int>> mov;
  auto fmov = mov.getSemiFuture();
  mov.setValue(std::make_unique<int>(42));
  unique_ptr<int> ptr = std::move(fmov.value());
  EXPECT_EQ(42, *ptr);

  Promise<Unit> v;
  auto fv = v.getSemiFuture();
  v.setValue();
  EXPECT_TRUE(fv.isReady());
}

TEST(Promise, setValue) {
  Promise<int> fund;
  auto ffund = fund.getFuture();
  fund.setValue(42);
  EXPECT_EQ(42, ffund.value());

  struct Foo {
    string name;
    int value;
  };

  Promise<Foo> pod;
  auto fpod = pod.getFuture();
  Foo f = {"the answer", 42};
  pod.setValue(f);
  Foo f2 = fpod.value();
  EXPECT_EQ(f.name, f2.name);
  EXPECT_EQ(f.value, f2.value);

  pod = Promise<Foo>();
  fpod = pod.getFuture();
  pod.setValue(std::move(f2));
  Foo f3 = fpod.value();
  EXPECT_EQ(f.name, f3.name);
  EXPECT_EQ(f.value, f3.value);

  Promise<unique_ptr<int>> mov;
  auto fmov = mov.getFuture();
  mov.setValue(std::make_unique<int>(42));
  unique_ptr<int> ptr = std::move(fmov.value());
  EXPECT_EQ(42, *ptr);

  Promise<Unit> v;
  auto fv = v.getFuture();
  v.setValue();
  EXPECT_TRUE(fv.isReady());
}

TEST(Promise, setException) {
  {
    Promise<Unit> p;
    auto f = p.getFuture();
    p.setException(eggs);
    EXPECT_THROW(f.value(), eggs_t);
  }
  {
    Promise<Unit> p;
    auto f = p.getFuture();
    p.setException(exception_wrapper(eggs));
    EXPECT_THROW(f.value(), eggs_t);
  }
}

TEST(Promise, setWith) {
  {
    Promise<int> p;
    auto f = p.getFuture();
    p.setWith([] { return 42; });
    EXPECT_EQ(42, f.value());
  }
  {
    Promise<int> p;
    auto f = p.getFuture();
    p.setWith([]() -> int { throw eggs; });
    EXPECT_THROW(f.value(), eggs_t);
  }
}

TEST(Promise, isFulfilled) {
  Promise<int> p;

  EXPECT_FALSE(p.isFulfilled());
  p.setValue(42);
  EXPECT_TRUE(p.isFulfilled());
}

TEST(Promise, isFulfilledWithFuture) {
  Promise<int> p;
  auto f = p.getFuture(); // so core_ will become null

  EXPECT_FALSE(p.isFulfilled());
  p.setValue(42); // after here
  EXPECT_TRUE(p.isFulfilled());
}

TEST(Promise, brokenOnDelete) {
  auto p = std::make_unique<Promise<int>>();
  auto f = p->getFuture();

  EXPECT_FALSE(f.isReady());

  p.reset();

  EXPECT_TRUE(f.isReady());

  auto t = f.result();

  EXPECT_TRUE(t.hasException<BrokenPromise>());
}

TEST(Promise, brokenPromiseHasTypeInfo) {
  auto pInt = std::make_unique<Promise<int>>();
  auto fInt = pInt->getFuture();

  auto pFloat = std::make_unique<Promise<float>>();
  auto fFloat = pFloat->getFuture();

  pInt.reset();
  pFloat.reset();

  std::string whatInt = fInt.result().exception().get_exception()->what();
  std::string whatFloat = fFloat.result().exception().get_exception()->what();
  EXPECT_EQ(whatInt, "Broken promise for type name `int`");
  EXPECT_EQ(whatFloat, "Broken promise for type name `float`");
}