1 | // process jam regression test output into XML -----------------------------// |
---|
2 | |
---|
3 | // Copyright Beman Dawes 2002. Distributed under the Boost |
---|
4 | // Software License, Version 1.0. (See accompanying file |
---|
5 | // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) |
---|
6 | |
---|
7 | // See http://www.boost.org/tools/regression for documentation. |
---|
8 | |
---|
9 | #include "detail/tiny_xml.hpp" |
---|
10 | #include "boost/filesystem/operations.hpp" |
---|
11 | #include "boost/filesystem/fstream.hpp" |
---|
12 | #include "boost/filesystem/exception.hpp" |
---|
13 | #include "boost/filesystem/convenience.hpp" |
---|
14 | |
---|
15 | #include <iostream> |
---|
16 | #include <string> |
---|
17 | #include <cstring> |
---|
18 | #include <map> |
---|
19 | #include <utility> // for make_pair |
---|
20 | #include <ctime> |
---|
21 | #include <cctype> // for tolower |
---|
22 | |
---|
23 | using std::string; |
---|
24 | namespace xml = boost::tiny_xml; |
---|
25 | namespace fs = boost::filesystem; |
---|
26 | |
---|
27 | #define BOOST_NO_CPP_MAIN_SUCCESS_MESSAGE |
---|
28 | #include <boost/test/included/prg_exec_monitor.hpp> |
---|
29 | |
---|
30 | // options |
---|
31 | |
---|
32 | static bool echo = false; |
---|
33 | static bool create_dirs = false; |
---|
34 | static bool boost_build_v2 = false; |
---|
35 | |
---|
36 | namespace |
---|
37 | { |
---|
38 | struct test_info |
---|
39 | { |
---|
40 | string file_path; // relative boost-root |
---|
41 | string type; |
---|
42 | bool always_show_run_output; |
---|
43 | }; |
---|
44 | typedef std::map< string, test_info > test2info_map; // key is test-name |
---|
45 | test2info_map test2info; |
---|
46 | |
---|
47 | fs::path boost_root; |
---|
48 | fs::path locate_root; // ALL_LOCATE_TARGET (or boost_root if none) |
---|
49 | |
---|
50 | // append_html -------------------------------------------------------------// |
---|
51 | |
---|
52 | void append_html( const string & src, string & target ) |
---|
53 | { |
---|
54 | // there are a few lines we want to ignore |
---|
55 | if ( src.find( "th target..." ) != string::npos |
---|
56 | || src.find( "cc1plus.exe: warning: changing search order for system directory" ) != string::npos |
---|
57 | || src.find( "cc1plus.exe: warning: as it has already been specified as a non-system directory" ) != string::npos |
---|
58 | ) return; |
---|
59 | |
---|
60 | // on some platforms (e.g. tru64cxx) the following line is a real performance boost |
---|
61 | target.reserve(src.size() * 2 + target.size()); |
---|
62 | |
---|
63 | for ( string::size_type pos = 0; pos < src.size(); ++pos ) |
---|
64 | { |
---|
65 | if ( src[pos] == '<' ) target += "<"; |
---|
66 | else if ( src[pos] == '>' ) target += ">"; |
---|
67 | else if ( src[pos] == '&' ) target += "&"; |
---|
68 | else target += src[pos]; |
---|
69 | } |
---|
70 | } |
---|
71 | |
---|
72 | // timestamp ---------------------------------------------------------------// |
---|
73 | |
---|
74 | string timestamp() |
---|
75 | { |
---|
76 | char run_date[128]; |
---|
77 | std::time_t tod; |
---|
78 | std::time( &tod ); |
---|
79 | std::strftime( run_date, sizeof(run_date), |
---|
80 | "%Y-%m-%d %X UTC", std::gmtime( &tod ) ); |
---|
81 | return string( run_date ); |
---|
82 | } |
---|
83 | |
---|
84 | // convert path separators to forward slashes ------------------------------// |
---|
85 | |
---|
86 | void convert_path_separators( string & s ) |
---|
87 | { |
---|
88 | for ( string::iterator itr = s.begin(); itr != s.end(); ++itr ) |
---|
89 | if ( *itr == '\\' || *itr == '!' ) *itr = '/'; |
---|
90 | } |
---|
91 | |
---|
92 | // extract a target directory path from a jam target string ----------------// |
---|
93 | // s may be relative to the initial_path: |
---|
94 | // ..\..\..\libs\foo\build\bin\libfoo.lib\vc7\debug\runtime-link-dynamic\boo.obj |
---|
95 | // s may be absolute: |
---|
96 | // d:\myboost\libs\foo\build\bin\libfoo.lib\vc7\debug\runtime-link-dynamic\boo.obj |
---|
97 | // return path is always relative to the boost directory tree: |
---|
98 | // libs/foo/build/bin/libfs.lib/vc7/debug/runtime-link-dynamic |
---|
99 | |
---|
100 | string target_directory( const string & s ) |
---|
101 | { |
---|
102 | string temp( s ); |
---|
103 | convert_path_separators( temp ); |
---|
104 | temp.erase( temp.find_last_of( "/" ) ); // remove leaf |
---|
105 | string::size_type pos = temp.find_last_of( " " ); // remove leading spaces |
---|
106 | if ( pos != string::npos ) temp.erase( 0, pos+1 ); |
---|
107 | if ( temp[0] == '.' ) temp.erase( 0, temp.find_first_not_of( "./" ) ); |
---|
108 | else temp.erase( 0, locate_root.string().size()+1 ); |
---|
109 | //std::cout << "\"" << s << "\", \"" << temp << "\"" << std::endl; |
---|
110 | return temp; |
---|
111 | } |
---|
112 | |
---|
113 | string::size_type target_name_end( const string & s ) |
---|
114 | { |
---|
115 | string::size_type pos = s.find( ".test/" ); |
---|
116 | if ( pos == string::npos ) pos = s.find( ".dll/" ); |
---|
117 | if ( pos == string::npos ) pos = s.find( ".so/" ); |
---|
118 | if ( pos == string::npos ) pos = s.find( ".lib/" ); |
---|
119 | if ( pos == string::npos ) pos = s.find( ".pyd/" ); |
---|
120 | if ( pos == string::npos ) pos = s.find( ".a/" ); |
---|
121 | return pos; |
---|
122 | } |
---|
123 | |
---|
124 | string toolset( const string & s ) |
---|
125 | { |
---|
126 | string::size_type pos = target_name_end( s ); |
---|
127 | if ( pos == string::npos ) return ""; |
---|
128 | pos = s.find( "/", pos ) + 1; |
---|
129 | return s.substr( pos, s.find( "/", pos ) - pos ); |
---|
130 | } |
---|
131 | |
---|
132 | string test_name( const string & s ) |
---|
133 | { |
---|
134 | string::size_type pos = target_name_end( s ); |
---|
135 | if ( pos == string::npos ) return ""; |
---|
136 | string::size_type pos_start = s.rfind( '/', pos ) + 1; |
---|
137 | return s.substr( pos_start, |
---|
138 | (s.find( ".test/" ) != string::npos |
---|
139 | ? pos : s.find( "/", pos )) - pos_start ); |
---|
140 | } |
---|
141 | |
---|
142 | // Take a path to a target directory of test, and |
---|
143 | // returns library name corresponding to that path. |
---|
144 | string test_path_to_library_name( string const& path ) |
---|
145 | { |
---|
146 | std::string result; |
---|
147 | string::size_type start_pos( path.find( "libs/" ) ); |
---|
148 | if ( start_pos != string::npos ) |
---|
149 | { |
---|
150 | // The path format is ...libs/functional/hash/test/something.test/.... |
---|
151 | // So, the part between "libs" and "test/something.test" can be considered |
---|
152 | // as library name. But, for some libraries tests are located too deep, |
---|
153 | // say numeric/ublas/test/test1 directory, and some libraries have tests |
---|
154 | // in several subdirectories (regex/example and regex/test). So, nested |
---|
155 | // directory may belong to several libraries. |
---|
156 | |
---|
157 | // To disambituate, it's possible to place a 'sublibs' file in |
---|
158 | // a directory. It means that child directories are separate libraries. |
---|
159 | // It's still possible to have tests in the directory that has 'sublibs' |
---|
160 | // file. |
---|
161 | |
---|
162 | std::string interesting; |
---|
163 | start_pos += 5; |
---|
164 | string::size_type end_pos( path.find( ".test/", start_pos ) ); |
---|
165 | end_pos = path.rfind('/', end_pos); |
---|
166 | if (path.substr(end_pos - 5, 5) == "/test") |
---|
167 | interesting = path.substr( start_pos, end_pos - 5 - start_pos ); |
---|
168 | else |
---|
169 | interesting = path.substr( start_pos, end_pos - start_pos ); |
---|
170 | |
---|
171 | // Take slash separate elements until we have corresponding 'sublibs'. |
---|
172 | end_pos = 0; |
---|
173 | for(;;) |
---|
174 | { |
---|
175 | end_pos = interesting.find('/', end_pos); |
---|
176 | if (end_pos == string::npos) { |
---|
177 | result = interesting; |
---|
178 | break; |
---|
179 | } |
---|
180 | result = interesting.substr(0, end_pos); |
---|
181 | |
---|
182 | if ( fs::exists( ( boost_root / "libs" ) / result / "sublibs" ) ) |
---|
183 | { |
---|
184 | end_pos = end_pos + 1; |
---|
185 | } |
---|
186 | else |
---|
187 | break; |
---|
188 | } |
---|
189 | } |
---|
190 | |
---|
191 | return result; |
---|
192 | } |
---|
193 | |
---|
194 | // Tries to find target name in the string 'msg', starting from |
---|
195 | // position start. |
---|
196 | // If found, extract the directory name from the target name and |
---|
197 | // stores it in 'dir', and return the position after the target name. |
---|
198 | // Otherwise, returns string::npos. |
---|
199 | string::size_type parse_skipped_msg_aux(const string& msg, |
---|
200 | string::size_type start, |
---|
201 | string& dir) |
---|
202 | { |
---|
203 | dir.clear(); |
---|
204 | string::size_type start_pos = msg.find( '<', start ); |
---|
205 | if ( start_pos == string::npos ) return string::npos; |
---|
206 | ++start_pos; |
---|
207 | string::size_type end_pos = msg.find( '>', start_pos ); |
---|
208 | dir += msg.substr( start_pos, end_pos - start_pos ); |
---|
209 | if ( boost_build_v2 ) |
---|
210 | { |
---|
211 | // The first letter is a magic value indicating |
---|
212 | // the type of grist. |
---|
213 | convert_path_separators( dir ); |
---|
214 | dir.erase( 0, 1 ); |
---|
215 | // We need path from root, not from 'status' dir. |
---|
216 | if (dir.find("../") == 0) |
---|
217 | dir.erase(0,3); |
---|
218 | } |
---|
219 | else |
---|
220 | { |
---|
221 | if ( dir[0] == '@' ) |
---|
222 | { |
---|
223 | // new style build path, rooted build tree |
---|
224 | convert_path_separators( dir ); |
---|
225 | dir.replace( 0, 1, "bin/" ); |
---|
226 | } |
---|
227 | else |
---|
228 | { |
---|
229 | // old style build path, integrated build tree |
---|
230 | start_pos = dir.rfind( '!' ); |
---|
231 | convert_path_separators( dir ); |
---|
232 | dir.insert( dir.find( '/', start_pos + 1), "/bin" ); |
---|
233 | } |
---|
234 | } |
---|
235 | return end_pos; |
---|
236 | } |
---|
237 | |
---|
238 | // the format of paths is really kinky, so convert to normal form |
---|
239 | // first path is missing the leading "..\". |
---|
240 | // first path is missing "\bin" after "status". |
---|
241 | // second path is missing the leading "..\". |
---|
242 | // second path is missing "\bin" after "build". |
---|
243 | // second path uses "!" for some separators. |
---|
244 | void parse_skipped_msg( const string & msg, |
---|
245 | string & first_dir, string & second_dir ) |
---|
246 | { |
---|
247 | string::size_type pos = parse_skipped_msg_aux(msg, 0, first_dir); |
---|
248 | if (pos == string::npos) |
---|
249 | return; |
---|
250 | parse_skipped_msg_aux(msg, pos, second_dir); |
---|
251 | } |
---|
252 | |
---|
253 | // test_log hides database details -----------------------------------------// |
---|
254 | |
---|
255 | class test_log |
---|
256 | : boost::noncopyable |
---|
257 | { |
---|
258 | const string & m_target_directory; |
---|
259 | xml::element_ptr m_root; |
---|
260 | public: |
---|
261 | test_log( const string & target_directory, |
---|
262 | const string & test_name, |
---|
263 | const string & toolset, |
---|
264 | bool force_new_file ) |
---|
265 | : m_target_directory( target_directory ) |
---|
266 | { |
---|
267 | if ( !force_new_file ) |
---|
268 | { |
---|
269 | fs::path pth( locate_root / target_directory / "test_log.xml" ); |
---|
270 | fs::ifstream file( pth ); |
---|
271 | if ( file ) // existing file |
---|
272 | { |
---|
273 | try |
---|
274 | { |
---|
275 | m_root = xml::parse( file, pth.string() ); |
---|
276 | return; |
---|
277 | } |
---|
278 | catch(...) |
---|
279 | { |
---|
280 | // unable to parse existing XML file, fall through |
---|
281 | } |
---|
282 | } |
---|
283 | } |
---|
284 | |
---|
285 | string library_name( test_path_to_library_name( target_directory ) ); |
---|
286 | |
---|
287 | test_info info; |
---|
288 | test2info_map::iterator itr( test2info.find( library_name + "/" + test_name ) ); |
---|
289 | if ( itr != test2info.end() ) |
---|
290 | info = itr->second; |
---|
291 | |
---|
292 | if ( !info.file_path.empty() ) |
---|
293 | library_name = test_path_to_library_name( info.file_path ); |
---|
294 | |
---|
295 | if ( info.type.empty() ) |
---|
296 | { |
---|
297 | if ( target_directory.find( ".lib/" ) != string::npos |
---|
298 | || target_directory.find( ".dll/" ) != string::npos |
---|
299 | || target_directory.find( ".so/" ) != string::npos |
---|
300 | || target_directory.find( ".dylib/" ) != string::npos |
---|
301 | ) |
---|
302 | { |
---|
303 | info.type = "lib"; |
---|
304 | } |
---|
305 | else if ( target_directory.find( ".pyd/" ) != string::npos ) |
---|
306 | info.type = "pyd"; |
---|
307 | } |
---|
308 | |
---|
309 | m_root.reset( new xml::element( "test-log" ) ); |
---|
310 | m_root->attributes.push_back( |
---|
311 | xml::attribute( "library", library_name ) ); |
---|
312 | m_root->attributes.push_back( |
---|
313 | xml::attribute( "test-name", test_name ) ); |
---|
314 | m_root->attributes.push_back( |
---|
315 | xml::attribute( "test-type", info.type ) ); |
---|
316 | m_root->attributes.push_back( |
---|
317 | xml::attribute( "test-program", info.file_path ) ); |
---|
318 | m_root->attributes.push_back( |
---|
319 | xml::attribute( "target-directory", target_directory ) ); |
---|
320 | m_root->attributes.push_back( |
---|
321 | xml::attribute( "toolset", toolset ) ); |
---|
322 | m_root->attributes.push_back( |
---|
323 | xml::attribute( "show-run-output", |
---|
324 | info.always_show_run_output ? "true" : "false" ) ); |
---|
325 | } |
---|
326 | |
---|
327 | ~test_log() |
---|
328 | { |
---|
329 | fs::path pth( locate_root / m_target_directory / "test_log.xml" ); |
---|
330 | if ( create_dirs && !fs::exists( pth.branch_path() ) ) |
---|
331 | fs::create_directories( pth.branch_path() ); |
---|
332 | fs::ofstream file( pth ); |
---|
333 | if ( !file ) |
---|
334 | { |
---|
335 | std::cout << "*****Warning - can't open output file: " |
---|
336 | << pth.string() << "\n"; |
---|
337 | } |
---|
338 | else xml::write( *m_root, file ); |
---|
339 | } |
---|
340 | |
---|
341 | const string & target_directory() const { return m_target_directory; } |
---|
342 | |
---|
343 | void remove_action( const string & action_name ) |
---|
344 | // no effect if action_name not found |
---|
345 | { |
---|
346 | xml::element_list::iterator itr; |
---|
347 | for ( itr = m_root->elements.begin(); |
---|
348 | itr != m_root->elements.end() && (*itr)->name != action_name; |
---|
349 | ++itr ) {} |
---|
350 | if ( itr != m_root->elements.end() ) m_root->elements.erase( itr ); |
---|
351 | } |
---|
352 | |
---|
353 | void add_action( const string & action_name, |
---|
354 | const string & result, |
---|
355 | const string & timestamp, |
---|
356 | const string & content ) |
---|
357 | { |
---|
358 | remove_action( action_name ); |
---|
359 | xml::element_ptr action( new xml::element(action_name) ); |
---|
360 | m_root->elements.push_back( action ); |
---|
361 | action->attributes.push_back( xml::attribute( "result", result ) ); |
---|
362 | action->attributes.push_back( xml::attribute( "timestamp", timestamp ) ); |
---|
363 | action->content = content; |
---|
364 | } |
---|
365 | }; |
---|
366 | |
---|
367 | // message_manager maps input messages into test_log actions ---------------// |
---|
368 | |
---|
369 | class message_manager |
---|
370 | : boost::noncopyable |
---|
371 | { |
---|
372 | string m_action_name; // !empty() implies action pending |
---|
373 | // IOW, a start_message awaits stop_message |
---|
374 | string m_target_directory; |
---|
375 | string m_test_name; |
---|
376 | string m_toolset; |
---|
377 | |
---|
378 | bool m_note; // if true, run result set to "note" |
---|
379 | // set false by start_message() |
---|
380 | |
---|
381 | // data needed to stop further compile action after a compile failure |
---|
382 | // detected in the same target directory |
---|
383 | string m_previous_target_directory; |
---|
384 | bool m_compile_failed; |
---|
385 | |
---|
386 | public: |
---|
387 | message_manager() : m_note(false) {} |
---|
388 | ~message_manager() { /*assert( m_action_name.empty() );*/ } |
---|
389 | |
---|
390 | bool note() const { return m_note; } |
---|
391 | void note( bool value ) { m_note = value; } |
---|
392 | |
---|
393 | void start_message( const string & action_name, |
---|
394 | const string & target_directory, |
---|
395 | const string & test_name, |
---|
396 | const string & toolset, |
---|
397 | const string & prior_content ) |
---|
398 | { |
---|
399 | if ( !m_action_name.empty() ) stop_message( prior_content ); |
---|
400 | m_action_name = action_name; |
---|
401 | m_target_directory = target_directory; |
---|
402 | m_test_name = test_name; |
---|
403 | m_toolset = toolset; |
---|
404 | m_note = false; |
---|
405 | if ( m_previous_target_directory != target_directory ) |
---|
406 | { |
---|
407 | m_previous_target_directory = target_directory; |
---|
408 | m_compile_failed = false; |
---|
409 | } |
---|
410 | } |
---|
411 | |
---|
412 | void stop_message( const string & content ) |
---|
413 | { |
---|
414 | if ( m_action_name.empty() ) return; |
---|
415 | stop_message( m_action_name, m_target_directory, |
---|
416 | "succeed", timestamp(), content ); |
---|
417 | } |
---|
418 | |
---|
419 | void stop_message( const string & action_name, |
---|
420 | const string & target_directory, |
---|
421 | const string & result, |
---|
422 | const string & timestamp, |
---|
423 | const string & content ) |
---|
424 | // the only valid action_names are "compile", "link", "run", "lib" |
---|
425 | { |
---|
426 | // My understanding of the jam output is that there should never be |
---|
427 | // a stop_message that was not preceeded by a matching start_message. |
---|
428 | // That understanding is built into message_manager code. |
---|
429 | assert( m_action_name == action_name ); |
---|
430 | assert( m_target_directory == target_directory ); |
---|
431 | assert( result == "succeed" || result == "fail" ); |
---|
432 | |
---|
433 | // if test_log.xml entry needed |
---|
434 | if ( !m_compile_failed |
---|
435 | || action_name != "compile" |
---|
436 | || m_previous_target_directory != target_directory ) |
---|
437 | { |
---|
438 | if ( action_name == "compile" |
---|
439 | && result == "fail" ) m_compile_failed = true; |
---|
440 | |
---|
441 | test_log tl( target_directory, |
---|
442 | m_test_name, m_toolset, action_name == "compile" ); |
---|
443 | tl.remove_action( "lib" ); // always clear out lib residue |
---|
444 | |
---|
445 | // dependency removal |
---|
446 | if ( action_name == "lib" ) |
---|
447 | { |
---|
448 | tl.remove_action( "compile" ); |
---|
449 | tl.remove_action( "link" ); |
---|
450 | tl.remove_action( "run" ); |
---|
451 | } |
---|
452 | else if ( action_name == "compile" ) |
---|
453 | { |
---|
454 | tl.remove_action( "link" ); |
---|
455 | tl.remove_action( "run" ); |
---|
456 | if ( result == "fail" ) m_compile_failed = true; |
---|
457 | } |
---|
458 | else if ( action_name == "link" ) { tl.remove_action( "run" ); } |
---|
459 | |
---|
460 | // dependency removal won't work right with random names, so assert |
---|
461 | else { assert( action_name == "run" ); } |
---|
462 | |
---|
463 | // add the "run" stop_message action |
---|
464 | tl.add_action( action_name, |
---|
465 | result == "succeed" && note() ? "note" : result, |
---|
466 | timestamp, content ); |
---|
467 | } |
---|
468 | |
---|
469 | m_action_name = ""; // signal no pending action |
---|
470 | m_previous_target_directory = target_directory; |
---|
471 | } |
---|
472 | }; |
---|
473 | } |
---|
474 | |
---|
475 | |
---|
476 | // main --------------------------------------------------------------------// |
---|
477 | |
---|
478 | |
---|
479 | int cpp_main( int argc, char ** argv ) |
---|
480 | { |
---|
481 | // Turn off synchronization with corresponding C standard library files. This |
---|
482 | // gives a significant speed improvement on platforms where the standard C++ |
---|
483 | // streams are implemented using standard C files. |
---|
484 | std::ios::sync_with_stdio(false); |
---|
485 | |
---|
486 | if ( argc <= 1 ) |
---|
487 | std::cout << "Usage: bjam [bjam-args] | process_jam_log [--echo] [--create-directories] [--v2] [locate-root]\n" |
---|
488 | "locate-root - the same as the bjam ALL_LOCATE_TARGET\n" |
---|
489 | " parameter, if any. Default is boost-root.\n" |
---|
490 | "create-directories - if the directory for xml file doesn't exists - creates it.\n" |
---|
491 | " usually used for processing logfile on different machine\n"; |
---|
492 | |
---|
493 | boost_root = fs::initial_path(); |
---|
494 | |
---|
495 | while ( !boost_root.empty() |
---|
496 | && !fs::exists( boost_root / "libs" ) ) |
---|
497 | { |
---|
498 | boost_root /= ".."; |
---|
499 | } |
---|
500 | |
---|
501 | if ( boost_root.empty() ) |
---|
502 | { |
---|
503 | std::cout << "must be run from within the boost-root directory tree\n"; |
---|
504 | return 1; |
---|
505 | } |
---|
506 | |
---|
507 | |
---|
508 | if ( argc > 1 && std::strcmp( argv[1], "--echo" ) == 0 ) |
---|
509 | { |
---|
510 | echo = true; |
---|
511 | --argc; ++argv; |
---|
512 | } |
---|
513 | |
---|
514 | |
---|
515 | if (argc > 1 && std::strcmp( argv[1], "--create-directories" ) == 0 ) |
---|
516 | { |
---|
517 | create_dirs = true; |
---|
518 | --argc; ++argv; |
---|
519 | } |
---|
520 | |
---|
521 | if ( argc > 1 && std::strcmp( argv[1], "--v2" ) == 0 ) |
---|
522 | { |
---|
523 | boost_build_v2 = true; |
---|
524 | --argc; ++argv; |
---|
525 | } |
---|
526 | |
---|
527 | |
---|
528 | if (argc > 1) |
---|
529 | { |
---|
530 | locate_root = fs::path( argv[1], fs::native ); |
---|
531 | --argc; ++argv; |
---|
532 | } |
---|
533 | else |
---|
534 | { |
---|
535 | locate_root = boost_root; |
---|
536 | } |
---|
537 | |
---|
538 | std::cout << "boost_root: " << boost_root.string() << '\n' |
---|
539 | << "locate_root: " << locate_root.string() << '\n'; |
---|
540 | |
---|
541 | message_manager mgr; |
---|
542 | |
---|
543 | string line; |
---|
544 | string content; |
---|
545 | bool capture_lines = false; |
---|
546 | |
---|
547 | std::istream* input; |
---|
548 | if (argc > 1) |
---|
549 | { |
---|
550 | input = new std::ifstream(argv[1]); |
---|
551 | } |
---|
552 | else |
---|
553 | { |
---|
554 | input = &std::cin; |
---|
555 | } |
---|
556 | |
---|
557 | // This loop looks at lines for certain signatures, and accordingly: |
---|
558 | // * Calls start_message() to start capturing lines. (start_message() will |
---|
559 | // automatically call stop_message() if needed.) |
---|
560 | // * Calls stop_message() to stop capturing lines. |
---|
561 | // * Capture lines if line capture on. |
---|
562 | |
---|
563 | while ( std::getline( *input, line ) ) |
---|
564 | { |
---|
565 | if ( echo ) std::cout << line << "\n"; |
---|
566 | |
---|
567 | // create map of test-name to test-info |
---|
568 | if ( line.find( "boost-test(" ) == 0 ) |
---|
569 | { |
---|
570 | string::size_type pos = line.find( '"' ); |
---|
571 | string test_name( line.substr( pos+1, line.find( '"', pos+1)-pos-1 ) ); |
---|
572 | test_info info; |
---|
573 | info.always_show_run_output |
---|
574 | = line.find( "\"always_show_run_output\"" ) != string::npos; |
---|
575 | info.type = line.substr( 11, line.find( ')' )-11 ); |
---|
576 | for (unsigned int i = 0; i!=info.type.size(); ++i ) |
---|
577 | { info.type[i] = std::tolower( info.type[i] ); } |
---|
578 | pos = line.find( ':' ); |
---|
579 | // the rest of line is missing if bjam didn't know how to make target |
---|
580 | if ( pos + 1 != line.size() ) |
---|
581 | { |
---|
582 | info.file_path = line.substr( pos+3, |
---|
583 | line.find( "\"", pos+3 )-pos-3 ); |
---|
584 | convert_path_separators( info.file_path ); |
---|
585 | if ( info.file_path.find( "libs/libs/" ) == 0 ) info.file_path.erase( 0, 5 ); |
---|
586 | if ( test_name.find( "/" ) == string::npos ) |
---|
587 | test_name = "/" + test_name; |
---|
588 | test2info.insert( std::make_pair( test_name, info ) ); |
---|
589 | // std::cout << test_name << ", " << info.type << ", " << info.file_path << "\n"; |
---|
590 | } |
---|
591 | else |
---|
592 | { |
---|
593 | std::cout << "*****Warning - missing test path: " << line << "\n" |
---|
594 | << " (Usually occurs when bjam doesn't know how to make a target)\n"; |
---|
595 | } |
---|
596 | continue; |
---|
597 | } |
---|
598 | |
---|
599 | // these actions represent both the start of a new action |
---|
600 | // and the end of a failed action |
---|
601 | else if ( line.find( "C++-action " ) != string::npos |
---|
602 | || line.find( "vc-C++ " ) != string::npos |
---|
603 | || line.find( "C-action " ) != string::npos |
---|
604 | || line.find( "Cc-action " ) != string::npos |
---|
605 | || line.find( "vc-Cc " ) != string::npos |
---|
606 | || line.find( "Link-action " ) != string::npos |
---|
607 | // archive can fail too |
---|
608 | || line.find( "Archive-action " ) != string::npos |
---|
609 | || line.find( "vc-Link " ) != string::npos |
---|
610 | || line.find( ".compile.") != string::npos |
---|
611 | || ( line.find( ".link") != string::npos && |
---|
612 | // .linkonce is present in gcc linker messages about |
---|
613 | // unresolved symbols. We don't have to parse those |
---|
614 | line.find( ".linkonce" ) == string::npos ) |
---|
615 | ) |
---|
616 | { |
---|
617 | string action( ( line.find( "Link-action " ) != string::npos |
---|
618 | || line.find( "vc-Link " ) != string::npos |
---|
619 | || line.find( ".link") != string::npos |
---|
620 | || line.find( "Archive-action ") != string::npos ) |
---|
621 | ? "link" : "compile" ); |
---|
622 | if ( line.find( "...failed " ) != string::npos ) |
---|
623 | mgr.stop_message( action, target_directory( line ), |
---|
624 | "fail", timestamp(), content ); |
---|
625 | else |
---|
626 | { |
---|
627 | string target_dir( target_directory( line ) ); |
---|
628 | mgr.start_message( action, target_dir, |
---|
629 | test_name( target_dir ), toolset( target_dir ), content ); |
---|
630 | } |
---|
631 | content = "\n"; |
---|
632 | capture_lines = true; |
---|
633 | } |
---|
634 | |
---|
635 | // these actions are only used to stop the previous action |
---|
636 | else if ( line.find( "-Archive" ) != string::npos |
---|
637 | || line.find( "MkDir" ) == 0 ) |
---|
638 | { |
---|
639 | mgr.stop_message( content ); |
---|
640 | content.clear(); |
---|
641 | capture_lines = false; |
---|
642 | } |
---|
643 | |
---|
644 | else if ( line.find( "execute-test" ) != string::npos |
---|
645 | || line.find( "capture-output" ) != string::npos ) |
---|
646 | { |
---|
647 | if ( line.find( "...failed " ) != string::npos ) |
---|
648 | { |
---|
649 | mgr.stop_message( "run", target_directory( line ), |
---|
650 | "fail", timestamp(), content ); |
---|
651 | content = "\n"; |
---|
652 | capture_lines = true; |
---|
653 | } |
---|
654 | else |
---|
655 | { |
---|
656 | string target_dir( target_directory( line ) ); |
---|
657 | mgr.start_message( "run", target_dir, |
---|
658 | test_name( target_dir ), toolset( target_dir ), content ); |
---|
659 | |
---|
660 | // contents of .output file for content |
---|
661 | capture_lines = false; |
---|
662 | content = "\n"; |
---|
663 | fs::ifstream file( locate_root / target_dir |
---|
664 | / (test_name(target_dir) + ".output") ); |
---|
665 | if ( file ) |
---|
666 | { |
---|
667 | string ln; |
---|
668 | while ( std::getline( file, ln ) ) |
---|
669 | { |
---|
670 | if ( ln.find( "<note>" ) != string::npos ) mgr.note( true ); |
---|
671 | append_html( ln, content ); |
---|
672 | content += "\n"; |
---|
673 | } |
---|
674 | } |
---|
675 | } |
---|
676 | } |
---|
677 | |
---|
678 | // bjam indicates some prior dependency failed by a "...skipped" message |
---|
679 | else if ( line.find( "...skipped <" ) != string::npos && line.find( "<directory-grist>" ) == string::npos) |
---|
680 | { |
---|
681 | mgr.stop_message( content ); |
---|
682 | content.clear(); |
---|
683 | capture_lines = false; |
---|
684 | |
---|
685 | if ( line.find( " for lack of " ) != string::npos ) |
---|
686 | { |
---|
687 | capture_lines = ( line.find( ".run for lack of " ) == string::npos ); |
---|
688 | |
---|
689 | string target_dir; |
---|
690 | string lib_dir; |
---|
691 | |
---|
692 | parse_skipped_msg( line, target_dir, lib_dir ); |
---|
693 | |
---|
694 | if ( target_dir != lib_dir ) // it's a lib problem |
---|
695 | { |
---|
696 | mgr.start_message( "lib", target_dir, |
---|
697 | test_name( target_dir ), toolset( target_dir ), content ); |
---|
698 | content = lib_dir; |
---|
699 | mgr.stop_message( "lib", target_dir, "fail", timestamp(), content ); |
---|
700 | content = "\n"; |
---|
701 | } |
---|
702 | } |
---|
703 | |
---|
704 | } |
---|
705 | |
---|
706 | else if ( line.find( "**passed**" ) != string::npos |
---|
707 | || line.find( "failed-test-file " ) != string::npos |
---|
708 | || line.find( "command-file-dump" ) != string::npos ) |
---|
709 | { |
---|
710 | mgr.stop_message( content ); |
---|
711 | content = "\n"; |
---|
712 | capture_lines = true; |
---|
713 | } |
---|
714 | |
---|
715 | else if ( capture_lines ) // hang onto lines for possible later use |
---|
716 | { |
---|
717 | append_html( line, content );; |
---|
718 | content += "\n"; |
---|
719 | } |
---|
720 | } |
---|
721 | |
---|
722 | mgr.stop_message( content ); |
---|
723 | if (input != &std::cin) |
---|
724 | delete input; |
---|
725 | return 0; |
---|
726 | } |
---|