// 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 #include #include #include #include #include #include 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& out) { typedef boost::unordered_map::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 unusable fail-unexpected fail-unexpected-new success-unexpected expected other #endif bool boost::regression::is_library_beta(const failures_markup_t& explicit_markup, const std::string& library) { boost::unordered_map::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::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& out, std::vector& notes) { boost::unordered_map::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 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& warnings, const std::string& purpose) { document << "
\n"; document << "
\n"; document << " Report Time: " << format_timestamp(run_date) << "\n"; document << "
\n"; if(!purpose.empty()) { document << "
\n"; document << " Purpose: " << purpose << "\n"; document << "
\n"; } BOOST_FOREACH(const std::string& warning, warnings) { document << "
\n"; document << " Warning: \n"; document << " \n"; document << " " << escape_xml(warning) << "\n"; document << " \n"; document << "
\n"; } document << "
\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 << "" "Full View" "\n"; } else { out << "" "Release View" ""; } } // 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 << "
\n"; // yes, really. The class is set to "" insert_view_link(document, page, "", release); std::string release_postfix(release? "_release" : ""); document << " | " "" << mode << " View" "" " | " "Legend\n" "
\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 << "\n" "  \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 << " \n" " " << escape_xml(platform.first) << "\n" " \n"; } } document << "  \n" "\n"; } document << "\n" "  \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 << " \n" " \n" " " << escape_xml(run.runner) << "\n" " \n" " \n"; } } } document << "  \n" "\n"; document << "\n" " \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 << " \n" " rev " << run.revision.substr(0, 6) << "\n" " \n"; } } } document << "  \n" "\n"; document << "\n" "  \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 << " \n" " " << format_timestamp(run.timestamp) << ""; if(run.run_type != "full") { document << "" << run.run_type[0] << "\n"; } document << " \n"; } } } document << "  \n" "\n"; if(top_or_bottom == "bottom") { document << "\n" "  \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 << " \n" " " << escape_xml(platform.first) << "\n" " \n"; } } document << "  \n" "\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& library_marks) { document << "\n"; std::string colspan = (mode == "summary") ? "1" : "2"; std::string title = (mode == "summary") ? " library / toolset " : " test / toolset "; document << " " << title << "\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 << "\n"; int age = timestamp_difference(run.timestamp, run_date); document << "\n"; // break toolset names into words BOOST_FOREACH(char ch, name) { document << ch; if(ch == '-') { document << ' '; } } if(mode == "details") { // std::set toolset_notes; typedef boost::unordered_map::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 << "\n"; bool first = true; BOOST_FOREACH(std::size_t note_index, toolset_notes) { if(!first) document << ", "; else first = false; document << "\n" " " << note_index << "\n" "\n"; } document << "\n"; } } document << "\n" "\n"; } } } document << "" << title << "\n" "\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(¬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 << "
\n"; std::string author = get_note_attr(note, "author"); std::string date = get_note_attr(note, "date"); document << " \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 << " \n"; if(references != "") { // lookup references (refid="17,18") std::vector refs; boost::algorithm::split(refs, references, boost::is_any_of(",")); BOOST_FOREACH(const std::string& refid, refs) { boost::unordered_map::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(¬e)) { write_contents(document, *node_note); } else if(const std::string* string_note = boost::get(¬e)) { document << *string_note; // not escaped--can contain html markup } document << "
\n"; } // requires: any note that is a string contains well-formed html void boost::regression::show_notes(html_writer& document, const std::vector& notes, const failures_markup_t& explicit_markup) { document << "
\n"; BOOST_FOREACH(const test_structure_t::note_t& note, notes) { document << "
\n"; std::string refid = get_note_attr(note, "refid"); ::show_note(document, note, refid, explicit_markup); document << "
\n"; } document << "
\n"; }