Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: downloads/boost_1_33_1/tools/regression/process_jam_log.cpp @ 12

Last change on this file since 12 was 12, checked in by landauf, 17 years ago

added boost

File size: 23.6 KB
Line 
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
23using std::string;
24namespace xml = boost::tiny_xml;
25namespace 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
32static bool echo = false;
33static bool create_dirs = false;
34static bool boost_build_v2 = false;
35
36namespace
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 += "&lt;";
66      else if ( src[pos] == '>' ) target += "&gt;";
67      else if ( src[pos] == '&' ) target += "&amp;";
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
479int 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}
Note: See TracBrowser for help on using the repository browser.