| /* |
| * Copyright 2015 Facebook, Inc. |
| * |
| * 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 <gtest/gtest.h> |
| |
| #include <folly/futures/Future.h> |
| #include <folly/futures/InlineExecutor.h> |
| #include <folly/futures/ManualExecutor.h> |
| #include <folly/futures/QueuedImmediateExecutor.h> |
| #include <folly/Baton.h> |
| |
| using namespace folly; |
| |
| TEST(ManualExecutor, runIsStable) { |
| ManualExecutor x; |
| size_t count = 0; |
| auto f1 = [&]() { count++; }; |
| auto f2 = [&]() { x.add(f1); x.add(f1); }; |
| x.add(f2); |
| x.run(); |
| } |
| |
| TEST(ManualExecutor, scheduleDur) { |
| ManualExecutor x; |
| size_t count = 0; |
| std::chrono::milliseconds dur {10}; |
| x.schedule([&]{ count++; }, dur); |
| EXPECT_EQ(count, 0); |
| x.run(); |
| EXPECT_EQ(count, 0); |
| x.advance(dur/2); |
| EXPECT_EQ(count, 0); |
| x.advance(dur/2); |
| EXPECT_EQ(count, 1); |
| } |
| |
| TEST(ManualExecutor, clockStartsAt0) { |
| ManualExecutor x; |
| EXPECT_EQ(x.now(), x.now().min()); |
| } |
| |
| TEST(ManualExecutor, scheduleAbs) { |
| ManualExecutor x; |
| size_t count = 0; |
| x.scheduleAt([&]{ count++; }, x.now() + std::chrono::milliseconds(10)); |
| EXPECT_EQ(count, 0); |
| x.advance(std::chrono::milliseconds(10)); |
| EXPECT_EQ(count, 1); |
| } |
| |
| TEST(ManualExecutor, advanceTo) { |
| ManualExecutor x; |
| size_t count = 0; |
| x.scheduleAt([&]{ count++; }, std::chrono::steady_clock::now()); |
| EXPECT_EQ(count, 0); |
| x.advanceTo(std::chrono::steady_clock::now()); |
| EXPECT_EQ(count, 1); |
| } |
| |
| TEST(ManualExecutor, advanceBack) { |
| ManualExecutor x; |
| size_t count = 0; |
| x.advance(std::chrono::microseconds(5)); |
| x.schedule([&]{ count++; }, std::chrono::microseconds(6)); |
| EXPECT_EQ(count, 0); |
| x.advanceTo(x.now() - std::chrono::microseconds(1)); |
| EXPECT_EQ(count, 0); |
| } |
| |
| TEST(ManualExecutor, advanceNeg) { |
| ManualExecutor x; |
| size_t count = 0; |
| x.advance(std::chrono::microseconds(5)); |
| x.schedule([&]{ count++; }, std::chrono::microseconds(6)); |
| EXPECT_EQ(count, 0); |
| x.advance(std::chrono::microseconds(-1)); |
| EXPECT_EQ(count, 0); |
| } |
| |
| TEST(ManualExecutor, waitForDoesNotDeadlock) { |
| ManualExecutor east, west; |
| folly::Baton<> baton; |
| auto f = makeFuture() |
| .via(&east) |
| .then([](Try<Unit>){ return makeFuture(); }) |
| .via(&west); |
| std::thread t([&]{ |
| baton.post(); |
| west.waitFor(f); |
| }); |
| baton.wait(); |
| east.run(); |
| t.join(); |
| } |
| |
| TEST(Executor, InlineExecutor) { |
| InlineExecutor x; |
| size_t counter = 0; |
| x.add([&]{ |
| x.add([&]{ |
| EXPECT_EQ(counter, 0); |
| counter++; |
| }); |
| EXPECT_EQ(counter, 1); |
| counter++; |
| }); |
| EXPECT_EQ(counter, 2); |
| } |
| |
| TEST(Executor, QueuedImmediateExecutor) { |
| QueuedImmediateExecutor x; |
| size_t counter = 0; |
| x.add([&]{ |
| x.add([&]{ |
| EXPECT_EQ(1, counter); |
| counter++; |
| }); |
| EXPECT_EQ(0, counter); |
| counter++; |
| }); |
| EXPECT_EQ(2, counter); |
| } |
| |
| TEST(Executor, Runnable) { |
| InlineExecutor x; |
| size_t counter = 0; |
| struct Runnable { |
| std::function<void()> fn; |
| void operator()() { fn(); } |
| }; |
| Runnable f; |
| f.fn = [&]{ counter++; }; |
| x.add(f); |
| EXPECT_EQ(counter, 1); |
| } |
| |
| TEST(Executor, RunnablePtr) { |
| InlineExecutor x; |
| struct Runnable { |
| std::function<void()> fn; |
| void operator()() { fn(); } |
| }; |
| size_t counter = 0; |
| auto fnp = std::make_shared<Runnable>(); |
| fnp->fn = [&]{ counter++; }; |
| x.addPtr(fnp); |
| EXPECT_EQ(counter, 1); |
| } |
| |
| TEST(Executor, ThrowableThen) { |
| InlineExecutor x; |
| auto f = Future<Unit>().via(&x).then([](){ |
| throw std::runtime_error("Faildog"); |
| }); |
| EXPECT_THROW(f.value(), std::exception); |
| } |
| |
| class CrappyExecutor : public Executor { |
| public: |
| void add(Func f) override { |
| throw std::runtime_error("bad"); |
| } |
| }; |
| |
| TEST(Executor, CrappyExecutor) { |
| CrappyExecutor x; |
| bool flag = false; |
| auto f = folly::via(&x).onError([&](std::runtime_error& e) { |
| EXPECT_STREQ("bad", e.what()); |
| flag = true; |
| }); |
| EXPECT_TRUE(flag); |
| } |
| |
| /* Exception should break out of Executor::run() even in Future destroyed */ |
| TEST(Executor, exceptionsOnAbandonedFutures) |
| { |
| ManualExecutor x; |
| Promise<unsigned> p; |
| |
| { |
| p.getFuture().via(&x) |
| .then([&] (Try<unsigned>&& i) { |
| i.value(); |
| }) |
| .onErrorThrowIn(&x); |
| } |
| |
| p.setException(std::logic_error("fubar")); |
| |
| EXPECT_THROW(while (x.run()) {}, std::logic_error); |
| } |
| |
| /* Implementation of onErrorThrowIn() shall not interfere with onError()'s behavior */ |
| TEST(Executor, execeptionsOnAbandondedFuturesOnError) |
| { |
| ManualExecutor x; |
| Promise<unsigned> p; |
| unsigned k = 0; |
| |
| { |
| auto f = p.getFuture().via(&x) |
| .then([&] (unsigned i) { EXPECT_TRUE(false); }) |
| .onError([&] (std::logic_error& e) { ++k; }); |
| } |
| |
| p.setException(std::logic_error("fubar")); |
| |
| while (x.run()) {} |
| |
| EXPECT_EQ(1, k); |
| } |