Skip to content

Commit 4894109

Browse files
authored
Add the ability to wait and retry futures based on multiple expected errors. (#1514)
1 parent 29da398 commit 4894109

File tree

2 files changed

+58
-24
lines changed

2 files changed

+58
-24
lines changed

testing/test_framework/src/firebase_test_framework.cc

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
#include "firebase_test_framework.h" // NOLINT
1616

17+
#include <algorithm>
1718
#include <cstdio>
1819
#include <cstring>
1920
#include <string>
@@ -126,10 +127,10 @@ bool FirebaseTest::RunFlakyBlockBase(bool (*flaky_block)(void* context),
126127

127128
firebase::FutureBase FirebaseTest::RunWithRetryBase(
128129
firebase::FutureBase (*run_future)(void* context), void* context,
129-
const char* name, int expected_error) {
130+
const char* name, std::vector<int> expected_errors) {
130131
// Run run_future(context), which returns a Future, then wait for that Future
131-
// to complete. If the Future returns Invalid, or if its error() does
132-
// not match expected_error, pause a moment and try again.
132+
// to complete. If the Future returns Invalid, or if its error() is
133+
// not present in expected_errors, pause a moment and try again.
133134
//
134135
// In most cases, this will return the Future once it's been completed.
135136
// However, if it reaches the last attempt, it will return immediately once
@@ -140,6 +141,10 @@ firebase::FutureBase FirebaseTest::RunWithRetryBase(
140141
const int kNumAttempts =
141142
1 + (sizeof(kRetryDelaysMs) / sizeof(kRetryDelaysMs[0]));
142143

144+
if (expected_errors.empty()) {
145+
expected_errors.push_back(0);
146+
}
147+
143148
int attempt = 0;
144149
firebase::FutureBase future;
145150

@@ -158,10 +163,14 @@ firebase::FutureBase FirebaseTest::RunWithRetryBase(
158163
app_framework::LogDebug(
159164
"RunWithRetry%s%s: Attempt %d returned invalid status",
160165
*name ? " " : "", name, attempt + 1);
161-
} else if (future.error() != expected_error) {
166+
} else if (std::find(expected_errors.begin(), expected_errors.end(),
167+
future.error()) == std::end(expected_errors)) {
168+
std::string expected_errors_str =
169+
VariantToString(firebase::Variant(expected_errors));
162170
app_framework::LogDebug(
163-
"RunWithRetry%s%s: Attempt %d returned error %d, expected %d",
164-
*name ? " " : "", name, attempt + 1, future.error(), expected_error);
171+
"RunWithRetry%s%s: Attempt %d returned error %d, expected one of %s",
172+
*name ? " " : "", name, attempt + 1, future.error(),
173+
expected_errors_str.c_str());
165174
} else {
166175
// Future is completed and the error matches what's expected, no need to
167176
// retry further.
@@ -178,18 +187,24 @@ firebase::FutureBase FirebaseTest::RunWithRetryBase(
178187
}
179188

180189
bool FirebaseTest::WaitForCompletion(const firebase::FutureBase& future,
181-
const char* name, int expected_error) {
190+
const char* name,
191+
std::vector<int> expected_errors) {
192+
if (expected_errors.empty()) {
193+
// If unspecified, default expected error is 0, success.
194+
expected_errors.push_back(0);
195+
}
182196
app_framework::LogDebug("WaitForCompletion %s", name);
183197
while (future.status() == firebase::kFutureStatusPending) {
184198
app_framework::ProcessEvents(100);
185199
}
186200
EXPECT_EQ(future.status(), firebase::kFutureStatusComplete)
187201
<< name << " returned an invalid status.";
188-
EXPECT_EQ(future.error(), expected_error)
189-
<< name << " returned error " << future.error() << ": "
202+
EXPECT_THAT(expected_errors, testing::Contains(future.error()))
203+
<< name << " returned unexpected error " << future.error() << ": "
190204
<< future.error_message();
191205
return (future.status() == firebase::kFutureStatusComplete &&
192-
future.error() == expected_error);
206+
std::find(expected_errors.begin(), expected_errors.end(),
207+
future.error()) != std::end(expected_errors));
193208
}
194209

195210
bool FirebaseTest::WaitForCompletionAnyResult(

testing/test_framework/src/firebase_test_framework.h

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -388,10 +388,20 @@ class FirebaseTest : public testing::Test {
388388
// Google Play services version. Otherwise, returns 0.
389389
static int GetGooglePlayServicesVersion();
390390

391+
// Returns true if the future completed with one of the expected
392+
// error codes, fails the test and returns false otherwise.
393+
static bool WaitForCompletion(const firebase::FutureBase& future,
394+
const char* name,
395+
std::vector<int> expected_errors = {});
396+
391397
// Returns true if the future completed as expected, fails the test and
392398
// returns false otherwise.
393399
static bool WaitForCompletion(const firebase::FutureBase& future,
394-
const char* name, int expected_error = 0);
400+
const char* name, int expected_error) {
401+
std::vector<int> error_list;
402+
error_list.push_back(expected_error);
403+
return WaitForCompletion(future, name, error_list);
404+
}
395405

396406
// Just wait for completion, not caring what the result is (as long as
397407
// it's not Invalid). Returns true, unless Invalid.
@@ -429,7 +439,7 @@ class FirebaseTest : public testing::Test {
429439
// exponential backoff if the operation fails.
430440
//
431441
// Blocks until the operation succeeds (the Future completes, with error
432-
// matching expected_error) or if the final attempt is started (in which case
442+
// matching expected_errors) or if the final attempt is started (in which case
433443
// the Future returned may still be in progress). You should use
434444
// WaitForCompletion to await the results of this function in any case.
435445
//
@@ -445,10 +455,9 @@ class FirebaseTest : public testing::Test {
445455
// return auth->DeleteUser(auth->current_user());
446456
// }, auth_), "DeleteUser"));
447457
template <class CallbackType, class ContextType>
448-
static firebase::FutureBase RunWithRetry(CallbackType run_future_typed,
449-
ContextType* context_typed,
450-
const char* name = "",
451-
int expected_error = 0) {
458+
static firebase::FutureBase RunWithRetry(
459+
CallbackType run_future_typed, ContextType* context_typed,
460+
const char* name = "", std::vector<int> expected_errors = {}) {
452461
struct RunData {
453462
CallbackType callback;
454463
ContextType* context;
@@ -460,7 +469,7 @@ class FirebaseTest : public testing::Test {
460469
ContextType* context = static_cast<RunData*>(ctx)->context;
461470
return static_cast<firebase::FutureBase>(callback(context));
462471
},
463-
static_cast<void*>(&run_data), name, expected_error);
472+
static_cast<void*>(&run_data), name, expected_errors);
464473
}
465474

466475
// Same as RunWithRetry, but templated to return a Future<ResultType>
@@ -470,7 +479,7 @@ class FirebaseTest : public testing::Test {
470479
template <class ResultType, class CallbackType, class ContextType>
471480
static firebase::Future<ResultType> RunWithRetry(
472481
CallbackType run_future_typed, ContextType* context_typed,
473-
const char* name = "", int expected_error = 0) {
482+
const char* name = "", std::vector<int> expected_errors = {}) {
474483
struct RunData {
475484
CallbackType callback;
476485
ContextType* context;
@@ -486,15 +495,15 @@ class FirebaseTest : public testing::Test {
486495
firebase::Future<ResultType> future_result = callback(context);
487496
return static_cast<firebase::FutureBase>(future_result);
488497
},
489-
static_cast<void*>(&run_data), name, expected_error);
498+
static_cast<void*>(&run_data), name, expected_errors);
490499
// Future<T> and FutureBase are reinterpret_cast-compatible, by design.
491500
return *reinterpret_cast<firebase::Future<ResultType>*>(&result_base);
492501
}
493502

494503
// Same as RunWithRetry above, but use std::function to allow captures.
495504
static firebase::FutureBase RunWithRetry(
496505
std::function<firebase::FutureBase()> run_future, const char* name = "",
497-
int expected_error = 0) {
506+
std::vector<int> expected_errors = {}) {
498507
struct RunData {
499508
std::function<firebase::FutureBase()>* callback;
500509
};
@@ -504,13 +513,13 @@ class FirebaseTest : public testing::Test {
504513
auto& callback = *static_cast<RunData*>(ctx)->callback;
505514
return static_cast<firebase::FutureBase>(callback());
506515
},
507-
static_cast<void*>(&run_data), name, expected_error);
516+
static_cast<void*>(&run_data), name, expected_errors);
508517
}
509518
// Same as RunWithRetry<type>, but use std::function to allow captures.
510519
template <class ResultType>
511520
static firebase::Future<ResultType> RunWithRetry(
512521
std::function<firebase::Future<ResultType>()> run_future,
513-
const char* name = "", int expected_error = 0) {
522+
const char* name = "", std::vector<int> expected_errors = {}) {
514523
struct RunData {
515524
std::function<firebase::Future<ResultType>()>* callback;
516525
};
@@ -524,7 +533,7 @@ class FirebaseTest : public testing::Test {
524533
firebase::Future<ResultType> future_result = callback();
525534
return static_cast<firebase::FutureBase>(future_result);
526535
},
527-
static_cast<void*>(&run_data), name, expected_error);
536+
static_cast<void*>(&run_data), name, expected_errors);
528537
// Future<T> and FutureBase are reinterpret_cast-compatible, by design.
529538
return *reinterpret_cast<firebase::Future<ResultType>*>(&result_base);
530539
}
@@ -569,7 +578,17 @@ class FirebaseTest : public testing::Test {
569578
// for type safety.
570579
static firebase::FutureBase RunWithRetryBase(
571580
firebase::FutureBase (*run_future)(void* context), void* context,
572-
const char* name, int expected_error);
581+
const char* name, std::vector<int> expected_errors);
582+
583+
// Untyped version of RunWithRetry with one expected error.
584+
static firebase::FutureBase RunWithRetryBase(
585+
firebase::FutureBase (*run_future)(void* context), void* context,
586+
const char* name, int expected_error) {
587+
std::vector<int> error_list;
588+
error_list.push_back(expected_error);
589+
return RunWithRetryBase(run_future, context, name, error_list);
590+
}
591+
573592
// Untyped version of RunFlakyBlock, with implementation.
574593
// This is kept private because the templated version should be used instead,
575594
// for type safety.

0 commit comments

Comments
 (0)