| /* |
| * 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/Timekeeper.h> |
| |
| #include <unistd.h> |
| |
| using namespace folly; |
| using std::chrono::milliseconds; |
| |
| std::chrono::milliseconds const zero_ms(0); |
| std::chrono::milliseconds const one_ms(1); |
| std::chrono::milliseconds const awhile(10); |
| std::chrono::milliseconds const context_switch(50); |
| std::chrono::seconds const too_long(10); |
| |
| std::chrono::steady_clock::time_point now() { |
| return std::chrono::steady_clock::now(); |
| } |
| |
| struct TimekeeperFixture : public testing::Test { |
| TimekeeperFixture() : |
| timeLord_(folly::detail::getTimekeeperSingleton()) |
| {} |
| |
| Timekeeper* timeLord_; |
| }; |
| |
| TEST_F(TimekeeperFixture, after) { |
| auto t1 = now(); |
| auto f = timeLord_->after(awhile); |
| EXPECT_FALSE(f.isReady()); |
| f.get(); |
| auto t2 = now(); |
| |
| EXPECT_GE(t2 - t1, awhile); |
| } |
| |
| TEST(Timekeeper, futureGet) { |
| Promise<int> p; |
| auto t = std::thread([&]{ p.setValue(42); }); |
| EXPECT_EQ(42, p.getFuture().get()); |
| t.join(); |
| } |
| |
| TEST(Timekeeper, futureGetBeforeTimeout) { |
| Promise<int> p; |
| auto t = std::thread([&]{ p.setValue(42); }); |
| // Technically this is a race and if the test server is REALLY overloaded |
| // and it takes more than a second to do that thread it could be flaky. But |
| // I want a low timeout (in human terms) so if this regresses and someone |
| // runs it by hand they're not sitting there forever wondering why it's |
| // blocked, and get a useful error message instead. If it does get flaky, |
| // empirically increase the timeout to the point where it's very improbable. |
| EXPECT_EQ(42, p.getFuture().get(std::chrono::seconds(2))); |
| t.join(); |
| } |
| |
| TEST(Timekeeper, futureGetTimeout) { |
| Promise<int> p; |
| EXPECT_THROW(p.getFuture().get(one_ms), folly::TimedOut); |
| } |
| |
| TEST(Timekeeper, futureSleep) { |
| auto t1 = now(); |
| futures::sleep(one_ms).get(); |
| EXPECT_GE(now() - t1, one_ms); |
| } |
| |
| TEST(Timekeeper, futureDelayed) { |
| auto t1 = now(); |
| auto dur = makeFuture() |
| .delayed(one_ms) |
| .then([=]{ return now() - t1; }) |
| .get(); |
| |
| EXPECT_GE(dur, one_ms); |
| } |
| |
| TEST(Timekeeper, futureWithinThrows) { |
| Promise<int> p; |
| auto f = p.getFuture() |
| .within(one_ms) |
| .onError([](TimedOut&) { return -1; }); |
| |
| EXPECT_EQ(-1, f.get()); |
| } |
| |
| TEST(Timekeeper, futureWithinAlreadyComplete) { |
| auto f = makeFuture(42) |
| .within(one_ms) |
| .onError([&](TimedOut&){ return -1; }); |
| |
| EXPECT_EQ(42, f.get()); |
| } |
| |
| TEST(Timekeeper, futureWithinFinishesInTime) { |
| Promise<int> p; |
| auto f = p.getFuture() |
| .within(std::chrono::minutes(1)) |
| .onError([&](TimedOut&){ return -1; }); |
| p.setValue(42); |
| |
| EXPECT_EQ(42, f.get()); |
| } |
| |
| TEST(Timekeeper, futureWithinVoidSpecialization) { |
| makeFuture().within(one_ms); |
| } |
| |
| TEST(Timekeeper, futureWithinException) { |
| Promise<Unit> p; |
| auto f = p.getFuture().within(awhile, std::runtime_error("expected")); |
| EXPECT_THROW(f.get(), std::runtime_error); |
| } |
| |
| TEST(Timekeeper, onTimeout) { |
| bool flag = false; |
| auto v = makeFuture(42).delayed(context_switch) |
| .onTimeout(zero_ms, [&]{ flag = true; return -1; }) |
| .get(); |
| EXPECT_TRUE(flag); |
| EXPECT_EQ(-1, v); |
| } |
| |
| TEST(Timekeeper, onTimeoutReturnsFuture) { |
| bool flag = false; |
| makeFuture(42).delayed(context_switch) |
| .onTimeout(zero_ms, [&]{ flag = true; return makeFuture(-1); }) |
| .get(); |
| EXPECT_TRUE(flag); |
| } |
| |
| TEST(Timekeeper, onTimeoutVoid) { |
| makeFuture().delayed(one_ms) |
| .onTimeout(zero_ms, [&]{ |
| }); |
| makeFuture().delayed(one_ms) |
| .onTimeout(zero_ms, [&]{ |
| return makeFuture<Unit>(std::runtime_error("expected")); |
| }); |
| // just testing compilation here |
| } |
| |
| TEST(Timekeeper, interruptDoesntCrash) { |
| auto f = futures::sleep(too_long); |
| f.cancel(); |
| } |
| |
| TEST(Timekeeper, chainedInterruptTest) { |
| bool test = false; |
| auto f = futures::sleep(milliseconds(100)).then([&](){ |
| test = true; |
| }); |
| f.cancel(); |
| f.wait(); |
| EXPECT_FALSE(test); |
| } |
| |
| TEST(Timekeeper, executor) { |
| class ExecutorTester : public Executor { |
| public: |
| void add(Func f) override { |
| count++; |
| f(); |
| } |
| std::atomic<int> count{0}; |
| }; |
| |
| auto f = makeFuture(); |
| ExecutorTester tester; |
| f.via(&tester).within(one_ms).then([&](){}).wait(); |
| EXPECT_EQ(2, tester.count); |
| } |
| |
| // TODO(5921764) |
| /* |
| TEST(Timekeeper, onTimeoutPropagates) { |
| bool flag = false; |
| EXPECT_THROW( |
| makeFuture(42).delayed(one_ms) |
| .onTimeout(zero_ms, [&]{ flag = true; }) |
| .get(), |
| TimedOut); |
| EXPECT_TRUE(flag); |
| } |
| */ |
| |
| TEST_F(TimekeeperFixture, atBeforeNow) { |
| auto f = timeLord_->at(now() - too_long); |
| EXPECT_TRUE(f.isReady()); |
| EXPECT_FALSE(f.hasException()); |
| } |
| |
| TEST_F(TimekeeperFixture, howToCastDuration) { |
| // I'm not sure whether this rounds up or down but it's irrelevant for the |
| // purpose of this example. |
| auto f = timeLord_->after(std::chrono::duration_cast<Duration>( |
| std::chrono::nanoseconds(1))); |
| } |