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 int
sync() override sync() override
{ {
write(str().c_str()); write(this->str().c_str());
str(""); this->str("");
return 0; return 0;
} }
}; };
@@ -107,8 +107,8 @@ public:
int int
sync() override sync() override
{ {
write(str().c_str()); write(this->str().c_str());
str(""); this->str("");
return 0; return 0;
} }
}; };
@@ -130,7 +130,7 @@ class basic_dstream
{ {
public: public:
basic_dstream() 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 { namespace detail {
template <class = void> /// Holds test suites registered during static initialization.
inline
suite_list& suite_list&
global_suites() global_suites()
{ {
@@ -26,23 +27,17 @@ global_suites()
template<class Suite> template<class Suite>
struct insert_suite struct insert_suite
{ {
template <class = void>
insert_suite(char const* name, char const* module, insert_suite(char const* name, char const* module,
char const* library, bool manual); char const* library, bool 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>( global_suites().insert<Suite>(
name, module, library, manual); name, module, library, manual);
} }
};
} // detail } // detail
/** Holds suites registered during static initialization. */ /// Holds test suites registered during static initialization.
inline inline
suite_list const& suite_list const&
global_suites() global_suites()

View File

@@ -6,12 +6,13 @@
// //
#include <beast/unit_test/amount.hpp> #include <beast/unit_test/amount.hpp>
#include <beast/unit_test/dstream.hpp>
#include <beast/unit_test/global_suites.hpp> #include <beast/unit_test/global_suites.hpp>
#include <beast/unit_test/match.hpp> #include <beast/unit_test/match.hpp>
#include <beast/unit_test/reporter.hpp> #include <beast/unit_test/reporter.hpp>
#include <beast/unit_test/suite.hpp> #include <beast/unit_test/suite.hpp>
#include <beast/unit_test/debug_ostream.hpp>
#include <boost/program_options.hpp> #include <boost/program_options.hpp>
#include <cstdlib>
#include <iostream> #include <iostream>
#include <vector> #include <vector>
@@ -25,11 +26,10 @@
# endif # endif
#endif #endif
#include <cstdlib>
namespace beast { namespace beast {
namespace unit_test { namespace unit_test {
static
std::string std::string
prefix(suite_info const& s) prefix(suite_info const& s)
{ {
@@ -38,32 +38,34 @@ prefix(suite_info const& s)
return " "; return " ";
} }
template<class Log> static
void void
print(Log& log, suite_list const& c) print(std::ostream& os, suite_list const& c)
{ {
std::size_t manual = 0; std::size_t manual = 0;
for(auto const& s : c) for(auto const& s : c)
{ {
log << os << prefix (s) << s.full_name() << '\n';
prefix (s) <<
s.full_name();
if(s.manual()) if(s.manual())
++manual; ++manual;
} }
log << os <<
amount(c.size(), "suite") << " total, " << 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 void
print(Log& log) print(std::ostream& os)
{ {
log << "------------------------------------------"; os << "------------------------------------------\n";
print(log, global_suites()); print(os, global_suites());
log << "------------------------------------------"; os << "------------------------------------------" <<
std::endl;
} }
} // unit_test } // unit_test
@@ -97,11 +99,11 @@ int main(int ac, char const* av[])
po::store(po::parse_command_line(ac, av, desc), vm); po::store(po::parse_command_line(ac, av, desc), vm);
po::notify(vm); po::notify(vm);
beast::debug_ostream log; dstream log;
if(vm.count("help")) if(vm.count("help"))
{ {
log << desc; log << desc << std::endl;
} }
else if(vm.count("print")) 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/amount.hpp>
#include <beast/unit_test/recorder.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 <boost/optional.hpp>
#include <algorithm> #include <algorithm>
#include <chrono> #include <chrono>
@@ -42,7 +40,11 @@ private:
std::size_t total = 0; std::size_t total = 0;
std::size_t failed = 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 struct suite_results
@@ -51,11 +53,13 @@ private:
std::size_t cases = 0; std::size_t cases = 0;
std::size_t total = 0; std::size_t total = 0;
std::size_t failed = 0; std::size_t failed = 0;
typename clock_type::time_point start = typename clock_type::time_point start = clock_type::now();
clock_type::now();
explicit explicit
suite_results (std::string const& name_ = ""); suite_results(std::string const& name_ = "")
: name(std::move(name_))
{
}
void void
add(case_results const& r); add(case_results const& r);
@@ -76,15 +80,13 @@ private:
std::size_t total = 0; std::size_t total = 0;
std::size_t failed = 0; std::size_t failed = 0;
std::vector<run_time> top; std::vector<run_time> top;
typename clock_type::time_point start = typename clock_type::time_point start = clock_type::now();
clock_type::now();
void void
add (suite_results const& r); add (suite_results const& r);
}; };
boost::optional <std_ostream> std_ostream_; std::ostream& os_;
std::reference_wrapper <beast::abstract_ostream> stream_;
results results_; results results_;
suite_results suite_results_; suite_results suite_results_;
case_results case_results_; case_results case_results_;
@@ -96,10 +98,7 @@ public:
~reporter(); ~reporter();
explicit explicit
reporter (std::ostream& stream = std::cout); reporter(std::ostream& os = std::cout);
explicit
reporter (beast::abstract_ostream& stream);
private: private:
static static
@@ -137,23 +136,10 @@ 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 void
reporter<_>::suite_results::add (case_results const& r) reporter<_>::
suite_results::add(case_results const& r)
{ {
++cases; ++cases;
total += r.total; total += r.total;
@@ -162,17 +148,15 @@ reporter<_>::suite_results::add (case_results const& r)
template<class _> template<class _>
void void
reporter<_>::results::add ( reporter<_>::
suite_results const& r) results::add(suite_results const& r)
{ {
++suites; ++suites;
total += r.total; total += r.total;
cases += r.cases; cases += r.cases;
failed += r.failed; failed += r.failed;
auto const elapsed = clock_type::now() - r.start;
auto const elapsed = if (elapsed >= std::chrono::seconds{1})
clock_type::now() - r.start;
if (elapsed >= std::chrono::seconds(1))
{ {
auto const iter = std::lower_bound(top.begin(), auto const iter = std::lower_bound(top.begin(),
top.end(), elapsed, top.end(), elapsed,
@@ -197,10 +181,9 @@ reporter<_>::results::add (
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
template<class _> template<class _>
reporter<_>::reporter ( reporter<_>::
std::ostream& stream) reporter(std::ostream& os)
: std_ostream_ (std::ref (stream)) : os_(os)
, stream_ (*std_ostream_)
{ {
} }
@@ -209,37 +192,28 @@ reporter<_>::~reporter()
{ {
if(results_.top.size() > 0) if(results_.top.size() > 0)
{ {
stream_.get() << "Longest suite times:"; os_ << "Longest suite times:\n";
for(auto const& i : results_.top) for(auto const& i : results_.top)
stream_.get() << std::setw(8) << os_ << std::setw(8) <<
fmtdur(i.second) << " " << i.first; fmtdur(i.second) << " " << i.first << '\n';
} }
auto const elapsed = auto const elapsed = clock_type::now() - results_.start;
clock_type::now() - results_.start; os_ <<
stream_.get() <<
fmtdur(elapsed) << ", " << fmtdur(elapsed) << ", " <<
amount (results_.suites, "suite") << ", " << amount{results_.suites, "suite"} << ", " <<
amount (results_.cases, "case") << ", " << amount{results_.cases, "case"} << ", " <<
amount (results_.total, "test") << " total, " << amount{results_.total, "test"} << " total, " <<
amount (results_.failed, "failure"); amount{results_.failed, "failure"} <<
} std::endl;
template <class _>
reporter<_>::reporter (
abstract_ostream& stream)
: stream_ (stream)
{
} }
template<class _> template<class _>
std::string std::string
reporter<_>::fmtdur ( reporter<_>::fmtdur(typename clock_type::duration const& d)
typename clock_type::duration const& d)
{ {
using namespace std::chrono; using namespace std::chrono;
auto const ms = auto const ms = duration_cast<milliseconds>(d);
duration_cast<milliseconds>(d); if (ms < seconds{1})
if (ms < seconds(1))
return std::to_string(ms.count()) + "ms"; return std::to_string(ms.count()) + "ms";
std::stringstream ss; std::stringstream ss;
ss << std::fixed << std::setprecision(1) << ss << std::fixed << std::setprecision(1) <<
@@ -249,10 +223,10 @@ reporter<_>::fmtdur (
template<class _> template<class _>
void void
reporter<_>::on_suite_begin ( reporter<_>::
suite_info const& info) 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 _>
@@ -264,50 +238,50 @@ reporter<_>::on_suite_end()
template<class _> template<class _>
void void
reporter<_>::on_case_begin ( reporter<_>::
std::string const& name) on_case_begin(std::string const& name)
{ {
case_results_ = case_results (name); case_results_ = case_results (name);
os_ <<
stream_.get() <<
suite_results_.name << suite_results_.name <<
(case_results_.name.empty() ? (case_results_.name.empty() ?
"" : (" " + case_results_.name)); "" : (" " + case_results_.name)) << std::endl;
} }
template<class _> template<class _>
void void
reporter<_>::on_case_end() reporter<_>::
on_case_end()
{ {
suite_results_.add(case_results_); suite_results_.add(case_results_);
} }
template<class _> template<class _>
void void
reporter<_>::on_pass() reporter<_>::
on_pass()
{ {
++case_results_.total; ++case_results_.total;
} }
template<class _> template<class _>
void void
reporter<_>::on_fail ( reporter<_>::
std::string const& reason) on_fail(std::string const& reason)
{ {
++case_results_.failed; ++case_results_.failed;
++case_results_.total; ++case_results_.total;
stream_.get() << os_ <<
"#" << case_results_.total << "#" << case_results_.total << " failed" <<
" failed" << (reason.empty() ? "" : ": ") << reason << std::endl;
(reason.empty() ? "" : ": ") << reason;
} }
template<class _> template<class _>
void void
reporter<_>::on_log ( reporter<_>::
std::string const& s) on_log(std::string const& s)
{ {
stream_.get() << s; os_ << s;
} }
} // detail } // detail

View File

@@ -9,9 +9,9 @@
#define BEAST_UNIT_TEST_RUNNER_H_INCLUDED #define BEAST_UNIT_TEST_RUNNER_H_INCLUDED
#include <beast/unit_test/suite_info.hpp> #include <beast/unit_test/suite_info.hpp>
#include <beast/unit_test/abstract_ostream.hpp>
#include <cassert> #include <cassert>
#include <mutex> #include <mutex>
#include <ostream>
#include <string> #include <string>
namespace beast { namespace beast {
@@ -23,28 +23,6 @@ namespace unit_test {
*/ */
class runner 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_; std::string arg_;
bool default_ = false; bool default_ = false;
bool failed_ = false; bool failed_ = false;
@@ -52,14 +30,13 @@ private:
std::recursive_mutex mutex_; std::recursive_mutex mutex_;
public: public:
runner() = default;
virtual ~runner() = default; virtual ~runner() = default;
runner (runner const&) = default; runner(runner const&) = delete;
runner& operator= (runner const&) = default; runner& operator=(runner const&) = delete;
template <class = void>
runner();
/** Set the argument string. /** Set the argument string.
The argument string is available to suites and The argument string is available to suites and
allows for customization of the test. Each suite allows for customization of the test. Each suite
defines its own syntax for the argumnet string. defines its own syntax for the argumnet string.
@@ -124,54 +101,54 @@ public:
bool bool
run_each_if (SequenceContainer const& c, Pred pred = Pred{}); run_each_if (SequenceContainer const& c, Pred pred = Pred{});
private: protected:
// //
// Overrides // Overrides
// //
/** Called when a new suite starts. */ /// Called when a new suite starts.
virtual virtual
void void
on_suite_begin(suite_info const&) on_suite_begin(suite_info const&)
{ {
} }
/** Called when a suite ends. */ /// Called when a suite ends.
virtual virtual
void void
on_suite_end() on_suite_end()
{ {
} }
/** Called when a new case starts. */ /// Called when a new case starts.
virtual virtual
void 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 virtual
void void
on_case_end() on_case_end()
{ {
} }
/** Called for each passing condition. */ /// Called for each passing condition.
virtual virtual
void void
on_pass() on_pass()
{ {
} }
/** Called for each failing condition. */ /// Called for each failing condition.
virtual virtual
void void
on_fail(std::string const&) on_fail(std::string const&)
{ {
} }
/** Called when a test logs output. */ /// Called when a test logs output.
virtual virtual
void void
on_log(std::string const&) on_log(std::string const&)
@@ -181,12 +158,6 @@ private:
private: private:
friend class suite; friend class suite;
abstract_ostream&
stream()
{
return stream_;
}
// Start a new testcase. // Start a new testcase.
template <class = void> template <class = void>
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> template <class>
bool bool
runner::run (suite_info const& s) runner::run (suite_info const& s)

View File

@@ -9,15 +9,23 @@
#define BEAST_UNIT_TEST_SUITE_HPP #define BEAST_UNIT_TEST_SUITE_HPP
#include <beast/unit_test/runner.hpp> #include <beast/unit_test/runner.hpp>
#include <string> #include <ostream>
#include <sstream> #include <sstream>
#include <string>
namespace beast { namespace beast {
namespace unit_test { namespace unit_test {
class thread; class thread;
enum abort_t
{
no_abort_on_fail,
abort_on_fail
};
/** A testsuite class. /** A testsuite class.
Derived classes execute a series of testcases, where each testcase is 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, 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 derive from it and use the BEAST_DEFINE_UNIT_TEST macro in a
@@ -25,13 +33,6 @@ class thread;
*/ */
class suite class suite
{ {
public:
enum abort_t
{
no_abort_on_fail,
abort_on_fail
};
private: private:
bool abort_ = false; bool abort_ = false;
bool aborted_ = false; bool aborted_ = false;
@@ -44,95 +45,100 @@ private:
char const* char const*
what() const noexcept override what() const noexcept override
{ {
return "suite aborted"; return "test suite aborted";
} }
}; };
public: template<class CharT, class Traits, class Allocator>
// Memberspace class log_buf
class log_t : public std::basic_stringbuf<CharT, Traits, Allocator>
{ {
private: suite& suite_;
friend class suite;
suite* suite_ = nullptr;
public: public:
log_t () = default; explicit
log_buf(suite& self)
: suite_(self)
{
}
template <class T> ~log_buf()
abstract_ostream::scoped_stream_type {
operator<< (T const& t); sync();
}
/** Returns the raw stream used for output. */ int
abstract_ostream& sync() override
stream(); {
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; class scoped_testcase;
// Memberspace
class testcase_t class testcase_t
{ {
private: suite& suite_;
friend class suite;
suite* suite_ = nullptr;
std::stringstream ss_; std::stringstream ss_;
public: public:
testcase_t() = default; explicit
testcase_t(suite& self)
: suite_(self)
{
}
/** Open a new testcase. /** 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 A testcase is a series of evaluated test conditions. A test
opened testcase. When the test first runs, a default unnamed suite may have multiple test cases. A test is associated with
case is opened. Tests with only one case may omit the call the last opened testcase. When the test first runs, a default
to testcase. unnamed case is opened. Tests with only one case may omit the
@param abort If `true`, the suite will be stopped on first failure. call to testcase.
@param abort Determines if suite continues running after a failure.
*/ */
void void
operator()(std::string const& name, operator()(std::string const& name,
abort_t abort = no_abort_on_fail); abort_t abort = no_abort_on_fail);
/** Stream style composition of testcase names. */
/** @{ */
scoped_testcase scoped_testcase
operator()(abort_t abort); operator()(abort_t abort);
template <class T> template <class T>
scoped_testcase scoped_testcase
operator<<(T const& t); operator<<(T const& t);
/** @} */
}; };
public: public:
/** Type for scoped stream logging. /** Logging output stream.
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();
@code Text sent to the log output stream will be forwarded to
the output stream associated with the runner.
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
*/ */
using scoped_stream = abstract_ostream::scoped_stream_type; log_os<char> log;
/** Memberspace for logging. */
log_t log;
/** Memberspace for declaring test cases. */ /** Memberspace for declaring test cases. */
testcase_t testcase; testcase_t testcase;
@@ -145,6 +151,12 @@ public:
return *p_this_suite(); return *p_this_suite();
} }
suite()
: log(*this)
, testcase(*this)
{
}
/** Invokes the test using the specified runner. /** Invokes the test using the specified runner.
Data members are set up here instead of the constructor as a Data members are set up here instead of the constructor as a
convenience to writing the derived class to avoid repetition of convenience to writing the derived class to avoid repetition of
@@ -272,83 +284,50 @@ 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 // Helper for streaming testcase names
class suite::scoped_testcase class suite::scoped_testcase
{ {
private: private:
suite* suite_; suite& suite_;
std::stringstream* ss_; std::stringstream& ss_;
public: 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);
}
scoped_testcase(suite& self, std::stringstream& ss)
: suite_(self)
, ss_(ss)
{
ss_.clear();
ss_.str({});
}
template<class T> template<class T>
scoped_testcase (suite* s, std::stringstream* ss, T const& t); scoped_testcase(suite& self,
std::stringstream& ss, T const& t)
scoped_testcase& operator= (scoped_testcase const&) = delete; : suite_(self)
, ss_(ss)
{
ss_.clear();
ss_.str({});
ss_ << t;
}
template<class T> template<class T>
scoped_testcase& scoped_testcase&
operator<< (T const& t); operator<<(T const& t)
};
inline
suite::scoped_testcase::~scoped_testcase()
{ {
auto const& name (ss_->str()); ss_ << t;
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; return *this;
} }
};
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@@ -357,16 +336,16 @@ void
suite::testcase_t::operator() (std::string const& name, suite::testcase_t::operator() (std::string const& name,
abort_t abort) abort_t abort)
{ {
suite_->abort_ = abort == abort_on_fail; suite_.abort_ = abort == abort_on_fail;
suite_->runner_->testcase (name); suite_.runner_->testcase (name);
} }
inline inline
suite::scoped_testcase suite::scoped_testcase
suite::testcase_t::operator() (abort_t abort) suite::testcase_t::operator() (abort_t abort)
{ {
suite_->abort_ = abort == abort_on_fail; suite_.abort_ = abort == abort_on_fail;
return { suite_, &ss_ }; return { suite_, ss_ };
} }
template<class T> template<class T>
@@ -374,7 +353,7 @@ inline
suite::scoped_testcase suite::scoped_testcase
suite::testcase_t::operator<< (T const& t) suite::testcase_t::operator<< (T const& t)
{ {
return { suite_, &ss_, t }; return { suite_, ss_, t };
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@@ -511,8 +490,6 @@ void
suite::run (runner& r) suite::run (runner& r)
{ {
runner_ = &r; runner_ = &r;
log.suite_ = this;
testcase.suite_ = this;
try try
{ {

View File

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

View File

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

View File

@@ -85,7 +85,7 @@ public:
{ {
using namespace std::chrono; using namespace std::chrono;
using clock_type = std::chrono::high_resolution_clock; using clock_type = std::chrono::high_resolution_clock;
log << name; log << name << std::endl;
for(std::size_t trial = 1; trial <= repeat; ++trial) for(std::size_t trial = 1; trial <= repeat; ++trial)
{ {
auto const t0 = clock_type::now(); auto const t0 = clock_type::now();
@@ -93,7 +93,7 @@ public:
auto const elapsed = clock_type::now() - t0; auto const elapsed = clock_type::now() - t0;
log << log <<
"Trial " << trial << ": " << "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; static std::size_t constexpr Repeat = 50;
log << "sizeof(request parser) == " << 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) == " << 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, " << testcase << "Parser speed test, " <<
((Repeat * size_ + 512) / 1024) << "KB in " << ((Repeat * size_ + 512) / 1024) << "KB in " <<