// Copyright 2021 The Chromium Authors. All rights reserved. // 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 "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 future; // // InvokeCallbackAsyncTwice(future.GetCallback()); // // ResultType first_result = future.Get(); // ResultType second_result = future.Get(); // // // 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 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 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 class RepeatingTestFuture { public: using TupleType = std::tuple...>; using FirstType = typename std::tuple_element<0, TupleType>::type; 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> FirstType 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. absl::optional run_loop_ GUARDED_BY_CONTEXT(sequence_checker_); SEQUENCE_CHECKER(sequence_checker_); base::WeakPtrFactory> weak_ptr_factory_{this}; }; } // namespace base::test #endif // BASE_TEST_REPEATING_TEST_FUTURE_H_