Browse Source

initial commit - review comments not yet applied

[SVN r15831]
Beman Dawes 23 years ago
parent
commit
58dbbb7318

+ 4 - 0
.gitmodules

@@ -178,3 +178,7 @@
 	path = libs/statechart
 	url = ../statechart.git
 	fetchRecurseSubmodules = on-demand
+[submodule "filesystem"]
+	path = libs/filesystem
+	url = ../filesystem.git
+	fetchRecurseSubmodules = on-demand

+ 1 - 0
libs/filesystem

@@ -0,0 +1 @@
+Subproject commit 543669496a5b57883ba16db264eece5e58665aba

+ 23 - 0
tools/regression/build/Jamfile

@@ -0,0 +1,23 @@
+# Regression test status reporting tools build Jamfile
+
+subproject tools/regression/build ;
+
+exe process_jam_log
+	:
+	../process_jam_log.cpp ../detail/tiny_xml.cpp
+	<lib>../../../libs/filesystem/build/fs$(SUFLIB)
+	:
+	<include>$(BOOST_ROOT)
+	:
+        release
+	;
+
+exe compiler_status
+	:
+	../compiler_status.cpp ../detail/tiny_xml.cpp
+	<lib>../../../libs/filesystem/build/fs$(SUFLIB)
+	:
+	<include>$(BOOST_ROOT)
+	:
+        release
+	;

+ 677 - 0
tools/regression/compiler_status.cpp

@@ -0,0 +1,677 @@
+//  Generate Compiler Status HTML from jam regression test output  -----------//
+
+//  (C) Copyright Beman Dawes 2002. Permission to copy,
+//  use, modify, sell and distribute this software is granted provided this
+//  copyright notice appears in all copies. This software is provided "as is"
+//  without express or implied warranty, and with no claim as to its
+//  suitability for any purpose.
+
+/*******************************************************************************
+
+    This program was designed to work unchanged on all platforms and
+    configurations.  All output which is platform or configuration dependent
+    is obtained from external sources such as the Jamfile, the residue
+    from jam execution, the tools/build/xxx-tools.jam files, or the output
+    of the config_info tests.
+
+    Please avoid adding platform or configuration dependencies during
+    program maintenance.
+
+*******************************************************************************/
+
+#include "boost/filesystem/operations.hpp"
+#include "boost/filesystem/fstream.hpp"
+#include "detail/tiny_xml.hpp"
+namespace fs = boost::filesystem;
+namespace xml = boost::tiny_xml;
+
+#include <cstdlib>  // for abort
+#include <string>
+#include <vector>
+#include <set>
+#include <algorithm>
+#include <iostream>
+#include <fstream>
+#include <ctime>
+#include <stdexcept>
+
+using std::string;
+
+const string pass_msg( "Pass" );
+const string warn_msg( "<font color=\"#FF9900\"><i>Warn</i></font>" );
+const string fail_msg( "<font color=\"#FF0000\">"
+                      "<big><i>Fail</i></big>"
+                       "</font>" );
+const string missing_residue_msg( "<i>Missing</i>" );
+
+const std::size_t max_compile_msg_size = 10000;
+
+namespace
+{
+  fs::path boost_root_dir;
+
+  bool ignore_pass;
+  bool no_warn;
+  bool no_links;
+
+  fs::directory_iterator end_itr;
+
+  // It's immportant for reliability that we find the same compilers for each
+  // test, and that they match the column header.  So save the names at the
+  // time column headings are generated.
+  std::vector<string> toolsets;
+
+  fs::ifstream jamfile;
+  fs::ofstream report;
+  fs::ofstream links_file;
+  string links_name;
+
+  string specific_compiler; // if running on one toolset only
+
+  const string empty_string;
+
+//  convert backslashes to forward slashes -----------------------------------//
+
+  void convert_backslashes( string & s )
+  {
+    for ( string::iterator itr = s.begin(); itr != s.end(); ++itr )
+      if ( *itr == '\\' ) *itr = '/';
+  }
+
+// extra information from target directory string  ---------------------------//
+
+  string extract_test_name( const string & s )
+  {
+    string t( s );
+    string::size_type pos = t.find( "/bin/" );
+    if ( pos != string::npos ) pos += 5;
+    else return "";
+    return t.substr( pos, t.find( ".", pos ) - pos );
+  }
+
+//  find_file  ---------------------------------------------------------------//
+//  given a directory to recursively search
+
+  bool find_file( const fs::path & dir_path, const string & name,
+    fs::path & path_found, const string & ignore_dir_named="" )
+  {
+    if ( !fs::exists( dir_path ) ) return false;
+    for ( fs::directory_iterator itr( dir_path ); itr != end_itr; ++itr )
+      if ( fs::is_directory( *itr )
+        && itr->leaf() != ignore_dir_named )
+      {
+        if ( find_file( *itr, name, path_found ) ) return true;
+      }
+      else if ( itr->leaf() == name )
+      {
+        path_found = *itr;
+        return true;
+      }
+    return false;
+  }
+
+//  platform_desc  -----------------------------------------------------------//
+//  from boost-root/status/bin/config_info.test/xxx/.../config_info.output
+
+  string platform_desc( const fs::path & boost_root_dir )
+  {
+    string result;
+    fs::path dot_output_path;
+
+    // the gcc config_info "Detected Platform" sometimes reports "cygwin", so
+    // prefer any of the other compilers.
+    if ( find_file( boost_root_dir << "status/bin/config_info.test",
+      "config_info.output", dot_output_path, "gcc" )
+      || find_file( boost_root_dir << "status/bin/config_info.test",
+      "config_info.output", dot_output_path ) )
+    {
+      fs::ifstream file( dot_output_path );
+      if ( file )
+      {
+        while( std::getline( file, result ) )
+        {
+          if ( result.find( "Detected Platform: " ) == 0 )
+          {
+            result.erase( 0, 19 );
+            return result;
+          }
+        }
+        result.clear();
+      }
+    }
+    return result;
+  }
+
+//  version_desc  ------------------------------------------------------------//
+//  from boost-root/status/bin/config_info.test/xxx/.../config_info.output
+
+  string version_desc( const fs::path & boost_root_dir,
+    const string & compiler_name )
+  {
+    string result;
+    fs::path dot_output_path;
+    if ( find_file( boost_root_dir << "status/bin/config_info.test"
+      << compiler_name, "config_info.output", dot_output_path ) )
+    {
+      fs::ifstream file( dot_output_path );
+      if ( file )
+      {
+        if( std::getline( file, result ) )
+        {
+          string::size_type pos = result.find( "version " );
+          if ( pos != string::npos )
+          {
+            result.erase( 0, pos+8 );
+          }
+          else result.clear();
+        }
+      }
+    }
+    return result;
+  }
+
+//  compiler_desc  -----------------------------------------------------------//
+//  from boost-root/tools/build/xxx-tools.jam
+
+  string compiler_desc( const fs::path & boost_root_dir,
+    const string & compiler_name )
+  {
+    string result;
+    fs::path tools_path( boost_root_dir << "tools/build" << compiler_name
+      + "-tools.jam" );
+    fs::ifstream file( tools_path );
+    if ( file )
+    {
+      while( std::getline( file, result ) )
+      {
+        if ( result.substr( 0, 3 ) == "#//" )
+        {
+          result.erase( 0, 3 );
+          return result;
+        }
+      }
+      result.clear();
+    }
+    return result;
+  }
+
+//  test_type_desc  ----------------------------------------------------------//
+//  from jamfile
+
+  string test_type_desc( const string & test_name )
+  {
+    // adding "/" and ".c" eliminates a couple of corner cases.
+    // ".c" rather than ".cpp" because regex library includes some .c tests
+    string search_name1( "/" + test_name + ".c" );
+    string search_name2( " " + test_name + " " );
+
+    string result;
+    if ( jamfile.is_open() )
+    {
+      jamfile.clear();
+      jamfile.seekg(0);
+      string line;
+      while( std::getline( jamfile, line ) )
+      {
+        if ( line.find( ".c" ) != string::npos )
+        {
+          if ( line.find( "run " ) != string::npos )
+            result = "run";
+          else if ( line.find( "run-fail " ) != string::npos )
+            result = "run-fail";
+          else if ( line.find( "link " ) != string::npos )
+            result = "link";
+          else if ( line.find( "link-fail " ) != string::npos )
+            result = "link-fail";
+          else if ( line.find( "compile " ) != string::npos )
+            result = "compile";
+          else if ( line.find( "compile-fail " ) != string::npos )
+            result = "compile-fail";
+        }
+        if ( result.size() &&
+          ( line.find( search_name1 ) != string::npos
+          || line.find( search_name2 ) != string::npos ) )
+        {
+          if ( line.find( "# compiler_status<always_show_run_output>" )
+            != string::npos ) result.insert( 0, 1, '*' );
+          return result;
+        }
+      }
+      result.clear();
+    }
+    return result;
+  }
+  
+//  target_directory  --------------------------------------------------------//
+//  this amounts to a request to find a unique leaf directory
+
+  fs::path target_directory( const fs::path & root )
+  {
+    if ( !fs::exists( root ) ) return fs::path("no-such-path");
+    fs::path child;
+    for ( fs::directory_iterator itr( root ); itr != end_itr; ++itr )
+    {
+      if ( fs::is_directory( *itr ) )
+      {
+        if ( child.is_null() ) child = *itr;
+        else throw std::runtime_error(
+          string( "two target possibilities found: \"" )
+            + child.generic_path() + "\" and \""
+            + (*itr).generic_path() + "\"" );
+      }
+    }
+    if ( child.is_null() ) return root; // this dir has no children
+    return target_directory( child );
+  }
+
+//  element_content  ---------------------------------------------------------//
+
+  const string & element_content(
+    const xml::element_ptr & root, const string & name )
+  {
+    static string empty_string;
+    xml::element_list::iterator itr;
+    for ( itr = root->elements.begin();
+          itr != root->elements.end() && (*itr)->name != name;
+          ++itr ) {}
+    return itr != root->elements.end() ? (*itr)->content : empty_string;
+  }
+
+//  generate_report  ---------------------------------------------------------//
+  
+  // return 0 if nothing generated, 1 otherwise, except 2 if compiler msgs
+  int generate_report( const xml::element_ptr & db,
+                       const string & test_name,
+                       const string & toolset,
+                       bool pass,
+                       bool always_show_run_output = false )
+  {
+    // compile msgs sometimes modified, so make a local copy
+    string compile( (pass && no_warn) 
+      ? empty_string :  element_content( db, "compile" ) );
+
+    const string & link( pass ? empty_string : element_content( db, "link" ) );
+    const string & run( (pass && !always_show_run_output) 
+      ? empty_string : element_content( db, "run" ) );
+    string lib( pass ? empty_string : element_content( db, "lib" ) );
+
+    // some compilers output the filename even if there are no errors or
+    // warnings; detect this if one line of output and it contains no space.
+    string::size_type pos = compile.find( '\n', 1 );
+    if ( pos != string::npos && compile.size()-pos <= 2 
+        && compile.find( ' ' ) == string::npos ) compile.clear();
+
+    if ( lib.empty() && compile.empty() && link.empty() && run.empty() )
+      return 0;
+
+    int result = 1; // some kind of msg for sure
+
+    // limit compile message length
+    if ( compile.size() > max_compile_msg_size )
+    {
+      compile.erase( max_compile_msg_size );
+      compile += "...\n   (remainder deleted because of excessive size)\n";
+    }
+
+    links_file << "<h2><a name=\""
+      << test_name << " " << toolset << "\">"
+      << test_name << " / " << toolset << "</a></h2>\n";
+
+    if ( !compile.empty() )
+    {
+      ++result;
+      links_file << "<h3>Compiler output:</h3><pre>"
+        << compile << "</pre>\n";
+    }
+    if ( !link.empty() )
+      links_file << "<h3>Linker output:</h3><pre>" << link << "</pre>\n";  
+    if ( !run.empty() )
+      links_file << "<h3>Run output:</h3><pre>" << run << "</pre>\n";
+
+    static std::set< string > failed_lib_target_dirs;
+    if ( !lib.empty() )
+    {
+      if ( lib[0] == '\n' ) lib.erase( 0, 1 );
+      string lib_test_name( extract_test_name( lib ) );
+      links_file << "<h3>Library build failure: </h3>\n"
+        "See <a href=\"#" << lib_test_name << " " << toolset << "\">"
+        << lib_test_name << " / " << toolset << "</a>";
+
+      if ( failed_lib_target_dirs.find( lib ) == failed_lib_target_dirs.end() )
+      {
+        failed_lib_target_dirs.insert( lib );
+        fs::ifstream file( boost_root_dir << lib << "test_log.xml" );
+        if ( file )
+        {
+          xml::element_ptr db = xml::parse( file );
+          generate_report( db, lib_test_name, toolset, false );
+        }
+        else
+        {
+          links_file << "<h2><a name=\""
+            << lib_test_name << " " << toolset << "\">"
+            << lib_test_name << " / " << toolset << "</a></h2>\n"
+            "test_log.xml not found\n";
+        }
+      }
+    }
+    return result;
+  }
+
+  //  do_cell  -----------------------------------------------------------------//
+
+  bool do_cell( const fs::path & test_dir,
+    const string & test_name,
+    const string & toolset,
+    string & target,
+    bool always_show_run_output )
+  // return true if any results except pass_msg
+  {
+    fs::path target_dir( target_directory( test_dir << toolset ) );
+    bool pass = false;
+
+    // missing jam residue
+    if ( fs::exists( target_dir << (test_name + ".success") ) ) pass = true;
+    else if ( !fs::exists( target_dir << (test_name + ".failure") )
+      && !fs::exists( target_dir << "test_log.xml" ) )
+    {
+      target += "<td>" + missing_residue_msg + "</td>";
+      return true;
+    }
+
+    int anything_generated = 0;
+
+    if ( !no_links )
+    {
+      fs::ifstream file( target_dir << "test_log.xml" );
+      if ( !file ) // missing jam_log.xml
+      {
+        std::cerr << "Missing jam_log.xml in target \""
+          << target_dir.generic_path() << "\"\n";
+        target += "<td>";
+        target += pass ? pass_msg : fail_msg;
+        target += "</td>";
+        return pass;
+      }
+      xml::element_ptr db = xml::parse( file );
+
+      // generate bookmarked report of results, and link to it
+      anything_generated
+        = generate_report( db, test_name, toolset, pass, always_show_run_output );
+    }
+
+    target += "<td>";
+    if ( anything_generated != 0 )
+    {
+      target += "<a href=\"";
+      target += links_name;
+      target += "#";
+      target += test_name;
+      target += " ";
+      target += toolset;
+      target += "\">";
+      target += pass ? (anything_generated < 2 ? pass_msg : warn_msg) : fail_msg;
+      target += "</a>";
+    }
+    else  target += pass ? pass_msg : fail_msg;
+    target += "</td>";
+    return (anything_generated != 0) || !pass;
+  }
+
+//  do_row  ------------------------------------------------------------------//
+
+  void do_row( const fs::path & boost_root_dir,
+    const fs::path & test_dir, // "c:/boost_root/status/bin/any_test.test"
+    const string & test_name, // "any_test"
+    string & target )
+  {
+    // get the path to the test program from the .test file contents
+    string test_path( test_name ); // test_name is default if missing .test
+    fs::path file_path;
+    if ( find_file( test_dir, test_name + ".test", file_path ) )
+    {
+      fs::ifstream file( file_path );
+      if ( file )
+      {
+        std::getline( file, test_path );
+        if ( test_path.size() )
+        {
+          if ( test_path[0] == '\"' ) // added for non-Win32 systems
+          {
+            test_path = test_path.erase( 0, 1 ); // strip "
+            test_path.erase( test_path.find( "\"" ) );
+          }
+          // test_path is now a disk path, so convert to URL style path
+          convert_backslashes( test_path );
+          string::size_type pos = test_path.find( "/libs/" );
+          if ( pos != string::npos ) test_path.replace( 0, pos, ".." );
+        }
+      }
+    }
+
+    // extract the library name from the test_path
+    string lib_name( test_path );
+    string::size_type pos = lib_name.find( "/libs/" );
+    if ( pos != string::npos )
+    {
+      lib_name.erase( 0, pos+6 );
+      pos = lib_name.find( "/" );
+      if ( pos != string::npos ) lib_name.erase( pos );
+    }
+    else lib_name.clear();
+
+    // find the library documentation path
+    string lib_docs_path( "../libs/" + lib_name + "/" );
+    fs::path cur_lib_docs_path( boost_root_dir << "libs" << lib_name );
+    if ( fs::exists( cur_lib_docs_path << "index.htm" ) )
+      lib_docs_path += "index.htm";
+    else if ( fs::exists( cur_lib_docs_path << "index.html" ) )
+      lib_docs_path += "index.html";
+    else if ( fs::exists( cur_lib_docs_path << "doc/index.htm" ) )
+      lib_docs_path += "doc/index.htm";
+    else if ( fs::exists( cur_lib_docs_path << "doc/index.html" ) )
+      lib_docs_path += "doc/index.html";
+    else if ( fs::exists( cur_lib_docs_path << (lib_name + ".htm") ) )
+      lib_docs_path += lib_name + ".htm";
+    else if ( fs::exists( cur_lib_docs_path << (lib_name + ".html") ) )
+      lib_docs_path += lib_name + ".html";
+    else if ( fs::exists( cur_lib_docs_path << "doc" << (lib_name + ".htm") ) )
+      lib_docs_path += lib_name + ".htm";
+    else if ( fs::exists( cur_lib_docs_path << "doc" << (lib_name + ".html") ) )
+      lib_docs_path += lib_name + ".html";
+
+    // generate the library name, test name, and test type table data
+    string::size_type row_start_pos = target.size();
+    target += "<tr><td><a href=\"" + lib_docs_path + "\">"  + lib_name  + "</a></td>";
+    target += "<td><a href=\"" + test_path + "\">" + test_name + "</a></td>";
+
+    string test_type = test_type_desc( test_name );
+    bool always_show_run_output = false;
+    if ( !test_type.empty() && test_type[0] == '*' )
+      { always_show_run_output = true; test_type.erase( 0, 1 ); }
+    target += "<td>" + test_type + "</td>";
+
+    bool no_warn_save = no_warn;
+    if ( test_type.find( "fail" ) != string::npos ) no_warn = true;
+
+    // for each compiler, generate <td>...</td> html
+    bool anything_to_report = false;
+    for ( std::vector<string>::const_iterator itr=toolsets.begin();
+      itr != toolsets.end(); ++itr )
+    {
+      anything_to_report |= do_cell( test_dir, test_name, *itr, target,
+        always_show_run_output );
+    }
+
+    target += "</tr>";
+    if ( ignore_pass && !anything_to_report ) target.erase( row_start_pos );
+    no_warn = no_warn_save;
+  }
+
+//  do_table_body  -----------------------------------------------------------//
+
+  void do_table_body(
+    const fs::path & boost_root_dir, const fs::path & build_dir )
+  {
+    // rows are held in a vector so they can be sorted, if desired.
+    std::vector<string> results;
+    
+    // each test directory
+    for ( fs::directory_iterator itr( build_dir ); itr != end_itr; ++itr )
+    {
+      if ( fs::is_directory( *itr ) )
+      {
+        results.push_back( std::string() ); // no sort required, but leave code
+                                            // in place in case that changes
+        do_row( boost_root_dir, *itr,
+                itr->leaf().substr( 0, itr->leaf().size()-5 ),
+                results[results.size()-1] );
+      }
+    }
+
+    std::sort( results.begin(), results.end() );
+
+    for ( std::vector<string>::iterator v(results.begin());
+      v != results.end(); ++v )
+      { report << *v << "\n"; }
+  }
+
+//  do_table  ----------------------------------------------------------------//
+
+  void do_table( const fs::path & boost_root_dir )
+  {
+//    fs::path build_dir( boost_root_dir << "status" << "bin" );
+    fs::path build_dir( fs::initial_directory() << "bin" );
+
+    report << "<table border=\"1\" cellspacing=\"0\" cellpadding=\"5\">\n";
+     
+    // generate the column headings
+
+    report << "<tr><td>Library</td><td>Test Name</td>\n"
+      "<td><a href=\"compiler_status.html#test-type\">Test Type</a></td>\n";
+
+    fs::directory_iterator itr( build_dir );
+    if ( itr != end_itr )
+    {
+      fs::directory_iterator compiler_itr( *itr );
+      if ( specific_compiler.empty() )
+        std::clog << "Using " << itr->generic_path() << " to determine compilers\n";
+      for (; compiler_itr != end_itr; ++compiler_itr )
+      {
+        if ( fs::is_directory( *compiler_itr )  // check just to be sure
+          && compiler_itr->leaf() != "test" ) // avoid strange directory (Jamfile bug?)
+        {
+          if ( specific_compiler.size() != 0
+            && specific_compiler != compiler_itr->leaf() ) continue;
+          toolsets.push_back( compiler_itr->leaf() );
+          string desc( compiler_desc( boost_root_dir,
+                                                 compiler_itr->leaf() ) );
+          string vers( version_desc( boost_root_dir,
+                                                compiler_itr->leaf() ) );
+          report << "<td>"
+               << (desc.size() ? desc : compiler_itr->leaf())
+               << (vers.size() ? (string( "<br>" ) + vers ) : string( "" ))
+               << "</td>\n";
+        } 
+      }
+    }
+
+    report << "</tr>\n";
+
+    // now the rest of the table body
+
+    do_table_body( boost_root_dir, build_dir );
+
+    report << "</table>\n";
+  }
+
+} // unnamed namespace
+
+//  main  --------------------------------------------------------------------//
+
+#define BOOST_NO_CPP_MAIN_SUCCESS_MESSAGE
+#include <boost/test/included/prg_exec_monitor.hpp>
+
+int cpp_main( int argc, char * argv[] ) // note name!
+{
+  while ( argc > 1 && *argv[1] == '-' )
+  {
+    if ( argc > 2 && std::strcmp( argv[1], "--compiler" ) == 0 )
+    { specific_compiler = argv[2]; --argc; ++argv; }
+    else if ( std::strcmp( argv[1], "--ignore-pass" ) == 0 ) ignore_pass = true;
+    else if ( std::strcmp( argv[1], "--no-warn" ) == 0 ) no_warn = true;
+    else { std::cerr << "Unknown option: " << argv[1] << "\n"; argc = 1; }
+    --argc;
+    ++argv;
+  }
+
+  if ( argc != 3 && argc != 4 )
+  {
+    std::cerr <<
+      "usage: compiler_status [options...] boost-root-dir status-file [links-file]\n"
+      "must be run from directory containing Jamfile\n"
+      "  options: --compiler name     Run for named compiler only\n"
+      "           --ignore-pass       Do not report tests which pass all compilers\n"
+      "           --no-warn           Warnings not reported if test passes\n"
+      "example: compiler_status --compiler gcc \\boost-root cs.html cs-links.html\n";
+    return 1;
+  }
+
+  boost_root_dir = fs::path( argv[1], fs::system_specific );
+  fs::path jamfile_path( fs::initial_directory() << "Jamfile" );
+  jamfile.open( jamfile_path );
+  if ( !jamfile )
+  {
+    std::cerr << "Could not open Jamfile: " << jamfile_path.file_path() << std::endl;
+    return 1;
+  }
+
+  report.open( argv[2] );
+  if ( !report )
+  {
+    std::cerr << "Could not open report output file: " << argv[2] << std::endl;
+    return 1;
+  }
+
+  if ( argc == 4 )
+  {
+    links_name = argv[3];
+    links_file.open( links_name );
+    if ( !links_file )
+    {
+      std::cerr << "Could not open links output file: " << links_name << std::endl;
+      return 1;
+    }
+  }
+  else no_links = true;
+
+  char run_date[128];
+  std::time_t tod;
+  std::time( &tod );
+  std::strftime( run_date, sizeof(run_date),
+    "%X UTC, %A %d %B %Y", std::gmtime( &tod ) );
+
+  report << "<html>\n"
+          "<head>\n"
+          "<title>Boost Compiler Status Automatic Test</title>\n"
+          "</head>\n"
+          "<body bgcolor=\"#ffffff\" text=\"#000000\">\n"
+          "<table border=\"0\">\n"
+          "<tr>\n"
+          "<td><img border=\"0\" src=\"../c++boost.gif\" width=\"277\" "
+          "height=\"86\"></td>\n"
+          "<td>\n"
+          "<h1>Compiler Status: " + platform_desc( boost_root_dir ) + "</h1>\n"
+          "<b>Run Date:</b> "
+       << run_date
+       << "\n</td>\n</table>\n<br>\n"
+       ;
+
+  do_table( boost_root_dir );
+
+  report << "</body>\n"
+          "</html>\n"
+          ;
+
+  return 0;
+}

+ 157 - 0
tools/regression/detail/tiny_xml.cpp

@@ -0,0 +1,157 @@
+//  tiny XML sub-set tools implementation  -----------------------------------//
+
+//  (C) Copyright Beman Dawes 2002. Permission to copy,
+//  use, modify, sell and distribute this software is granted provided this
+//  copyright notice appears in all copies. This software is provided "as is"
+//  without express or implied warranty, and with no claim as to its
+//  suitability for any purpose.
+
+#include "tiny_xml.hpp"
+#include <cassert>
+
+namespace
+{
+
+  void eat_whitespace( char & c, std::istream & in )
+  {
+    while ( c == ' ' || c == '\r' || c == '\n' || c == '\t' )
+      in.get( c );
+  }
+
+  std::string get_name( char & c, std::istream & in )
+  {
+    std::string result;
+    eat_whitespace( c, in );
+    while ( std::strchr(
+      "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-.", c )
+      != 0 )
+    {
+      result += c;
+      in.get( c );
+    }
+    return result;
+  }
+
+  void eat_delim( char & c, std::istream & in, char delim )
+  {
+    eat_whitespace( c, in );
+    if ( c != delim )
+      throw (std::string("xml syntax error, expected ") + delim);
+    in.get( c );
+  }
+
+  std::string get_value( char & c, std::istream & in )
+  {
+    std::string result;
+    while ( c != '\"' )
+    {
+      result += c;
+      in.get( c );
+    }
+    in.get( c );
+    return result;
+  }
+
+}
+
+namespace boost
+{
+  namespace tiny_xml
+  {
+
+  //  parse  -----------------------------------------------------------------//
+
+    element_ptr parse( std::istream & in )
+    {
+      char c = 0;  // current character
+      element_ptr e( new element );
+
+      in.get( c );
+      if ( c == '<' ) in.get( c );
+
+      e->name = get_name( c, in );
+      eat_whitespace( c, in );
+
+      // attributes
+      while ( c != '>' )
+      {
+        attribute a;
+        a.name = get_name( c, in );
+
+        eat_delim( c, in, '=' );
+        eat_delim( c, in, '\"' );
+
+        a.value = get_value( c, in );
+
+        e->attributes.push_back( a );
+        eat_whitespace( c, in );
+      }
+      in.get( c ); // next after '>'
+      eat_whitespace( c, in );
+
+      // sub-elements
+      while ( c == '<' )
+      {
+        if ( in.peek() == '/' ) break;
+        e->elements.push_back( parse( in ) );
+        in.get( c ); // next after '>'
+        eat_whitespace( c, in );
+      }
+
+      // content
+      if ( c != '<' )
+      {
+        e->content += '\n';
+        while ( c != '<' )
+        {
+          e->content += c;
+          in.get( c );
+        }
+      }
+
+      assert( c == '<' );
+      in.get( c ); // next after '<'
+
+      eat_delim( c, in, '/' );
+      std::string end_name( get_name( c, in ) );
+      if ( e->name != end_name )
+        throw (std::string("xml syntax error: beginning name ") +
+          e->name + " did not match end name " + end_name);
+
+      eat_delim( c, in, '>' );
+      return e;
+    }
+
+    //  write  ---------------------------------------------------------------//
+
+    void write( const element & e, std::ostream & out )
+    {
+      out << "<" << e.name;
+      if ( !e.attributes.empty() )
+      {
+        for( attribute_list::const_iterator itr = e.attributes.begin();
+             itr != e.attributes.end(); ++itr )
+        {
+          out << " " << itr->name << "=\"" << itr->value << "\"";
+        }
+      }
+      out << ">";
+      if ( !e.elements.empty() )
+      {
+        out << "\n";
+        for( element_list::const_iterator itr = e.elements.begin();
+             itr != e.elements.end(); ++itr )
+        {
+          write( **itr, out );
+        }
+      }
+      if ( !e.content.empty() )
+      {
+        out << e.content;
+      }
+      out << "</" << e.name << ">\n";
+    }
+
+  } // namespace tiny_xml
+} // namespace boost
+

+ 71 - 0
tools/regression/detail/tiny_xml.hpp

@@ -0,0 +1,71 @@
+//  tiny XML sub-set tools  --------------------------------------------------//
+
+//  (C) Copyright Beman Dawes 2002. Permission to copy,
+//  use, modify, sell and distribute this software is granted provided this
+//  copyright notice appears in all copies. This software is provided "as is"
+//  without express or implied warranty, and with no claim as to its
+//  suitability for any purpose.
+
+//  Provides self-contained tools for this XML sub-set:
+//
+//    element ::= { "<" name { name "=" "\"" value "\"" } ">"
+//                  {element} [contents] "</" name ">" }
+//
+//  The point of "self-contained" is to minimize tool-chain dependencies.
+
+#ifndef BOOST_TINY_XML_H
+#define BOOST_TINY_XML_H
+
+#include "boost/smart_ptr.hpp" // for shared_ptr
+#include "boost/utility.hpp"   // for noncopyable
+#include <list>
+#include <iostream>
+#include <string>
+
+namespace boost
+{
+  namespace tiny_xml
+  {
+    class element;
+    struct attribute
+    {
+      std::string name;
+      std::string value;
+
+      attribute(){}
+      attribute( const std::string & name, const std::string & value )
+        : name(name), value(value) {}
+    };
+    typedef boost::shared_ptr< element >  element_ptr;
+    typedef std::list< element_ptr  >     element_list;
+    typedef std::list< attribute >        attribute_list;
+
+    struct element
+      : boost::noncopyable  // because of shared_ptr use
+    {
+      std::string     name;
+      attribute_list  attributes;
+      element_list    elements;
+      std::string     content;
+
+      element() {}
+      explicit element( const std::string & name ) : name(name) {}
+    };
+
+    element_ptr parse( std::istream & in );
+    // Precondition: stream positioned at either the initial "<"
+    // or the first character after the initial "<".
+    // Postcondition: stream positioned at the first character after final
+    //  ">" (or eof).
+    // Returns: an element_ptr to an element representing the parsed stream.
+    // Throws: std::string on syntax error.
+
+    void write( const element & e, std::ostream & out );
+
+  }
+}
+
+#endif  // BOOST_TINY_XML_H
+
+
+

+ 19 - 0
tools/regression/detail/tiny_xml_test.cpp

@@ -0,0 +1,19 @@
+//  tiny XML test program  ---------------------------------------------------//
+
+//  (C) Copyright Beman Dawes 2002. Permission to copy,
+//  use, modify, sell and distribute this software is granted provided this
+//  copyright notice appears in all copies. This software is provided "as is"
+//  without express or implied warranty, and with no claim as to its
+//  suitability for any purpose.
+
+#include "tiny_xml.hpp"
+
+#include <iostream>
+
+int main()
+{
+  boost::tiny_xml::element_ptr tree( boost::tiny_xml::parse( std::cin ) );
+  boost::tiny_xml::write( *tree, std::cout );
+  return 0;
+}
+

+ 12 - 0
tools/regression/detail/tiny_xml_test.txt

@@ -0,0 +1,12 @@
+<root>
+<element-1 at-1="abcd" at-2 = "defg" >
+<element-1a>
+It's Howdy Doody time!
+</element-1a>
+<element-1b>It's not Howdy Doody time!</element-1b>
+</element-1>
+<element-2>
+It's
+Eastern Standard time!
+</element-2>
+</root>

+ 380 - 0
tools/regression/process_jam_log.cpp

@@ -0,0 +1,380 @@
+//  process jam regression test output into XML  -----------------------------//
+
+//  (C) Copyright Beman Dawes 2002. Permission to copy,
+//  use, modify, sell and distribute this software is granted provided this
+//  copyright notice appears in all copies. This software is provided "as is"
+//  without express or implied warranty, and with no claim as to its
+//  suitability for any purpose.
+
+#include "detail/tiny_xml.hpp"
+#include "boost/filesystem/operations.hpp"
+#include "boost/filesystem/fstream.hpp"
+#include "boost/filesystem/exception.hpp"
+
+#include <iostream>
+#include <string>
+#include <ctime>
+
+using std::string;
+namespace xml = boost::tiny_xml;
+namespace fs = boost::filesystem;
+
+#define BOOST_NO_CPP_MAIN_SUCCESS_MESSAGE
+#include <boost/test/included/prg_exec_monitor.hpp>
+
+namespace
+{
+//  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;
+
+    for ( string::size_type pos = 0; pos < src.size(); ++pos )
+    {
+      if ( src[pos] == '<' ) target += "&lt;";
+      else if ( src[pos] == '>' ) target += "&gt;";
+      else if ( src[pos] == '&' ) target += "&amp;";
+      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 = '/';
+  }
+
+//  extract a directory from a jam target string  ----------------------------//
+
+  string target_directory( const string & s )
+  {
+    string temp( s );
+    convert_path_separators( temp );
+    temp.erase( temp.find_last_of( "/" ) );
+    string::size_type pos = temp.find_last_of( " " );
+    if ( pos != string::npos ) temp.erase( 0, pos+1 );
+    return temp;
+  }
+
+  string toolset( const string & s )
+  {
+    string t( s );
+    string::size_type pos = t.find( "/bin/" );
+    if ( pos != string::npos ) pos += 5;
+    else return "";
+    pos = t.find( "/", pos );
+    if ( pos != string::npos ) pos += 1;
+    else return "";
+    return t.substr( pos, t.find( "/", pos ) - pos );
+  }
+
+  string test_name( const string & s )
+  {
+    string t( s );
+    string::size_type pos = t.find( "/bin/" );
+    if ( pos != string::npos ) pos += 5;
+    else return "";
+    return t.substr( pos, t.find( ".", pos ) - 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 )
+  {
+    first_dir.clear();
+    second_dir.clear();
+    string::size_type start_pos( msg.find( '<' ) );
+    if ( start_pos == string::npos ) return;
+    ++start_pos;
+    string::size_type end_pos( msg.find( '>', start_pos ) );
+    first_dir += msg.substr( start_pos, end_pos - start_pos );
+    convert_path_separators( first_dir );
+    first_dir.insert( 6, "/bin" );
+
+    start_pos = msg.find( '<', end_pos );
+    if ( start_pos == string::npos ) return;
+    ++start_pos;
+    end_pos = msg.find( '>', start_pos );
+    second_dir += msg.substr( start_pos, end_pos - start_pos );
+    convert_path_separators( second_dir );
+    second_dir.insert( second_dir.find( "/build/" )+6, "/bin" );
+  }
+
+
+
+//  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 )
+      : m_target_directory( target_directory )
+    {
+      fs::path pth( target_directory );
+      pth <<= "test_log.xml";
+      fs::ifstream file( pth );
+      if ( !file )
+      {
+        m_root.reset( new xml::element( "test-log" ) );
+        m_root->attributes.push_back(
+          xml::attribute( "target-directory", target_directory ) );
+        m_root->attributes.push_back(
+          xml::attribute( "test-name", test_name ) );
+        m_root->attributes.push_back(
+          xml::attribute( "toolset", toolset ) );
+      }
+      else // existing file
+      {
+        m_root = xml::parse( file );
+      }
+//std::cout <<     m_root->elements.size() << std::endl;
+    }
+
+    ~test_log()
+    {
+      fs::path pth( m_target_directory );
+      pth <<= "test_log.xml";
+      fs::ofstream file( pth );
+      if ( !file ) throw fs::filesystem_error( pth.generic_path() );
+      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;
+
+  public:
+    ~message_manager() { /*assert( m_action_name.empty() );*/ }
+
+    void start_message( const string & action_name,
+                      const string & target_directory,
+                      const string & test_name,
+                      const string & toolset,
+                      const string & prior_content )
+    {
+      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;
+    }
+
+    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.
+      assert( m_action_name == action_name );
+      assert( m_target_directory == target_directory );
+
+      test_log tl( target_directory, m_test_name, m_toolset );
+
+      // 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" );
+      }
+      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 stop_message action
+      tl.add_action( action_name, result, timestamp, content );
+
+      m_action_name = ""; // signal no pending action
+    }
+  };
+}
+
+//  main  --------------------------------------------------------------------//
+
+int cpp_main( int argc, char ** argv )
+{
+  message_manager mgr;
+
+  string line;
+  string content;
+  bool capture_lines;
+
+  while ( std::getline( std::cin, line ) )
+  {
+//std::cout << line << "\n";
+    if ( line.find( "C++-action " ) != string::npos
+      || line.find( "vc-C++ " ) != string::npos
+      || line.find( "C-action " ) != string::npos
+      || line.find( "Cc-action " ) != string::npos
+      || line.find( "Link-action " ) != string::npos
+      || line.find( "vc-Link " ) != string::npos )
+    {
+      string action( ( line.find( "Link-action " ) != string::npos
+        || line.find( "vc-Link " ) != string::npos )
+        ? "link" : "compile" );
+      if ( line.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;
+    }
+
+    else if ( line.find( "capture-run-output" ) != string::npos )
+    {
+      if ( line.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( fs::path(target_dir)
+          << (test_name(target_dir) + ".output") );
+        if ( file )
+        {
+          string ln;
+          while ( std::getline( file, ln ) )
+          {
+            append_html( ln, content );;
+            content += "\n";
+          }
+        }
+      }
+    }
+
+    else if ( line.find( "...skipped <" ) != string::npos
+      && line.find( ".test for lack of " ) != string::npos )
+    {
+      mgr.stop_message( content );
+      content.clear();
+      capture_lines = true;
+
+      string target_dir;
+      string lib_dir;
+
+      parse_skipped_msg( line, target_dir, lib_dir );
+
+      if ( target_dir != lib_dir ) // it's a lib problem
+      {
+        target_dir.insert( 0, "../" );
+        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.find( "succeeded-test" ) != string::npos
+      || line.find( "failed-test-file " ) != string::npos
+      || line.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 );
+  return 0;
+}

粤ICP备19079148号