// 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 #include #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" namespace base::test { // DEPRECATED! // // Please use `TestFuture` with `TestFuture::GetRepeatingCallback()` instead. template class RepeatingTestFuture { public: using TupleType = std::tuple...>; 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(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 future; // // // returns base::RepeatingCallback // future.GetCallback(); // // // returns base::RepeatingCallback // future.GetCallback(); // template base::RepeatingCallback GetCallback() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); return base::BindRepeating( [](WeakPtr> future, CallbackArgumentsTypes... values) { if (future) { future->AddValue(std::forward(values)...); } }, weak_ptr_factory_.GetWeakPtr()); } base::RepeatingCallback GetCallback() { return GetCallback(); } // 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 = 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 = 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 elements_ GUARDED_BY_CONTEXT(sequence_checker_); // Used by Wait() to know when AddValue() is called. std::optional run_loop_ GUARDED_BY_CONTEXT(sequence_checker_); SEQUENCE_CHECKER(sequence_checker_); base::WeakPtrFactory> weak_ptr_factory_{this}; }; // Specialization so you can use `RepeatingTestFuture` to wait for a no-args // callback. template <> class RepeatingTestFuture { public: void AddValue() { implementation_.AddValue(true); } // Waits until the callback or `AddValue()` is invoked. // Returns immediately if one or more invocations have already happened. // // Returns true if an invocation arrived, or false if a timeout happens. // // Directly calling Wait() is not required as Take() will also wait for // the invocation 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() { return implementation_.Wait(); } // Returns a callback that when invoked will unblock any waiters. base::RepeatingClosure GetCallback() { return base::BindRepeating(implementation_.GetCallback(), true); } // 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 { return implementation_.IsEmpty(); } // Waits until the callback or `AddValue()` is invoked. // // Will DCHECK if a timeout happens. void Take() { implementation_.Take(); } private: RepeatingTestFuture implementation_; }; } // namespace base::test #endif // BASE_TEST_REPEATING_TEST_FUTURE_H_