| // 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_ |