// ---
//
// $Id: htmloutput.cpp,v 1.7 2008/07/15 20:33:31 hartwork Exp $
//
// CppTest - A C++ Unit Testing Framework
// Copyright (c) 2003 Niklas Lundell
//
// ---
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the
// Free Software Foundation, Inc., 59 Temple Place - Suite 330,
// Boston, MA 02111-1307, USA.
//
// ---
#include "cpptest-htmloutput.h"
#include "utils.h"
namespace Test
{
namespace
{
void
strreplace(std::string &value, char search, const std::string &replace)
{
std::string::size_type idx = 0;
while((idx = value.find(search, idx)) != std::string::npos)
{
value.replace(idx, 1, replace);
idx += replace.size();
}
}
std::string
escape(std::string value)
{
strreplace(value, '&', "&");
strreplace(value, '<', "<");
strreplace(value, '>', ">");
strreplace(value, '"', """);
strreplace(value, '\'', "'");
return value;
}
void
header(std::ostream& os, std::string name)
{
if (!name.empty())
name += " ";
name = escape(name);
os <<
"\n"
"\n"
"
\n"
" \n"
" \n"
" \n"
" " << name << "Unit Tests Results\n"
" \n"
" \n"
"\n"
"\n"
"\n"
"\n"
"" << name << "Unit Tests Results
\n"
"\n"
"\n"
"
\n"
"\n";
}
void
footer(std::ostream& os)
{
os <<
"\n"
"\n"
" \n"
" Valid XHTML 1.0 Strict\n"
" \n"
"
\n"
"\n\n";
}
void
back_ref(std::ostream& os, const std::string& ref, bool prepend_newline = true)
{
os << "Back to " << escape(ref) << "\n
\n";
}
void
sub_title(std::ostream& os, const std::string& title, int size)
{
std::ostringstream h;
h << "h" << size;
os << "<" << h.str() << ">" << escape(title) << "" << h.str() << ">\n";
}
void
sub_title(std::ostream& os, const std::string& title, int size, const std::string& mark)
{
std::ostringstream h;
h << "h" << size;
os << "<" << h.str() << ">" << escape(title) << "" << h.str() << ">\n";
}
enum ClassTableType { TableClass_Summary, TableClass_Suites, TableClass_Suite, TableClass_Result };
void
table_header(std::ostream& os, ClassTableType type, const std::string &summary = "")
{
static const char* class_tabletypes[] = { "summary", "suites", "suite", "result" };
os << "\n";
}
void
table_footer(std::ostream& os)
{
os << "
\n";
}
void
table_tr_header(std::ostream& os)
{
os << " \n";
}
void
table_tr_footer(std::ostream& os)
{
os << "
\n";
}
enum ClassType { Title, Success, Error };
void
table_entry(std::ostream& os, ClassType type, const std::string& s,
int width = 0, const std::string& link = "")
{
static const char* class_types[] = { "title", "success", "error" };
os << " " << escape(s) << "";
else
os << " class=\"tablecell_" << class_types[type] << "\">" << escape(s);
os << " | \n";
}
} // anonymous namespace
// Test suite table
//
struct HtmlOutput::SuiteRow
{
std::ostream& _os;
SuiteRow(std::ostream& os) : _os(os) {}
void operator()(const SuiteInfo& si)
{
ClassType type(si._errors > 0 ? Error : Success);
std::ostringstream ss;
table_tr_header(_os);
table_entry(_os, type, si._name, 0, si._name);
ss.str(""), ss << si._tests.size();
table_entry(_os, type, ss.str(), 10);
ss.str(""), ss << si._errors;
table_entry(_os, type, ss.str(), 10);
ss.str(""), ss << correct(si._tests.size(), si._errors) << "%";
table_entry(_os, type, ss.str(), 10);
ss.str(""), ss << si._time;
table_entry(_os, type, ss.str(), 10);
table_tr_footer(_os);
}
};
// Individual tests tables, tests
//
struct HtmlOutput::TestRow
{
bool _incl_ok_tests;
std::ostream& _os;
TestRow(std::ostream& os, bool incl_ok_tests)
: _incl_ok_tests(incl_ok_tests), _os(os) {}
void operator()(const TestInfo& ti)
{
if (!ti._success || _incl_ok_tests)
{
std::string link = ti._success ? std::string(""):
ti._sources.front().suite() + "_" + ti._name;
ClassType type(ti._success ? Success : Error);
std::ostringstream ss;
table_tr_header(_os);
table_entry(_os, type, ti._name, 0, link);
ss.str(""), ss << ti._sources.size();
table_entry(_os, type, ss.str());
table_entry(_os, type, ti._success ? "true" : "false");
ss.str(""), ss << ti._time;
table_entry(_os, type, ss.str());
table_tr_footer(_os);
}
}
};
// Individual tests tables, header
//
struct HtmlOutput::TestSuiteRow
{
bool _incl_ok_tests;
std::ostream& _os;
TestSuiteRow(std::ostream& os, bool incl_ok_tests)
: _incl_ok_tests(incl_ok_tests), _os(os) {}
void operator()(const SuiteInfo& si)
{
std::ostringstream ss;
sub_title(_os, "Suite: " + si._name, 3, si._name);
table_header(_os, TableClass_Suite, "Details for suite " + si._name);
table_tr_header(_os);
table_entry(_os, Title, "Name");
table_entry(_os, Title, "Errors", 10);
table_entry(_os, Title, "Success", 10);
table_entry(_os, Title, "Time (s)", 10);
table_tr_footer(_os);
std::for_each(si._tests.begin(), si._tests.end(),
TestRow(_os, _incl_ok_tests));
table_footer(_os);
back_ref(_os, "top");
}
};
// Individual tests result tables
//
struct HtmlOutput::TestResult
{
std::ostream& _os;
TestResult(std::ostream& os) : _os(os) {}
void operator()(const Source& s)
{
const int TitleSize = 15;
std::ostringstream ss;
table_header(_os, TableClass_Result, "Test Failure");
table_tr_header(_os);
table_entry(_os, Title, "Test", TitleSize);
table_entry(_os, Success, s.suite() + "::" + s.test());
table_tr_footer(_os);
table_tr_header(_os);
table_entry(_os, Title, "File", TitleSize);
ss << s.file() << ":" << s.line();
table_entry(_os, Success, ss.str());
table_tr_footer(_os);
table_tr_header(_os);
table_entry(_os, Title, "Message", TitleSize);
table_entry(_os, Success, s.message());
table_tr_footer(_os);
table_footer(_os);
}
};
// All tests result tables
//
struct HtmlOutput::TestResultAll
{
std::ostream& _os;
TestResultAll(std::ostream& os) : _os(os) {}
void operator()(const TestInfo& ti)
{
if (!ti._success)
{
const std::string& suite = ti._sources.front().suite();
sub_title(_os, suite + "::" + ti._name, 3, suite + "_" + ti._name);
std::for_each(ti._sources.begin(), ti._sources.end(), TestResult(_os));
back_ref(_os, suite, false);
}
}
};
// Individual tests result tables, iterator
//
struct HtmlOutput::SuiteTestResult
{
std::ostream& _os;
SuiteTestResult(std::ostream& os) : _os(os) {}
void operator()(const SuiteInfo& si)
{
std::for_each(si._tests.begin(), si._tests.end(), TestResultAll(_os));
}
};
/// Generates the HTML table. This function should only be called after
/// run(), when all tests have been executed.
///
/// \param os Output stream.
/// \param incl_ok_tests Set if successful tests should be shown;
/// false otherwise.
/// \param name Name of generated report.
///
void
HtmlOutput::generate(std::ostream& os, bool incl_ok_tests, const std::string& name)
{
ClassType type(_total_errors > 0 ? Error : Success);
std::ostringstream ss;
header(os, name);
// Table: Summary
//
sub_title(os, "Summary", 2);
table_header(os, TableClass_Summary, "Summary of test results");
table_tr_header(os);
table_entry(os, Title, "Tests", 30);
table_entry(os, Title, "Errors", 30);
table_entry(os, Title, "Success", 30);
table_entry(os, Title, "Time (s)", 10);
table_tr_footer(os);
table_tr_header(os);
ss.str(""), ss << _total_tests;
table_entry(os, type, ss.str(), 30);
ss.str(""), ss << _total_errors;
table_entry(os, type, ss.str(), 30);
ss.str(""), ss << correct(_total_tests, _total_errors) << "%";
table_entry(os, type, ss.str(), 30);
ss.str(""), ss << _total_time;
table_entry(os, type, ss.str(), 10);
table_tr_footer(os);
table_footer(os);
os << "
\n\n";
// Table: Test suites
//
sub_title(os, "Test suites", 2);
table_header(os, TableClass_Suites, "Test Suites");
table_tr_header(os);
table_entry(os, Title, "Name");
table_entry(os, Title, "Tests", 10);
table_entry(os, Title, "Errors", 10);
table_entry(os, Title, "Success", 10);
table_entry(os, Title, "Time (s)", 10);
table_tr_footer(os);
std::for_each(_suites.begin(), _suites.end(), SuiteRow(os));
table_footer(os);
os << "
\n\n";
// Individual tests tables
//
std::for_each(_suites.begin(), _suites.end(), TestSuiteRow(os, incl_ok_tests));
os << "
\n\n";
// Individual tests result tables
//
if(_total_errors != 0)
{
sub_title(os, "Test results", 2);
std::for_each(_suites.begin(), _suites.end(), SuiteTestResult(os));
os << "
\n\n";
}
// EOF
//
footer(os);
}
} // namespace Test