blob: 1351590969a59eef40f530a02afc496bc8a4e492 [file] [log] [blame]
// Copyright 2022 The Fuchsia 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 SRC_SYS_FUZZING_COMMON_TESTING_ASYNC_TEST_H_
#define SRC_SYS_FUZZING_COMMON_TESTING_ASYNC_TEST_H_
#include <lib/syslog/cpp/macros.h>
#include <gtest/gtest.h>
#include "src/sys/fuzzing/common/async-types.h"
#include "testing/fidl/async_loop_for_test.h"
namespace fuzzing {
// This class acts as a base class for various unit tests. It extends gTest's |Test| class
// by providing an async loop for testing with an |async::Executor| has been set up.
class AsyncTest : public ::testing::Test {
protected:
async_dispatcher_t* dispatcher() { return loop_.dispatcher(); }
const ExecutorPtr& executor() const { return executor_; }
size_t active() const { return active_; }
void SetUp() override { executor_ = MakeExecutor(dispatcher()); }
// Each of these macros creates a promise from a provided handler with additional source location
// information. The promise expects the indicated kind of result.
//
// Example usage:
// auto f = FUZZING_EXPECT_OK([&] { return AwaitSomething(); });
// auto g = FUZZING_EXPECT_ERROR([&] { return AwaitFailure(); }, ZX_ERR_BAD_STATE);
//
#define FUZZING_EXPECT_OK(...) Schedule(ExpectOk(__FILE__, __LINE__, __VA_ARGS__))
#define FUZZING_EXPECT_ERROR(...) Schedule(ExpectError(__FILE__, __LINE__, __VA_ARGS__))
// Runs a promise using this object's test loop.
template <typename Handler>
void Schedule(Handler handler) {
auto promise = fpromise::make_promise(std::move(handler));
auto wrapper =
promise.inspect([this](typename decltype(promise)::result_type& result) { --active_; });
++active_;
executor_->schedule_task(std::move(wrapper));
}
// Checks if a promise returns ok. |handler| can be anything that can be passed to
// |fpromise::make_promise| to make a promise.
//
// Callers should use |EXPECT_OK| instead of calling this directly.
//
template <typename Handler>
Promise<> ExpectOk(const char* file, int line, Handler&& handler) {
auto promise = fpromise::make_promise(std::move(handler));
return promise
.inspect([file, line](typename decltype(promise)::result_type& result) {
EXPECT_TRUE(result.is_ok()) << "Called from " << file << ":" << line;
})
.discard_result();
}
// Checks if a promise returns an|expected| value. |handler| can be anything that can be passed to
// |fpromise::make_promise| to make a promise.
//
// Callers should use |EXPECT_OK| instead of calling this directly.
//
template <typename Handler, typename ValueType>
Promise<> ExpectOk(const char* file, int line, Handler&& handler, ValueType expected) {
auto promise = fpromise::make_promise(std::move(handler));
return promise
.inspect([file, line,
expected = std::move(expected)](typename decltype(promise)::result_type& result) {
ASSERT_TRUE(result.is_ok()) << "Called from " << file << ":" << line;
EXPECT_EQ(result.value(), expected) << "Called from " << file << ":" << line;
})
.discard_result();
}
// Checks if a promise returns ok and returns its value via |out|. |handler| can be anything that
// can be passed to |fpromise::make_promise| to make a promise.
//
// Callers should use |EXPECT_OK| instead of calling this directly.
//
template <typename Handler, typename ValueType>
Promise<> ExpectOk(const char* file, int line, Handler&& handler, ValueType* out) {
auto promise = fpromise::make_promise(std::move(handler));
return promise.then(
[file, line, out](typename decltype(promise)::result_type& result) -> Result<> {
EXPECT_TRUE(result.is_ok()) << "Called from " << file << ":" << line;
if (result.is_ok()) {
*out = result.take_value();
}
return fpromise::ok();
});
}
// Checks if a promise returns an error. |handler| can be anything that can be passed to
// |fpromise::make_promise| to make a promise.
//
// Callers should use |EXPECT_ERROR| instead of calling this directly.
//
template <typename Handler>
Promise<> ExpectError(const char* file, int line, Handler&& handler) {
auto promise = fpromise::make_promise(std::move(handler));
return promise
.inspect([file, line](typename decltype(promise)::result_type& result) {
EXPECT_TRUE(result.is_error()) << "Called from " << file << ":" << line;
})
.discard_result();
}
// Checks if a promise returns an |expected| error. |handler| can be anything that can be passed
// to |fpromise::make_promise| to make a promise.
//
// Callers should use |EXPECT_ERROR| instead of calling this directly.
//
template <typename Handler, typename ErrorType>
Promise<> ExpectError(const char* file, int line, Handler&& handler, ErrorType expected) {
auto promise = fpromise::make_promise(std::move(handler));
return promise
.inspect([file, line,
expected = std::move(expected)](typename decltype(promise)::result_type& result) {
ASSERT_TRUE(result.is_error()) << "Called from " << file << ":" << line;
EXPECT_EQ(result.error(), expected) << "Called from " << file << ":" << line;
})
.discard_result();
}
// Runs the test async loop.
void RunUntilIdle() {
// TODO(https://fxbug.dev/42174170): This is a transitional implementation to be used while the code has
// multiple dispatchers. It treats the test loop as not being idle until all its outstanding
// tasks have completed. This is useful in unit tests where the test loop's dispatcher may be
// distinct from an existing dispatcher running on a dedicated thread. When the migration to a
// purely async approach is complete, and the dedicated threads removed, this can become simply
// |loop_.RunUntilIdle()|, and |RunOnce| can be removed.
RunOnce();
while (active_) {
zx::nanosleep(zx::deadline_after(zx::msec(10)));
RunOnce();
}
}
void RunOnce() { loop_.RunUntilIdle(); }
// Checks for unfinished promises at test end.
void TearDown() override {
EXPECT_EQ(active_, 0U) << active_ << " unfinished task(s) at tear-down.";
}
private:
fidl::test::AsyncLoopForTest loop_;
ExecutorPtr executor_;
size_t active_ = 0;
};
} // namespace fuzzing
#endif // SRC_SYS_FUZZING_COMMON_TESTING_ASYNC_TEST_H_