blob: 7f466e7d4cf4c9f51f2bc98ab4e2ea9317009cf8 [file] [log] [blame]
/*
* Copyright 2015 Nest Labs, Inc. All Rights Reserved.
*
* 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/detail/TimerMap.h>
#include <thread>
#include <atomic>
//#define ADD_DEBUGGING_OUTPUT
/* The DEBUG(x) macro will include the expression in the compilation
* unit if ADD_DEBUGGING_OUTPUT is defined. Otherwise, the expression
* is removed by the preprocessor. It is expected that DEBUG(x) has
* a semicolon following it: DEBUG(x);
*/
#ifdef ADD_DEBUGGING_OUTPUT
#define DEBUG(x) x
#else
#define DEBUG(x) ((void)0)
#endif
using namespace folly;
using folly::detail::TimerMap;
using std::cout;
using std::endl;
using std::atomic;
TEST(TimerMap, PleaseCompile)
{
TimerMap tm(std::chrono::milliseconds(20));
}
TEST(TimerMap, GetTick)
{
{
auto tick = std::chrono::milliseconds(20);
TimerMap tm(tick);
EXPECT_EQ(tick, tm.getTick());
}
{
auto tick = std::chrono::milliseconds(67);
TimerMap tm(tick);
EXPECT_EQ(tick, tm.getTick());
}
}
TEST(TimerMap, OneTimer)
{
TimerMap tm(std::chrono::milliseconds(20));
const std::chrono::milliseconds delay(1000);
bool to = false;
EXPECT_TRUE(tm.empty());
tm.scheduleTimeout(delay, [&]() {
to = true;
});
EXPECT_FALSE(tm.empty());
EXPECT_FALSE(to);
auto epoch = TimerMap::clock_type::now();
do {
auto n = TimerMap::clock_type::now();
auto later = tm.nextTimeout();
auto elapsed = n - epoch;
auto wait = later - n;
if (!tm.empty()) {
EXPECT_LE(elapsed, delay);
EXPECT_LE(wait, tm.getTick());
} else {
EXPECT_GE(elapsed, delay);
EXPECT_LE(wait, tm.getTick());
}
std::this_thread::sleep_for(wait);
EXPECT_FALSE(to);
tm.invokeTimers();
} while (!tm.empty());
EXPECT_TRUE(to);
EXPECT_TRUE(tm.empty());
}
/* Test that >1 timers with the same execution time will all get fired. */
TEST(TimerMap, IdenticalTimers)
{
TimerMap tm(std::chrono::milliseconds(20));
auto now = TimerMap::clock_type::now();
auto expire = now + std::chrono::milliseconds(20);
bool one = false;
bool two = false;
bool three = false;
EXPECT_TRUE(tm.empty());
tm.scheduleAlarm(expire, [&]() { one = true; });
EXPECT_FALSE(tm.empty());
tm.scheduleAlarm(expire, [&]() { two = true; });
EXPECT_FALSE(tm.empty());
tm.scheduleAlarm(expire, [&]() { three = true; });
EXPECT_FALSE(tm.empty());
std::this_thread::sleep_for(std::chrono::milliseconds(20));
EXPECT_FALSE(one);
EXPECT_FALSE(two);
EXPECT_FALSE(three);
EXPECT_FALSE(tm.empty());
while (!tm.empty()) {
tm.invokeTimers();
}
EXPECT_TRUE(tm.empty());
EXPECT_TRUE(one);
EXPECT_TRUE(two);
EXPECT_TRUE(three);
}
/* Test that timers come out in proper order */
TEST(TimerMap, timerSequencing)
{
const std::chrono::milliseconds ten_ms(10);
const std::chrono::milliseconds twenty_ms(20);
const std::chrono::milliseconds now(0);
TimerMap tm(std::chrono::milliseconds(1));
atomic<unsigned> ctr(0);
tm.scheduleTimeout(twenty_ms, [&] {
unsigned exp = 2;
EXPECT_TRUE(ctr.compare_exchange_strong(exp, 3));
EXPECT_EQ(2, exp);
});
tm.scheduleTimeout(ten_ms, [&] {
unsigned exp = 1;
EXPECT_TRUE(ctr.compare_exchange_strong(exp, 2));
EXPECT_EQ(1, exp);
});
tm.scheduleTimeout(now, [&] {
unsigned exp = 0;
EXPECT_TRUE(ctr.compare_exchange_strong(exp, 1));
EXPECT_EQ(0, exp);
});
while (!tm.empty()) {
tm.invokeTimers();
}
EXPECT_EQ(3, ctr);
}
#ifndef ARRAY_SIZE
#define ARRAY_SIZE(ra) (sizeof(ra)/sizeof(ra[0]))
#endif
namespace {
std::ostream& operator<<(std::ostream& os, const std::chrono::steady_clock::time_point& tp)
{
os << tp.time_since_epoch().count();
return os;
}
} /* anonymous namespace */
TEST(TimerMap, SeveralTimers)
{
/* Set high resolution so tests aren't brittle to timings */
TimerMap tm(std::chrono::milliseconds(250));
struct timeout_data {
std::chrono::milliseconds delay;
bool value;
};
using msecs = std::chrono::milliseconds;
timeout_data timeouts[] = {
{ msecs(100), false }, // 0: 2nd pass
{ msecs( 50), false }, // 1: 2nd pass
{ msecs(700), false }, // 2: 4th pass
{ msecs(666), false }, // 3: 4th pass
{ msecs( 0), false }, // 4: 1st pass (immediate)
{ msecs(300), false }, // 5: 3rd pass
{ msecs( 10), false }, // 6: 2nd pass
{ msecs(400), false }, // 7: 3rd pass
{ msecs(900), false }, // 8: 5th pass
{ msecs(410), false }, // 9: 3rd pass
};
for (unsigned k = 0 ; k < ARRAY_SIZE(timeouts) ; ++k) {
tm.scheduleTimeout(timeouts[k].delay, [k, &timeouts]() {
timeouts[k].value = true;
});
}
std::set<unsigned> complete;
// 1st pass: 4
{
auto next = tm.nextTimeout();
std::this_thread::sleep_until(next);
auto n = TimerMap::clock_type::now();
EXPECT_LT(next, n);
tm.invokeTimers();
complete.insert(4);
DEBUG(cout << "1ST PASS...\n");
for (unsigned k = 0 ; k < ARRAY_SIZE(timeouts) ; ++k) {
DEBUG(cout << k << "\n");
if (complete.find(k) == complete.end()) {
EXPECT_FALSE(timeouts[k].value);
} else {
EXPECT_TRUE(timeouts[k].value);
}
}
DEBUG(cout << endl);
}
// 2nd pass: 0, 1, 6
{
auto next = tm.nextTimeout();
std::this_thread::sleep_until(next);
auto n = TimerMap::clock_type::now();
DEBUG(cout << "next = " << next << endl);
DEBUG(cout << "now = " << n << endl);
EXPECT_LT(next, n);
tm.invokeTimers();
complete.insert(0);
complete.insert(1);
complete.insert(6);
DEBUG(cout << "2ND PASS...\n");
for (unsigned k = 0 ; k < ARRAY_SIZE(timeouts) ; ++k) {
DEBUG(cout << k << "\n");
if (complete.find(k) == complete.end()) {
EXPECT_FALSE(timeouts[k].value);
} else {
EXPECT_TRUE(timeouts[k].value);
}
}
DEBUG(cout << endl);
}
// 3rd pass: 5, 7, 9
{
auto next = tm.nextTimeout();
std::this_thread::sleep_until(next);
auto n = TimerMap::clock_type::now();
DEBUG(cout << "next = " << next << endl);
DEBUG(cout << "now = " << n << endl);
EXPECT_LT(next, n);
tm.invokeTimers();
complete.insert(5);
complete.insert(7);
complete.insert(9);
DEBUG(cout << "3RD PASS...\n");
for (unsigned k = 0 ; k < ARRAY_SIZE(timeouts) ; ++k) {
DEBUG(cout << k << "\n");
if (complete.find(k) == complete.end()) {
EXPECT_FALSE(timeouts[k].value);
} else {
EXPECT_TRUE(timeouts[k].value);
}
}
DEBUG(cout << endl);
}
// 4th pass: 2, 3
{
auto next = tm.nextTimeout();
std::this_thread::sleep_until(next);
auto n = TimerMap::clock_type::now();
DEBUG(cout << "next = " << next << endl);
DEBUG(cout << "now = " << n << endl);
EXPECT_LT(next, n);
tm.invokeTimers();
complete.insert(2);
complete.insert(3);
DEBUG(cout << "4TH PASS...\n");
for (unsigned k = 0 ; k < ARRAY_SIZE(timeouts) ; ++k) {
DEBUG(cout << k << "\n");
if (complete.find(k) == complete.end()) {
EXPECT_FALSE(timeouts[k].value);
} else {
EXPECT_TRUE(timeouts[k].value);
}
}
DEBUG(cout << endl);
}
// 5th pass: 8
{
auto next = tm.nextTimeout();
std::this_thread::sleep_until(next);
auto n = TimerMap::clock_type::now();
DEBUG(cout << "next = " << next << endl);
DEBUG(cout << "now = " << n << endl);
EXPECT_LT(next, n);
tm.invokeTimers();
complete.insert(8);
DEBUG(cout << "5TH PASS...\n");
for (unsigned k = 0 ; k < ARRAY_SIZE(timeouts) ; ++k) {
DEBUG(cout << k << "\n");
if (complete.find(k) == complete.end()) {
EXPECT_FALSE(timeouts[k].value);
} else {
EXPECT_TRUE(timeouts[k].value);
}
}
DEBUG(cout << endl);
}
EXPECT_TRUE(tm.empty());
}