Commit afb57872 authored by Marshall Cline's avatar Marshall Cline Committed by Facebook Github Bot

add valid() to Future, SemiFuture, Promise

Summary:
Lets callers determine whether a Future/SemiFuture/Promise has a shared-state.
`x.valid()` (where `x` is a Future, SemiFuture, or Promise) is is true if/only if the object has a shared state.

The `valid()` method will also be key in a near-term diff that will use it to clarify the contracts/rules.

Note: the concept of validity already shows up in the doc-blocks; this gives callers a conceptual oppty to check it.

Reviewed By: yfeldblum

Differential Revision: D7839038

fbshipit-source-id: f8390b3d1a8c3d5d89356ae48937e03c424d1174
parent 7897c65f
...@@ -89,6 +89,12 @@ class FutureBase { ...@@ -89,6 +89,12 @@ class FutureBase {
~FutureBase(); ~FutureBase();
/// true if this has a shared state;
/// false if this has been consumed/moved-out.
bool valid() const noexcept {
return core_ != nullptr;
}
/// Returns a reference to the result, with a reference category and const- /// Returns a reference to the result, with a reference category and const-
/// qualification equivalent to the reference category and const-qualification /// qualification equivalent to the reference category and const-qualification
/// of the receiver. /// of the receiver.
...@@ -271,6 +277,7 @@ class SemiFuture : private futures::detail::FutureBase<T> { ...@@ -271,6 +277,7 @@ class SemiFuture : private futures::detail::FutureBase<T> {
using Base::raise; using Base::raise;
using Base::result; using Base::result;
using Base::setCallback_; using Base::setCallback_;
using Base::valid;
using Base::value; using Base::value;
SemiFuture& operator=(SemiFuture const&) = delete; SemiFuture& operator=(SemiFuture const&) = delete;
...@@ -521,6 +528,7 @@ class Future : private futures::detail::FutureBase<T> { ...@@ -521,6 +528,7 @@ class Future : private futures::detail::FutureBase<T> {
using Base::poll; using Base::poll;
using Base::raise; using Base::raise;
using Base::setCallback_; using Base::setCallback_;
using Base::valid;
using Base::value; using Base::value;
using Base::result; using Base::result;
......
...@@ -115,6 +115,12 @@ class Promise { ...@@ -115,6 +115,12 @@ class Promise {
template <class F> template <class F>
void setWith(F&& func); void setWith(F&& func);
/// true if this has a shared state;
/// false if this has been consumed/moved-out.
bool valid() const noexcept {
return core_ != nullptr;
}
bool isFulfilled() const noexcept; bool isFulfilled() const noexcept;
private: private:
......
...@@ -81,6 +81,207 @@ TEST(Future, makeFutureWithUnit) { ...@@ -81,6 +81,207 @@ TEST(Future, makeFutureWithUnit) {
EXPECT_EQ(1, count); EXPECT_EQ(1, count);
} }
namespace {
auto makeValid() {
auto valid = makeFuture<int>(42);
EXPECT_TRUE(valid.valid());
return valid;
}
auto makeInvalid() {
auto invalid = Future<int>::makeEmpty();
EXPECT_FALSE(invalid.valid());
return invalid;
}
} // namespace
TEST(Future, ctorPostconditionValid) {
// Ctors/factories that promise valid -- postcondition: valid()
#define DOIT(CREATION_EXPR) \
do { \
auto f1 = (CREATION_EXPR); \
EXPECT_TRUE(f1.valid()); \
auto f2 = std::move(f1); \
EXPECT_FALSE(f1.valid()); \
EXPECT_TRUE(f2.valid()); \
} while (false)
auto const except = std::logic_error("foo");
auto const ewrap = folly::exception_wrapper(except);
DOIT(makeValid());
DOIT(Future<int>(42));
DOIT(Future<int>{42});
DOIT(Future<Unit>());
DOIT(Future<Unit>{});
DOIT(makeFuture());
DOIT(makeFuture(Unit{}));
DOIT(makeFuture<Unit>(Unit{}));
DOIT(makeFuture(42));
DOIT(makeFuture<int>(42));
DOIT(makeFuture<int>(except));
DOIT(makeFuture<int>(ewrap));
DOIT(makeFuture(Try<int>(42)));
DOIT(makeFuture<int>(Try<int>(42)));
DOIT(makeFuture<int>(Try<int>(ewrap)));
#undef DOIT
}
TEST(Future, ctorPostconditionInvalid) {
// Ctors/factories that promise invalid -- postcondition: !valid()
#define DOIT(CREATION_EXPR) \
do { \
auto f1 = (CREATION_EXPR); \
EXPECT_FALSE(f1.valid()); \
auto f2 = std::move(f1); \
EXPECT_FALSE(f1.valid()); \
EXPECT_FALSE(f2.valid()); \
} while (false)
DOIT(makeInvalid());
DOIT(Future<int>::makeEmpty());
#undef DOIT
}
TEST(Future, lacksPreconditionValid) {
// Ops that don't throw NoState if !valid() -- without precondition: valid()
#define DOIT(STMT) \
do { \
auto f = makeValid(); \
{ STMT; } \
copy(std::move(f)); \
EXPECT_NO_THROW(STMT); \
} while (false)
// .valid() itself
DOIT(f.valid());
// move-ctor - move-copy to local, copy(), pass-by-move-value
DOIT(auto other = std::move(f));
DOIT(copy(std::move(f)));
DOIT(([](auto) {})(std::move(f)));
// move-assignment into either {valid | invalid}
DOIT({
auto other = makeValid();
other = std::move(f);
});
DOIT({
auto other = makeInvalid();
other = std::move(f);
});
#undef DOIT
}
TEST(Future, hasPreconditionValid) {
// Ops that require validity; precondition: valid(); throw NoState if !valid()
#define DOIT(STMT) \
do { \
auto f = makeValid(); \
EXPECT_NO_THROW(STMT); \
copy(std::move(f)); \
EXPECT_THROW(STMT, NoState); \
} while (false)
DOIT(f.isReady());
DOIT(f.result());
DOIT(f.getTry());
DOIT(f.hasValue());
DOIT(f.hasException());
DOIT(f.value());
DOIT(f.poll());
DOIT(f.then());
DOIT(f.then([](auto&&) {}));
#undef DOIT
}
TEST(Future, hasPostconditionValid) {
// Ops that preserve validity -- postcondition: valid()
#define DOIT(STMT) \
do { \
auto f = makeValid(); \
EXPECT_NO_THROW(STMT); \
EXPECT_TRUE(f.valid()); \
} while (false)
auto const swallow = [](auto) {};
DOIT(swallow(f.valid())); // f.valid() itself preserves validity
DOIT(swallow(f.isReady()));
DOIT(swallow(f.hasValue()));
DOIT(swallow(f.hasException()));
DOIT(swallow(f.value()));
DOIT(swallow(f.getTry()));
DOIT(swallow(f.poll()));
DOIT(f.raise(std::logic_error("foo")));
DOIT(f.cancel());
DOIT(swallow(f.get()));
DOIT(swallow(f.getTry()));
DOIT(f.wait());
DOIT(std::move(f.wait()));
#undef DOIT
}
TEST(Future, hasPostconditionInvalid) {
// Ops that consume *this -- postcondition: !valid()
#define DOIT(CTOR, STMT) \
do { \
auto f = (CTOR); \
EXPECT_NO_THROW(STMT); \
EXPECT_FALSE(f.valid()); \
} while (false)
// move-ctor of {valid|invalid}
DOIT(makeValid(), { auto other{std::move(f)}; });
DOIT(makeInvalid(), { auto other{std::move(f)}; });
// move-assignment of {valid|invalid} into {valid|invalid}
DOIT(makeValid(), {
auto other = makeValid();
other = std::move(f);
});
DOIT(makeValid(), {
auto other = makeInvalid();
other = std::move(f);
});
DOIT(makeInvalid(), {
auto other = makeValid();
other = std::move(f);
});
DOIT(makeInvalid(), {
auto other = makeInvalid();
other = std::move(f);
});
// pass-by-value of {valid|invalid}
DOIT(makeValid(), {
auto const byval = [](auto) {};
byval(std::move(f));
});
DOIT(makeInvalid(), {
auto const byval = [](auto) {};
byval(std::move(f));
});
// other consuming ops
auto const swallow = [](auto) {};
DOIT(makeValid(), swallow(std::move(f).wait()));
DOIT(makeValid(), swallow(std::move(f.wait())));
DOIT(makeValid(), swallow(f.semi()));
#undef DOIT
}
namespace { namespace {
Future<int> onErrorHelperEggs(const eggs_t&) { Future<int> onErrorHelperEggs(const eggs_t&) {
return makeFuture(10); return makeFuture(10);
......
...@@ -55,6 +55,181 @@ TEST(Promise, setValueUnit) { ...@@ -55,6 +55,181 @@ TEST(Promise, setValueUnit) {
p.setValue(); 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 NoState 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 NoState if !valid()
#define DOIT(STMT) \
do { \
auto p = makeValid(); \
EXPECT_NO_THROW(STMT); \
copy(std::move(p)); \
EXPECT_THROW(STMT, NoState); \
} 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) { TEST(Promise, setValueSemiFuture) {
Promise<int> fund; Promise<int> fund;
auto ffund = fund.getSemiFuture(); auto ffund = fund.getSemiFuture();
......
...@@ -55,6 +55,202 @@ TEST(SemiFuture, makeSemiFutureWithUnit) { ...@@ -55,6 +55,202 @@ TEST(SemiFuture, makeSemiFutureWithUnit) {
EXPECT_EQ(1, count); EXPECT_EQ(1, count);
} }
namespace {
auto makeValid() {
auto valid = makeSemiFuture<int>(42);
EXPECT_TRUE(valid.valid());
return valid;
}
auto makeInvalid() {
auto invalid = SemiFuture<int>::makeEmpty();
EXPECT_FALSE(invalid.valid());
return invalid;
}
} // namespace
TEST(SemiFuture, ctorPostconditionValid) {
// Ctors/factories that promise valid -- postcondition: valid()
#define DOIT(CREATION_EXPR) \
do { \
auto f1 = (CREATION_EXPR); \
EXPECT_TRUE(f1.valid()); \
auto f2 = std::move(f1); \
EXPECT_FALSE(f1.valid()); \
EXPECT_TRUE(f2.valid()); \
} while (false)
auto const except = std::logic_error("foo");
auto const ewrap = folly::exception_wrapper(except);
DOIT(makeValid());
DOIT(SemiFuture<int>(42));
DOIT(SemiFuture<int>{42});
DOIT(SemiFuture<Unit>());
DOIT(SemiFuture<Unit>{});
DOIT(makeSemiFuture());
DOIT(makeSemiFuture(Unit{}));
DOIT(makeSemiFuture<Unit>(Unit{}));
DOIT(makeSemiFuture(42));
DOIT(makeSemiFuture<int>(42));
DOIT(makeSemiFuture<int>(except));
DOIT(makeSemiFuture<int>(ewrap));
DOIT(makeSemiFuture(Try<int>(42)));
DOIT(makeSemiFuture<int>(Try<int>(42)));
DOIT(makeSemiFuture<int>(Try<int>(ewrap)));
#undef DOIT
}
TEST(SemiFuture, ctorPostconditionInvalid) {
// Ctors/factories that promise invalid -- postcondition: !valid()
#define DOIT(CREATION_EXPR) \
do { \
auto f1 = (CREATION_EXPR); \
EXPECT_FALSE(f1.valid()); \
auto f2 = std::move(f1); \
EXPECT_FALSE(f1.valid()); \
EXPECT_FALSE(f2.valid()); \
} while (false)
DOIT(makeInvalid());
DOIT(SemiFuture<int>::makeEmpty());
#undef DOIT
}
TEST(SemiFuture, lacksPreconditionValid) {
// Ops that don't throw NoState if !valid() -- without precondition: valid()
#define DOIT(STMT) \
do { \
auto f = makeValid(); \
{ STMT; } \
copy(std::move(f)); \
EXPECT_NO_THROW(STMT); \
} while (false)
// .valid() itself
DOIT(f.valid());
// move-ctor - move-copy to local, copy(), pass-by-move-value
DOIT(auto other = std::move(f));
DOIT(copy(std::move(f)));
DOIT(([](auto) {})(std::move(f)));
// move-assignment into either {valid | invalid}
DOIT({
auto other = makeValid();
other = std::move(f);
});
DOIT({
auto other = makeInvalid();
other = std::move(f);
});
#undef DOIT
}
TEST(SemiFuture, hasPreconditionValid) {
// Ops that require validity; precondition: valid(); throw NoState if !valid()
#define DOIT(STMT) \
do { \
auto f = makeValid(); \
EXPECT_NO_THROW(STMT); \
copy(std::move(f)); \
EXPECT_THROW(STMT, NoState); \
} while (false)
DOIT(f.isReady());
DOIT(f.result());
DOIT(std::move(f).getTry());
DOIT(f.hasValue());
DOIT(f.hasException());
DOIT(f.value());
#undef DOIT
}
TEST(SemiFuture, hasPostconditionValid) {
// Ops that preserve validity -- postcondition: valid()
#define DOIT(STMT) \
do { \
auto f = makeValid(); \
EXPECT_NO_THROW(STMT); \
EXPECT_TRUE(f.valid()); \
} while (false)
auto const swallow = [](auto) {};
DOIT(swallow(f.valid())); // f.valid() itself preserves validity
DOIT(swallow(f.isReady()));
DOIT(swallow(f.hasValue()));
DOIT(swallow(f.hasException()));
DOIT(swallow(f.value()));
DOIT(swallow(f.poll()));
DOIT(f.raise(std::logic_error("foo")));
DOIT(f.cancel());
DOIT(f.wait());
DOIT(std::move(f.wait()));
#undef DOIT
}
TEST(SemiFuture, hasPostconditionInvalid) {
// Ops that consume *this -- postcondition: !valid()
#define DOIT(CTOR, STMT) \
do { \
auto f = (CTOR); \
EXPECT_NO_THROW(STMT); \
EXPECT_FALSE(f.valid()); \
} while (false)
// move-ctor of {valid|invalid}
DOIT(makeValid(), { auto other{std::move(f)}; });
DOIT(makeInvalid(), { auto other{std::move(f)}; });
// move-assignment of {valid|invalid} into {valid|invalid}
DOIT(makeValid(), {
auto other = makeValid();
other = std::move(f);
});
DOIT(makeValid(), {
auto other = makeInvalid();
other = std::move(f);
});
DOIT(makeInvalid(), {
auto other = makeValid();
other = std::move(f);
});
DOIT(makeInvalid(), {
auto other = makeInvalid();
other = std::move(f);
});
// pass-by-value of {valid|invalid}
DOIT(makeValid(), {
auto const byval = [](auto) {};
byval(std::move(f));
});
DOIT(makeInvalid(), {
auto const byval = [](auto) {};
byval(std::move(f));
});
// other consuming ops
auto const swallow = [](auto) {};
DOIT(makeValid(), swallow(std::move(f).get()));
DOIT(makeValid(), swallow(std::move(f).getTry()));
DOIT(makeValid(), swallow(std::move(f).wait()));
DOIT(makeValid(), swallow(std::move(f.wait())));
#undef DOIT
}
namespace { namespace {
SemiFuture<int> onErrorHelperEggs(const eggs_t&) { SemiFuture<int> onErrorHelperEggs(const eggs_t&) {
return makeSemiFuture(10); return makeSemiFuture(10);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment