Skip to content

Commit 3eaca03

Browse files
authored
test(common): deflake TimerQueue test (googleapis#10560)
1 parent 8847ffa commit 3eaca03

File tree

1 file changed

+55
-44
lines changed

1 file changed

+55
-44
lines changed

google/cloud/internal/timer_queue_test.cc

Lines changed: 55 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "google/cloud/testing_util/chrono_output.h"
1717
#include "google/cloud/testing_util/status_matchers.h"
1818
#include <gmock/gmock.h>
19+
#include <sstream>
1920
#include <thread>
2021

2122
namespace google {
@@ -26,7 +27,9 @@ namespace {
2627

2728
using ::google::cloud::testing_util::IsOk;
2829
using ::google::cloud::testing_util::StatusIs;
29-
using testing::Each;
30+
using ::testing::Each;
31+
using ::testing::ElementsAreArray;
32+
using ::testing::WhenSorted;
3033

3134
TEST(TimerQueueTest, ScheduleSingleRunner) {
3235
auto tq = TimerQueue::Create();
@@ -76,27 +79,62 @@ TEST(TimerQueueTest, CancelTimers) {
7679
for (auto& t : runners) t.join();
7780
}
7881

79-
TEST(TimerQueueTest, ScheduleEarlierTimerSingleRunner) {
82+
/// @test Verify timers are executed in order.
83+
TEST(TimerQueueTest, SingleRunnerOrdering) {
84+
// Populate some delays with intentionally out-of-order and in-order data.
85+
using duration = std::chrono::system_clock::duration;
86+
std::vector<duration> const delays{
87+
duration(10), duration(9), duration(8), duration(5), duration(20),
88+
duration(21), duration(22), duration(4), duration(3),
89+
};
90+
91+
// Create the timer queue, but do not run the servicing thread until the
92+
// timers are scheduled. Otherwise "later" timers may be immediately expired
93+
// because clocks are hard.
8094
auto tq = TimerQueue::Create();
95+
96+
auto const start = std::chrono::system_clock::now();
97+
std::vector<std::chrono::system_clock::time_point> expected(delays.size());
98+
std::transform(delays.begin(), delays.end(), expected.begin(),
99+
[start](auto d) { return start + d; });
100+
// We are going to store the expiration of each timer in this vector. Note
101+
// that first it is used only by the timer servicing thread (`std::thread t`)
102+
// below, and then by the testing thread. Synchronization is provided by
103+
// the thread join() call.
104+
std::vector<std::chrono::system_clock::time_point> actual;
105+
std::vector<future<Status>> timers(expected.size());
106+
std::transform(expected.begin(), expected.end(), timers.begin(), [&](auto d) {
107+
// As the timers expire we add their time (if successful) to `expirations`,
108+
// and return the `Status`.
109+
return tq->Schedule(d).then([&, d](auto f) {
110+
auto e = f.get();
111+
if (!e) return std::move(e).status();
112+
if (*e != d) {
113+
std::ostringstream os;
114+
os << "mismatched expiration, expected=" << d << ", got=" << *e;
115+
return Status(StatusCode::kFailedPrecondition, std::move(os).str());
116+
}
117+
actual.push_back(*std::move(e));
118+
return Status{};
119+
});
120+
});
121+
122+
// Start expiring timers.
81123
std::thread t([tq] { tq->Service(); });
82-
auto const duration = std::chrono::milliseconds(50);
83-
auto const now = std::chrono::system_clock::now();
84-
auto later = tq->Schedule(now + 2 * duration);
85-
auto earlier = tq->Schedule(now + duration);
86-
auto earlier_expire_time = earlier
87-
.then([&](auto f) {
88-
EXPECT_FALSE(later.is_ready());
89-
return f.get();
90-
})
91-
.get();
92-
auto later_expire_time = later.get();
124+
125+
// Wait for all the timers to expire.
126+
std::vector<Status> status(timers.size());
127+
std::transform(timers.begin(), timers.end(), status.begin(),
128+
[](auto& f) { return f.get(); });
129+
93130
tq->Shutdown();
94131
t.join();
95132

96-
ASSERT_THAT(earlier_expire_time, IsOk());
97-
ASSERT_THAT(later_expire_time, IsOk());
98-
EXPECT_EQ(*earlier_expire_time, now + duration);
99-
EXPECT_EQ(*later_expire_time, now + 2 * duration);
133+
EXPECT_THAT(status, Each(IsOk()));
134+
135+
// At this point we expect the expirations to be in order and match the
136+
// expiration times set in `expected`.
137+
EXPECT_THAT(expected, WhenSorted(ElementsAreArray(actual)));
100138
}
101139

102140
TEST(TimerQueueTest, ScheduleMultipleRunners) {
@@ -184,33 +222,6 @@ TEST(TimerQueueTest, ShutdownMultipleRunners) {
184222
EXPECT_THAT(status, Each(StatusIs(StatusCode::kCancelled)));
185223
}
186224

187-
TEST(TimerQueueTest, ScheduleEarlierTimerMultipleRunner) {
188-
auto tq = TimerQueue::Create();
189-
auto constexpr kRunners = 8;
190-
std::vector<std::thread> runners;
191-
for (auto i = 0; i != kRunners; ++i) {
192-
runners.emplace_back([&] { tq->Service(); });
193-
}
194-
auto const duration = std::chrono::milliseconds(50);
195-
auto now = std::chrono::system_clock::now();
196-
auto later = tq->Schedule(now + 2 * duration);
197-
auto earlier = tq->Schedule(now + duration);
198-
auto earlier_expire_time = earlier
199-
.then([&](auto f) {
200-
EXPECT_FALSE(later.is_ready());
201-
return f.get();
202-
})
203-
.get();
204-
auto later_expire_time = later.get();
205-
tq->Shutdown();
206-
for (auto& t : runners) t.join();
207-
208-
ASSERT_THAT(earlier_expire_time, IsOk());
209-
ASSERT_THAT(later_expire_time, IsOk());
210-
EXPECT_EQ(*earlier_expire_time, now + duration);
211-
EXPECT_EQ(*later_expire_time, now + 2 * duration);
212-
}
213-
214225
} // namespace
215226
} // namespace internal
216227
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END

0 commit comments

Comments
 (0)