mirror of
https://github.com/boostorg/regression.git
synced 2026-02-01 20:52:14 +00:00
751 lines
30 KiB
C++
751 lines
30 KiB
C++
// Copyright MetaCommunications, Inc. 2003-2005.
|
|
// Copyright Steven Watanabe 2010
|
|
//
|
|
// 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)
|
|
|
|
#include "common.hpp"
|
|
#include "xml.hpp"
|
|
#include "html.hpp"
|
|
|
|
#include <boost/regex.hpp>
|
|
#include <boost/foreach.hpp>
|
|
#include <boost/algorithm/string/split.hpp>
|
|
#include <boost/algorithm/string/classification.hpp>
|
|
#include <boost/date_time/posix_time/posix_time_io.hpp>
|
|
#include <algorithm>
|
|
#include <set>
|
|
|
|
using namespace boost::regression;
|
|
|
|
std::string boost::regression::alternate_mode(const std::string& mode) {
|
|
if(mode == "user") {
|
|
return "developer";
|
|
} else {
|
|
return "user";
|
|
}
|
|
}
|
|
|
|
std::string boost::regression::release_postfix(bool is_release) {
|
|
if(is_release) {
|
|
return "_release";
|
|
} else {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
// safe
|
|
void boost::regression::get_libraries(const test_structure_t& test_structure, std::set<std::string>& out) {
|
|
typedef boost::unordered_map<std::string, test_structure_t::platform_t>::const_reference outer_elem;
|
|
BOOST_FOREACH(outer_elem platform, test_structure.platforms) {
|
|
BOOST_FOREACH(test_structure_t::platform_t::const_reference run, platform.second) {
|
|
BOOST_FOREACH(test_structure_t::toolset_group_t::const_reference toolset, run.toolsets) {
|
|
BOOST_FOREACH(test_structure_t::toolset_t::const_reference library, toolset.second) {
|
|
out.insert(library.first);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
|
|
<func:function name="meta:test_case_status">
|
|
<xsl:param name="explicit_markup"/>
|
|
<xsl:param name="test_log"/>
|
|
|
|
<xsl:variable name="status">
|
|
<xsl:choose>
|
|
<xsl:when test="meta:is_unusable( $explicit_markup, $test_log/@library, $test_log/@toolset )">
|
|
<xsl:text>unusable</xsl:text>
|
|
</xsl:when>
|
|
<xsl:when test="$test_log/@result='fail' and $test_log/@status='unexpected' and $test_log/@is-new='no'">
|
|
<xsl:text>fail-unexpected</xsl:text>
|
|
</xsl:when>
|
|
<xsl:when test="$test_log/@result='fail' and $test_log/@status='unexpected' and $test_log/@is-new='yes'">
|
|
<xsl:text>fail-unexpected-new</xsl:text>
|
|
</xsl:when>
|
|
<xsl:when test="$test_log/@result='success' and $test_log/@status='unexpected'">
|
|
<xsl:text>success-unexpected</xsl:text>
|
|
</xsl:when>
|
|
<xsl:when test="$test_log/@status='expected'">
|
|
<xsl:text>expected</xsl:text>
|
|
</xsl:when>
|
|
<xsl:otherwise>
|
|
<xsl:text>other</xsl:text>
|
|
</xsl:otherwise>
|
|
</xsl:choose>
|
|
</xsl:variable>
|
|
<func:result select="$status"/>
|
|
</func:function>
|
|
|
|
<func:function name="meta:is_toolset_required">
|
|
<xsl:param name="toolset"/>
|
|
<func:result select="count( $explicit_markup/explicit-failures-markup/mark-toolset[ @name = $toolset and @status='required' ] ) > 0"/>
|
|
</func:function>
|
|
|
|
#endif
|
|
|
|
bool boost::regression::is_library_beta(const failures_markup_t& explicit_markup, const std::string& library) {
|
|
boost::unordered_map<std::string, node_ptr>::const_iterator pos = explicit_markup.libraries.find(library);
|
|
if(pos != explicit_markup.libraries.end()) {
|
|
return check_attr(pos->second, "status", "beta");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool boost::regression::is_test_log_a_test_case(const test_structure_t::test_log_t& test_log) {
|
|
const std::string& type = test_log.test_type;
|
|
return type == "compile" || type == "compile_fail" || type == "link" || type == "link_fail" ||
|
|
type == "run" || type == "run_fail" || type == "run_pyd" || type == "run_mpi";
|
|
}
|
|
|
|
// Does not assume any constraints on contents of the strings
|
|
bool boost::regression::is_unusable(const failures_markup_t& markup, const std::string& library, const std::string& toolset) {
|
|
boost::unordered_map<std::string, node_ptr>::const_iterator pos = markup.libraries.find(library);
|
|
if(pos != markup.libraries.end()) {
|
|
FOR_EACH_ELEMENT(mark_unusable, pos->second) {
|
|
if(check_name(mark_unusable, "mark-unusable")) {
|
|
FOR_EACH_ELEMENT(toolset_node, mark_unusable) {
|
|
std::string name;
|
|
if(lookup_attr(toolset_node, "name", name) && re_match(name, toolset)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void boost::regression::get_unusable(const failures_markup_t& markup,
|
|
const std::string& library,
|
|
const test_structure_t& test_structure,
|
|
boost::unordered_map<std::string, std::size_t>& out,
|
|
std::vector<node_ptr>& notes) {
|
|
boost::unordered_map<std::string, node_ptr>::const_iterator pos = markup.libraries.find(library);
|
|
if(pos != markup.libraries.end()) {
|
|
FOR_EACH_ELEMENT(mark_unusable, pos->second) {
|
|
if(check_name(mark_unusable, "mark-unusable")) {
|
|
node_ptr note = 0;
|
|
std::vector<std::string> toolsets;
|
|
FOR_EACH_ELEMENT(toolset_node, mark_unusable) {
|
|
std::string name;
|
|
if(check_name(toolset_node, "toolset") && lookup_attr(toolset_node, "name", name)) {
|
|
BOOST_FOREACH(test_structure_t::platform_group_t::const_reference platform, test_structure.platforms) {
|
|
BOOST_FOREACH(test_structure_t::platform_t::const_reference run, platform.second) {
|
|
BOOST_FOREACH(test_structure_t::toolset_group_t::const_reference toolset, run.toolsets) {
|
|
if(re_match(name, toolset.first)) {
|
|
toolsets.push_back(toolset.first);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else if(check_name(toolset_node, "note")) {
|
|
note = toolset_node;
|
|
}
|
|
}
|
|
if(note != 0 && !toolsets.empty()) {
|
|
notes.push_back(note);
|
|
BOOST_FOREACH(const std::string& toolset, toolsets) {
|
|
out[toolset] = notes.size();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// There are no restrictions on the pattern or the
|
|
// string. The only special character in the pattern
|
|
// is '*', which matches any number of consecutive characters.
|
|
bool boost::regression::re_match(const std::string& pattern, const std::string& text) {
|
|
std::size_t pattern_start = 0;
|
|
std::size_t pattern_end = 0;
|
|
std::size_t text_start = 0;
|
|
// check that the leading portion of the string matches
|
|
std::size_t first = pattern.find('*');
|
|
if(first == std::string::npos) return pattern == text;
|
|
if(pattern.substr(0, first) != text.substr(0, first)) return false;
|
|
text_start = first;
|
|
pattern_start = pattern_end = first + 1;
|
|
for(; pattern_end != pattern.size(); ++pattern_end) {
|
|
// split into blocks at '*'
|
|
if(pattern[pattern_end] == '*') {
|
|
// and search for each block
|
|
std::size_t size = pattern_end - pattern_start;
|
|
std::size_t off = text.find(pattern.data() + pattern_start, text_start, size);
|
|
// if not found, the pattern doesn't match
|
|
if(off == std::string::npos) return false;
|
|
text_start = off + size;
|
|
pattern_start = pattern_end + 1; // skip past the '*'
|
|
}
|
|
}
|
|
// check that the tails of the strings are the same
|
|
std::size_t tail_size = pattern_end - pattern_start;
|
|
return tail_size <= text.size() - text_start &&
|
|
pattern.substr(pattern_start, tail_size) == text.substr(text.size() - tail_size, tail_size);
|
|
}
|
|
|
|
// date-time
|
|
|
|
// The result is clamped to the range [0,30]
|
|
int boost::regression::timestamp_difference(const boost::posix_time::ptime& x, const boost::posix_time::ptime& y) {
|
|
boost::posix_time::time_duration diff = y - x;
|
|
int result = diff.hours() / 24;
|
|
if(result < 0) return 0;
|
|
else if(result > 30) return 30;
|
|
else return result;
|
|
}
|
|
|
|
std::string boost::regression::format_timestamp(const boost::posix_time::ptime& timestamp) {
|
|
std::ostringstream stream;
|
|
stream.imbue(std::locale(std::locale::classic(), new boost::posix_time::time_facet("%a, %d %b %Y %H:%M:%S +0000")));
|
|
stream << timestamp;
|
|
return stream.str();
|
|
}
|
|
|
|
// path
|
|
|
|
// FIXME: The result MUST be a valid filesystem path.
|
|
std::string boost::regression::encode_path(const std::string& path) {
|
|
std::string result;
|
|
BOOST_FOREACH(char ch, path) {
|
|
if(ch == '.' || ch == '/') {
|
|
ch = '-';
|
|
}
|
|
// FIXME: allow only characters from the following set:
|
|
// "[a-z][A-Z][0-9][-+_. ,()$!~?]...
|
|
result += ch;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::string boost::regression::escape_uri(const std::string& path) {
|
|
std::string result;
|
|
BOOST_FOREACH(char ch, path) {
|
|
if (('a' <= ch && ch <= 'z') ||
|
|
('A' <= ch && ch <= 'Z') ||
|
|
('0' <= ch && ch <= '9') ||
|
|
ch == '-' || ch == '_' || ch == '~' || ch == '.' ||
|
|
// We're intentionally allowing '/' to go through.
|
|
// to escape it as well, use escape_literal_uri
|
|
ch == '/' ||
|
|
// FIXME: reserved characters
|
|
ch == '+')
|
|
result += ch;
|
|
else {
|
|
unsigned digit = ch;
|
|
ch &= 0xFF;
|
|
const char * xdigits = "0123456789ABCDEF";
|
|
result += '%';
|
|
result += xdigits[digit >> 4];
|
|
result += xdigits[digit & 0xF];
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::string boost::regression::escape_literal_uri(const std::string& path) {
|
|
std::string result;
|
|
BOOST_FOREACH(char ch, path) {
|
|
// FIXME: Assumes UTF-8
|
|
if (('a' <= ch && ch <= 'z') ||
|
|
('A' <= ch && ch <= 'Z') ||
|
|
('0' <= ch && ch <= '9') ||
|
|
ch == '-' || ch == '_' || ch == '~' || ch == '.')
|
|
result += ch;
|
|
else {
|
|
unsigned digit = ch;
|
|
ch &= 0xFF;
|
|
const char * xdigits = "0123456789ABCDEF";
|
|
result += '%';
|
|
result += xdigits[digit >> 4];
|
|
result += xdigits[digit & 0xF];
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// okay
|
|
std::string boost::regression::output_file_path(const std::string& path) {
|
|
return("output/" + (encode_path(path) + ".html"));
|
|
}
|
|
|
|
// okay
|
|
std::string boost::regression::log_file_path(
|
|
const failures_markup_t& explicit_markup,
|
|
const test_structure_t::test_log_t& test_log,
|
|
const std::string& runner,
|
|
const std::string& release_postfix)
|
|
{
|
|
if(show_output(explicit_markup, test_log)) {
|
|
return output_file_path(runner + "-" + test_log.target_directory + release_postfix);
|
|
} else {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
bool boost::regression::show_library(const failures_markup_t& explicit_markup, const std::string& library, bool release) {
|
|
return !release || !is_library_beta(explicit_markup, library);
|
|
}
|
|
|
|
bool boost::regression::show_output(const failures_markup_t& explicit_markup, const test_structure_t::test_log_t& test_log) {
|
|
return ((!test_log.result || test_log.show_run_output) ||
|
|
(test_log.result && !test_log.status))
|
|
&& !(is_unusable(explicit_markup, test_log.library, test_log.toolset));
|
|
}
|
|
|
|
bool boost::regression::show_toolset(const failures_markup_t& explicit_markup, const std::string& toolset, bool release) {
|
|
return !release || explicit_markup.required_toolsets.find(toolset) != explicit_markup.required_toolsets.end();
|
|
}
|
|
|
|
std::string boost::regression::result_cell_name_new(test_structure_t::fail_info_t const& fail_info)
|
|
{
|
|
if ( fail_info == test_structure_t::fail_comp )
|
|
return "comp";
|
|
else if ( fail_info == test_structure_t::fail_link )
|
|
return "link";
|
|
else if ( fail_info == test_structure_t::fail_run )
|
|
return "run";
|
|
else if ( fail_info == test_structure_t::fail_time )
|
|
return "time";
|
|
else if ( fail_info == test_structure_t::fail_file )
|
|
return "file";
|
|
else if ( fail_info == test_structure_t::fail_cerr )
|
|
return "cerr";
|
|
else
|
|
return "fail";
|
|
}
|
|
|
|
std::string boost::regression::result_cell_name_new(test_structure_t::test_log_t const& log)
|
|
{
|
|
return boost::regression::result_cell_name_new(log.fail_info);
|
|
}
|
|
|
|
std::string boost::regression::result_cell_name_new(test_log_group_t const& logs)
|
|
{
|
|
test_structure_t::fail_info_t most_significant_fail = test_structure_t::fail_none;
|
|
BOOST_FOREACH(test_log_group_t::value_type log, logs) {
|
|
if ( log->fail_info > most_significant_fail ) {
|
|
most_significant_fail = log->fail_info;
|
|
if ( most_significant_fail == test_structure_t::fail_comp ){
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return boost::regression::result_cell_name_new(most_significant_fail);
|
|
}
|
|
|
|
std::string boost::regression::result_cell_class_new(test_structure_t::fail_info_t fail_info)
|
|
{
|
|
if ( fail_info == test_structure_t::fail_comp )
|
|
return "fail-unexpected-new-comp";
|
|
else if ( fail_info == test_structure_t::fail_link )
|
|
return "fail-unexpected-new-link";
|
|
else if ( fail_info == test_structure_t::fail_run )
|
|
return "fail-unexpected-new-run";
|
|
else if ( fail_info == test_structure_t::fail_time )
|
|
return "fail-unexpected-new-time";
|
|
else if ( fail_info == test_structure_t::fail_file )
|
|
return "fail-unexpected-new-file";
|
|
else if ( fail_info == test_structure_t::fail_cerr )
|
|
return "fail-unexpected-new-cerr";
|
|
else
|
|
return "fail-unexpected-new-other";
|
|
}
|
|
|
|
std::string boost::regression::result_cell_class_new(test_structure_t::test_log_t const& log)
|
|
{
|
|
return result_cell_class_new(log.fail_info);
|
|
}
|
|
|
|
// safe: no assumptions, enumerated result
|
|
std::string boost::regression::result_cell_class(const failures_markup_t& explicit_markup,
|
|
const std::string& library,
|
|
const std::string& toolset,
|
|
const test_log_group_t& test_logs,
|
|
bool enable_specific) {
|
|
if(is_unusable(explicit_markup, library, toolset)) {
|
|
return "unusable";
|
|
}
|
|
if(test_logs.empty()) {
|
|
return "missing";
|
|
}
|
|
BOOST_FOREACH(test_log_group_t::value_type log, test_logs) {
|
|
if(!log->result && log->expected_result && !log->is_new) {
|
|
return "fail-unexpected";
|
|
}
|
|
}
|
|
{
|
|
test_structure_t::fail_info_t most_significant_fail = test_structure_t::fail_none;
|
|
BOOST_FOREACH(test_log_group_t::value_type log, test_logs) {
|
|
if(!log->result && log->expected_result && log->is_new) {
|
|
if ( !enable_specific ) {
|
|
return "fail-unexpected-new";
|
|
} else {
|
|
// store the most significant error
|
|
if ( log->fail_info > most_significant_fail ) {
|
|
most_significant_fail = log->fail_info;
|
|
// the failure can't be more significant
|
|
if ( most_significant_fail == test_structure_t::fail_comp) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ( most_significant_fail != test_structure_t::fail_none) {
|
|
return result_cell_class_new(most_significant_fail);
|
|
}
|
|
}
|
|
BOOST_FOREACH(test_log_group_t::value_type log, test_logs) {
|
|
if(!log->result && log->expected_reason != "") {
|
|
return "fail-expected-unreasearched";
|
|
}
|
|
}
|
|
BOOST_FOREACH(test_log_group_t::value_type log, test_logs) {
|
|
if(!log->result) {
|
|
return "fail-expected";
|
|
}
|
|
}
|
|
BOOST_FOREACH(test_log_group_t::value_type log, test_logs) {
|
|
if(log->result && !log->expected_result) {
|
|
return "success-unexpected";
|
|
}
|
|
}
|
|
BOOST_FOREACH(test_log_group_t::value_type log, test_logs) {
|
|
if(log->result && log->expected_result) {
|
|
return "success-expected";
|
|
}
|
|
}
|
|
return "unknown";
|
|
}
|
|
// safe
|
|
std::string boost::regression::result_cell_class(const failures_markup_t& explicit_markup,
|
|
const std::string& library,
|
|
const std::string& toolset,
|
|
const test_structure_t::library_t& test_logs,
|
|
bool enable_specific)
|
|
{
|
|
test_log_group_t tmp;
|
|
BOOST_FOREACH(test_structure_t::library_t::const_reference test_case, test_logs) {
|
|
BOOST_FOREACH(test_structure_t::test_case_t::const_reference log, test_case.second) {
|
|
tmp.push_back(&log);
|
|
}
|
|
}
|
|
return result_cell_class(explicit_markup, library, toolset, tmp, enable_specific);
|
|
}
|
|
|
|
// requires: purpose must be well-formed html
|
|
void boost::regression::insert_report_header(
|
|
html_writer& document,
|
|
const boost::posix_time::ptime& run_date,
|
|
const std::vector<std::string>& warnings,
|
|
const std::string& purpose)
|
|
{
|
|
document << "<div class=\"report-info\">\n";
|
|
document << " <div>\n";
|
|
document << " <b>Report Time: </b> " << format_timestamp(run_date) << "\n";
|
|
document << " </div>\n";
|
|
|
|
if(!purpose.empty()) {
|
|
document << " <div>\n";
|
|
document << " <b>Purpose: </b> " << purpose << "\n";
|
|
document << " </div>\n";
|
|
}
|
|
|
|
BOOST_FOREACH(const std::string& warning, warnings) {
|
|
document << " <div class=\"report-warning\">\n";
|
|
document << " <b>Warning: </b>\n";
|
|
document << " <a href=\"mailto:boost-testing@lists.boost.org?subject=[Report Pages]%20" << escape_literal_uri(warning) << " (" << format_timestamp(run_date) << ")\" class=\"warning-link\">\n";
|
|
document << " " << escape_xml(warning) << "\n";
|
|
document << " </a>\n";
|
|
document << " </div>\n";
|
|
}
|
|
|
|
document << "</div>\n";
|
|
}
|
|
|
|
// requires class_ is enumerated
|
|
void boost::regression::insert_view_link(html_writer& out, const std::string& page, const std::string& class_, bool release) {
|
|
if(release) {
|
|
out << "<a href=\"" << escape_uri(page) << ".html\" class=\"" << class_ << "\" target=\"_top\">"
|
|
"Full View"
|
|
"</a>\n";
|
|
} else {
|
|
out << "<a href=\"" << escape_uri(page) << "_release.html\" class=\"" << class_ << "\" target=\"_top\">"
|
|
"Release View"
|
|
"</a>";
|
|
}
|
|
|
|
}
|
|
|
|
// requires: mode = developer | user (Should be the opposite of the current page)
|
|
// requires: page is the base name of the current page. It should be valid
|
|
// according to encode_path, but should not be URI escaped.
|
|
void boost::regression::insert_page_links(html_writer& document,
|
|
const std::string& page,
|
|
bool release,
|
|
const std::string& mode) {
|
|
document << "<div class=\"links\">\n";
|
|
|
|
// yes, really. The class is set to ""
|
|
insert_view_link(document, page, "", release);
|
|
|
|
std::string release_postfix(release? "_release" : "");
|
|
|
|
document << " | "
|
|
"<a href=\"../" << mode << "/" << escape_uri(page) << release_postfix << ".html\" class=\"view-link\" target=\"_top\">"
|
|
<< mode << " View"
|
|
"</a>"
|
|
" | "
|
|
"<a href=\"" << escape_uri(page) << release_postfix << "_.html#legend\">Legend</a>\n"
|
|
|
|
"</div>\n";
|
|
|
|
}
|
|
|
|
// requires: mode = summary | details
|
|
// requires: top_or_bottom = top | bottom
|
|
void boost::regression::insert_runners_rows(html_writer& document,
|
|
const std::string& mode,
|
|
const std::string& top_or_bottom,
|
|
const test_structure_t& test_structure,
|
|
const boost::posix_time::ptime& run_date) {
|
|
std::string colspan = (mode == "summary") ? "1" : "2";
|
|
|
|
if(top_or_bottom == "top") {
|
|
document << "<tr>\n"
|
|
" <td colspan=\"" << colspan << "\"> </td>\n";
|
|
BOOST_FOREACH(test_structure_t::platform_group_t::const_reference platform, test_structure.platforms) {
|
|
std::size_t count = 0;
|
|
BOOST_FOREACH(test_structure_t::platform_t::const_reference run, platform.second) {
|
|
count += run.toolsets.size();
|
|
}
|
|
if(count > 0) {
|
|
document << " <td colspan=\"" << count << "\" class=\"runner\">\n"
|
|
" " << escape_xml(platform.first) << "\n"
|
|
" </td>\n";
|
|
}
|
|
}
|
|
document << " <td colspan=\"" << colspan << "\"> </td>\n"
|
|
"</tr>\n";
|
|
}
|
|
|
|
document << "<tr>\n"
|
|
" <td colspan=\"" << colspan << "\"> </td>\n";
|
|
BOOST_FOREACH(test_structure_t::platform_group_t::const_reference platform, test_structure.platforms) {
|
|
BOOST_FOREACH(test_structure_t::platform_t::const_reference run, platform.second) {
|
|
if(run.toolsets.size() > 0) {
|
|
document << " <td colspan=\"" << run.toolsets.size() << "\" class=\"runner\">\n"
|
|
" <a href=\"../" << escape_uri(encode_path(run.runner)) << ".html\">\n"
|
|
" " << escape_xml(run.runner) << "\n"
|
|
" </a>\n"
|
|
" </td>\n";
|
|
}
|
|
}
|
|
}
|
|
document << " <td colspan=\"" << colspan << "\"> </td>\n"
|
|
"</tr>\n";
|
|
|
|
document << "<tr>\n"
|
|
"<td colspan=\"" << colspan << "\"> </td>\n";
|
|
BOOST_FOREACH(test_structure_t::platform_group_t::const_reference platform, test_structure.platforms) {
|
|
BOOST_FOREACH(test_structure_t::platform_t::const_reference run, platform.second) {
|
|
if(run.toolsets.size() > 0) {
|
|
document << " <td colspan=\"" << run.toolsets.size() << "\" class=\"revision\">\n"
|
|
" rev " << run.revision.substr(0, 6) << "\n"
|
|
" </td>\n";
|
|
}
|
|
}
|
|
}
|
|
document << " <td colspan=\"" << colspan << "\"> </td>\n"
|
|
"</tr>\n";
|
|
|
|
document << "<tr>\n"
|
|
" <td colspan=\"" << colspan << "\"> </td>\n";
|
|
BOOST_FOREACH(test_structure_t::platform_group_t::const_reference platform, test_structure.platforms) {
|
|
BOOST_FOREACH(test_structure_t::platform_t::const_reference run, platform.second) {
|
|
if(run.toolsets.size() > 0) {
|
|
int age = timestamp_difference(run.timestamp, run_date);
|
|
document << " <td colspan=\"" << run.toolsets.size() << "\" class=\"timestamp\">\n"
|
|
" <span class=\"timestamp-" << age << "\">" << format_timestamp(run.timestamp) << "</span>";
|
|
if(run.run_type != "full") {
|
|
document << "<span class=\"run-type-" << run.run_type << "\">" << run.run_type[0] << "</span>\n";
|
|
}
|
|
document << " </td>\n";
|
|
}
|
|
}
|
|
}
|
|
document << " <td colspan=\"" << colspan << "\"> </td>\n"
|
|
"</tr>\n";
|
|
|
|
if(top_or_bottom == "bottom") {
|
|
document << "<tr>\n"
|
|
" <td colspan=\"" << colspan << "\"> </td>\n";
|
|
BOOST_FOREACH(test_structure_t::platform_group_t::const_reference platform, test_structure.platforms) {
|
|
std::size_t count = 0;
|
|
BOOST_FOREACH(test_structure_t::platform_t::const_reference run, platform.second) {
|
|
count += run.toolsets.size();
|
|
}
|
|
if(count > 0) {
|
|
document << " <td colspan=\"" << count << "\" class=\"runner\">\n"
|
|
" " << escape_xml(platform.first) << "\n"
|
|
" </td>\n";
|
|
}
|
|
}
|
|
document << " <td colspan=\"" << colspan << "\"> </td>\n"
|
|
"</tr>\n";
|
|
}
|
|
}
|
|
|
|
// requires mode = summary | details
|
|
void boost::regression::insert_toolsets_row(html_writer& document,
|
|
const test_structure_t& test_structure,
|
|
const failures_markup_t& explicit_markup,
|
|
const std::string& mode,
|
|
const boost::posix_time::ptime& run_date,
|
|
const std::string& library,
|
|
const boost::unordered_map<std::string, std::size_t>& library_marks) {
|
|
|
|
document << "<tr valign=\"middle\">\n";
|
|
|
|
std::string colspan = (mode == "summary") ? "1" : "2";
|
|
std::string title = (mode == "summary") ?
|
|
" library / toolset " :
|
|
" test / toolset ";
|
|
|
|
document << " <td class=\"head\" colspan=\"" << colspan << "\" width=\"1%\">" << title << "</td>\n";
|
|
BOOST_FOREACH(const test_structure_t::platform_group_t::const_reference platform, test_structure.platforms) {
|
|
BOOST_FOREACH(const test_structure_t::platform_t::const_reference run, platform.second) {
|
|
BOOST_FOREACH(const test_structure_t::toolset_group_t::const_reference toolset, run.toolsets) {
|
|
std::string name = toolset.first;
|
|
std::string class_ = (explicit_markup.required_toolsets.find(name) != explicit_markup.required_toolsets.end())?
|
|
"required-toolset-name" :
|
|
"toolset-name";
|
|
|
|
document << "<td class=\"" << class_ << "\">\n";
|
|
int age = timestamp_difference(run.timestamp, run_date);
|
|
|
|
document << "<span class=\"timestamp-" << age << "\">\n";
|
|
|
|
// break toolset names into words
|
|
BOOST_FOREACH(char ch, name) {
|
|
document << ch;
|
|
if(ch == '-') {
|
|
document << ' ';
|
|
}
|
|
}
|
|
|
|
if(mode == "details") {
|
|
// <!-- prepare toolset notes -->
|
|
std::set<std::size_t> toolset_notes;
|
|
typedef boost::unordered_map<std::string, std::size_t>::const_reference ref_type;
|
|
BOOST_FOREACH(ref_type toolset_markup, library_marks.equal_range(name)) {
|
|
toolset_notes.insert(toolset_markup.second);
|
|
}
|
|
if(!toolset_notes.empty()) {
|
|
document << "<span class=\"super\">\n";
|
|
bool first = true;
|
|
BOOST_FOREACH(std::size_t note_index, toolset_notes) {
|
|
if(!first) document << ", "; else first = false;
|
|
document << "<a href=\"#" << escape_uri(library) << "-note-" << note_index << "\" title=\"Note " << note_index << "\">\n"
|
|
" " << note_index << "\n"
|
|
"</a>\n";
|
|
}
|
|
document << "</span>\n";
|
|
}
|
|
}
|
|
|
|
document << "</span>\n"
|
|
"</td>\n";
|
|
}
|
|
}
|
|
}
|
|
document << "<td class=\"head\" width=\"1%\">" << title << "</td>\n"
|
|
"</tr>\n";
|
|
}
|
|
|
|
namespace {
|
|
|
|
std::string get_note_attr(const test_structure_t::note_t& note, const std::string& name) {
|
|
if(const node_ptr* node = boost::get<node_ptr>(¬e)) {
|
|
std::string result;
|
|
lookup_attr(*node, name, result);
|
|
return result;
|
|
} else {
|
|
return std::string();
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// requires: if note is a string, it is well-formed html
|
|
void boost::regression::show_note(
|
|
html_writer& document,
|
|
const test_structure_t::note_t& note,
|
|
const std::string& references,
|
|
const failures_markup_t& explicit_markup)
|
|
{
|
|
document << "<div class=\"note\">\n";
|
|
|
|
std::string author = get_note_attr(note, "author");
|
|
std::string date = get_note_attr(note, "date");
|
|
|
|
document << " <span class=\"note-header\">\n";
|
|
|
|
if(author != "" && date != "") {
|
|
document << " [ " << escape_xml(author) << " " << escape_xml(date) << " ]\n";
|
|
} else if(author != "") {
|
|
document << " [ " << escape_xml(author) << " ]\n";
|
|
} else if(date != "") {
|
|
document << " [ " << escape_xml(date) << " ]\n";
|
|
}
|
|
|
|
document << " </span>\n";
|
|
|
|
if(references != "") {
|
|
// lookup references (refid="17,18")
|
|
std::vector<std::string> refs;
|
|
boost::algorithm::split(refs, references, boost::is_any_of(","));
|
|
BOOST_FOREACH(const std::string& refid, refs) {
|
|
boost::unordered_map<std::string, node_ptr>::const_iterator pos = explicit_markup.notes.find(refid);
|
|
if(pos != explicit_markup.notes.end()) {
|
|
write_contents(document, pos->second);
|
|
} else {
|
|
document << " " << escape_xml(refid) << "\n";
|
|
}
|
|
}
|
|
}
|
|
if(const node_ptr* node_note = boost::get<node_ptr>(¬e)) {
|
|
write_contents(document, *node_note);
|
|
} else if(const std::string* string_note = boost::get<std::string>(¬e)) {
|
|
document << *string_note; // not escaped--can contain html markup
|
|
}
|
|
|
|
document << "</div>\n";
|
|
}
|
|
|
|
// requires: any note that is a string contains well-formed html
|
|
void boost::regression::show_notes(html_writer& document,
|
|
const std::vector<test_structure_t::note_t>& notes,
|
|
const failures_markup_t& explicit_markup)
|
|
{
|
|
document << "<div class=\"notes\">\n";
|
|
|
|
BOOST_FOREACH(const test_structure_t::note_t& note, notes) {
|
|
|
|
document << " <div>\n";
|
|
|
|
std::string refid = get_note_attr(note, "refid");
|
|
::show_note(document, note, refid, explicit_markup);
|
|
|
|
document << " </div>\n";
|
|
|
|
}
|
|
|
|
document << "</div>\n";
|
|
}
|