blob: 2f62a32f9a9c07faa7111f64f255f694c59cff60 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TEST_REPEATING_TEST_FUTURE_H_
#define BASE_TEST_REPEATING_TEST_FUTURE_H_
#include <utility>
#include "base/check.h"
#include "base/containers/queue.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/sequence_checker.h"
#include "base/test/test_future_internal.h"
#include "base/thread_annotations.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
namespace base::test {
// Related class to `base::test::TestFuture`, which allows its callback and
// AddValue() method to be called multiple times.
//
// Each call to Take() will return one element in FIFO fashion.
// If no element is available, Take() will wait until an element becomes
// available.
//
// Just like `base::test::TestFuture`, `base::test::RepeatingTestFuture` also
// supports callbacks which take multiple values. If this is the case Take()
// will return a tuple containing all values passed to the callback.
//
// Example usage:
//
// TEST_F(MyTestFixture, MyTest) {
// RepeatingTestFuture<ResultType> future;
//
// InvokeCallbackAsyncTwice(future.GetCallback());
//
// ResultType first_result = future.Take();
// ResultType second_result = future.Take();
//
// // When you come here, InvokeCallbackAsyncTwice has finished,
// // `first_result` contains the value passed to the first invocation
// // of the callback, and `second_result` has the result of the second
// // invocation.
// }
//
// Example without using a callback but using AddValue() instead:
//
// TEST_F(MyTestFixture, MyTest) {
// RepeatingTestFuture<std::string> future;
//
// // AddValue() can be used to add an element to the future.
// future.AddValue("first-value");
// future.AddValue("second-value");
//
// EXPECT_EQ("first-value", future.Take());
// EXPECT_EQ("second-value", future.Take());
// }
//
// Or an example using RepeatingTestFuture::Wait():
//
// TEST_F(MyTestFixture, MyWaitTest) {
// RepeatingTestFuture<ResultType> future;
//
// object_under_test.DoSomethingAsync(future.GetCallback());
//
// // Optional. The Take() call below will also wait until the value
// // arrives, but this explicit call to Wait() can be useful if you want to
// // add extra information.
// ASSERT_TRUE(future.Wait()) << "Detailed error message";
//
// ResultType actual_result = future.Take();
// }
//
// All access to this class must be made from the same sequence.
template <typename... Types>
class RepeatingTestFuture {
public:
using TupleType = std::tuple<std::decay_t<Types>...>;
RepeatingTestFuture() = default;
RepeatingTestFuture(const RepeatingTestFuture&) = delete;
RepeatingTestFuture& operator=(const RepeatingTestFuture&) = delete;
RepeatingTestFuture(RepeatingTestFuture&&) = delete;
RepeatingTestFuture& operator=(RepeatingTestFuture&&) = delete;
~RepeatingTestFuture() = default;
void AddValue(Types... values) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
elements_.push(std::make_tuple(std::forward<Types>(values)...));
SignalElementIsAvailable();
}
// Waits until an element is available.
// Returns immediately if one or more elements are already available.
//
// Returns true if an element arrived, or false if a timeout happens.
//
// Directly calling Wait() is not required as Take() will also wait for
// the value to arrive, however you can use a direct call to Wait() to
// improve the error reported:
//
// ASSERT_TRUE(queue.Wait()) << "Detailed error message";
//
[[nodiscard]] bool Wait() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (IsEmpty())
WaitForANewElement();
return !IsEmpty();
}
// Returns a callback that when invoked will store all the argument values,
// and unblock any waiters.
// This method is templated so you can specify how you need the arguments to
// be passed - be it const, as reference, or anything you can think off.
// By default the callback accepts the arguments as `Types...`.
//
// Example usage:
//
// RepeatingTestFuture<int, std::string> future;
//
// // returns base::RepeatingCallback<void(int, std::string)>
// future.GetCallback();
//
// // returns base::RepeatingCallback<void(int, const std::string&)>
// future.GetCallback<int, const std::string&>();
//
template <typename... CallbackArgumentsTypes>
base::RepeatingCallback<void(CallbackArgumentsTypes...)> GetCallback() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return base::BindRepeating(
[](WeakPtr<RepeatingTestFuture<Types...>> future,
CallbackArgumentsTypes... values) {
if (future)
future->AddValue(std::forward<CallbackArgumentsTypes>(values)...);
},
weak_ptr_factory_.GetWeakPtr());
}
base::RepeatingCallback<void(Types...)> GetCallback() {
return GetCallback<Types...>();
}
// Returns true if no elements are currently present. Note that consuming all
// elements through Take() will cause this method to return true after the
// last available element has been consumed.
bool IsEmpty() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return elements_.empty();
}
//////////////////////////////////////////////////////////////////////////////
// Accessor methods only available if each element in the future holds a
// single value.
//////////////////////////////////////////////////////////////////////////////
// Wait for an element to arrive, and move its value out.
//
// Will DCHECK if a timeout happens.
template <typename T = TupleType, internal::EnableIfSingleValue<T> = true>
auto Take() {
return std::get<0>(TakeTuple());
}
//////////////////////////////////////////////////////////////////////////////
// Accessor methods only available if each element in the future holds
// multiple values.
//////////////////////////////////////////////////////////////////////////////
// Wait for an element to arrive, and move a tuple with its values out.
//
// Will DCHECK if a timeout happens.
template <typename T = TupleType, internal::EnableIfMultiValue<T> = true>
TupleType Take() {
return TakeTuple();
}
private:
// Wait until a new element is available.
void WaitForANewElement() VALID_CONTEXT_REQUIRED(sequence_checker_) {
DCHECK(!run_loop_.has_value());
// Create a new run loop.
run_loop_.emplace();
// Wait until 'run_loop_->Quit()' is called.
run_loop_->Run();
run_loop_.reset();
}
void SignalElementIsAvailable() VALID_CONTEXT_REQUIRED(sequence_checker_) {
if (run_loop_.has_value())
run_loop_->Quit();
}
TupleType TakeTuple() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Ensure an element is available.
bool success = Wait();
DCHECK(success) << "Waiting for an element timed out.";
auto result = std::move(elements_.front());
elements_.pop();
return result;
}
base::queue<TupleType> elements_ GUARDED_BY_CONTEXT(sequence_checker_);
// Used by Wait() to know when AddValue() is called.
absl::optional<base::RunLoop> run_loop_ GUARDED_BY_CONTEXT(sequence_checker_);
SEQUENCE_CHECKER(sequence_checker_);
base::WeakPtrFactory<RepeatingTestFuture<Types...>> weak_ptr_factory_{this};
};
} // namespace base::test
#endif // BASE_TEST_REPEATING_TEST_FUTURE_H_