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:
Vinnie Falco
2015-01-08 17:48:41 -08:00
committed by Edward Hennis
parent adc69e72df
commit faf91d6697
2 changed files with 210 additions and 81 deletions

View File

@@ -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
View 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