| // process jam regression test output into XML -----------------------------// |
| |
| // Copyright Beman Dawes 2002. 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) |
| |
| // See http://www.boost.org/tools/regression for documentation. |
| |
| #include <boost/config/warning_disable.hpp> |
| |
| #include "detail/tiny_xml.hpp" |
| #include "boost/filesystem/operations.hpp" |
| #include "boost/filesystem/fstream.hpp" |
| #include "boost/filesystem/exception.hpp" |
| #include "boost/filesystem/convenience.hpp" |
| |
| #include <iostream> |
| #include <string> |
| #include <cstring> |
| #include <map> |
| #include <utility> // for make_pair |
| #include <ctime> |
| #include <cctype> // for tolower |
| #include <cstdlib> // for exit |
| |
| using std::string; |
| namespace xml = boost::tiny_xml; |
| namespace fs = boost::filesystem; |
| |
| // options |
| |
| static bool echo = false; |
| static bool create_dirs = false; |
| static bool boost_build_v2 = true; |
| |
| namespace |
| { |
| struct test_info |
| { |
| string file_path; // relative boost-root |
| string type; |
| bool always_show_run_output; |
| }; |
| typedef std::map< string, test_info > test2info_map; // key is test-name |
| test2info_map test2info; |
| |
| fs::path boost_root; |
| fs::path locate_root; // ALL_LOCATE_TARGET (or boost_root if none) |
| |
| // set_boost_root --------------------------------------------------------// |
| |
| void set_boost_root() |
| { |
| |
| boost_root = fs::initial_path(); |
| |
| for(;;) |
| { |
| if ( fs::exists( boost_root / "libs" ) ) |
| { |
| fs::current_path( fs::initial_path() ); // restore initial path |
| return; |
| } |
| fs::current_path( ".." ); |
| if ( boost_root == fs::current_path() ) |
| { |
| fs::current_path( fs::initial_path() ); // restore initial path |
| std::cout << |
| "Abort: process_jam_log must be run from within a boost directory tree\n"; |
| std::exit(1); |
| } |
| boost_root = fs::current_path(); |
| } |
| } |
| |
| // append_html -------------------------------------------------------------// |
| |
| void append_html( const string & src, string & target ) |
| { |
| // there are a few lines we want to ignore |
| if ( src.find( "th target..." ) != string::npos |
| || src.find( "cc1plus.exe: warning: changing search order for system directory" ) != string::npos |
| || src.find( "cc1plus.exe: warning: as it has already been specified as a non-system directory" ) != string::npos |
| ) return; |
| |
| // on some platforms (e.g. tru64cxx) the following line is a real performance boost |
| target.reserve(src.size() * 2 + target.size()); |
| |
| for ( string::size_type pos = 0; pos < src.size(); ++pos ) |
| { |
| if ( src[pos] == '<' ) target += "<"; |
| else if ( src[pos] == '>' ) target += ">"; |
| else if ( src[pos] == '&' ) target += "&"; |
| else target += src[pos]; |
| } |
| } |
| |
| // timestamp ---------------------------------------------------------------// |
| |
| string timestamp() |
| { |
| char run_date[128]; |
| std::time_t tod; |
| std::time( &tod ); |
| std::strftime( run_date, sizeof(run_date), |
| "%Y-%m-%d %X UTC", std::gmtime( &tod ) ); |
| return string( run_date ); |
| } |
| |
| // convert path separators to forward slashes ------------------------------// |
| |
| void convert_path_separators( string & s ) |
| { |
| for ( string::iterator itr = s.begin(); itr != s.end(); ++itr ) |
| if ( *itr == '\\' || *itr == '!' ) *itr = '/'; |
| } |
| |
| // trim_left ----------------------------------------------------------------// |
| |
| std::string trim_left( std::string const& s ) |
| { |
| std::string::size_type const pos( s.find_first_not_of(' ') ); |
| return pos != std::string::npos |
| ? s.substr( pos, s.size() - pos + 1 ) |
| : "" |
| ; |
| } |
| |
| |
| // split --------------------------------------------------------------------// |
| |
| std::vector<std::string> split( std::string const& s ) |
| { |
| std::string::size_type const pos( s.find_first_of(' ') ); |
| std::vector<std::string> result( 1, s.substr( 0, pos ) ); |
| if ( pos == std::string::npos ) |
| return result; |
| |
| std::vector<std::string> const rest( split( trim_left( s.substr( pos, s.size() - pos + 1 ) ) ) ); |
| result.insert( result.end(), rest.begin(), rest.end() ); |
| return result; |
| } |
| |
| |
| // extract a target directory path from a jam target string ----------------// |
| // s may be relative to the initial_path: |
| // ..\..\..\libs\foo\build\bin\libfoo.lib\vc7\debug\runtime-link-dynamic\boo.obj |
| // s may be absolute: |
| // d:\myboost\libs\foo\build\bin\libfoo.lib\vc7\debug\runtime-link-dynamic\boo.obj |
| // return path is always relative to the boost directory tree: |
| // libs/foo/build/bin/libfs.lib/vc7/debug/runtime-link-dynamic |
| |
| string target_directory( const string & s ) |
| { |
| string temp( s ); |
| convert_path_separators( temp ); |
| temp.erase( temp.find_last_of( "/" ) ); // remove leaf |
| temp = split( trim_left( temp ) ).back(); |
| if ( temp[0] == '.' ) temp.erase( 0, temp.find_first_not_of( "./" ) ); |
| else temp.erase( 0, locate_root.string().size()+1 ); |
| if ( echo ) |
| std::cout << "\ttarget_directory( \"" << s << "\") -> \"" << temp << "\"" << std::endl; |
| return temp; |
| } |
| |
| string::size_type target_name_end( const string & s ) |
| { |
| string::size_type pos = s.find( ".test/" ); |
| if ( pos == string::npos ) pos = s.find( ".dll/" ); |
| if ( pos == string::npos ) pos = s.find( ".so/" ); |
| if ( pos == string::npos ) pos = s.find( ".lib/" ); |
| if ( pos == string::npos ) pos = s.find( ".pyd/" ); |
| if ( pos == string::npos ) pos = s.find( ".a/" ); |
| return pos; |
| } |
| |
| string toolset( const string & s ) |
| { |
| string::size_type pos = target_name_end( s ); |
| if ( pos == string::npos ) pos = s.find( "build/" ); |
| if ( pos == string::npos ) return ""; |
| pos = s.find( "/", pos ) + 1; |
| return s.substr( pos, s.find( "/", pos ) - pos ); |
| } |
| |
| string test_name( const string & s ) |
| { |
| string::size_type pos = target_name_end( s ); |
| if ( pos == string::npos ) return ""; |
| string::size_type pos_start = s.rfind( '/', pos ) + 1; |
| return s.substr( pos_start, |
| (s.find( ".test/" ) != string::npos |
| ? pos : s.find( "/", pos )) - pos_start ); |
| } |
| |
| // Take a path to a target directory of test, and |
| // returns library name corresponding to that path. |
| string test_path_to_library_name( string const& path ) |
| { |
| std::string result; |
| string::size_type start_pos( path.find( "libs/" ) ); |
| if ( start_pos != string::npos ) |
| { |
| // The path format is ...libs/functional/hash/test/something.test/.... |
| // So, the part between "libs" and "test/something.test" can be considered |
| // as library name. But, for some libraries tests are located too deep, |
| // say numeric/ublas/test/test1 directory, and some libraries have tests |
| // in several subdirectories (regex/example and regex/test). So, nested |
| // directory may belong to several libraries. |
| |
| // To disambituate, it's possible to place a 'sublibs' file in |
| // a directory. It means that child directories are separate libraries. |
| // It's still possible to have tests in the directory that has 'sublibs' |
| // file. |
| |
| std::string interesting; |
| start_pos += 5; |
| string::size_type end_pos( path.find( ".test/", start_pos ) ); |
| end_pos = path.rfind('/', end_pos); |
| if (path.substr(end_pos - 5, 5) == "/test") |
| interesting = path.substr( start_pos, end_pos - 5 - start_pos ); |
| else |
| interesting = path.substr( start_pos, end_pos - start_pos ); |
| |
| // Take slash separate elements until we have corresponding 'sublibs'. |
| end_pos = 0; |
| for(;;) |
| { |
| end_pos = interesting.find('/', end_pos); |
| if (end_pos == string::npos) { |
| result = interesting; |
| break; |
| } |
| result = interesting.substr(0, end_pos); |
| |
| if ( fs::exists( ( boost_root / "libs" ) / result / "sublibs" ) ) |
| { |
| end_pos = end_pos + 1; |
| } |
| else |
| break; |
| } |
| } |
| |
| return result; |
| } |
| |
| // Tries to find target name in the string 'msg', starting from |
| // position start. |
| // If found, extract the directory name from the target name and |
| // stores it in 'dir', and return the position after the target name. |
| // Otherwise, returns string::npos. |
| string::size_type parse_skipped_msg_aux(const string& msg, |
| string::size_type start, |
| string& dir) |
| { |
| dir.clear(); |
| string::size_type start_pos = msg.find( '<', start ); |
| if ( start_pos == string::npos ) return string::npos; |
| ++start_pos; |
| string::size_type end_pos = msg.find( '>', start_pos ); |
| dir += msg.substr( start_pos, end_pos - start_pos ); |
| if ( boost_build_v2 ) |
| { |
| // The first letter is a magic value indicating |
| // the type of grist. |
| convert_path_separators( dir ); |
| dir.erase( 0, 1 ); |
| // We need path from root, not from 'status' dir. |
| if (dir.find("../") == 0) |
| dir.erase(0,3); |
| else // dir is always relative to the boost directory tree |
| dir.erase( 0, locate_root.string().size()+1 ); |
| } |
| else |
| { |
| if ( dir[0] == '@' ) |
| { |
| // new style build path, rooted build tree |
| convert_path_separators( dir ); |
| dir.replace( 0, 1, "bin/" ); |
| } |
| else |
| { |
| // old style build path, integrated build tree |
| start_pos = dir.rfind( '!' ); |
| convert_path_separators( dir ); |
| string::size_type path_sep_pos = dir.find( '/', start_pos + 1 ); |
| if ( path_sep_pos != string::npos ) |
| dir.insert( path_sep_pos, "/bin" ); |
| else |
| { |
| // see http://article.gmane.org/gmane.comp.lib.boost.devel/146688; |
| // the following code assumes that: a) 'dir' is not empty, |
| // b) 'end_pos != string::npos' and c) 'msg' always ends with '...' |
| if ( dir[dir.size() - 1] == '@' ) |
| dir += "/" + msg.substr( end_pos + 1, msg.size() - end_pos - 1 - 3 ); |
| } |
| } |
| } |
| return end_pos; |
| } |
| |
| // the format of paths is really kinky, so convert to normal form |
| // first path is missing the leading "..\". |
| // first path is missing "\bin" after "status". |
| // second path is missing the leading "..\". |
| // second path is missing "\bin" after "build". |
| // second path uses "!" for some separators. |
| void parse_skipped_msg( const string & msg, |
| string & first_dir, string & second_dir ) |
| { |
| string::size_type pos = parse_skipped_msg_aux(msg, 0, first_dir); |
| if (pos == string::npos) |
| return; |
| parse_skipped_msg_aux(msg, pos, second_dir); |
| } |
| |
| // test_log hides database details -----------------------------------------// |
| |
| class test_log |
| : boost::noncopyable |
| { |
| const string & m_target_directory; |
| xml::element_ptr m_root; |
| public: |
| test_log( const string & target_directory, |
| const string & test_name, |
| const string & toolset, |
| bool force_new_file ) |
| : m_target_directory( target_directory ) |
| { |
| if ( !force_new_file ) |
| { |
| fs::path pth( locate_root / target_directory / "test_log.xml" ); |
| fs::ifstream file( pth ); |
| if ( file ) // existing file |
| { |
| try |
| { |
| m_root = xml::parse( file, pth.string() ); |
| return; |
| } |
| catch(...) |
| { |
| // unable to parse existing XML file, fall through |
| } |
| } |
| } |
| |
| string library_name( test_path_to_library_name( target_directory ) ); |
| |
| test_info info; |
| test2info_map::iterator itr( test2info.find( library_name + "/" + test_name ) ); |
| if ( itr != test2info.end() ) |
| info = itr->second; |
| |
| if ( !info.file_path.empty() ) |
| library_name = test_path_to_library_name( info.file_path ); |
| |
| if ( info.type.empty() ) |
| { |
| if ( target_directory.find( ".lib/" ) != string::npos |
| || target_directory.find( ".dll/" ) != string::npos |
| || target_directory.find( ".so/" ) != string::npos |
| || target_directory.find( ".dylib/" ) != string::npos |
| || target_directory.find( "/build/" ) != string::npos |
| ) |
| { |
| info.type = "lib"; |
| } |
| else if ( target_directory.find( ".pyd/" ) != string::npos ) |
| info.type = "pyd"; |
| } |
| |
| m_root.reset( new xml::element( "test-log" ) ); |
| m_root->attributes.push_back( |
| xml::attribute( "library", library_name ) ); |
| m_root->attributes.push_back( |
| xml::attribute( "test-name", test_name ) ); |
| m_root->attributes.push_back( |
| xml::attribute( "test-type", info.type ) ); |
| m_root->attributes.push_back( |
| xml::attribute( "test-program", info.file_path ) ); |
| m_root->attributes.push_back( |
| xml::attribute( "target-directory", target_directory ) ); |
| m_root->attributes.push_back( |
| xml::attribute( "toolset", toolset ) ); |
| m_root->attributes.push_back( |
| xml::attribute( "show-run-output", |
| info.always_show_run_output ? "true" : "false" ) ); |
| } |
| |
| ~test_log() |
| { |
| fs::path pth( locate_root / m_target_directory / "test_log.xml" ); |
| if ( create_dirs && !fs::exists( pth.branch_path() ) ) |
| fs::create_directories( pth.branch_path() ); |
| fs::ofstream file( pth ); |
| if ( !file ) |
| { |
| std::cout << "*****Warning - can't open output file: " |
| << pth.string() << "\n"; |
| } |
| else xml::write( *m_root, file ); |
| } |
| |
| const string & target_directory() const { return m_target_directory; } |
| |
| void remove_action( const string & action_name ) |
| // no effect if action_name not found |
| { |
| xml::element_list::iterator itr; |
| for ( itr = m_root->elements.begin(); |
| itr != m_root->elements.end() && (*itr)->name != action_name; |
| ++itr ) {} |
| if ( itr != m_root->elements.end() ) m_root->elements.erase( itr ); |
| } |
| |
| void add_action( const string & action_name, |
| const string & result, |
| const string & timestamp, |
| const string & content ) |
| { |
| remove_action( action_name ); |
| xml::element_ptr action( new xml::element(action_name) ); |
| m_root->elements.push_back( action ); |
| action->attributes.push_back( xml::attribute( "result", result ) ); |
| action->attributes.push_back( xml::attribute( "timestamp", timestamp ) ); |
| action->content = content; |
| } |
| }; |
| |
| // message_manager maps input messages into test_log actions ---------------// |
| |
| class message_manager |
| : boost::noncopyable |
| { |
| string m_action_name; // !empty() implies action pending |
| // IOW, a start_message awaits stop_message |
| string m_target_directory; |
| string m_test_name; |
| string m_toolset; |
| |
| bool m_note; // if true, run result set to "note" |
| // set false by start_message() |
| |
| // data needed to stop further compile action after a compile failure |
| // detected in the same target directory |
| string m_previous_target_directory; |
| bool m_compile_failed; |
| |
| public: |
| message_manager() : m_note(false) {} |
| ~message_manager() { /*assert( m_action_name.empty() );*/ } |
| |
| bool note() const { return m_note; } |
| void note( bool value ) { m_note = value; } |
| |
| void start_message( const string & action_name, |
| const string & target_directory, |
| const string & test_name, |
| const string & toolset, |
| const string & prior_content ) |
| { |
| assert( !target_directory.empty() ); |
| |
| if ( !m_action_name.empty() ) stop_message( prior_content ); |
| m_action_name = action_name; |
| m_target_directory = target_directory; |
| m_test_name = test_name; |
| m_toolset = toolset; |
| m_note = false; |
| |
| if ( m_previous_target_directory != target_directory ) |
| { |
| m_previous_target_directory = target_directory; |
| m_compile_failed = false; |
| } |
| } |
| |
| void stop_message( const string & content ) |
| { |
| if ( m_action_name.empty() ) return; |
| stop_message( m_action_name, m_target_directory, |
| "succeed", timestamp(), content ); |
| } |
| |
| void stop_message( const string & action_name, |
| const string & target_directory, |
| const string & result, |
| const string & timestamp, |
| const string & content ) |
| // the only valid action_names are "compile", "link", "run", "lib" |
| { |
| // My understanding of the jam output is that there should never be |
| // a stop_message that was not preceeded by a matching start_message. |
| // That understanding is built into message_manager code. |
| assert( m_action_name == action_name ); |
| assert( m_target_directory == target_directory ); |
| assert( result == "succeed" || result == "fail" ); |
| |
| // if test_log.xml entry needed |
| if ( !m_compile_failed |
| || action_name != "compile" |
| || m_previous_target_directory != target_directory ) |
| { |
| if ( action_name == "compile" |
| && result == "fail" ) m_compile_failed = true; |
| |
| test_log tl( target_directory, |
| m_test_name, m_toolset, action_name == "compile" ); |
| tl.remove_action( "lib" ); // always clear out lib residue |
| |
| // dependency removal |
| if ( action_name == "lib" ) |
| { |
| tl.remove_action( "compile" ); |
| tl.remove_action( "link" ); |
| tl.remove_action( "run" ); |
| } |
| else if ( action_name == "compile" ) |
| { |
| tl.remove_action( "link" ); |
| tl.remove_action( "run" ); |
| if ( result == "fail" ) m_compile_failed = true; |
| } |
| else if ( action_name == "link" ) |
| { |
| tl.remove_action( "run" ); |
| } |
| |
| // dependency removal won't work right with random names, so assert |
| else { assert( action_name == "run" ); } |
| |
| // add the "run" stop_message action |
| tl.add_action( action_name, |
| result == "succeed" && note() ? std::string("note") : result, |
| timestamp, content ); |
| } |
| |
| m_action_name = ""; // signal no pending action |
| m_previous_target_directory = target_directory; |
| } |
| }; |
| } |
| |
| |
| // main --------------------------------------------------------------------// |
| |
| |
| int main( int argc, char ** argv ) |
| { |
| // Turn off synchronization with corresponding C standard library files. This |
| // gives a significant speed improvement on platforms where the standard C++ |
| // streams are implemented using standard C files. |
| std::ios::sync_with_stdio(false); |
| |
| fs::initial_path(); |
| std::istream* input = 0; |
| |
| if ( argc <= 1 ) |
| { |
| std::cout << "process_jam_log [--echo] [--create-directories] [--v1|--v2]\n" |
| " [--boost-root boost_root] [--locate-root locate_root]\n" |
| " [--input-file input_file]\n" |
| " [locate-root]\n" |
| "--echo - verbose diagnostic output.\n" |
| "--create-directories - if the directory for xml file doesn't exists - creates it.\n" |
| " usually used for processing logfile on different machine\n" |
| "--v2 - bjam version 2 used (default).\n" |
| "--v1 - bjam version 1 used.\n" |
| "--boost-root - the root of the boost installation being used. If not defined\n" |
| " assume to run from within it and discover it heuristically.\n" |
| "--locate-root - the same as the bjam ALL_LOCATE_TARGET\n" |
| " parameter, if any. Default is boost-root.\n" |
| "--input-file - the output of a bjam --dump-tests run. Default is std input.\n" |
| ; |
| return 1; |
| } |
| |
| while ( argc > 1 ) |
| { |
| if ( std::strcmp( argv[1], "--echo" ) == 0 ) |
| { |
| echo = true; |
| --argc; ++argv; |
| } |
| else if ( std::strcmp( argv[1], "--create-directories" ) == 0 ) |
| { |
| create_dirs = true; |
| --argc; ++argv; |
| } |
| else if ( std::strcmp( argv[1], "--v2" ) == 0 ) |
| { |
| boost_build_v2 = true; |
| --argc; ++argv; |
| } |
| else if ( std::strcmp( argv[1], "--v1" ) == 0 ) |
| { |
| boost_build_v2 = false; |
| --argc; ++argv; |
| } |
| else if ( std::strcmp( argv[1], "--boost-root" ) == 0 ) |
| { |
| --argc; ++argv; |
| if ( argc == 1 ) |
| { |
| std::cout << "Abort: option --boost-root requires a directory argument\n"; |
| std::exit(1); |
| } |
| boost_root = fs::path( argv[1], fs::native ); |
| if ( !boost_root.is_complete() ) |
| boost_root = ( fs::initial_path() / boost_root ).normalize(); |
| |
| --argc; ++argv; |
| } |
| else if ( std::strcmp( argv[1], "--locate-root" ) == 0 ) |
| { |
| --argc; ++argv; |
| if ( argc == 1 ) |
| { |
| std::cout << "Abort: option --locate-root requires a directory argument\n"; |
| std::exit(1); |
| } |
| locate_root = fs::path( argv[1], fs::native ); |
| --argc; ++argv; |
| } |
| else if ( std::strcmp( argv[1], "--input-file" ) == 0 ) |
| { |
| --argc; ++argv; |
| if ( argc == 1 ) |
| { |
| std::cout << "Abort: option --input-file requires a filename argument\n"; |
| std::exit(1); |
| } |
| input = new std::ifstream(argv[1]); |
| --argc; ++argv; |
| } |
| else if ( *argv[1] == '-' ) |
| { |
| std::cout << "Abort: unknown option; invoke with no arguments to see list of valid options\n"; |
| return 1; |
| } |
| else |
| { |
| locate_root = fs::path( argv[1], fs::native ); |
| --argc; ++argv; |
| } |
| } |
| |
| if ( boost_root.empty() ) |
| { |
| set_boost_root(); |
| boost_root.normalize(); |
| } |
| |
| |
| if ( locate_root.empty() ) |
| { |
| locate_root = boost_root; |
| } |
| else if ( !locate_root.is_complete() ) |
| { |
| locate_root = ( fs::initial_path() / locate_root ).normalize(); |
| } |
| |
| if ( input == 0 ) |
| { |
| input = &std::cin; |
| } |
| |
| std::cout << "boost_root: " << boost_root.string() << '\n' |
| << "locate_root: " << locate_root.string() << '\n'; |
| |
| message_manager mgr; |
| |
| string line; |
| string content; |
| bool capture_lines = false; |
| |
| // This loop looks at lines for certain signatures, and accordingly: |
| // * Calls start_message() to start capturing lines. (start_message() will |
| // automatically call stop_message() if needed.) |
| // * Calls stop_message() to stop capturing lines. |
| // * Capture lines if line capture on. |
| |
| static const int max_line_length = 8192; |
| int line_num = 0; |
| while ( std::getline( *input, line ) ) |
| { |
| if (max_line_length < line.size()) line = line.substr(0, max_line_length); |
| |
| ++line_num; |
| |
| std::vector<std::string> const line_parts( split( line ) ); |
| std::string const line_start( line_parts[0] != "...failed" |
| ? line_parts[0] |
| : line_parts[0] + " " + line_parts[1] |
| ); |
| |
| if ( echo ) |
| { |
| std::cout |
| << "line " << line_num << ": " << line << "\n" |
| << "\tline_start: " << line_start << "\n"; |
| } |
| |
| // create map of test-name to test-info |
| if ( line_start.find( "boost-test(" ) == 0 ) |
| { |
| string::size_type pos = line.find( '"' ); |
| string test_name( line.substr( pos+1, line.find( '"', pos+1)-pos-1 ) ); |
| test_info info; |
| info.always_show_run_output |
| = line.find( "\"always_show_run_output\"" ) != string::npos; |
| info.type = line.substr( 11, line.find( ')' )-11 ); |
| for (unsigned int i = 0; i!=info.type.size(); ++i ) |
| { info.type[i] = std::tolower( info.type[i] ); } |
| pos = line.find( ':' ); |
| // the rest of line is missing if bjam didn't know how to make target |
| if ( pos + 1 != line.size() ) |
| { |
| info.file_path = line.substr( pos+3, |
| line.find( "\"", pos+3 )-pos-3 ); |
| convert_path_separators( info.file_path ); |
| if ( info.file_path.find( "libs/libs/" ) == 0 ) info.file_path.erase( 0, 5 ); |
| if ( test_name.find( "/" ) == string::npos ) |
| test_name = "/" + test_name; |
| test2info.insert( std::make_pair( test_name, info ) ); |
| // std::cout << test_name << ", " << info.type << ", " << info.file_path << "\n"; |
| } |
| else |
| { |
| std::cout << "*****Warning - missing test path: " << line << "\n" |
| << " (Usually occurs when bjam doesn't know how to make a target)\n"; |
| } |
| continue; |
| } |
| |
| // these actions represent both the start of a new action |
| // and the end of a failed action |
| else if ( line_start.find( "C++-action" ) != string::npos |
| || line_start.find( "vc-C++" ) != string::npos |
| || line_start.find( "C-action" ) != string::npos |
| || line_start.find( "Cc-action" ) != string::npos |
| || line_start.find( "vc-Cc" ) != string::npos |
| || line_start.find( ".compile.") != string::npos |
| || line_start.find( "compile-") != string::npos |
| || line_start.find( "-compile") != string::npos |
| || line_start.find( "Link-action" ) != string::npos |
| || line_start.find( "vc-Link" ) != string::npos |
| || line_start.find( "Archive-action" ) != string::npos |
| || line_start.find( ".archive") != string::npos |
| || ( line_start.find( ".link") != string::npos && |
| // .linkonce is present in gcc linker messages about |
| // unresolved symbols. We don't have to parse those |
| line_start.find( ".linkonce" ) == string::npos ) |
| ) |
| { |
| if ( !test2info.size() ) |
| { |
| std::cout << "*****Error - No \"boost-test\" lines encountered.\n" |
| " (Usually occurs when bjam was envoked without the --dump-tests option\n" |
| " or bjam was envoked in the wrong directory)\n"; |
| return 1; |
| } |
| |
| string action( ( line_start.find( "Link-action" ) != string::npos |
| || line_start.find( "vc-Link" ) != string::npos |
| || line_start.find( "Archive-action" ) != string::npos |
| || line_start.find( ".archive") != string::npos |
| || line_start.find( ".link") != string::npos |
| ) |
| ? "link" : "compile" |
| ); |
| |
| if ( line_start.find( "...failed " ) != string::npos ) |
| { |
| mgr.stop_message( action, target_directory( line ), |
| "fail", timestamp(), content ); |
| } |
| else |
| { |
| string target_dir( target_directory( line ) ); |
| mgr.start_message( action, target_dir, |
| test_name( target_dir ), toolset( target_dir ), content ); |
| } |
| content = "\n"; |
| capture_lines = true; |
| } |
| |
| // these actions are only used to stop the previous action |
| else if ( line_start.find( "-Archive" ) != string::npos |
| || line_start.find( "MkDir" ) == 0 ) |
| { |
| mgr.stop_message( content ); |
| content.clear(); |
| capture_lines = false; |
| } |
| |
| else if ( line_start.find( "execute-test" ) != string::npos |
| || line_start.find( "capture-output" ) != string::npos ) |
| { |
| if ( line_start.find( "...failed " ) != string::npos ) |
| { |
| mgr.stop_message( "run", target_directory( line ), |
| "fail", timestamp(), content ); |
| content = "\n"; |
| capture_lines = true; |
| } |
| else |
| { |
| string target_dir( target_directory( line ) ); |
| mgr.start_message( "run", target_dir, |
| test_name( target_dir ), toolset( target_dir ), content ); |
| |
| // contents of .output file for content |
| capture_lines = false; |
| content = "\n"; |
| fs::ifstream file( locate_root / target_dir |
| / (test_name(target_dir) + ".output") ); |
| if ( file ) |
| { |
| string ln; |
| while ( std::getline( file, ln ) ) |
| { |
| if ( ln.find( "<note>" ) != string::npos ) mgr.note( true ); |
| append_html( ln, content ); |
| content += "\n"; |
| } |
| } |
| } |
| } |
| |
| // bjam indicates some prior dependency failed by a "...skipped" message |
| else if ( line_start.find( "...skipped" ) != string::npos |
| && line.find( "<directory-grist>" ) == string::npos |
| ) |
| { |
| mgr.stop_message( content ); |
| content.clear(); |
| capture_lines = false; |
| |
| if ( line.find( " for lack of " ) != string::npos ) |
| { |
| capture_lines = ( line.find( ".run for lack of " ) == string::npos ); |
| |
| string target_dir; |
| string lib_dir; |
| |
| parse_skipped_msg( line, target_dir, lib_dir ); |
| |
| if ( target_dir != lib_dir ) // it's a lib problem |
| { |
| mgr.start_message( "lib", target_dir, |
| test_name( target_dir ), toolset( target_dir ), content ); |
| content = lib_dir; |
| mgr.stop_message( "lib", target_dir, "fail", timestamp(), content ); |
| content = "\n"; |
| } |
| } |
| |
| } |
| |
| else if ( line_start.find( "**passed**" ) != string::npos |
| || line_start.find( "failed-test-file" ) != string::npos |
| || line_start.find( "command-file-dump" ) != string::npos ) |
| { |
| mgr.stop_message( content ); |
| content = "\n"; |
| capture_lines = true; |
| } |
| |
| else if ( capture_lines ) // hang onto lines for possible later use |
| { |
| append_html( line, content );; |
| content += "\n"; |
| } |
| } |
| |
| mgr.stop_message( content ); |
| if (input != &std::cin) |
| delete input; |
| return 0; |
| } |