225 lines
7.4 KiB
C++
225 lines
7.4 KiB
C++
// 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_
|