Refactor unit_test logging:

The log member is changed to derive from std::ostream. A new
class dstream is derived from std::ostream to support redirection
to the Visual Studio Output Window if a debugger is attached.

Obsolete classes abstract_ostream and its derived variants are
removed.
This commit is contained in:
Vinnie Falco
2016-05-10 07:09:57 -04:00
parent 908794bab2
commit e47811bef2
15 changed files with 316 additions and 844 deletions

View File

@@ -1,20 +0,0 @@
//
// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BEAST_UNIT_TEST_ABSTRACT_OSTREAM_HPP
#define BEAST_UNIT_TEST_ABSTRACT_OSTREAM_HPP
#include <beast/unit_test/basic_abstract_ostream.hpp>
namespace beast {
/** An abstract ostream for `char`. */
using abstract_ostream = basic_abstract_ostream <char>;
} // beast
#endif

View File

@@ -1,85 +0,0 @@
//
// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BEAST_UNIT_TEST_BASIC_ABSTRACT_OSTREAM_HPP
#define BEAST_UNIT_TEST_BASIC_ABSTRACT_OSTREAM_HPP
#include <beast/unit_test/basic_scoped_ostream.hpp>
#include <functional>
#include <memory>
#include <sstream>
namespace beast {
/** Abstraction for an output stream similar to std::basic_ostream. */
template <
class CharT,
class Traits = std::char_traits <CharT>
>
class basic_abstract_ostream
{
public:
using string_type = std::basic_string <CharT, Traits>;
using scoped_stream_type = basic_scoped_ostream <CharT, Traits>;
basic_abstract_ostream() = default;
virtual
~basic_abstract_ostream() = default;
basic_abstract_ostream (basic_abstract_ostream const&) = default;
basic_abstract_ostream& operator= (
basic_abstract_ostream const&) = default;
/** Returns `true` if the stream is active.
Inactive streams do not produce output.
*/
/** @{ */
virtual
bool
active() const
{
return true;
}
explicit
operator bool() const
{
return active();
}
/** @} */
/** Called to output each string. */
virtual
void
write (string_type const& s) = 0;
scoped_stream_type
operator<< (std::ostream& manip (std::ostream&))
{
return scoped_stream_type (manip,
[this](string_type const& s)
{
this->write (s);
});
}
template <class T>
scoped_stream_type
operator<< (T const& t)
{
return scoped_stream_type (t,
[this](string_type const& s)
{
this->write (s);
});
}
};
} // beast
#endif

View File

@@ -1,136 +0,0 @@
//
// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BEAST_UNIT_TEST_BASIC_SCOPED_OSTREAM_HPP
#define BEAST_UNIT_TEST_BASIC_SCOPED_OSTREAM_HPP
#include <functional>
#include <memory>
#include <sstream>
// gcc libstd++ doesn't have move constructors for basic_ostringstream
// http://gcc.gnu.org/bugzilla/show_bug.cgi?id=54316
//
#ifndef BEAST_NO_STDLIB_STREAM_MOVE
# ifdef __GLIBCXX__
# define BEAST_NO_STDLIB_STREAM_MOVE 1
# else
# define BEAST_NO_STDLIB_STREAM_MOVE 0
# endif
#endif
namespace beast {
template <
class CharT,
class Traits
>
class basic_abstract_ostream;
/** Scoped output stream that forwards to a functor upon destruction. */
template <
class CharT,
class Traits = std::char_traits <CharT>,
class Allocator = std::allocator <CharT>
>
class basic_scoped_ostream
{
private:
using handler_t = std::function <void (
std::basic_string <CharT, Traits, Allocator> const&)>;
using stream_type = std::basic_ostringstream <
CharT, Traits, Allocator>;
handler_t m_handler;
#if BEAST_NO_STDLIB_STREAM_MOVE
std::unique_ptr <stream_type> m_ss;
stream_type& stream()
{
return *m_ss;
}
#else
stream_type m_ss;
stream_type& stream()
{
return m_ss;
}
#endif
public:
using string_type = std::basic_string <CharT, Traits>;
// Disallow copy since that would duplicate the output
basic_scoped_ostream (basic_scoped_ostream const&) = delete;
basic_scoped_ostream& operator= (basic_scoped_ostream const) = delete;
template <class Handler>
explicit basic_scoped_ostream (Handler&& handler)
: m_handler (std::forward <Handler> (handler))
#if BEAST_NO_STDLIB_STREAM_MOVE
, m_ss (new stream_type())
#endif
{
}
template <class T, class Handler>
basic_scoped_ostream (T const& t, Handler&& handler)
: m_handler (std::forward <Handler> (handler))
#if BEAST_NO_STDLIB_STREAM_MOVE
, m_ss (new stream_type())
#endif
{
stream() << t;
}
basic_scoped_ostream (basic_abstract_ostream <
CharT, Traits>& ostream)
: m_handler (
[&](string_type const& s)
{
ostream.write (s);
})
{
}
basic_scoped_ostream (basic_scoped_ostream&& other)
: m_handler (std::move (other.m_handler))
, m_ss (std::move (other.m_ss))
{
}
~basic_scoped_ostream()
{
auto const& s (stream().str());
if (! s.empty())
m_handler (s);
}
basic_scoped_ostream&
operator<< (std::ostream& manip (std::ostream&))
{
stream() << manip;
return *this;
}
template <class T>
basic_scoped_ostream&
operator<< (T const& t)
{
stream() << t;
return *this;
}
};
} // beast
#endif

View File

@@ -1,60 +0,0 @@
//
// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BEAST_UNIT_TEST_BASIC_STD_OSTREAM_HPP
#define BEAST_UNIT_TEST_BASIC_STD_OSTREAM_HPP
#include <beast/unit_test/basic_abstract_ostream.hpp>
#include <ostream>
namespace beast {
/** Wraps an existing std::basic_ostream as an abstract_ostream. */
template <
class CharT,
class Traits = std::char_traits <CharT>
>
class basic_std_ostream
: public basic_abstract_ostream <CharT, Traits>
{
private:
using typename basic_abstract_ostream <CharT, Traits>::string_type;
std::reference_wrapper <std::ostream> m_stream;
public:
explicit basic_std_ostream (
std::basic_ostream <CharT, Traits>& stream)
: m_stream (stream)
{
}
void
write (string_type const& s) override
{
m_stream.get() << s << std::endl;
}
};
using std_ostream = basic_std_ostream <char>;
//------------------------------------------------------------------------------
/** Returns a basic_std_ostream using template argument deduction. */
template <
class CharT,
class Traits = std::char_traits <CharT>
>
basic_std_ostream <CharT, Traits>
make_std_ostream (std::basic_ostream <CharT, Traits>& stream)
{
return basic_std_ostream <CharT, Traits> (stream);
}
} // beast
#endif

View File

@@ -1,78 +0,0 @@
//
// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BEAST_UNIT_TEST_DEBUG_OSTREAM_HPP
#define BEAST_UNIT_TEST_DEBUG_OSTREAM_HPP
#include <beast/unit_test/abstract_ostream.hpp>
#include <iostream>
#ifdef _MSC_VER
# ifndef WIN32_LEAN_AND_MEAN // VC_EXTRALEAN
# define WIN32_LEAN_AND_MEAN
#include <windows.h>
# undef WIN32_LEAN_AND_MEAN
# else
#include <windows.h>
# endif
# ifdef min
# undef min
# endif
# ifdef max
# undef max
# endif
#endif
namespace beast {
#ifdef _MSC_VER
/** A basic_abstract_ostream that redirects output to an attached debugger. */
class debug_ostream
: public abstract_ostream
{
private:
bool m_debugger;
public:
debug_ostream()
: m_debugger (IsDebuggerPresent() != FALSE)
{
// Note that the check for an attached debugger is made only
// during construction time, for efficiency. A stream created before
// the debugger is attached will not have output redirected.
}
void
write (string_type const& s) override
{
if (m_debugger)
{
OutputDebugStringA ((s + "\n").c_str());
return;
}
std::cout << s << std::endl;
}
};
#else
class debug_ostream
: public abstract_ostream
{
public:
void
write (string_type const& s) override
{
std::cout << s << std::endl;
}
};
#endif
} // beast
#endif

View File

@@ -73,8 +73,8 @@ public:
int
sync() override
{
write(str().c_str());
str("");
write(this->str().c_str());
this->str("");
return 0;
}
};
@@ -107,8 +107,8 @@ public:
int
sync() override
{
write(str().c_str());
str("");
write(this->str().c_str());
this->str("");
return 0;
}
};
@@ -130,7 +130,7 @@ class basic_dstream
{
public:
basic_dstream()
: std::basic_ostream<CharT, Traits>(&member)
: std::basic_ostream<CharT, Traits>(&this->member)
{
}
};

View File

@@ -15,7 +15,8 @@ namespace unit_test {
namespace detail {
template <class = void>
/// Holds test suites registered during static initialization.
inline
suite_list&
global_suites()
{
@@ -23,26 +24,20 @@ global_suites()
return s;
}
template <class Suite>
template<class Suite>
struct insert_suite
{
template <class = void>
insert_suite (char const* name, char const* module,
char const* library, bool manual);
insert_suite(char const* name, char const* module,
char const* library, bool manual)
{
global_suites().insert<Suite>(
name, module, library, manual);
}
};
template <class Suite>
template <class>
insert_suite<Suite>::insert_suite (char const* name,
char const* module, char const* library, bool manual)
{
global_suites().insert <Suite> (
name, module, library, manual);
}
} // detail
/** Holds suites registered during static initialization. */
/// Holds test suites registered during static initialization.
inline
suite_list const&
global_suites()

View File

@@ -6,12 +6,13 @@
//
#include <beast/unit_test/amount.hpp>
#include <beast/unit_test/dstream.hpp>
#include <beast/unit_test/global_suites.hpp>
#include <beast/unit_test/match.hpp>
#include <beast/unit_test/reporter.hpp>
#include <beast/unit_test/suite.hpp>
#include <beast/unit_test/debug_ostream.hpp>
#include <boost/program_options.hpp>
#include <cstdlib>
#include <iostream>
#include <vector>
@@ -25,11 +26,10 @@
# endif
#endif
#include <cstdlib>
namespace beast {
namespace unit_test {
static
std::string
prefix(suite_info const& s)
{
@@ -38,32 +38,34 @@ prefix(suite_info const& s)
return " ";
}
template<class Log>
static
void
print(Log& log, suite_list const& c)
print(std::ostream& os, suite_list const& c)
{
std::size_t manual = 0;
for(auto const& s : c)
{
log <<
prefix (s) <<
s.full_name();
os << prefix (s) << s.full_name() << '\n';
if(s.manual())
++manual;
}
log <<
os <<
amount(c.size(), "suite") << " total, " <<
amount(manual, "manual suite")
amount(manual, "manual suite") <<
'\n'
;
}
template<class Log>
// Print the list of suites
// Used with the --print command line option
static
void
print(Log& log)
print(std::ostream& os)
{
log << "------------------------------------------";
print(log, global_suites());
log << "------------------------------------------";
os << "------------------------------------------\n";
print(os, global_suites());
os << "------------------------------------------" <<
std::endl;
}
} // unit_test
@@ -97,11 +99,11 @@ int main(int ac, char const* av[])
po::store(po::parse_command_line(ac, av, desc), vm);
po::notify(vm);
beast::debug_ostream log;
dstream log;
if(vm.count("help"))
{
log << desc;
log << desc << std::endl;
}
else if(vm.count("print"))
{

View File

@@ -1,67 +0,0 @@
//
// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BEAST_UNIT_TEST_PRINT_H_INCLUDED
#define BEAST_UNIT_TEST_PRINT_H_INCLUDED
#include <beast/unit_test/amount.hpp>
#include <beast/unit_test/results.hpp>
#include <beast/unit_test/abstract_ostream.hpp>
#include <beast/unit_test/basic_std_ostream.hpp>
#include <iostream>
#include <string>
namespace beast {
namespace unit_test {
/** Write test results to the specified output stream. */
/** @{ */
template <class = void>
void
print (results const& r, abstract_ostream& stream)
{
for (auto const& s : r)
{
for (auto const& c : s)
{
stream <<
s.name() <<
(c.name().empty() ? "" : ("." + c.name()));
std::size_t i (1);
for (auto const& t : c.tests)
{
if (! t.pass)
stream <<
"#" << i <<
" failed: " << t.reason;
++i;
}
}
}
stream <<
amount (r.size(), "suite") << ", " <<
amount (r.cases(), "case") << ", " <<
amount (r.total(), "test") << " total, " <<
amount (r.failed(), "failure")
;
}
template <class = void>
void
print (results const& r, std::ostream& stream = std::cout)
{
auto s (make_std_ostream (stream));
print (r, s);
}
} // unit_test
} // beast
#endif

View File

@@ -10,8 +10,6 @@
#include <beast/unit_test/amount.hpp>
#include <beast/unit_test/recorder.hpp>
#include <beast/unit_test/abstract_ostream.hpp>
#include <beast/unit_test/basic_std_ostream.hpp>
#include <boost/optional.hpp>
#include <algorithm>
#include <chrono>
@@ -30,7 +28,7 @@ namespace detail {
/** A simple test runner that writes everything to a stream in real time.
The totals are output when the object is destroyed.
*/
template <class = void>
template<class = void>
class reporter : public runner
{
private:
@@ -42,7 +40,11 @@ private:
std::size_t total = 0;
std::size_t failed = 0;
case_results (std::string const& name_ = "");
explicit
case_results(std::string name_ = "")
: name(std::move(name_))
{
}
};
struct suite_results
@@ -51,14 +53,16 @@ private:
std::size_t cases = 0;
std::size_t total = 0;
std::size_t failed = 0;
typename clock_type::time_point start =
clock_type::now();
typename clock_type::time_point start = clock_type::now();
explicit
suite_results (std::string const& name_ = "");
suite_results(std::string const& name_ = "")
: name(std::move(name_))
{
}
void
add (case_results const& r);
add(case_results const& r);
};
struct results
@@ -76,35 +80,30 @@ private:
std::size_t total = 0;
std::size_t failed = 0;
std::vector<run_time> top;
typename clock_type::time_point start =
clock_type::now();
typename clock_type::time_point start = clock_type::now();
void
add (suite_results const& r);
};
boost::optional <std_ostream> std_ostream_;
std::reference_wrapper <beast::abstract_ostream> stream_;
std::ostream& os_;
results results_;
suite_results suite_results_;
case_results case_results_;
public:
reporter (reporter const&) = delete;
reporter& operator= (reporter const&) = delete;
reporter(reporter const&) = delete;
reporter& operator=(reporter const&) = delete;
~reporter();
explicit
reporter (std::ostream& stream = std::cout);
explicit
reporter (beast::abstract_ostream& stream);
reporter(std::ostream& os = std::cout);
private:
static
std::string
fmtdur (typename clock_type::duration const& d);
fmtdur(typename clock_type::duration const& d);
virtual
void
@@ -137,42 +136,27 @@ private:
//------------------------------------------------------------------------------
template <class _>
reporter<_>::case_results::case_results (
std::string const& name_)
: name (name_)
{
}
template <class _>
reporter<_>::suite_results::suite_results (
std::string const& name_)
: name (name_)
{
}
template <class _>
template<class _>
void
reporter<_>::suite_results::add (case_results const& r)
reporter<_>::
suite_results::add(case_results const& r)
{
++cases;
total += r.total;
failed += r.failed;
}
template <class _>
template<class _>
void
reporter<_>::results::add (
suite_results const& r)
reporter<_>::
results::add(suite_results const& r)
{
++suites;
total += r.total;
cases += r.cases;
failed += r.failed;
auto const elapsed =
clock_type::now() - r.start;
if (elapsed >= std::chrono::seconds(1))
auto const elapsed = clock_type::now() - r.start;
if (elapsed >= std::chrono::seconds{1})
{
auto const iter = std::lower_bound(top.begin(),
top.end(), elapsed,
@@ -196,50 +180,40 @@ reporter<_>::results::add (
//------------------------------------------------------------------------------
template <class _>
reporter<_>::reporter (
std::ostream& stream)
: std_ostream_ (std::ref (stream))
, stream_ (*std_ostream_)
template<class _>
reporter<_>::
reporter(std::ostream& os)
: os_(os)
{
}
template <class _>
template<class _>
reporter<_>::~reporter()
{
if (results_.top.size() > 0)
if(results_.top.size() > 0)
{
stream_.get() << "Longest suite times:";
for (auto const& i : results_.top)
stream_.get() << std::setw(8) <<
fmtdur(i.second) << " " << i.first;
os_ << "Longest suite times:\n";
for(auto const& i : results_.top)
os_ << std::setw(8) <<
fmtdur(i.second) << " " << i.first << '\n';
}
auto const elapsed =
clock_type::now() - results_.start;
stream_.get() <<
auto const elapsed = clock_type::now() - results_.start;
os_ <<
fmtdur(elapsed) << ", " <<
amount (results_.suites, "suite") << ", " <<
amount (results_.cases, "case") << ", " <<
amount (results_.total, "test") << " total, " <<
amount (results_.failed, "failure");
amount{results_.suites, "suite"} << ", " <<
amount{results_.cases, "case"} << ", " <<
amount{results_.total, "test"} << " total, " <<
amount{results_.failed, "failure"} <<
std::endl;
}
template <class _>
reporter<_>::reporter (
abstract_ostream& stream)
: stream_ (stream)
{
}
template <class _>
template<class _>
std::string
reporter<_>::fmtdur (
typename clock_type::duration const& d)
reporter<_>::fmtdur(typename clock_type::duration const& d)
{
using namespace std::chrono;
auto const ms =
duration_cast<milliseconds>(d);
if (ms < seconds(1))
auto const ms = duration_cast<milliseconds>(d);
if (ms < seconds{1})
return std::to_string(ms.count()) + "ms";
std::stringstream ss;
ss << std::fixed << std::setprecision(1) <<
@@ -247,67 +221,67 @@ reporter<_>::fmtdur (
return ss.str();
}
template <class _>
template<class _>
void
reporter<_>::on_suite_begin (
suite_info const& info)
reporter<_>::
on_suite_begin(suite_info const& info)
{
suite_results_ = suite_results (info.full_name());
suite_results_ = suite_results{info.full_name()};
}
template <class _>
template<class _>
void
reporter<_>::on_suite_end()
{
results_.add (suite_results_);
results_.add(suite_results_);
}
template <class _>
template<class _>
void
reporter<_>::on_case_begin (
std::string const& name)
reporter<_>::
on_case_begin(std::string const& name)
{
case_results_ = case_results (name);
stream_.get() <<
os_ <<
suite_results_.name <<
(case_results_.name.empty() ?
"" : (" " + case_results_.name));
"" : (" " + case_results_.name)) << std::endl;
}
template <class _>
template<class _>
void
reporter<_>::on_case_end()
reporter<_>::
on_case_end()
{
suite_results_.add (case_results_);
suite_results_.add(case_results_);
}
template <class _>
template<class _>
void
reporter<_>::on_pass()
reporter<_>::
on_pass()
{
++case_results_.total;
}
template <class _>
template<class _>
void
reporter<_>::on_fail (
std::string const& reason)
reporter<_>::
on_fail(std::string const& reason)
{
++case_results_.failed;
++case_results_.total;
stream_.get() <<
"#" << case_results_.total <<
" failed" <<
(reason.empty() ? "" : ": ") << reason;
os_ <<
"#" << case_results_.total << " failed" <<
(reason.empty() ? "" : ": ") << reason << std::endl;
}
template <class _>
template<class _>
void
reporter<_>::on_log (
std::string const& s)
reporter<_>::
on_log(std::string const& s)
{
stream_.get() << s;
os_ << s;
}
} // detail

View File

@@ -9,9 +9,9 @@
#define BEAST_UNIT_TEST_RUNNER_H_INCLUDED
#include <beast/unit_test/suite_info.hpp>
#include <beast/unit_test/abstract_ostream.hpp>
#include <cassert>
#include <mutex>
#include <ostream>
#include <string>
namespace beast {
@@ -23,28 +23,6 @@ namespace unit_test {
*/
class runner
{
private:
// Reroutes log output to the runner
class stream_t : public abstract_ostream
{
private:
runner& owner_;
public:
stream_t() = delete;
stream_t& operator= (stream_t const&) = delete;
template <class = void>
stream_t (runner& owner);
void
write (string_type const& s) override
{
owner_.log (s);
}
};
stream_t stream_;
std::string arg_;
bool default_ = false;
bool failed_ = false;
@@ -52,21 +30,20 @@ private:
std::recursive_mutex mutex_;
public:
runner() = default;
virtual ~runner() = default;
runner (runner const&) = default;
runner& operator= (runner const&) = default;
template <class = void>
runner();
runner(runner const&) = delete;
runner& operator=(runner const&) = delete;
/** Set the argument string.
The argument string is available to suites and
allows for customization of the test. Each suite
defines its own syntax for the argumnet string.
The same argument is passed to all suites.
*/
void
arg (std::string const& s)
arg(std::string const& s)
{
arg_ = s;
}
@@ -81,7 +58,7 @@ public:
/** Run the specified suite.
@return `true` if any conditions failed.
*/
template <class = void>
template<class = void>
bool
run (suite_info const& s);
@@ -124,69 +101,63 @@ public:
bool
run_each_if (SequenceContainer const& c, Pred pred = Pred{});
private:
protected:
//
// Overrides
//
/** Called when a new suite starts. */
/// Called when a new suite starts.
virtual
void
on_suite_begin (suite_info const&)
on_suite_begin(suite_info const&)
{
}
/** Called when a suite ends. */
/// Called when a suite ends.
virtual
void
on_suite_end()
{
}
/** Called when a new case starts. */
/// Called when a new case starts.
virtual
void
on_case_begin (std::string const&)
on_case_begin(std::string const&)
{
}
/** Called when a new case ends. */
/// Called when a new case ends.
virtual
void
on_case_end()
{
}
/** Called for each passing condition. */
/// Called for each passing condition.
virtual
void
on_pass ()
on_pass()
{
}
/** Called for each failing condition. */
/// Called for each failing condition.
virtual
void
on_fail (std::string const&)
on_fail(std::string const&)
{
}
/** Called when a test logs output. */
/// Called when a test logs output.
virtual
void
on_log (std::string const&)
on_log(std::string const&)
{
}
private:
friend class suite;
abstract_ostream&
stream()
{
return stream_;
}
// Start a new testcase.
template <class = void>
void
@@ -207,20 +178,6 @@ private:
//------------------------------------------------------------------------------
template <class>
runner::stream_t::stream_t (runner& owner)
: owner_ (owner)
{
}
//------------------------------------------------------------------------------
template <class>
runner::runner()
: stream_ (*this)
{
}
template <class>
bool
runner::run (suite_info const& s)

View File

@@ -9,15 +9,23 @@
#define BEAST_UNIT_TEST_SUITE_HPP
#include <beast/unit_test/runner.hpp>
#include <string>
#include <ostream>
#include <sstream>
#include <string>
namespace beast {
namespace unit_test {
class thread;
enum abort_t
{
no_abort_on_fail,
abort_on_fail
};
/** 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,
derive from it and use the BEAST_DEFINE_UNIT_TEST macro in a
@@ -25,13 +33,6 @@ class thread;
*/
class suite
{
public:
enum abort_t
{
no_abort_on_fail,
abort_on_fail
};
private:
bool abort_ = false;
bool aborted_ = false;
@@ -44,95 +45,100 @@ private:
char const*
what() const noexcept override
{
return "suite aborted";
return "test suite aborted";
}
};
public:
// Memberspace
class log_t
template<class CharT, class Traits, class Allocator>
class log_buf
: public std::basic_stringbuf<CharT, Traits, Allocator>
{
private:
friend class suite;
suite* suite_ = nullptr;
suite& suite_;
public:
log_t () = default;
explicit
log_buf(suite& self)
: suite_(self)
{
}
template <class T>
abstract_ostream::scoped_stream_type
operator<< (T const& t);
~log_buf()
{
sync();
}
/** Returns the raw stream used for output. */
abstract_ostream&
stream();
int
sync() override
{
auto const& s = this->str();
if(s.size() > 0)
suite_.runner_->on_log(s);
this->str("");
return 0;
}
};
template<
class CharT,
class Traits = std::char_traits<CharT>,
class Allocator = std::allocator<CharT>
>
class log_os : public std::basic_ostream<CharT, Traits>
{
log_buf<CharT, Traits, Allocator> buf_;
public:
explicit
log_os(suite& self)
: std::basic_ostream<CharT, Traits>(&buf_)
, buf_(self)
{
}
};
private:
class scoped_testcase;
// Memberspace
class testcase_t
{
private:
friend class suite;
suite* suite_ = nullptr;
suite& suite_;
std::stringstream ss_;
public:
testcase_t() = default;
explicit
testcase_t(suite& self)
: suite_(self)
{
}
/** 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.
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 Determines if suite continues running after a failure.
*/
void
operator() (std::string const& name,
operator()(std::string const& name,
abort_t abort = no_abort_on_fail);
/** Stream style composition of testcase names. */
/** @{ */
scoped_testcase
operator() (abort_t abort);
operator()(abort_t abort);
template <class T>
scoped_testcase
operator<< (T const& t);
/** @} */
operator<<(T const& t);
};
public:
/** Type for scoped stream logging.
To use this type, declare a local variable of the type
on the stack in derived class member function and construct
it from log.stream();
/** Logging output stream.
@code
scoped_stream ss (log.stream();
ss << "Hello" << std::endl;
ss << "world" << std::endl;
@endcode
Streams constructed in this fashion will not have the line
ending automatically appended.
Thread safety:
The scoped_stream may only be used by one thread.
Multiline output sent to the stream will be atomically
written to the underlying abstract_Ostream
Text sent to the log output stream will be forwarded to
the output stream associated with the runner.
*/
using scoped_stream = abstract_ostream::scoped_stream_type;
/** Memberspace for logging. */
log_t log;
log_os<char> log;
/** Memberspace for declaring test cases. */
testcase_t testcase;
@@ -145,6 +151,12 @@ public:
return *p_this_suite();
}
suite()
: log(*this)
, testcase(*this)
{
}
/** 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
@@ -272,84 +284,51 @@ private:
//------------------------------------------------------------------------------
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_;
suite& suite_;
std::stringstream& ss_;
public:
~scoped_testcase();
scoped_testcase& operator=(scoped_testcase const&) = delete;
scoped_testcase (suite* s, std::stringstream* ss);
~scoped_testcase()
{
auto const& name = ss_.str();
if(! name.empty())
suite_.runner_->testcase (name);
}
template <class T>
scoped_testcase (suite* s, std::stringstream* ss, T const& t);
scoped_testcase(suite& self, std::stringstream& ss)
: suite_(self)
, ss_(ss)
{
ss_.clear();
ss_.str({});
}
scoped_testcase& operator= (scoped_testcase const&) = delete;
template<class T>
scoped_testcase(suite& self,
std::stringstream& ss, T const& t)
: suite_(self)
, ss_(ss)
{
ss_.clear();
ss_.str({});
ss_ << t;
}
template <class T>
template<class T>
scoped_testcase&
operator<< (T const& t);
operator<<(T const& t)
{
ss_ << t;
return *this;
}
};
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
@@ -357,16 +336,16 @@ void
suite::testcase_t::operator() (std::string const& name,
abort_t abort)
{
suite_->abort_ = abort == abort_on_fail;
suite_->runner_->testcase (name);
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_ };
suite_.abort_ = abort == abort_on_fail;
return { suite_, ss_ };
}
template<class T>
@@ -374,7 +353,7 @@ inline
suite::scoped_testcase
suite::testcase_t::operator<< (T const& t)
{
return { suite_, &ss_, t };
return { suite_, ss_, t };
}
//------------------------------------------------------------------------------
@@ -511,8 +490,6 @@ void
suite::run (runner& r)
{
runner_ = &r;
log.suite_ = this;
testcase.suite_ = this;
try
{

View File

@@ -8,6 +8,7 @@
#ifndef BEAST_UNIT_TEST_SUITE_INFO_HPP
#define BEAST_UNIT_TEST_SUITE_INFO_HPP
#include <cstring>
#include <functional>
#include <string>
#include <utility>
@@ -20,19 +21,28 @@ class runner;
/** Associates a unit test type with metadata. */
class suite_info
{
private:
using run_type = std::function <void (runner&)>;
using run_type = std::function<void(runner&)>;
std::string name_;
std::string module_;
std::string library_;
bool m_manual;
run_type m_run;
bool manual_;
run_type run_;
public:
template <class = void>
suite_info (std::string const& name, std::string const& module,
std::string const& library, bool manual, run_type run);
suite_info(
std::string name,
std::string module,
std::string library,
bool manual,
run_type run)
: name_(std::move(name))
, module_(std::move(module))
, library_(std::move(library))
, manual_(manual)
, run_(std::move(run))
{
}
std::string const&
name() const
@@ -52,61 +62,58 @@ public:
return library_;
}
/** Returns `true` if this suite only runs manually. */
/// Returns `true` if this suite only runs manually.
bool
manual() const
{
return m_manual;
return manual_;
}
/** Return the canonical suite name as a string. */
template <class = void>
/// Return the canonical suite name as a string.
std::string
full_name() const;
/** Run a new instance of the associated test suite. */
void
run (runner& r) const
full_name() const
{
m_run (r);
return library_ + "." + module_ + "." + name_;
}
/// Run a new instance of the associated test suite.
void
run(runner& r) const
{
run_ (r);
}
friend
bool
operator<(suite_info const& lhs, suite_info const& rhs)
{
return
std::tie(lhs.library_, lhs.module_, lhs.name_) <
std::tie(rhs.library_, rhs.module_, rhs.name_);
}
};
//------------------------------------------------------------------------------
template <class>
suite_info::suite_info (std::string const& name, std::string const& module,
std::string const& library, bool manual, run_type run)
: name_ (name)
, module_ (module)
, library_ (library)
, m_manual (manual)
, m_run (std::move (run))
{
}
template <class>
std::string
suite_info::full_name() const
{
return library_ + "." + module_ + "." + name_;
}
inline
bool
operator< (suite_info const& lhs, suite_info const& rhs)
{
return lhs.full_name() < rhs.full_name();
}
/** Convenience for producing suite_info for a given test type. */
template <class Suite>
/// Convenience for producing suite_info for a given test type.
template<class Suite>
suite_info
make_suite_info (std::string const& name, std::string const& module,
std::string const& library, bool manual)
make_suite_info(
std::string name,
std::string module,
std::string library,
bool manual)
{
return suite_info(name, module, library, manual,
[](runner& r) { return Suite{}(r); });
return suite_info(
std::move(name),
std::move(module),
std::move(library),
manual,
[](runner& r)
{
Suite{}(r);
}
);
}
} // unit_test

View File

@@ -18,23 +18,27 @@
namespace beast {
namespace unit_test {
/** A container of test suites. */
/// A container of test suites.
class suite_list
: public detail::const_container <std::set <suite_info>>
{
private:
#ifndef NDEBUG
std::unordered_set <std::string> names_;
std::unordered_set <std::type_index> classes_;
std::unordered_set<std::string> names_;
std::unordered_set<std::type_index> classes_;
#endif
public:
/** Insert a suite into the set.
The suite must not already exist.
*/
template <class Suite>
void
insert (char const* name, char const* module, char const* library,
insert(
char const* name,
char const* module,
char const* library,
bool manual);
};
@@ -42,7 +46,10 @@ public:
template <class Suite>
void
suite_list::insert (char const* name, char const* module, char const* library,
suite_list::insert(
char const* name,
char const* module,
char const* library,
bool manual)
{
#ifndef NDEBUG
@@ -59,9 +66,8 @@ suite_list::insert (char const* name, char const* module, char const* library,
assert (result.second); // Duplicate type
}
#endif
cont().emplace (std::move (make_suite_info <Suite> (
name, module, library, manual)));
cont().emplace(make_suite_info<Suite>(
name, module, library, manual));
}
} // unit_test

View File

@@ -85,7 +85,7 @@ public:
{
using namespace std::chrono;
using clock_type = std::chrono::high_resolution_clock;
log << name;
log << name << std::endl;
for(std::size_t trial = 1; trial <= repeat; ++trial)
{
auto const t0 = clock_type::now();
@@ -93,7 +93,7 @@ public:
auto const elapsed = clock_type::now() - t0;
log <<
"Trial " << trial << ": " <<
duration_cast<milliseconds>(elapsed).count() << " ms";
duration_cast<milliseconds>(elapsed).count() << " ms" << std::endl;
}
}
@@ -109,10 +109,10 @@ public:
static std::size_t constexpr Repeat = 50;
log << "sizeof(request parser) == " <<
sizeof(basic_parser_v1<true, null_parser<true>>);
sizeof(basic_parser_v1<true, null_parser<true>>) << '\n';
log << "sizeof(response parser) == " <<
sizeof(basic_parser_v1<false, null_parser<true>>);
sizeof(basic_parser_v1<false, null_parser<true>>)<< '\n';
testcase << "Parser speed test, " <<
((Repeat * size_ + 512) / 1024) << "KB in " <<