Support stream composition of testcase names

This commit is contained in:
Vinnie Falco
2014-07-15 13:46:37 -07:00
parent 7a92ac91d0
commit bb730c00e9
9 changed files with 482 additions and 244 deletions

View File

@@ -362,10 +362,14 @@ public:
{
typedef UnsignedInteger <Bytes> UInt;
#if 0
std::stringstream ss;
ss <<
c.name() << " <" << Bytes << ">";
testcase (ss.str());
#else
testcase << c.name() << " <" << Bytes << ">";
#endif
Random r;
for (int i = 0; i < 50; ++i)

View File

@@ -97,7 +97,7 @@ bool PeerTest::Results::report (unit_test::suite& suite,
bool beginTestCase) const
{
if (beginTestCase)
suite.testcase (name.toStdString());
suite.testcase << name.toStdString();
bool success = true;
if (! client.report (suite))
success = false;

View File

@@ -34,7 +34,7 @@ namespace unit_test {
/** Write test results to the specified output stream. */
/** @{ */
inline
template <class = void>
void
print (results const& r, abstract_ostream& stream)
{
@@ -66,7 +66,7 @@ print (results const& r, abstract_ostream& stream)
;
}
inline
template <class = void>
void
print (results const& r, std::ostream& stream = std::cout)
{

View File

@@ -35,15 +35,13 @@ private:
case_results m_case;
public:
recorder() = default;
recorder (recorder const&) = default;
recorder& operator= (recorder const&) = default;
recorder()
{
}
/** Returns a report with the results of all completed suites. */
results const& report() const
results const&
report() const
{
return m_results;
}

View File

@@ -127,7 +127,8 @@ public:
;
}
explicit reporter (abstract_ostream& stream)
explicit
reporter (abstract_ostream& stream)
: m_stream (stream)
{
}

View File

@@ -41,59 +41,43 @@ private:
class stream_t : public abstract_ostream
{
private:
runner& m_owner;
runner& owner_;
public:
stream_t() = delete;
stream_t& operator= (stream_t const&) = delete;
stream_t (runner& owner)
: m_owner (owner)
: owner_ (owner)
{
}
void
write (string_type const& s)
{
m_owner.log (s);
owner_.log (s);
}
};
stream_t m_stream;
bool m_default;
bool m_failed;
bool m_cond;
stream_t stream_;
bool default_;
bool failed_;
bool cond_;
public:
virtual ~runner() = default;
runner (runner const&) = default;
runner& operator= (runner const&) = default;
runner()
: m_stream (*this)
, m_default (false)
, m_failed (false)
, m_cond (false)
{
}
template <class = void>
runner();
/** Run the specified suite.
@return `true` if any conditions failed.
*/
template <class = void>
bool
run (suite_info const& s)
{
// Enable 'default' testcase
m_default = true;
m_failed = false;
on_suite_begin (s);
s.run (*this);
// Forgot to call pass or fail.
assert (m_cond);
on_case_end();
on_suite_end();
return m_failed;
}
run (suite_info const& s);
/** Run a sequence of suites.
The expression
@@ -103,13 +87,7 @@ public:
*/
template <class FwdIter>
bool
run (FwdIter first, FwdIter last)
{
bool failed (false);
for (;first != last; ++first)
failed = run (*first) || failed;
return failed;
}
run (FwdIter first, FwdIter last);
/** Conditionally run a sequence of suites.
pred will be called as:
@@ -120,27 +98,14 @@ public:
*/
template <class FwdIter, class Pred>
bool
run_if (FwdIter first, FwdIter last, Pred pred = Pred())
{
bool failed (false);
for (;first != last; ++first)
if (pred (*first))
failed = run (*first) || failed;
return failed;
}
run_if (FwdIter first, FwdIter last, Pred pred = Pred{});
/** Run all suites in a container.
@return `true` if any conditions failed.
*/
template <class SequenceContainer>
bool
run_each (SequenceContainer const& c)
{
bool failed (false);
for (auto const& s : c)
failed = run (s) || failed;
return failed;
}
run_each (SequenceContainer const& c);
/** Conditionally run suites in a container.
pred will be called as:
@@ -151,14 +116,7 @@ public:
*/
template <class SequenceContainer, class Pred>
bool
run_each_if (SequenceContainer const& c, Pred pred = Pred())
{
bool failed (false);
for (auto const& s : c)
if (pred (s))
failed = run (s) || failed;
return failed;
}
run_each_if (SequenceContainer const& c, Pred pred = Pred{});
private:
//
@@ -220,52 +178,141 @@ private:
abstract_ostream&
stream()
{
return m_stream;
return stream_;
}
// Start a new testcase.
template <class = void>
void
testcase (std::string const& name)
{
// Name may not be empty
assert (m_default || ! name.empty());
// Forgot to call pass or fail
assert (m_default || m_cond);
if (! m_default)
on_case_end();
m_default = false;
m_cond = false;
on_case_begin (name);
}
testcase (std::string const& name);
template <class = void>
void
pass()
{
if (m_default)
testcase ("");
on_pass();
m_cond = true;
}
pass();
template <class = void>
void
fail (std::string const& reason)
{
if (m_default)
testcase ("");
on_fail (reason);
m_failed = true;
m_cond = true;
}
fail (std::string const& reason);
template <class = void>
void
log (std::string const& s)
{
if (m_default)
testcase ("");
on_log (s);
}
log (std::string const& s);
};
//------------------------------------------------------------------------------
template <class>
runner::runner()
: stream_ (*this)
, default_ (false)
, failed_ (false)
, cond_ (false)
{
}
template <class>
bool
runner::run (suite_info const& s)
{
// Enable 'default' testcase
default_ = true;
failed_ = false;
on_suite_begin (s);
s.run (*this);
// Forgot to call pass or fail.
assert (cond_);
on_case_end();
on_suite_end();
return failed_;
}
template <class FwdIter>
bool
runner::run (FwdIter first, FwdIter last)
{
bool failed (false);
for (;first != last; ++first)
failed = run (*first) || failed;
return failed;
}
template <class FwdIter, class Pred>
bool
runner::run_if (FwdIter first, FwdIter last, Pred pred)
{
bool failed (false);
for (;first != last; ++first)
if (pred (*first))
failed = run (*first) || failed;
return failed;
}
template <class SequenceContainer>
bool
runner::run_each (SequenceContainer const& c)
{
bool failed (false);
for (auto const& s : c)
failed = run (s) || failed;
return failed;
}
template <class SequenceContainer, class Pred>
bool
runner::run_each_if (SequenceContainer const& c, Pred pred)
{
bool failed (false);
for (auto const& s : c)
if (pred (s))
failed = run (s) || failed;
return failed;
}
template <class>
void
runner::testcase (std::string const& name)
{
// Name may not be empty
assert (default_ || ! name.empty());
// Forgot to call pass or fail
assert (default_ || cond_);
if (! default_)
on_case_end();
default_ = false;
cond_ = false;
on_case_begin (name);
}
template <class>
void
runner::pass()
{
if (default_)
testcase ("");
on_pass();
cond_ = true;
}
template <class>
void
runner::fail (std::string const& reason)
{
if (default_)
testcase ("");
on_fail (reason);
failed_ = true;
cond_ = true;
}
template <class>
void
runner::log (std::string const& s)
{
if (default_)
testcase ("");
on_log (s);
}
} // unit_test
} // beast

View File

@@ -24,6 +24,7 @@
#include <beast/utility/noexcept.h>
#include <string>
#include <sstream>
namespace beast {
namespace unit_test {
@@ -36,53 +37,83 @@ namespace unit_test {
*/
class suite
{
private:
// 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
public:
enum abort_t
{
char const*
what() const noexcept override
{
return "suite aborted on failed condition";
}
no_abort_on_fail,
abort_on_fail
};
private:
bool abort_ = false;
runner* runner_ = nullptr;
struct abort_exception;
// Memberspace
class log_t
{
private:
friend class suite;
runner* m_runner;
runner*
operator-> ()
{
return m_runner;
}
suite* suite_ = nullptr;
public:
log_t ()
: m_runner (nullptr)
{
}
log_t () = default;
template <class T>
abstract_ostream::scoped_stream_type
operator<< (T const& t)
{
return m_runner->stream() << t;
}
operator<< (T const& t);
/** Returns the raw stream used for output. */
abstract_ostream&
stream()
{
return m_runner->stream();
}
stream();
};
bool m_abort = false;
class scoped_testcase;
// Memberspace
class testcase_t
{
private:
friend class suite;
suite* suite_ = nullptr;
std::stringstream ss_;
public:
testcase_t() = default;
/** Open a new testcase.
A testcase is a series of evaluated test conditions. A test suite
may have multiple test cases. A test is associated with the last
opened testcase. When the test first runs, a default unnamed
case is opened. Tests with only one case may omit the call
to testcase.
@param abort If `true`, the suite will be stopped on first failure.
*/
void
operator() (std::string const& name,
abort_t abort = no_abort_on_fail);
/** Stream style composition of testcase names. */
/** @{ */
scoped_testcase
operator() (abort_t abort);
template <class T>
scoped_testcase
operator<< (T const& t);
/** @} */
};
// 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.
@@ -113,6 +144,9 @@ public:
/** Memberspace for logging. */
log_t log;
/** Memberspace for declaring test cases. */
testcase_t testcase;
/** Invokes the test using the specified runner.
Data members are set up here instead of the constructor as a
convenience to writing the derived class to avoid repetition of
@@ -120,50 +154,7 @@ public:
Normally this is called by the framework for you.
*/
void
operator() (runner& r)
{
log.m_runner = &r;
try
{
run();
}
catch (abort_exception const&)
{
// ends the suite
}
catch (std::exception const& e)
{
fail (std::string ("unhandled exception: ") +
std::string (e.what()));
}
catch (...)
{
fail ("unhandled exception");
}
}
enum abort_t
{
no_abort_on_fail,
abort_on_fail
};
/** Open a new testcase.
A testcase is a series of evaluated test conditions. A test suite
may have multiple test cases. A test is associated with the last
opened testcase. When the test first runs, a default unnamed
case is opened. Tests with only one case may omit the call
to testcase.
@param abort If `true`, the suite will be stopped on first failure.
*/
void
testcase (std::string const& name,
abort_t abort = no_abort_on_fail)
{
m_abort = abort == abort_on_fail;
log->testcase (name);
}
operator() (runner& r);
/** Evaluate a test condition.
The condition is passed as a template argument instead of `bool` so
@@ -173,43 +164,24 @@ public:
*/
template <class Condition>
bool
expect (Condition shouldBeTrue, std::string const& reason = "")
{
if (shouldBeTrue)
pass();
else
fail (reason);
return shouldBeTrue;
}
expect (Condition shouldBeTrue, std::string const& reason = "");
// DEPRECATED
// @return `true` if the test condition indicates success (a false value)
template <class Condition>
bool
unexpected (Condition shouldBeFalse, std::string const& reason = "")
{
if (! shouldBeFalse)
pass();
else
fail (reason);
return ! shouldBeFalse;
}
unexpected (Condition shouldBeFalse, std::string const& reason = "");
/** Record a successful test condition. */
void
pass()
{
log->pass();
runner_->pass();
}
/** Record a failure. */
void
fail (std::string const& reason= "")
{
log->fail (reason);
if (m_abort)
throw abort_exception();
}
fail (std::string const& reason = "");
private:
/** Runs the suite. */
@@ -218,6 +190,206 @@ private:
run() = 0;
};
//------------------------------------------------------------------------------
// 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
suite::log_t::operator<< (T const& t)
{
return suite_->runner_->stream() << t;
}
/** Returns the raw stream used for output. */
inline
abstract_ostream&
suite::log_t::stream()
{
return suite_->runner_->stream();
}
//------------------------------------------------------------------------------
// Helper for streaming testcase names
class suite::scoped_testcase
{
private:
suite* suite_;
std::stringstream* ss_;
public:
~scoped_testcase();
scoped_testcase (suite* s, std::stringstream* ss);
template <class T>
scoped_testcase (suite* s, std::stringstream* ss, T const& t);
scoped_testcase& operator= (scoped_testcase const&) = delete;
template <class T>
scoped_testcase&
operator<< (T const& t);
};
inline
suite::scoped_testcase::~scoped_testcase()
{
auto const& name (ss_->str());
if (! name.empty())
suite_->runner_->testcase (name);
}
inline
suite::scoped_testcase::scoped_testcase (suite* s, std::stringstream* ss)
: suite_ (s)
, ss_ (ss)
{
ss_->clear();
ss_->str({});
}
template <class T>
inline
suite::scoped_testcase::scoped_testcase (suite* s, std::stringstream* ss, T const& t)
: suite_ (s)
, ss_ (ss)
{
ss_->clear();
ss_->str({});
*ss_ << t;
}
template <class T>
inline
suite::scoped_testcase&
suite::scoped_testcase::operator<< (T const& t)
{
*ss_ << t;
return *this;
}
//------------------------------------------------------------------------------
inline
void
suite::testcase_t::operator() (std::string const& name,
abort_t abort)
{
suite_->abort_ = abort == abort_on_fail;
suite_->runner_->testcase (name);
}
inline
suite::scoped_testcase
suite::testcase_t::operator() (abort_t abort)
{
suite_->abort_ = abort == abort_on_fail;
return { suite_, &ss_ };
}
template <class T>
inline
suite::scoped_testcase
suite::testcase_t::operator<< (T const& t)
{
return { suite_, &ss_, t };
}
//------------------------------------------------------------------------------
template <class>
void
suite::run (runner& r)
{
runner_ = &r;
log.suite_ = this;
testcase.suite_ = this;
try
{
run();
}
catch (abort_exception const&)
{
// ends the suite
}
catch (std::exception const& e)
{
fail (std::string ("unhandled exception: ") +
std::string (e.what()));
}
catch (...)
{
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

View File

@@ -42,19 +42,9 @@ private:
run_type m_run;
public:
suite_info (
char const* name,
char const* module,
char const* library,
bool manual,
run_type run)
: m_name (name)
, m_module (module)
, m_library (library)
, m_manual (manual)
, m_run (std::move (run))
{
}
template <class = void>
suite_info (char const* name, char const* module, char const* library,
bool manual, run_type run);
char const*
name() const
@@ -82,14 +72,9 @@ public:
}
/** Return the canonical suite name as a string. */
template <class = void>
std::string
full_name() const
{
return
std::string (m_library) + "." +
std::string (m_module) + "." +
std::string (m_name);
}
full_name() const;
/** Run a new instance of the associated test suite. */
void
@@ -99,6 +84,33 @@ public:
}
};
//------------------------------------------------------------------------------
template <class>
suite_info::suite_info (
char const* name,
char const* module,
char const* library,
bool manual,
run_type run)
: m_name (name)
, m_module (module)
, m_library (library)
, m_manual (manual)
, m_run (std::move (run))
{
}
template <class>
std::string
suite_info::full_name() const
{
return
std::string (m_library) + "." +
std::string (m_module) + "." +
std::string (m_name);
}
inline
bool
operator< (suite_info const& lhs, suite_info const& rhs)
@@ -108,8 +120,9 @@ operator< (suite_info const& lhs, suite_info const& rhs)
/** Convenience for producing suite_info for a given test type. */
template <class Suite>
suite_info make_suite_info (char const* name, char const* module,
char const* library, bool manual)
suite_info
make_suite_info (char const* name, char const* module, char const* library,
bool manual)
{
return suite_info (name, module, library, manual,
[](runner& r)

View File

@@ -35,15 +35,12 @@ namespace unit_test {
/** A container of test suites. */
class suite_list
: public const_container <
std::set <suite_info>
//std::list <suite_info>
>
: public const_container <std::set <suite_info>>
{
private:
#ifndef NDEBUG
std::unordered_set <std::string> m_names;
std::unordered_set <std::type_index> m_classes;
std::unordered_set <std::string> names_;
std::unordered_set <std::type_index> classes_;
#endif
public:
@@ -52,28 +49,34 @@ public:
*/
template <class Suite>
void
insert (char const* name,
char const* module, char const* library,
bool manual)
{
#ifndef NDEBUG
{
auto const result (m_names.insert (name));
assert (result.second); // Duplicate name
}
{
auto const result (m_classes.insert (
std::type_index (typeid(Suite))));
assert (result.second); // Duplicate type
}
#endif
cont().emplace (std::move (make_suite_info <Suite> (
name, module, library, manual)));
}
insert (char const* name, char const* module, char const* library,
bool manual);
};
//------------------------------------------------------------------------------
template <class Suite>
void
suite_list::insert (char const* name, char const* module, char const* library,
bool manual)
{
#ifndef NDEBUG
{
auto const result (names_.insert (name));
assert (result.second); // Duplicate name
}
{
auto const result (classes_.insert (
std::type_index (typeid(Suite))));
assert (result.second); // Duplicate type
}
#endif
cont().emplace (std::move (make_suite_info <Suite> (
name, module, library, manual)));
}
} // unit_test
} // beast