mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-20 02:55:50 +00:00
Improved support for exceptions in threads spawned by unit tests:
Unit tests that wish to spawn threads for testing concurrency may now do so by using unit_test::thread as a replacement for std::thread. These threads propagate unhandled exceptions to the unit test, and work with the abort on failure feature.
This commit is contained in:
committed by
Edward Hennis
parent
adc69e72df
commit
faf91d6697
@@ -29,6 +29,8 @@
|
||||
namespace beast {
|
||||
namespace unit_test {
|
||||
|
||||
class thread;
|
||||
|
||||
/** A testsuite class.
|
||||
Derived classes execute a series of testcases, where each testcase is
|
||||
a series of pass/fail tests. To provide a unit test using this class,
|
||||
@@ -46,9 +48,19 @@ public:
|
||||
|
||||
private:
|
||||
bool abort_ = false;
|
||||
bool aborted_ = false;
|
||||
runner* runner_ = nullptr;
|
||||
|
||||
struct abort_exception;
|
||||
// This exception is thrown internally to stop the current suite
|
||||
// in the event of a failure, if the option to stop is set.
|
||||
struct abort_exception : public std::exception
|
||||
{
|
||||
char const*
|
||||
what() const noexcept override
|
||||
{
|
||||
return "suite aborted";
|
||||
}
|
||||
};
|
||||
|
||||
// Memberspace
|
||||
class log_t
|
||||
@@ -105,16 +117,6 @@ private:
|
||||
/** @} */
|
||||
};
|
||||
|
||||
// Hacks to make this header-only
|
||||
|
||||
template <class = void>
|
||||
void
|
||||
run (runner& r);
|
||||
|
||||
template <class = void>
|
||||
void
|
||||
do_fail (std::string const& reason);
|
||||
|
||||
public:
|
||||
/** Type for scoped stream logging.
|
||||
To use this type, declare a local variable of the type
|
||||
@@ -180,42 +182,34 @@ public:
|
||||
unexpected (Condition shouldBeFalse, std::string const& reason = "");
|
||||
|
||||
/** Record a successful test condition. */
|
||||
template <class = void>
|
||||
void
|
||||
pass()
|
||||
{
|
||||
runner_->pass();
|
||||
}
|
||||
pass();
|
||||
|
||||
/** Record a failure. */
|
||||
template <class = void>
|
||||
void
|
||||
fail (std::string const& reason = "");
|
||||
|
||||
private:
|
||||
friend class thread;
|
||||
|
||||
/** Runs the suite. */
|
||||
virtual
|
||||
void
|
||||
run() = 0;
|
||||
|
||||
template <class = void>
|
||||
void
|
||||
propagate_abort();
|
||||
|
||||
template <class = void>
|
||||
void
|
||||
run (runner& r);
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// This exception is thrown internally to stop the current suite
|
||||
// in the event of a failure, if the option to stop is set.
|
||||
struct suite::abort_exception : public std::exception
|
||||
{
|
||||
char const*
|
||||
what() const noexcept override;
|
||||
};
|
||||
|
||||
inline
|
||||
char const*
|
||||
suite::abort_exception::what() const noexcept
|
||||
{
|
||||
return "suite aborted on failed condition";
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template <class T>
|
||||
inline
|
||||
abstract_ostream::scoped_stream_type
|
||||
@@ -323,6 +317,66 @@ suite::testcase_t::operator<< (T const& t)
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
inline
|
||||
void
|
||||
suite::operator() (runner& r)
|
||||
{
|
||||
run (r);
|
||||
}
|
||||
|
||||
template <class Condition>
|
||||
inline
|
||||
bool
|
||||
suite::expect (Condition shouldBeTrue, std::string const& reason)
|
||||
{
|
||||
if (shouldBeTrue)
|
||||
pass();
|
||||
else
|
||||
fail (reason);
|
||||
return shouldBeTrue;
|
||||
}
|
||||
|
||||
template <class Condition>
|
||||
inline
|
||||
bool
|
||||
suite::unexpected (Condition shouldBeFalse, std::string const& reason)
|
||||
{
|
||||
if (! shouldBeFalse)
|
||||
pass();
|
||||
else
|
||||
fail (reason);
|
||||
return ! shouldBeFalse;
|
||||
}
|
||||
|
||||
template <class>
|
||||
void
|
||||
suite::pass()
|
||||
{
|
||||
propagate_abort();
|
||||
runner_->pass();
|
||||
}
|
||||
|
||||
template <class>
|
||||
void
|
||||
suite::fail (std::string const& reason)
|
||||
{
|
||||
propagate_abort();
|
||||
runner_->fail (reason);
|
||||
if (abort_)
|
||||
{
|
||||
aborted_ = true;
|
||||
throw abort_exception();
|
||||
}
|
||||
}
|
||||
|
||||
template <class>
|
||||
void
|
||||
suite::propagate_abort()
|
||||
{
|
||||
if (abort_ && aborted_)
|
||||
throw abort_exception();
|
||||
}
|
||||
|
||||
template <class>
|
||||
void
|
||||
suite::run (runner& r)
|
||||
@@ -341,62 +395,15 @@ suite::run (runner& r)
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
fail (std::string ("unhandled exception: ") +
|
||||
runner_->fail ("unhandled exception: " +
|
||||
std::string (e.what()));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
fail ("unhandled exception");
|
||||
runner_->fail ("unhandled exception");
|
||||
}
|
||||
}
|
||||
|
||||
inline
|
||||
void
|
||||
suite::operator() (runner& r)
|
||||
{
|
||||
run (r);
|
||||
}
|
||||
|
||||
template <class Condition>
|
||||
inline
|
||||
bool
|
||||
suite::expect (Condition shouldBeTrue, std::string const& reason)
|
||||
{
|
||||
if (shouldBeTrue)
|
||||
pass();
|
||||
else
|
||||
do_fail (reason);
|
||||
return shouldBeTrue;
|
||||
}
|
||||
|
||||
template <class Condition>
|
||||
inline
|
||||
bool
|
||||
suite::unexpected (Condition shouldBeFalse, std::string const& reason)
|
||||
{
|
||||
if (! shouldBeFalse)
|
||||
pass();
|
||||
else
|
||||
do_fail (reason);
|
||||
return ! shouldBeFalse;
|
||||
}
|
||||
|
||||
template <class>
|
||||
void
|
||||
suite::do_fail (std::string const& reason)
|
||||
{
|
||||
runner_->fail (reason);
|
||||
if (abort_)
|
||||
throw abort_exception();
|
||||
}
|
||||
|
||||
inline
|
||||
void
|
||||
suite::fail (std::string const& reason)
|
||||
{
|
||||
do_fail (reason);
|
||||
}
|
||||
|
||||
} // unit_test
|
||||
} // beast
|
||||
|
||||
|
||||
122
beast/unit_test/thread.h
Normal file
122
beast/unit_test/thread.h
Normal file
@@ -0,0 +1,122 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of Beast: https://github.com/vinniefalco/Beast
|
||||
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef BEAST_UNIT_TEST_THREAD_H_INCLUDED
|
||||
#define BEAST_UNIT_TEST_THREAD_H_INCLUDED
|
||||
|
||||
#include <beast/utility/noexcept.h>
|
||||
#include <beast/unit_test/suite.h>
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
namespace beast {
|
||||
namespace unit_test {
|
||||
|
||||
/** Replacement for std::thread that handles exceptions in unit tests. */
|
||||
class thread
|
||||
{
|
||||
private:
|
||||
suite* s_ = nullptr;
|
||||
std::thread t_;
|
||||
|
||||
public:
|
||||
using id = std::thread::id;
|
||||
using native_handle_type = std::thread::native_handle_type;
|
||||
|
||||
thread() = default;
|
||||
thread (thread const&) = delete;
|
||||
thread& operator= (thread const&) = delete;
|
||||
|
||||
thread (thread&& other)
|
||||
: s_ (other.s_)
|
||||
, t_ (std::move(other.t_))
|
||||
{
|
||||
}
|
||||
|
||||
thread& operator= (thread&& other)
|
||||
{
|
||||
s_ = other.s_;
|
||||
t_ = std::move(other.t_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <class F, class... Args>
|
||||
explicit
|
||||
thread (suite& s, F&& f, Args&&... args)
|
||||
: s_ (&s)
|
||||
{
|
||||
std::function<void(void)> b =
|
||||
std::bind(std::forward<F>(f),
|
||||
std::forward<Args>(args)...);
|
||||
t_ = std::thread (&thread::run, this,
|
||||
std::move(b));
|
||||
}
|
||||
|
||||
bool
|
||||
joinable() const
|
||||
{
|
||||
return t_.joinable();
|
||||
}
|
||||
|
||||
std::thread::id
|
||||
get_id() const
|
||||
{
|
||||
return t_.get_id();
|
||||
}
|
||||
|
||||
static
|
||||
unsigned
|
||||
hardware_concurrency() noexcept
|
||||
{
|
||||
return std::thread::hardware_concurrency();
|
||||
}
|
||||
|
||||
void
|
||||
join()
|
||||
{
|
||||
t_.join();
|
||||
s_->propagate_abort();
|
||||
}
|
||||
|
||||
void
|
||||
swap (thread& other)
|
||||
{
|
||||
std::swap(s_, other.s_);
|
||||
std::swap(t_, other.t_);
|
||||
}
|
||||
|
||||
private:
|
||||
void
|
||||
run (std::function <void(void)> f)
|
||||
{
|
||||
try
|
||||
{
|
||||
f();
|
||||
}
|
||||
catch (suite::abort_exception const&)
|
||||
{
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // unit_test
|
||||
} // beast
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user