-
Notifications
You must be signed in to change notification settings - Fork 117
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Rework how tests deal with future timeout #496
base: main
Are you sure you want to change the base?
Changes from all commits
66f36f0
560d56d
7998543
c86d850
3629636
4644cb6
5759044
886a452
da3aba9
477e012
9759b3b
94e46a3
86124c8
0e98a9c
baa79d3
a26cc96
49add61
59c8492
dd187e3
4684e79
80f6e63
dc10d7e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,6 +17,7 @@ | |
#ifndef FIREBASE_FIRESTORE_INTEGRATION_TEST_INTERNAL_SRC_FIRESTORE_INTEGRATION_TEST_H_ | ||
#define FIREBASE_FIRESTORE_INTEGRATION_TEST_INTERNAL_SRC_FIRESTORE_INTEGRATION_TEST_H_ | ||
|
||
#include <chrono> // NOLINT(build/c++11) | ||
var-const marked this conversation as resolved.
Show resolved
Hide resolved
|
||
#include <cstdio> | ||
#include <iostream> | ||
#include <map> | ||
|
@@ -59,11 +60,16 @@ bool ProcessEvents(int msec); | |
// enum, but this function will gracefully handle the case where it is not. | ||
std::string ToFirestoreErrorCodeName(int error_code); | ||
|
||
// Waits for a Future to complete. If a timeout is reached then this method | ||
// returns as if successful; therefore, the caller should verify the status of | ||
// the given Future after this function returns. Returns the number of cycles | ||
// that were left before a timeout would have occurred. | ||
int WaitFor(const FutureBase& future); | ||
// Blocks until either the given predicate `pred` returns `true` or timeout | ||
// occurs. Returns `true` on success, `false` on timeout or if exit signal was | ||
// received. | ||
template <typename PredT> | ||
bool WaitUntil(const PredT& pred, int timeout_ms = kTimeOutMillis); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider returning an enum from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought about this but was leaning towards not doing it. It seems to complicate the common case for the sake of a very rare occurrence. I'm not sure this even works correctly -- e.g. on Linux the integration tests seem to completely ignore There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Please disregard -- this is actually working correctly. I'm still hesitant to complicate the common case, though. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, it seems like it actually works in some tests and doesn't work in others. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My concern still stands, that returning a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems to me that the root cause here is that these functions just don't have a great way of reporting this event, which really seems more akin to an exception. There also isn't much tests can do about it, so I'm not sure they need the ability to check for this condition. I think that this type of exit usually happens due to user input, so it shouldn't be confusing to the user, at least in the general case. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we should throw an exception now that exceptions are enabled in tests, especially once #561 is merged. In any case, that's out of scope for this PR so I'm fine with this code as-is. |
||
|
||
// Blocks until either the future completes or timeout occurs. Returns `true` | ||
// on success, `false` on timeout or if exit signal was received. | ||
bool WaitUntilFutureCompletes(const FutureBase& future, | ||
int timeout_ms = kTimeOutMillis); | ||
|
||
template <typename T> | ||
class EventAccumulator; | ||
|
@@ -295,37 +301,30 @@ class FirestoreIntegrationTest : public testing::Test { | |
|
||
// TODO(zxu): add a helper function to block on signal. | ||
|
||
// A helper function to block until the future completes. | ||
// Blocks until the future is completed and returns the future result. | ||
template <typename T> | ||
static const T* Await(const Future<T>& future) { | ||
int cycles = WaitFor(future); | ||
EXPECT_GT(cycles, 0) << "Waiting future timed out."; | ||
if (future.status() == FutureStatus::kFutureStatusComplete) { | ||
if (future.result() == nullptr) { | ||
std::cout << "WARNING: " << DescribeFailedFuture(future) << std::endl; | ||
} | ||
} else { | ||
std::cout << "WARNING: Future is not completed." << std::endl; | ||
} | ||
static const T* Await(const Future<T>& future, | ||
int timeout_ms = kTimeOutMillis) { | ||
EXPECT_TRUE(WaitUntilFutureCompletes(future, timeout_ms)) | ||
<< "Future<T> timed out."; | ||
|
||
EXPECT_EQ(future.status(), FutureStatus::kFutureStatusComplete) | ||
<< DescribeFailedFuture(future); | ||
EXPECT_NE(future.result(), nullptr) << DescribeFailedFuture(future); | ||
|
||
return future.result(); | ||
} | ||
|
||
static void Await(const Future<void>& future); | ||
// Blocks until the future completes. | ||
static void Await(const Future<void>& future, | ||
int timeout_ms = kTimeOutMillis); | ||
|
||
// A helper function to block until there is at least n event. | ||
// Blocks until there is at least `event_count` events. | ||
var-const marked this conversation as resolved.
Show resolved
Hide resolved
|
||
template <typename T> | ||
static void Await(const TestEventListener<T>& listener, int n = 1) { | ||
// Instead of getting a clock, we count the cycles instead. | ||
int cycles = kTimeOutMillis / kCheckIntervalMillis; | ||
while (listener.event_count() < n && cycles > 0) { | ||
if (ProcessEvents(kCheckIntervalMillis)) { | ||
std::cout << "WARNING: app receives an event requesting exit." | ||
<< std::endl; | ||
return; | ||
} | ||
--cycles; | ||
} | ||
EXPECT_GT(cycles, 0) << "Waiting listener timed out."; | ||
static void Await(const TestEventListener<T>& listener, int event_count = 1) { | ||
bool success = | ||
WaitUntil([&] { return listener.event_count() >= event_count; }); | ||
EXPECT_TRUE(success) << "Waiting for a listener timed out."; | ||
} | ||
|
||
// Fails the current test if the given future did not complete or contained an | ||
|
@@ -370,6 +369,24 @@ class FirestoreIntegrationTest : public testing::Test { | |
mutable std::unordered_map<Firestore*, FirestoreInfo> firestores_; | ||
}; | ||
|
||
template <typename PredT> | ||
bool WaitUntil(const PredT& pred, int timeout_ms) { | ||
auto now = std::chrono::steady_clock::now(); | ||
dconeybe marked this conversation as resolved.
Show resolved
Hide resolved
|
||
auto timeout_time = now + std::chrono::milliseconds(timeout_ms); | ||
|
||
while (!pred() && now < timeout_time) { | ||
if (ProcessEvents(kCheckIntervalMillis)) { | ||
std::cout << "WARNING: app received an event requesting exit." | ||
<< std::endl; | ||
return false; | ||
} | ||
|
||
now = std::chrono::steady_clock::now(); | ||
} | ||
|
||
return now < timeout_time; | ||
} | ||
|
||
} // namespace firestore | ||
} // namespace firebase | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Previously, the overload of
Await
taking aFuture<void>
didn't have a timeout, unlike itsFuture<T>
counterpart. Adding a timeout revealed that some of our transaction tests that do retries can take a varying amount of time, depending on the exact values of exponential backoff during that run, that just happens to be within a range of 11-18 seconds. The default timeout is 15 seconds, so that resulted in flaky tests. Adding a way to override the default timeout fixes the issue.