Files
regression/reports/src/common.cpp

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 << "&#160;|&#160;"
"<a href=\"../" << mode << "/" << escape_uri(page) << release_postfix << ".html\" class=\"view-link\" target=\"_top\">"
<< mode << " View"
"</a>"
"&#160;|&#160;"
"<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 << "\">&#160;</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 << "\">&#160;</td>\n"
"</tr>\n";
}
document << "<tr>\n"
" <td colspan=\"" << colspan << "\">&#160;</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 << "\">&#160;</td>\n"
"</tr>\n";
document << "<tr>\n"
"<td colspan=\"" << colspan << "\">&#160;</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 << "\">&#160;</td>\n"
"</tr>\n";
document << "<tr>\n"
" <td colspan=\"" << colspan << "\">&#160;</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 << "\">&#160;</td>\n"
"</tr>\n";
if(top_or_bottom == "bottom") {
document << "<tr>\n"
" <td colspan=\"" << colspan << "\">&#160;</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 << "\">&#160;</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") ?
"&#160;library&#160;/&#160;toolset&#160;" :
"&#160;test&#160;/&#160;toolset&#160;";
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>(&note)) {
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 << " [&#160;" << escape_xml(author) << "&#160;" << escape_xml(date) << "&#160;]\n";
} else if(author != "") {
document << " [&#160;" << escape_xml(author) << "&#160;]\n";
} else if(date != "") {
document << " [&#160;" << escape_xml(date) << "&#160;]\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>(&note)) {
write_contents(document, *node_note);
} else if(const std::string* string_note = boost::get<std::string>(&note)) {
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";
}