X-Git-Url: http://plrg.eecs.uci.edu/git/?a=blobdiff_plain;f=folly%2Ffutures%2Ftest%2FFutureTest.cpp;h=3322d2f4af52b5d40b028f99c2f5bbe138b0ddd2;hb=49f2f8b170dfee4871a84c57d6e149623126a2d3;hp=48358e16470e7793c2854ceac9efb16ca73915c5;hpb=275ca94d04e44f28cfa411668eb1c1dd8db90b80;p=folly.git diff --git a/folly/futures/test/FutureTest.cpp b/folly/futures/test/FutureTest.cpp index 48358e16..3322d2f4 100644 --- a/folly/futures/test/FutureTest.cpp +++ b/folly/futures/test/FutureTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2015 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,84 +14,76 @@ * limitations under the License. */ +#include +#include +#include +#include +#include +#include +#include + #include #include -#include -#include #include +#include #include #include #include -#include -#include -#include -#include -#include -#include -#include - -#include -#include using namespace folly; -using std::pair; -using std::string; -using std::unique_ptr; -using std::vector; -using std::chrono::milliseconds; #define EXPECT_TYPE(x, T) \ EXPECT_TRUE((std::is_same::value)) -/// Simple executor that does work in another thread -class ThreadExecutor : public Executor { - folly::MPMCQueue funcs; - std::atomic done {false}; - std::thread worker; - folly::Baton<> baton; - - void work() { - baton.post(); - Func fn; - while (!done) { - while (!funcs.isEmpty()) { - funcs.blockingRead(fn); - fn(); - } - } - } +typedef FutureException eggs_t; +static eggs_t eggs("eggs"); - public: - explicit ThreadExecutor(size_t n = 1024) - : funcs(n), worker(std::bind(&ThreadExecutor::work, this)) {} +// Future - ~ThreadExecutor() { - done = true; - funcs.write([]{}); - worker.join(); - } +TEST(Future, futureDefaultCtor) { + Future(); +} - void add(Func fn) override { - funcs.blockingWrite(std::move(fn)); - } +TEST(Future, futureToUnit) { + Future fu = makeFuture(42).unit(); + fu.value(); + EXPECT_TRUE(makeFuture(eggs).unit().hasException()); +} - void waitForStartup() { - baton.wait(); - } -}; +TEST(Future, voidFutureToUnit) { + Future fu = makeFuture().unit(); + fu.value(); + EXPECT_TRUE(makeFuture(eggs).unit().hasException()); +} -typedef FutureException eggs_t; -static eggs_t eggs("eggs"); +TEST(Future, unitFutureToUnitIdentity) { + Future fu = makeFuture(Unit{}).unit(); + fu.value(); + EXPECT_TRUE(makeFuture(eggs).unit().hasException()); +} -// Core +TEST(Future, toUnitWhileInProgress) { + Promise p; + Future fu = p.getFuture().unit(); + EXPECT_FALSE(fu.isReady()); + p.setValue(42); + EXPECT_TRUE(fu.isReady()); +} -TEST(Future, coreSize) { - // If this number goes down, it's fine! - // If it goes up, please seek professional advice ;-) - EXPECT_EQ(128, sizeof(detail::Core)); +TEST(Future, makeFutureWithUnit) { + int count = 0; + Future fu = makeFutureWith([&] { count++; }); + EXPECT_EQ(1, count); } -// Future +namespace { +Future onErrorHelperEggs(const eggs_t&) { + return makeFuture(10); +} +Future onErrorHelperGeneric(const std::exception&) { + return makeFuture(20); +} +} TEST(Future, onError) { bool theFlag = false; @@ -110,173 +102,266 @@ TEST(Future, onError) { // By reference { - auto f = makeFuture() - .then([] { throw eggs; }) - .onError([&] (eggs_t& e) { flag(); }); + auto f = makeFuture().then([] { + throw eggs; + }).onError([&](eggs_t& /* e */) { flag(); }); EXPECT_FLAG(); EXPECT_NO_THROW(f.value()); } { auto f = makeFuture() - .then([] { throw eggs; }) - .onError([&] (eggs_t& e) { flag(); return makeFuture(); }); + .then([] { throw eggs; }) + .onError([&](eggs_t& /* e */) { + flag(); + return makeFuture(); + }); EXPECT_FLAG(); EXPECT_NO_THROW(f.value()); } // By value { - auto f = makeFuture() - .then([] { throw eggs; }) - .onError([&] (eggs_t e) { flag(); }); + auto f = makeFuture().then([] { + throw eggs; + }).onError([&](eggs_t /* e */) { flag(); }); EXPECT_FLAG(); EXPECT_NO_THROW(f.value()); } { auto f = makeFuture() - .then([] { throw eggs; }) - .onError([&] (eggs_t e) { flag(); return makeFuture(); }); + .then([] { throw eggs; }) + .onError([&](eggs_t /* e */) { + flag(); + return makeFuture(); + }); EXPECT_FLAG(); EXPECT_NO_THROW(f.value()); } // Polymorphic { - auto f = makeFuture() - .then([] { throw eggs; }) - .onError([&] (std::exception& e) { flag(); }); + auto f = makeFuture().then([] { + throw eggs; + }).onError([&](std::exception& /* e */) { flag(); }); EXPECT_FLAG(); EXPECT_NO_THROW(f.value()); } { auto f = makeFuture() - .then([] { throw eggs; }) - .onError([&] (std::exception& e) { flag(); return makeFuture(); }); + .then([] { throw eggs; }) + .onError([&](std::exception& /* e */) { + flag(); + return makeFuture(); + }); EXPECT_FLAG(); EXPECT_NO_THROW(f.value()); } // Non-exceptions { - auto f = makeFuture() - .then([] { throw -1; }) - .onError([&] (int e) { flag(); }); + auto f = makeFuture().then([] { + throw - 1; + }).onError([&](int /* e */) { flag(); }); EXPECT_FLAG(); EXPECT_NO_THROW(f.value()); } { auto f = makeFuture() - .then([] { throw -1; }) - .onError([&] (int e) { flag(); return makeFuture(); }); + .then([] { throw - 1; }) + .onError([&](int /* e */) { + flag(); + return makeFuture(); + }); EXPECT_FLAG(); EXPECT_NO_THROW(f.value()); } // Mutable lambda { - auto f = makeFuture() - .then([] { throw eggs; }) - .onError([&] (eggs_t& e) mutable { flag(); }); + auto f = makeFuture().then([] { + throw eggs; + }).onError([&](eggs_t& /* e */) mutable { flag(); }); EXPECT_FLAG(); EXPECT_NO_THROW(f.value()); } { auto f = makeFuture() - .then([] { throw eggs; }) - .onError([&] (eggs_t& e) mutable { flag(); return makeFuture(); }); + .then([] { throw eggs; }) + .onError([&](eggs_t& /* e */) mutable { + flag(); + return makeFuture(); + }); EXPECT_FLAG(); EXPECT_NO_THROW(f.value()); } + // Function pointer + { + auto f = makeFuture() + .then([]() -> int { throw eggs; }) + .onError(onErrorHelperEggs) + .onError(onErrorHelperGeneric); + EXPECT_EQ(10, f.value()); + } + { + auto f = makeFuture() + .then([]() -> int { throw std::runtime_error("test"); }) + .onError(onErrorHelperEggs) + .onError(onErrorHelperGeneric); + EXPECT_EQ(20, f.value()); + } + { + auto f = makeFuture() + .then([]() -> int { throw std::runtime_error("test"); }) + .onError(onErrorHelperEggs); + EXPECT_THROW(f.value(), std::runtime_error); + } + // No throw { auto f = makeFuture() - .then([] { return 42; }) - .onError([&] (eggs_t& e) { flag(); return -1; }); + .then([] { return 42; }) + .onError([&](eggs_t& /* e */) { + flag(); + return -1; + }); EXPECT_NO_FLAG(); EXPECT_EQ(42, f.value()); } { auto f = makeFuture() - .then([] { return 42; }) - .onError([&] (eggs_t& e) { flag(); return makeFuture(-1); }); + .then([] { return 42; }) + .onError([&](eggs_t& /* e */) { + flag(); + return makeFuture(-1); + }); EXPECT_NO_FLAG(); EXPECT_EQ(42, f.value()); } // Catch different exception { - auto f = makeFuture() - .then([] { throw eggs; }) - .onError([&] (std::runtime_error& e) { flag(); }); + auto f = makeFuture().then([] { + throw eggs; + }).onError([&](std::runtime_error& /* e */) { flag(); }); EXPECT_NO_FLAG(); EXPECT_THROW(f.value(), eggs_t); } { auto f = makeFuture() - .then([] { throw eggs; }) - .onError([&] (std::runtime_error& e) { flag(); return makeFuture(); }); + .then([] { throw eggs; }) + .onError([&](std::runtime_error& /* e */) { + flag(); + return makeFuture(); + }); EXPECT_NO_FLAG(); EXPECT_THROW(f.value(), eggs_t); } // Returned value propagates { - auto f = makeFuture() - .then([] { throw eggs; return 0; }) - .onError([&] (eggs_t& e) { return 42; }); + auto f = makeFuture().then([]() -> int { + throw eggs; + }).onError([&](eggs_t& /* e */) { return 42; }); EXPECT_EQ(42, f.value()); } // Returned future propagates { - auto f = makeFuture() - .then([] { throw eggs; return 0; }) - .onError([&] (eggs_t& e) { return makeFuture(42); }); + auto f = makeFuture().then([]() -> int { + throw eggs; + }).onError([&](eggs_t& /* e */) { return makeFuture(42); }); EXPECT_EQ(42, f.value()); } // Throw in callback { auto f = makeFuture() - .then([] { throw eggs; return 0; }) - .onError([&] (eggs_t& e) { throw e; return -1; }); + .then([]() -> int { throw eggs; }) + .onError([&] (eggs_t& e) -> int { throw e; }); EXPECT_THROW(f.value(), eggs_t); } { auto f = makeFuture() - .then([] { throw eggs; return 0; }) - .onError([&] (eggs_t& e) { throw e; return makeFuture(-1); }); + .then([]() -> int { throw eggs; }) + .onError([&] (eggs_t& e) -> Future { throw e; }); EXPECT_THROW(f.value(), eggs_t); } -} -TEST(Future, try) { - class A { - public: - A(int x) : x_(x) {} + // exception_wrapper, return Future + { + auto f = makeFuture() + .then([] { throw eggs; }) + .onError([&](exception_wrapper /* e */) { + flag(); + return makeFuture(); + }); + EXPECT_FLAG(); + EXPECT_NO_THROW(f.value()); + } - int x() const { - return x_; - } - private: - int x_; - }; + // exception_wrapper, return Future but throw + { + auto f = makeFuture() + .then([]() -> int { + throw eggs; + }) + .onError([&](exception_wrapper /* e */) -> Future { + flag(); + throw eggs; + }); + EXPECT_FLAG(); + EXPECT_THROW(f.value(), eggs_t); + } - A a(5); - Try t_a(std::move(a)); + // exception_wrapper, return T + { + auto f = makeFuture() + .then([]() -> int { + throw eggs; + }) + .onError([&](exception_wrapper /* e */) { + flag(); + return -1; + }); + EXPECT_FLAG(); + EXPECT_EQ(-1, f.value()); + } + + // exception_wrapper, return T but throw + { + auto f = makeFuture() + .then([]() -> int { + throw eggs; + }) + .onError([&](exception_wrapper /* e */) -> int { + flag(); + throw eggs; + }); + EXPECT_FLAG(); + EXPECT_THROW(f.value(), eggs_t); + } - Try t_void; + // const exception_wrapper& + { + auto f = makeFuture() + .then([] { throw eggs; }) + .onError([&](const exception_wrapper& /* e */) { + flag(); + return makeFuture(); + }); + EXPECT_FLAG(); + EXPECT_NO_THROW(f.value()); + } - EXPECT_EQ(5, t_a.value().x()); } TEST(Future, special) { @@ -287,20 +372,33 @@ TEST(Future, special) { } TEST(Future, then) { - auto f = makeFuture("0") - .then([](){ return makeFuture("1"); }) - .then([](Try&& t) { return makeFuture(t.value() + ";2"); }) - .then([](const Try&& t) { return makeFuture(t.value() + ";3"); }) - .then([](Try& t) { return makeFuture(t.value() + ";4"); }) - .then([](const Try& t) { return makeFuture(t.value() + ";5"); }) - .then([](Try t) { return makeFuture(t.value() + ";6"); }) - .then([](const Try t) { return makeFuture(t.value() + ";7"); }) - .then([](string&& s) { return makeFuture(s + ";8"); }) - .then([](const string&& s) { return makeFuture(s + ";9"); }) - .then([](string& s) { return makeFuture(s + ";10"); }) - .then([](const string& s) { return makeFuture(s + ";11"); }) - .then([](string s) { return makeFuture(s + ";12"); }) - .then([](const string s) { return makeFuture(s + ";13"); }) + auto f = makeFuture("0") + .then([](){ + return makeFuture("1"); }) + .then([](Try&& t) { + return makeFuture(t.value() + ";2"); }) + .then([](const Try&& t) { + return makeFuture(t.value() + ";3"); }) + .then([](Try& t) { + return makeFuture(t.value() + ";4"); }) + .then([](const Try& t) { + return makeFuture(t.value() + ";5"); }) + .then([](Try t) { + return makeFuture(t.value() + ";6"); }) + .then([](const Try t) { + return makeFuture(t.value() + ";7"); }) + .then([](std::string&& s) { + return makeFuture(s + ";8"); }) + .then([](const std::string&& s) { + return makeFuture(s + ";9"); }) + .then([](std::string& s) { + return makeFuture(s + ";10"); }) + .then([](const std::string& s) { + return makeFuture(s + ";11"); }) + .then([](std::string s) { + return makeFuture(s + ";12"); }) + .then([](const std::string s) { + return makeFuture(s + ";13"); }) ; EXPECT_EQ(f.value(), "1;2;3;4;5;6;7;8;9;10;11;12;13"); } @@ -319,11 +417,11 @@ TEST(Future, thenTry) { .then([&](Try&& t) { flag = true; EXPECT_EQ(42, t.value()); }); EXPECT_TRUE(flag); flag = false; - makeFuture().then([&](Try&& t) { flag = true; t.value(); }); + makeFuture().then([&](Try&& t) { flag = true; t.value(); }); EXPECT_TRUE(flag); flag = false; - Promise p; - auto f = p.getFuture().then([&](Try&& t) { flag = true; }); + Promise p; + auto f = p.getFuture().then([&](Try&& /* t */) { flag = true; }); EXPECT_FALSE(flag); EXPECT_FALSE(f.isReady()); p.setValue(); @@ -349,10 +447,10 @@ TEST(Future, thenValue) { }); EXPECT_TRUE(flag); flag = false; - auto f = makeFuture(eggs).then([&](int i){}); + auto f = makeFuture(eggs).then([&](int /* i */) {}); EXPECT_THROW(f.value(), eggs_t); - f = makeFuture(eggs).then([&]{}); + f = makeFuture(eggs).then([&]{}); EXPECT_THROW(f.value(), eggs_t); } @@ -363,27 +461,27 @@ TEST(Future, thenValueFuture) { .then([&](Try&& t) { flag = true; EXPECT_EQ(42, t.value()); }); EXPECT_TRUE(flag); flag = false; - makeFuture() - .then([]{ return makeFuture(); }) - .then([&](Try&& t) { flag = true; }); + makeFuture().then([] { + return makeFuture(); + }).then([&](Try&& /* t */) { flag = true; }); EXPECT_TRUE(flag); flag = false; } -static string doWorkStatic(Try&& t) { +static std::string doWorkStatic(Try&& t) { return t.value() + ";static"; } TEST(Future, thenFunction) { struct Worker { - string doWork(Try&& t) { + std::string doWork(Try&& t) { return t.value() + ";class"; } - static string doWorkStatic(Try&& t) { + static std::string doWorkStatic(Try&& t) { return t.value() + ";class-static"; } } w; - auto f = makeFuture("start") + auto f = makeFuture("start") .then(doWorkStatic) .then(Worker::doWorkStatic) .then(&Worker::doWork, &w); @@ -391,21 +489,21 @@ TEST(Future, thenFunction) { EXPECT_EQ(f.value(), "start;static;class-static;class"); } -static Future doWorkStaticFuture(Try&& t) { +static Future doWorkStaticFuture(Try&& t) { return makeFuture(t.value() + ";static"); } TEST(Future, thenFunctionFuture) { struct Worker { - Future doWorkFuture(Try&& t) { + Future doWorkFuture(Try&& t) { return makeFuture(t.value() + ";class"); } - static Future doWorkStaticFuture(Try&& t) { + static Future doWorkStaticFuture(Try&& t) { return makeFuture(t.value() + ";class-static"); } } w; - auto f = makeFuture("start") + auto f = makeFuture("start") .then(doWorkStaticFuture) .then(Worker::doWorkStaticFuture) .then(&Worker::doWorkFuture, &w); @@ -413,6 +511,31 @@ TEST(Future, thenFunctionFuture) { EXPECT_EQ(f.value(), "start;static;class-static;class"); } +TEST(Future, thenStdFunction) { + { + std::function fn = [](){ return 42; }; + auto f = makeFuture().then(std::move(fn)); + EXPECT_EQ(f.value(), 42); + } + { + std::function fn = [](int i){ return i + 23; }; + auto f = makeFuture(19).then(std::move(fn)); + EXPECT_EQ(f.value(), 42); + } + { + std::function&)> fn = [](Try& t){ return t.value() + 2; }; + auto f = makeFuture(1).then(std::move(fn)); + EXPECT_EQ(f.value(), 3); + } + { + bool flag = false; + std::function fn = [&flag](){ flag = true; }; + auto f = makeFuture().then(std::move(fn)); + EXPECT_TRUE(f.isReady()); + EXPECT_TRUE(flag); + } +} + TEST(Future, thenBind) { auto l = []() { return makeFuture("bind"); @@ -423,17 +546,17 @@ TEST(Future, thenBind) { } TEST(Future, thenBindTry) { - auto l = [](Try&& t) { + auto l = [](Try&& t) { return makeFuture(t.value() + ";bind"); }; auto b = std::bind(l, std::placeholders::_1); - auto f = makeFuture("start").then(std::move(b)); + auto f = makeFuture("start").then(std::move(b)); EXPECT_EQ(f.value(), "start;bind"); } TEST(Future, value) { - auto f = makeFuture(unique_ptr(new int(42))); + auto f = makeFuture(std::unique_ptr(new int(42))); auto up = std::move(f.value()); EXPECT_EQ(42, *up); @@ -472,120 +595,76 @@ TEST(Future, makeFuture) { EXPECT_EQ(42, makeFuture(42).value()); auto fun = [] { return 42; }; - EXPECT_TYPE(makeFutureTry(fun), Future); - EXPECT_EQ(42, makeFutureTry(fun).value()); + EXPECT_TYPE(makeFutureWith(fun), Future); + EXPECT_EQ(42, makeFutureWith(fun).value()); - auto failfun = []() -> int { throw eggs; }; - EXPECT_TYPE(makeFutureTry(failfun), Future); - EXPECT_THROW(makeFutureTry(failfun).value(), eggs_t); + auto funf = [] { return makeFuture(43); }; + EXPECT_TYPE(makeFutureWith(funf), Future); + EXPECT_EQ(43, makeFutureWith(funf).value()); - EXPECT_TYPE(makeFuture(), Future); -} + auto failfun = []() -> int { throw eggs; }; + EXPECT_TYPE(makeFutureWith(failfun), Future); + EXPECT_NO_THROW(makeFutureWith(failfun)); + EXPECT_THROW(makeFutureWith(failfun).value(), eggs_t); -// Promise + auto failfunf = []() -> Future { throw eggs; }; + EXPECT_TYPE(makeFutureWith(failfunf), Future); + EXPECT_NO_THROW(makeFutureWith(failfunf)); + EXPECT_THROW(makeFutureWith(failfunf).value(), eggs_t); -TEST(Promise, special) { - EXPECT_FALSE(std::is_copy_constructible>::value); - EXPECT_FALSE(std::is_copy_assignable>::value); - EXPECT_TRUE(std::is_move_constructible>::value); - EXPECT_TRUE(std::is_move_assignable>::value); + EXPECT_TYPE(makeFuture(), Future); } -TEST(Promise, getFuture) { +TEST(Future, finish) { + auto x = std::make_shared(0); + Promise p; - Future f = p.getFuture(); - EXPECT_FALSE(f.isReady()); -} + auto f = p.getFuture().then([x](Try&& t) { *x = t.value(); }); -TEST(Promise, setValue) { - Promise fund; - auto ffund = fund.getFuture(); - fund.setValue(42); - EXPECT_EQ(42, ffund.value()); + // The callback hasn't executed + EXPECT_EQ(0, *x); - struct Foo { - string name; - int value; - }; + // The callback has a reference to x + EXPECT_EQ(2, x.use_count()); - Promise 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(); - 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> mov; - auto fmov = mov.getFuture(); - mov.setValue(unique_ptr(new int(42))); - unique_ptr ptr = std::move(fmov.value()); - EXPECT_EQ(42, *ptr); - - Promise v; - auto fv = v.getFuture(); - v.setValue(); - EXPECT_TRUE(fv.isReady()); -} + p.setValue(42); -TEST(Promise, setException) { - { - Promise p; - auto f = p.getFuture(); - p.setException(eggs); - EXPECT_THROW(f.value(), eggs_t); - } - { - Promise p; - auto f = p.getFuture(); - try { - throw eggs; - } catch (...) { - p.setException(exception_wrapper(std::current_exception())); - } - EXPECT_THROW(f.value(), eggs_t); - } -} + // the callback has executed + EXPECT_EQ(42, *x); -TEST(Promise, fulfil) { - { - Promise p; - auto f = p.getFuture(); - p.fulfil([] { return 42; }); - EXPECT_EQ(42, f.value()); - } - { - Promise p; - auto f = p.getFuture(); - p.fulfil([]() -> int { throw eggs; }); - EXPECT_THROW(f.value(), eggs_t); - } + // the callback has been destructed + // and has released its reference to x + EXPECT_EQ(1, x.use_count()); } -TEST(Future, finish) { +TEST(Future, finishBigLambda) { auto x = std::make_shared(0); - { - Promise p; - auto f = p.getFuture().then([x](Try&& t) { *x = t.value(); }); - // The callback hasn't executed - EXPECT_EQ(0, *x); + // bulk_data, to be captured in the lambda passed to Future::then. + // This is meant to force that the lambda can't be stored inside + // the Future object. + std::array)> bulk_data = {{0}}; - // The callback has a reference to x - EXPECT_EQ(2, x.use_count()); + // suppress gcc warning about bulk_data not being used + EXPECT_EQ(bulk_data[0], 0); - p.setValue(42); + Promise p; + auto f = p.getFuture().then([x, bulk_data](Try&& t) { + (void)bulk_data; + *x = t.value(); + }); + + // The callback hasn't executed + EXPECT_EQ(0, *x); + + // The callback has a reference to x + EXPECT_EQ(2, x.use_count()); + + p.setValue(42); + + // the callback has executed + EXPECT_EQ(42, *x); - // the callback has executed - EXPECT_EQ(42, *x); - } // the callback has been destructed // and has released its reference to x EXPECT_EQ(1, x.use_count()); @@ -626,289 +705,21 @@ TEST(Future, unwrap) { EXPECT_EQ(7, f.value()); } -TEST(Future, whenAll) { - // returns a vector variant - { - vector> promises(10); - vector> futures; - - for (auto& p : promises) - futures.push_back(p.getFuture()); - - auto allf = whenAll(futures.begin(), futures.end()); - - random_shuffle(promises.begin(), promises.end()); - for (auto& p : promises) { - EXPECT_FALSE(allf.isReady()); - p.setValue(42); - } - - EXPECT_TRUE(allf.isReady()); - auto& results = allf.value(); - for (auto& t : results) { - EXPECT_EQ(42, t.value()); - } - } - - // check error semantics - { - vector> promises(4); - vector> futures; - - for (auto& p : promises) - futures.push_back(p.getFuture()); - - auto allf = whenAll(futures.begin(), futures.end()); - - - promises[0].setValue(42); - promises[1].setException(eggs); - - EXPECT_FALSE(allf.isReady()); - - promises[2].setValue(42); - - EXPECT_FALSE(allf.isReady()); - - promises[3].setException(eggs); - - EXPECT_TRUE(allf.isReady()); - EXPECT_FALSE(allf.getTry().hasException()); - - auto& results = allf.value(); - EXPECT_EQ(42, results[0].value()); - EXPECT_TRUE(results[1].hasException()); - EXPECT_EQ(42, results[2].value()); - EXPECT_TRUE(results[3].hasException()); - } - - // check that futures are ready in then() - { - vector> promises(10); - vector> futures; - - for (auto& p : promises) - futures.push_back(p.getFuture()); - - auto allf = whenAll(futures.begin(), futures.end()) - .then([](Try>>&& ts) { - for (auto& f : ts.value()) - f.value(); - }); - - random_shuffle(promises.begin(), promises.end()); - for (auto& p : promises) - p.setValue(); - EXPECT_TRUE(allf.isReady()); - } -} - - -TEST(Future, whenAny) { - { - vector> promises(10); - vector> futures; - - for (auto& p : promises) - futures.push_back(p.getFuture()); - - for (auto& f : futures) { - EXPECT_FALSE(f.isReady()); - } - - auto anyf = whenAny(futures.begin(), futures.end()); - - /* futures were moved in, so these are invalid now */ - EXPECT_FALSE(anyf.isReady()); - - promises[7].setValue(42); - EXPECT_TRUE(anyf.isReady()); - auto& idx_fut = anyf.value(); - - auto i = idx_fut.first; - EXPECT_EQ(7, i); - - auto& f = idx_fut.second; - EXPECT_EQ(42, f.value()); - } - - // error - { - vector> promises(10); - vector> futures; - - for (auto& p : promises) - futures.push_back(p.getFuture()); - - for (auto& f : futures) { - EXPECT_FALSE(f.isReady()); - } - - auto anyf = whenAny(futures.begin(), futures.end()); - - EXPECT_FALSE(anyf.isReady()); - - promises[3].setException(eggs); - EXPECT_TRUE(anyf.isReady()); - EXPECT_TRUE(anyf.value().second.hasException()); - } - - // then() - { - vector> promises(10); - vector> futures; - - for (auto& p : promises) - futures.push_back(p.getFuture()); - - auto anyf = whenAny(futures.begin(), futures.end()) - .then([](pair> p) { - EXPECT_EQ(42, p.second.value()); - }); - - promises[3].setValue(42); - EXPECT_TRUE(anyf.isReady()); - } -} - - -TEST(when, already_completed) { - { - vector> fs; - for (int i = 0; i < 10; i++) - fs.push_back(makeFuture()); - - whenAll(fs.begin(), fs.end()) - .then([&](vector> ts) { - EXPECT_EQ(fs.size(), ts.size()); - }); - } - { - vector> fs; - for (int i = 0; i < 10; i++) - fs.push_back(makeFuture(i)); - - whenAny(fs.begin(), fs.end()) - .then([&](pair> p) { - EXPECT_EQ(p.first, p.second.value()); - }); - } -} - -TEST(when, whenN) { - vector> promises(10); - vector> futures; - - for (auto& p : promises) - futures.push_back(p.getFuture()); - - bool flag = false; - size_t n = 3; - whenN(futures.begin(), futures.end(), n) - .then([&](vector>> v) { - flag = true; - EXPECT_EQ(n, v.size()); - for (auto& tt : v) - EXPECT_TRUE(tt.second.hasValue()); - }); - - promises[0].setValue(); - EXPECT_FALSE(flag); - promises[1].setValue(); - EXPECT_FALSE(flag); - promises[2].setValue(); - EXPECT_TRUE(flag); -} - -/* Ensure that we can compile when_{all,any} with folly::small_vector */ -TEST(when, small_vector) { - - static_assert(!FOLLY_IS_TRIVIALLY_COPYABLE(Future), - "Futures should not be trivially copyable"); - static_assert(!FOLLY_IS_TRIVIALLY_COPYABLE(Future), - "Futures should not be trivially copyable"); - - using folly::small_vector; - { - small_vector> futures; - - for (int i = 0; i < 10; i++) - futures.push_back(makeFuture()); - - auto anyf = whenAny(futures.begin(), futures.end()); - } - - { - small_vector> futures; - - for (int i = 0; i < 10; i++) - futures.push_back(makeFuture()); - - auto allf = whenAll(futures.begin(), futures.end()); - } -} - -TEST(Future, whenAllVariadic) { - Promise pb; - Promise pi; - Future fb = pb.getFuture(); - Future fi = pi.getFuture(); - bool flag = false; - whenAll(std::move(fb), std::move(fi)) - .then([&](std::tuple, Try> tup) { - flag = true; - EXPECT_TRUE(std::get<0>(tup).hasValue()); - EXPECT_EQ(std::get<0>(tup).value(), true); - EXPECT_TRUE(std::get<1>(tup).hasValue()); - EXPECT_EQ(std::get<1>(tup).value(), 42); - }); - pb.setValue(true); - EXPECT_FALSE(flag); - pi.setValue(42); - EXPECT_TRUE(flag); -} - -TEST(Future, whenAllVariadicReferences) { - Promise pb; - Promise pi; - Future fb = pb.getFuture(); - Future fi = pi.getFuture(); - bool flag = false; - whenAll(fb, fi) - .then([&](std::tuple, Try> tup) { - flag = true; - EXPECT_TRUE(std::get<0>(tup).hasValue()); - EXPECT_EQ(std::get<0>(tup).value(), true); - EXPECT_TRUE(std::get<1>(tup).hasValue()); - EXPECT_EQ(std::get<1>(tup).value(), 42); - }); - pb.setValue(true); - EXPECT_FALSE(flag); - pi.setValue(42); - EXPECT_TRUE(flag); -} - -TEST(Future, whenAll_none) { - vector> fs; - auto f = whenAll(fs.begin(), fs.end()); - EXPECT_TRUE(f.isReady()); -} - TEST(Future, throwCaughtInImmediateThen) { // Neither of these should throw "Promise already satisfied" makeFuture().then( - [=](Try&&) -> int { throw std::exception(); }); + [=](Try&&) -> int { throw std::exception(); }); makeFuture().then( - [=](Try&&) -> Future { throw std::exception(); }); + [=](Try&&) -> Future { throw std::exception(); }); } TEST(Future, throwIfFailed) { - makeFuture(eggs) - .then([=](Try&& t) { + makeFuture(eggs) + .then([=](Try&& t) { EXPECT_THROW(t.throwIfFailed(), eggs_t); }); makeFuture() - .then([=](Try&& t) { + .then([=](Try&& t) { EXPECT_NO_THROW(t.throwIfFailed()); }); @@ -922,262 +733,15 @@ TEST(Future, throwIfFailed) { }); } -TEST(Future, waitImmediate) { - makeFuture().wait(); - auto done = makeFuture(42).wait().value(); - EXPECT_EQ(42, done); - - vector v{1,2,3}; - auto done_v = makeFuture(v).wait().value(); - EXPECT_EQ(v.size(), done_v.size()); - EXPECT_EQ(v, done_v); - - vector> v_f; - v_f.push_back(makeFuture()); - v_f.push_back(makeFuture()); - auto done_v_f = whenAll(v_f.begin(), v_f.end()).wait().value(); - EXPECT_EQ(2, done_v_f.size()); - - vector> v_fb; - v_fb.push_back(makeFuture(true)); - v_fb.push_back(makeFuture(false)); - auto fut = whenAll(v_fb.begin(), v_fb.end()); - auto done_v_fb = std::move(fut.wait().value()); - EXPECT_EQ(2, done_v_fb.size()); -} - -TEST(Future, wait) { - Promise p; - Future f = p.getFuture(); - std::atomic flag{false}; - std::atomic result{1}; - std::atomic id; - - std::thread t([&](Future&& tf){ - auto n = tf.then([&](Try && t) { - id = std::this_thread::get_id(); - return t.value(); - }); - flag = true; - result.store(n.wait().value()); - }, - std::move(f) - ); - while(!flag){} - EXPECT_EQ(result.load(), 1); - p.setValue(42); - t.join(); - // validate that the callback ended up executing in this thread, which - // is more to ensure that this test actually tests what it should - EXPECT_EQ(id, std::this_thread::get_id()); - EXPECT_EQ(result.load(), 42); -} - -struct MoveFlag { - MoveFlag() = default; - MoveFlag(const MoveFlag&) = delete; - MoveFlag(MoveFlag&& other) noexcept { - other.moved = true; - } - bool moved{false}; -}; - -TEST(Future, waitReplacesSelf) { - // wait - { - // lvalue - auto f1 = makeFuture(MoveFlag()); - f1.wait(); - EXPECT_FALSE(f1.value().moved); - - // rvalue - auto f2 = makeFuture(MoveFlag()).wait(); - EXPECT_FALSE(f2.value().moved); - } - - // wait(Duration) - { - // lvalue - auto f1 = makeFuture(MoveFlag()); - f1.wait(milliseconds(1)); - EXPECT_FALSE(f1.value().moved); - - // rvalue - auto f2 = makeFuture(MoveFlag()).wait(milliseconds(1)); - EXPECT_FALSE(f2.value().moved); - } - - // waitVia - { - folly::EventBase eb; - // lvalue - auto f1 = makeFuture(MoveFlag()); - f1.waitVia(&eb); - EXPECT_FALSE(f1.value().moved); - - // rvalue - auto f2 = makeFuture(MoveFlag()).waitVia(&eb); - EXPECT_FALSE(f2.value().moved); - } -} - -TEST(Future, waitWithDuration) { - { - Promise p; - Future f = p.getFuture(); - f.wait(milliseconds(1)); - EXPECT_FALSE(f.isReady()); - p.setValue(1); - EXPECT_TRUE(f.isReady()); - } - { - Promise p; - Future f = p.getFuture(); - p.setValue(1); - f.wait(milliseconds(1)); - EXPECT_TRUE(f.isReady()); - } - { - vector> v_fb; - v_fb.push_back(makeFuture(true)); - v_fb.push_back(makeFuture(false)); - auto f = whenAll(v_fb.begin(), v_fb.end()); - f.wait(milliseconds(1)); - EXPECT_TRUE(f.isReady()); - EXPECT_EQ(2, f.value().size()); - } - { - vector> v_fb; - Promise p1; - Promise p2; - v_fb.push_back(p1.getFuture()); - v_fb.push_back(p2.getFuture()); - auto f = whenAll(v_fb.begin(), v_fb.end()); - f.wait(milliseconds(1)); - EXPECT_FALSE(f.isReady()); - p1.setValue(true); - EXPECT_FALSE(f.isReady()); - p2.setValue(true); - EXPECT_TRUE(f.isReady()); - } - { - auto f = makeFuture().wait(milliseconds(1)); - EXPECT_TRUE(f.isReady()); - } - - { - Promise p; - auto start = std::chrono::steady_clock::now(); - auto f = p.getFuture().wait(milliseconds(100)); - auto elapsed = std::chrono::steady_clock::now() - start; - EXPECT_GE(elapsed, milliseconds(100)); - EXPECT_FALSE(f.isReady()); - p.setValue(); - EXPECT_TRUE(f.isReady()); - } - - { - // Try to trigger the race where the resultant Future is not yet complete - // even if we didn't hit the timeout, and make sure we deal with it properly - Promise p; - folly::Baton<> b; - auto t = std::thread([&]{ - b.post(); - /* sleep override */ std::this_thread::sleep_for(milliseconds(100)); - p.setValue(); - }); - b.wait(); - auto f = p.getFuture().wait(std::chrono::seconds(3600)); - EXPECT_TRUE(f.isReady()); - t.join(); - } -} - -class DummyDrivableExecutor : public DrivableExecutor { - public: - void add(Func f) override {} - void drive() override { ran = true; } - bool ran{false}; -}; - -TEST(Future, getVia) { - { - // non-void - ManualExecutor x; - auto f = via(&x).then([]{ return true; }); - EXPECT_TRUE(f.getVia(&x)); - } - - { - // void - ManualExecutor x; - auto f = via(&x).then(); - f.getVia(&x); - } - - { - DummyDrivableExecutor x; - auto f = makeFuture(true); - EXPECT_TRUE(f.getVia(&x)); - EXPECT_FALSE(x.ran); - } -} - -TEST(Future, waitVia) { - { - ManualExecutor x; - auto f = via(&x).then(); - EXPECT_FALSE(f.isReady()); - f.waitVia(&x); - EXPECT_TRUE(f.isReady()); - } - - { - // try rvalue as well - ManualExecutor x; - auto f = via(&x).then().waitVia(&x); - EXPECT_TRUE(f.isReady()); - } - - { - DummyDrivableExecutor x; - makeFuture(true).waitVia(&x); - EXPECT_FALSE(x.ran); - } -} - -TEST(Future, viaRaces) { - ManualExecutor x; - Promise p; - auto tid = std::this_thread::get_id(); - bool done = false; - - std::thread t1([&] { - p.getFuture() - .via(&x) - .then([&](Try&&) { EXPECT_EQ(tid, std::this_thread::get_id()); }) - .then([&](Try&&) { EXPECT_EQ(tid, std::this_thread::get_id()); }) - .then([&](Try&&) { done = true; }); - }); - - std::thread t2([&] { - p.setValue(); - }); - - while (!done) x.run(); - t1.join(); - t2.join(); -} - -TEST(Future, getFuture_after_setValue) { +TEST(Future, getFutureAfterSetValue) { Promise p; p.setValue(42); EXPECT_EQ(42, p.getFuture().value()); } -TEST(Future, getFuture_after_setException) { - Promise p; - p.fulfil([]() -> void { throw std::logic_error("foo"); }); +TEST(Future, getFutureAfterSetException) { + Promise p; + p.setWith([]() -> void { throw std::logic_error("foo"); }); EXPECT_THROW(p.getFuture().value(), std::logic_error); } @@ -1192,8 +756,8 @@ TEST(Future, detachRace) { // slow test so I won't do that but if it ever fails, take it seriously, and // run the test binary with "--gtest_repeat=10000 --gtest_filter=*detachRace" // (Don't forget to enable ASAN) - auto p = folly::make_unique>(); - auto f = folly::make_unique>(p->getFuture()); + auto p = std::make_unique>(); + auto f = std::make_unique>(p->getFuture()); folly::Baton<> baton; std::thread t1([&]{ baton.post(); @@ -1204,72 +768,6 @@ TEST(Future, detachRace) { t1.join(); } -class TestData : public RequestData { - public: - explicit TestData(int data) : data_(data) {} - virtual ~TestData() {} - int data_; -}; - -TEST(Future, context) { - - // Start a new context - RequestContext::create(); - - EXPECT_EQ(nullptr, RequestContext::get()->getContextData("test")); - - // Set some test data - RequestContext::get()->setContextData( - "test", - std::unique_ptr(new TestData(10))); - - // Start a future - Promise p; - auto future = p.getFuture().then([&]{ - // Check that the context followed the future - EXPECT_TRUE(RequestContext::get() != nullptr); - auto a = dynamic_cast( - RequestContext::get()->getContextData("test")); - auto data = a->data_; - EXPECT_EQ(10, data); - }); - - // Clear the context - RequestContext::setContext(nullptr); - - EXPECT_EQ(nullptr, RequestContext::get()->getContextData("test")); - - // Fulfil the promise - p.setValue(); -} - - -// This only fails about 1 in 1k times when the bug is present :( -TEST(Future, t5506504) { - ThreadExecutor x; - - auto fn = [&x]{ - auto promises = std::make_shared>>(4); - vector> futures; - - for (auto& p : *promises) { - futures.emplace_back( - p.getFuture() - .via(&x) - .then([](Try&&){})); - } - - x.waitForStartup(); - x.add([promises]{ - for (auto& p : *promises) p.setValue(); - }); - - return whenAll(futures.begin(), futures.end()); - }; - - fn().wait(); -} - // Test of handling of a circular dependency. It's never recommended // to have one because of possible memory leaks. Here we test that // we can handle freeing of the Future while it is running. @@ -1277,27 +775,25 @@ TEST(Future, CircularDependencySharedPtrSelfReset) { Promise promise; auto ptr = std::make_shared>(promise.getFuture()); - ptr->then( - [ptr] (folly::Try&& uid) mutable { - EXPECT_EQ(1, ptr.use_count()); + ptr->then([ptr](folly::Try&& /* uid */) mutable { + EXPECT_EQ(1, ptr.use_count()); - // Leaving no references to ourselves. - ptr.reset(); - EXPECT_EQ(0, ptr.use_count()); - } - ); + // Leaving no references to ourselves. + ptr.reset(); + EXPECT_EQ(0, ptr.use_count()); + }); EXPECT_EQ(2, ptr.use_count()); ptr.reset(); - promise.fulfil([]{return 1l;}); + promise.setValue(1); } TEST(Future, Constructor) { auto f1 = []() -> Future { return Future(3); }(); EXPECT_EQ(f1.value(), 3); - auto f2 = []() -> Future { return Future(); }(); + auto f2 = []() -> Future { return Future(); }(); EXPECT_NO_THROW(f2.value()); } @@ -1306,26 +802,127 @@ TEST(Future, ImplicitConstructor) { EXPECT_EQ(f1.value(), 3); // Unfortunately, the C++ standard does not allow the // following implicit conversion to work: - //auto f2 = []() -> Future { }(); + //auto f2 = []() -> Future { }(); } -TEST(Future, via_then_get_was_racy) { - ThreadExecutor x; - std::unique_ptr val = folly::via(&x) - .then([] { return folly::make_unique(42); }) - .get(); - ASSERT_TRUE(!!val); - EXPECT_EQ(42, *val); +TEST(Future, thenDynamic) { + // folly::dynamic has a constructor that takes any T, this test makes + // sure that we call the then lambda with folly::dynamic and not + // Try because that then fails to compile + Promise p; + Future f = p.getFuture().then( + [](const folly::dynamic& d) { + return folly::dynamic(d.asInt() + 3); + } + ); + p.setValue(2); + EXPECT_EQ(f.get(), 5); } -TEST(Future, ensure) { - size_t count = 0; - auto cob = [&]{ count++; }; - auto f = makeFuture(42) - .ensure(cob) - .then([](int) { throw std::runtime_error("ensure"); }) - .ensure(cob); +TEST(Future, RequestContext) { + class NewThreadExecutor : public Executor { + public: + ~NewThreadExecutor() override { + std::for_each(v_.begin(), v_.end(), [](std::thread& t){ t.join(); }); + } + void add(Func f) override { + if (throwsOnAdd_) { throw std::exception(); } + v_.emplace_back(std::move(f)); + } + void addWithPriority(Func f, int8_t /* prio */) override { + add(std::move(f)); + } + uint8_t getNumPriorities() const override { return numPriorities_; } + + void setHandlesPriorities() { numPriorities_ = 2; } + void setThrowsOnAdd() { throwsOnAdd_ = true; } + private: + std::vector v_; + uint8_t numPriorities_ = 1; + bool throwsOnAdd_ = false; + }; + + struct MyRequestData : RequestData { + MyRequestData(bool value = false) : value(value) {} + bool value; + }; + + Promise p1, p2; + NewThreadExecutor e; + { + folly::RequestContextScopeGuard rctx; + RequestContext::get()->setContextData( + "key", std::make_unique(true)); + auto checker = [](int lineno) { + return [lineno](Try&& /* t */) { + auto d = static_cast( + RequestContext::get()->getContextData("key")); + EXPECT_TRUE(d && d->value) << "on line " << lineno; + }; + }; + + makeFuture(1).via(&e).then(checker(__LINE__)); + + e.setHandlesPriorities(); + makeFuture(2).via(&e).then(checker(__LINE__)); + + p1.getFuture().then(checker(__LINE__)); + + e.setThrowsOnAdd(); + p2.getFuture().via(&e).then(checker(__LINE__)); + } + // Assert that no RequestContext is set + EXPECT_FALSE(RequestContext::saveContext()); + p1.setValue(3); + p2.setValue(4); +} + +TEST(Future, makeFutureNoThrow) { + makeFuture().value(); +} + +TEST(Future, invokeCallbackReturningValueAsRvalue) { + struct Foo { + int operator()(int x) & { + return x + 1; + } + int operator()(int x) const& { + return x + 2; + } + int operator()(int x) && { + return x + 3; + } + }; + + Foo foo; + Foo const cfoo; + + // The callback will be copied when given as lvalue or const ref, and moved + // if provided as rvalue. Either way, it should be executed as rvalue. + EXPECT_EQ(103, makeFuture(100).then(foo).value()); + EXPECT_EQ(203, makeFuture(200).then(cfoo).value()); + EXPECT_EQ(303, makeFuture(300).then(Foo()).value()); +} + +TEST(Future, invokeCallbackReturningFutureAsRvalue) { + struct Foo { + Future operator()(int x) & { + return x + 1; + } + Future operator()(int x) const& { + return x + 2; + } + Future operator()(int x) && { + return x + 3; + } + }; + + Foo foo; + Foo const cfoo; - EXPECT_THROW(f.get(), std::runtime_error); - EXPECT_EQ(2, count); + // The callback will be copied when given as lvalue or const ref, and moved + // if provided as rvalue. Either way, it should be executed as rvalue. + EXPECT_EQ(103, makeFuture(100).then(foo).value()); + EXPECT_EQ(203, makeFuture(200).then(cfoo).value()); + EXPECT_EQ(303, makeFuture(300).then(Foo()).value()); }