blob: 86b0d0ae908dcb6b2bc9a4ccff5cbec5c65cc74a [file] [log] [blame]
// Copyright 2019 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 LIB_TEST_EXCEPTIONS_EXCEPTION_CATCHER_H_
#define LIB_TEST_EXCEPTIONS_EXCEPTION_CATCHER_H_
#include <lib/zx/channel.h>
#include <lib/zx/exception.h>
#include <lib/zx/result.h>
#include <lib/zx/task.h>
#include <lib/zx/thread.h>
#include <zircon/assert.h>
#include <zircon/errors.h>
#include <zircon/status.h>
#include <zircon/syscalls/exception.h>
#include <zircon/types.h>
#include <list>
namespace test_exceptions {
// Test utility to catch and handle exceptions
//
// The simplest usage is to allow the constructor and destructor to start and
// stop. This will assert-fail if anything went wrong (e.g. an unexpected
// exception was found) which is fine for most tests:
// {
// ExceptionCatcher catcher(process);
// ...
// }
//
// If you want to be able to explicitly check for failure or avoid asserts,
// the ExceptionCatcher can be started and stopped manually instead, it
// just involves a bit of extra boilerplate:
// ExceptionCatcher catcher;
// ASSERT_OK(catcher.Start(process);
// ...
// ASSERT_OK(catcher.Stop());
//
// This class is thread-unsafe so external locking must be applied if it is
// used across threads.
class ExceptionCatcher {
public:
ExceptionCatcher() = default;
// Calls Start() automatically and asserts that it succeeded.
template <typename T>
explicit ExceptionCatcher(const zx::task<T>& task);
// Calls Stop() automatically and asserts that it succeeded.
~ExceptionCatcher();
// Starts watching for exceptions on |task|. Can only be bound to a single
// task at a time.
template <typename T>
zx_status_t Start(const zx::task<T>& task);
// Stops watching for exceptions. Returns ZX_ERR_CANCELED if we got any
// exceptions that were not handled via ExpectException().
//
// Any unhandled exceptions will be closed with TRY_NEXT.
zx_status_t Stop();
// Blocks until an exception is received. It then returns the exception. This will
// return an error if the task exits without throwing an exception.
zx::result<zx::exception> ExpectException();
// Same as ExpectException() but only matches exceptions on |thread|.
//
// Any non-|thread| exceptions received will be held until they are
// handled or the catcher is stopped.
zx::result<zx::exception> ExpectException(const zx::thread& thread);
// Same as ExpectException() but only matches exceptions on |process|.
//
// Any non-|process| exceptions received will be held until they are
// handled or the catcher is stopped.
zx::result<zx::exception> ExpectException(const zx::process& process);
private:
struct ActiveException {
zx_exception_info_t info;
zx::exception exception;
};
zx::result<zx::exception> ExpectException(zx_koid_t pid, zx_koid_t tid);
zx::channel exception_channel_;
std::list<ActiveException> active_exceptions_;
};
template <typename T>
ExceptionCatcher::ExceptionCatcher(const zx::task<T>& task) {
zx_status_t status = Start(task);
ZX_ASSERT_MSG(status == ZX_OK, "ExceptionCatcher::Start() failed (%s)",
zx_status_get_string(status));
}
template <typename T>
zx_status_t ExceptionCatcher::Start(const zx::task<T>& task) {
if (exception_channel_) {
return ZX_ERR_ALREADY_BOUND;
}
return task.create_exception_channel(0, &exception_channel_);
}
} // namespace test_exceptions
#endif // LIB_TEST_EXCEPTIONS_EXCEPTION_CATCHER_H_