blob: ac85262c758363d06e4ea84999dffbb852241c6d [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 COMPONENTS_REPORTING_UTIL_TEST_SUPPORT_CALLBACKS_H_
#define COMPONENTS_REPORTING_UTIL_TEST_SUPPORT_CALLBACKS_H_
#include <tuple>
#include <utility>
#include "base/atomic_ref_count.h"
#include "base/functional/bind.h"
#include "base/functional/callback_forward.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/synchronization/lock.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/thread_annotations.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace reporting::test {
// Usage (in tests only):
//
// TestMultiEvent<ResType1, Restype2, ...> e;
//
// ... Do some async work passing e.cb() as a completion callback of
// base::OnceCallback<void(ResType1, Restype2, ...)> type which also may
// perform some other action specified by |done| callback provided by the
// caller. Now wait for e.cb() to be called and return the collected results.
//
// std::tie(res1, res2, ...) = e.result();
//
template <typename... ResType>
class TestMultiEvent {
public:
TestMultiEvent() = default;
~TestMultiEvent() = default;
TestMultiEvent(const TestMultiEvent& other) = delete;
TestMultiEvent& operator=(const TestMultiEvent& other) = delete;
using TupleType = std::tuple<std::remove_reference_t<ResType>...>;
[[nodiscard]] const TupleType& ref_result() {
static_assert(
!IsMovable<TupleType>(),
"std::tuple<ResType...> is not movable. Please use result().");
RunUntilHasResult();
CHECK(result_.has_value()) << "Result must have been set";
return result_.value();
}
[[nodiscard]] TupleType result() {
static_assert(
IsMovable<TupleType>(),
"std::tuple<ResType...> is movable. Please use ref_result().");
CHECK(!result_retrieved_) << "Result already retrieved";
RunUntilHasResult();
CHECK(result_.has_value()) << "Result must have been set";
auto value = std::move(result_.value());
result_.reset();
result_retrieved_ = true;
return value;
}
// Returns true if the event callback was never invoked.
[[nodiscard]] bool no_result() const { return !result_.has_value(); }
// Completion callback to hand over to the processing method.
[[nodiscard]] base::OnceCallback<void(ResType... res)> cb() {
return base::BindPostTaskToCurrentDefault(base::BindOnce(
&TestMultiEvent::SetResult, weak_ptr_factory_.GetWeakPtr()));
}
// Repeating completion callback to hand over to the processing method.
// Even though it is repeating, it can be only called once, since
// `result` only waits for one value; repeating declaration is only needed
// for cases when the caller requires it.
[[nodiscard]] base::RepeatingCallback<void(ResType... res)> repeating_cb() {
return base::BindPostTaskToCurrentDefault(base::BindRepeating(
[](base::WeakPtr<TestMultiEvent<ResType...>> self, ResType... res) {
if (!self) {
return;
}
ASSERT_FALSE(self->repeated_cb_called_)
<< "repeating_cb() called more than once, but it is only "
"intended to be called once.";
self->SetResult(std::forward<ResType>(res)...);
self->repeated_cb_called_ = true;
},
weak_ptr_factory_.GetWeakPtr()));
}
protected:
// Can type T be moved from, i.e., T is move constructible and assignable.
template <typename T>
static constexpr bool IsMovable() {
return std::is_move_constructible_v<T> && std::is_move_assignable_v<T>;
}
private:
void RunUntilHasResult() {
base::RunLoop run_loop;
while (true) {
{
base::AutoLock auto_lock(lock_);
if (result_.has_value()) {
break;
}
}
run_loop.RunUntilIdle();
}
}
void SetResult(ResType... res) {
base::AutoLock auto_lock(lock_);
CHECK(!result_.has_value()) << "Can only be called once";
result_.emplace(std::forward<ResType>(res)...);
}
base::Lock lock_;
std::optional<TupleType> result_;
bool repeated_cb_called_ = false;
bool result_retrieved_ = false;
base::WeakPtrFactory<TestMultiEvent<ResType...>> weak_ptr_factory_{this};
};
// Usage (in tests only):
//
// TestEvent<ResType> e;
//
// ... Do some async work passing e.cb() as a completion callback of
// base::OnceCallback<void(ResType res)> type which also may perform some
// other action specified by |done| callback provided by the caller.
// Now wait for e.cb() to be called and return the collected result.
//
// ... = e.result();
//
template <typename ResType>
class TestEvent : public TestMultiEvent<ResType> {
public:
TestEvent() = default;
~TestEvent() = default;
TestEvent(const TestEvent& other) = delete;
TestEvent& operator=(const TestEvent& other) = delete;
[[nodiscard]] const ResType& ref_result() {
static_assert(!TestMultiEvent<ResType>::template IsMovable<ResType>(),
"ResType is movable. Please use result().");
return std::get<0>(TestMultiEvent<ResType>::ref_result());
}
[[nodiscard]] ResType result() {
static_assert(TestMultiEvent<ResType>::template IsMovable<ResType>(),
"ResType is not movable. Please use ref_result().");
return std::get<0>(TestMultiEvent<ResType>::result());
}
};
// Usage (in tests only):
//
// TestCallbackWaiter waiter;
// ... do something
// waiter.Wait();
//
// or, with multithreadeded activity:
//
// TestCallbackWaiter waiter;
// waiter.Attach(N); // N - is a number of asynchronous actions
// ...
// waiter.Wait();
//
// And in each of N actions: waiter.Signal(); when done
class TestCallbackWaiter : public TestEvent<bool> {
public:
TestCallbackWaiter();
~TestCallbackWaiter();
TestCallbackWaiter(const TestCallbackWaiter& other) = delete;
TestCallbackWaiter& operator=(const TestCallbackWaiter& other) = delete;
void Attach(int more = 1) {
const int old_counter = counter_.Increment(more);
CHECK_GT(old_counter, 0) << "Cannot attach when already being released";
}
void Signal() {
if (counter_.Decrement()) {
// There are more owners.
return;
}
// Dropping the last owner.
std::move(signaled_cb_).Run(true);
}
void Wait() {
Signal(); // Rid of the constructor's ownership.
ASSERT_TRUE(result());
}
private:
base::AtomicRefCount counter_{1}; // Owned by constructor.
base::OnceCallback<void(bool)> signaled_cb_;
};
// RAII wrapper for TestCallbackWaiter.
//
// Usage:
// {
// TestCallbackAutoWaiter waiter; // Implicitly Attach(1);
// ...
// Launch async activity, which will eventually do waiter.Signal();
// ...
// } // Here the waiter will automatically wait.
class TestCallbackAutoWaiter : public TestCallbackWaiter {
public:
TestCallbackAutoWaiter();
~TestCallbackAutoWaiter();
};
} // namespace reporting::test
#endif // COMPONENTS_REPORTING_UTIL_TEST_SUPPORT_CALLBACKS_H_