/* boost regression test program * * Copyright Jens Maurer 2000 * Permission to use, copy, modify, sell, and distribute this software * is hereby granted without fee provided that the above copyright notice * appears in all copies and that both that copyright notice and this * permission notice appear in supporting documentation, * * Jens Maurer makes no representations about the suitability of this * software for any purpose. It is provided "as is" without express or * implied warranty. * * See http://www.boost.org for most recent version including documentation. * * 2001-01-23 made it compile with Borland (David Abrahams) * 2001-01-22 added --diff option (Jens Maurer) */ #include #include #include #include #include #include #include #include #ifdef __unix #include #include #include #include #include #include #include #else #include #endif // It is OK to use boost headers which contain entirely inline code. #include #ifdef BOOST_NO_STDC_NAMESPACE namespace std { using ::exit; using ::system; using ::strftime; using ::gmtime; using ::time; using ::time_t; using ::getenv; } #endif std::string get_host() { #if defined __linux__ return "linux"; #elif defined __osf__ return "tru64"; #elif defined __sgi return "irix"; #elif defined __sun return "solaris"; #elif defined _WIN32 return "win32"; #elif defined __BEOS__ return "beos"; #elif defined __hpux return "hpux"; #elif defined __IBMCPP__ return "aix"; #elif defined __MSL__ && __dest_os == __mac_os return "macos"; #elif defined __MSL__ && __dest_os == __mac_os_x || defined(__APPLE_CC__) return "macosx"; #else # error Please adapt for your platform #endif } // retrieve precise system configuration as descriptive string #ifdef __unix #include std::string get_system_configuration() { struct utsname ut; // "struct" is required for the DEC Tru64 compiler if(uname(&ut) < 0) return ""; std::string config = std::string(ut.sysname) + " " + ut.release; config = config + " (CPU: " + ut.machine + ")"; return config; } #elif defined _WIN32 std::string get_system_configuration() { return "Microsoft Windows 32bit"; } #elif defined __BEOS__ std::string get_system_configuration() { return "BeOS 5 Intel Edition"; } #elif defined __MSL__ && (__dest_os == __mac_os || __dest_os == __mac_os_x) || defined(__APPLE_CC__) std::string get_system_configuration() { #if __dest_os == _mac_os return "Mac OS 9"; #else return "Mac OS X"; #endif } #else # error Please adapt for your platform #endif struct configuration { std::string compiler_config_file, test_config_file; std::string boostpath; std::string html_output; bool highlight_differences; std::string compiler, test; // defaults configuration() : compiler_config_file("compiler.cfg"), test_config_file("regression.cfg"), boostpath(".."), html_output("cs-" + get_host() + ".html"), highlight_differences(false), compiler("*"), test("") { } }; configuration parse_command_line(char **first, char **last) { configuration cfg; bool output_redirected = false; for( ; first != last; ++first) { std::string arg = *first; if(arg == "--config") { cfg.compiler_config_file = *++first; } else if(arg == "--tests") { cfg.test_config_file = *++first; } else if(arg == "--boost") { cfg.boostpath = *++first; } else if(arg == "-o" || arg == "--output") { cfg.html_output = *++first; output_redirected = true; } else if(arg == "--diff") { cfg.highlight_differences = true; } else if(arg == "--compiler") { cfg.compiler = *++first; } else if(arg.substr(0,1) == "-") { std::cerr << "usage: regression [-h | --help] [--config compiler.cfg]\n" << " [--tests regression.cfg] [--boost path] [-o output.html] [--compiler ]\n" << " [test]\n" << " -h or --help print this help message\n" << " --config compiler configuration file (default: compiler.cfg)\n" << " --tests test configuration file (default: regression.cfg)\n" << " --boost filesystem path to main boost directory (default: ..)\n" << " -o name of output file (default: cs-OS.html)\n" << " --diff highlight differences in output file\n" << " --compiler use only compiler (default: *)\n" << " test a single test, including the action (compile, run, etc.)\n"; std::exit(1); } else { // add the rest of the command line to the "test" line for( ; first != last; ++first) cfg.test += std::string(*first) + " "; break; } } if(cfg.test != "" && !output_redirected) { std::cerr << "Error: Please specify the HTML output file explicitly\n" << "(using \"--output file\") when running a single test only.\n"; std::exit(1); } return cfg; } struct entry { std::string os, identifier, name, compile_only_command, compile_link_command, html; }; // replace the first %name in s with value void replace(std::string & s, const std::string & name, const std::string & value) { std::string::size_type p = s.find(name); if(p != std::string::npos) s.replace(p, name.length(), value); } // replace all $XXX in s with the value of getenv("XXX") void replace_environment(std::string & s) { std::string::size_type end = 0; for(;;) { std::string::size_type pos = s.find('$', end); if(pos == std::string::npos) break; end = s.find_first_not_of("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", pos+1); const char * env = std::getenv(s.substr(pos+1, end-pos-1).c_str()); if(env) replace(s, s.substr(pos, end-pos), env); else break; } } // get a string line, ignoring empty lines and comment lines void getstringline( std::ifstream & is, std::string & s ) { do { std::getline( static_cast(is), s ); // cast required by IRIX } while ( is.good() && (!s.size() || (s.size() >= 2 && s[0] == '/' && s[1] == '/')) ); } // read the compiler configuration from file and push entry's to out template void read_compiler_configuration(const std::string & file, OutputIterator out) { std::ifstream f(file.c_str()); int lineno = 0; while(f.good()) { entry e; getstringline(f, e.os); getstringline(f, e.identifier); getstringline(f, e.name); getstringline(f, e.compile_only_command); getstringline(f, e.compile_link_command); getstringline(f, e.html); *out = e; ++out; std::string l; std::getline(f, l); lineno += 6; if(l != "") { std::cerr << file << ", line " << lineno << ": Empty line expected, got " << l << "\n"; std::exit(1); } } } std::string pass_string = "Pass"; std::string fail_string = "Fail"; // map test name to results, one character ("P" or "F") for each compiler typedef std::map previous_results_type; previous_results_type read_previous_results(std::istream & is) { previous_results_type result; // finite state machine enum { prefix, testname, command, testresult } status = prefix; std::string line, current_test; while(std::getline(is, line)) { if(status == prefix) { if(line.substr(0, 17) == "", 5); if(pos == std::string::npos || pos+1 >= line.size()) { std::cerr << "Line '" << line << "' has unknown format."; continue; } std::string::size_type pos_end = line.find("<", pos); if(pos_end == std::string::npos) { std::cerr << "Line '" << line << "' has unknown format."; continue; } current_test = line.substr(pos+1, pos_end - (pos+1)); status = command; } else if(line.substr(0, 8) == "
") { break; } } else if(status == command) { status = testresult; } else if(status == testresult) { if(line == "") status = testname; else if(line.find(pass_string) != std::string::npos) result[current_test].append("P"); else if(line.find(fail_string) != std::string::npos) result[current_test].append("F"); else std::cerr << "Line '" << line << "' has unknown format."; } } return result; } // run command (possibly needs portability adjustment) bool execute(const std::string & command) { std::cout << command << std::endl; // fix: endl ensures cout ordering #ifdef __unix int ret = 0; pid_t pid = fork(); if(pid == -1) { ret = 1; std::cout << "ERROR: cannot fork: " << std::strerror(errno) << std::endl; } else if(pid == 0) { // child context execl("/bin/sh", "sh", "-c", command.c_str(), 0); std::cout << "ERROR: cannot execl: " << std::strerror(errno) << std::endl; std::exit(1); } else { int status; struct rusage usage; int result = wait3(&status, 0, &usage); if(WIFEXITED(status)) ret = WEXITSTATUS(status); else if(WIFSIGNALED(status)) ret = 1000+WTERMSIG(status); std::cout << "CPU time: " << usage.ru_utime.tv_sec + usage.ru_utime.tv_usec/1e6 << " s user, " << usage.ru_stime.tv_sec + usage.ru_stime.tv_usec/1e6 << " s system" << std::endl; } #else int ret; { boost::progress_timer pt; ret = std::system(command.c_str()); } #endif if(ret != 0) std::cout << "Return code: " << ret << std::endl; return ret == 0; } enum test_result { ok = 0, unknown_type, compile_failed, compile_ok, link_failed, link_ok, run_failed, run_ok }; test_result compile(std::string command, const std::string & boostpath, const std::string & file) { replace(command, "%source", boostpath + "/" + file); return execute(command) ? compile_ok : compile_failed; } test_result link(std::string command, const std::string & boostpath, const std::string & file) { replace(command, "%source", boostpath + "/" + file); return execute(command) ? link_ok : link_failed; } test_result run(std::string command, const std::string & boostpath, const std::string & file, std::string args) { std::string exename = "boosttmp.exe"; replace(command, "%source", boostpath + "/" + file); if(execute(command)) { // cygwin seems to require leading ./ on some systems (JM) #if !defined(__CYGWIN__) && defined(_WIN32) if(get_host() != "win32") #endif exename = "./" + exename; replace(args, "%boost", boostpath); return execute(exename + " " + args) ? run_ok : run_failed; } else { return link_failed; } } std::pair run_test(const std::string & type, std::string compile_only_command, std::string compile_link_command, const std::string & boostpath, const std::string & source, const std::string & args) { replace(compile_only_command, "%include", boostpath); replace(compile_link_command, "%include", boostpath); if(type == "compile") return std::make_pair(compile(compile_only_command, boostpath, source), compile_ok); else if(type == "compile-fail") return std::make_pair(compile(compile_only_command, boostpath, source), compile_failed); else if(type == "link") return std::make_pair(link(compile_link_command, boostpath, source), link_ok); else if(type == "link-fail") return std::make_pair(link(compile_link_command, boostpath, source), link_failed); else if(type == "run") return std::make_pair(run(compile_link_command, boostpath, source, args), run_ok); else if(type == "run-fail") return std::make_pair(run(compile_link_command, boostpath, source, args), run_failed); else return std::make_pair(unknown_type, ok); } template void do_tests(std::ostream & out, ForwardIterator firstcompiler, ForwardIterator lastcompiler, const std::string & testconfig, const std::string & boostpath, const previous_results_type& previous_results, bool highlight_diff) { out << "\n" << "Program\n" << "Test
Type\n"; for(ForwardIterator it = firstcompiler; it != lastcompiler; ++it) { out << "" << it->html << "\n"; } out << "\n"; std::ifstream f(testconfig.c_str()); while(f.good()) { std::string l; getstringline(f, l); if (!f.good()) break; typedef std::string::size_type sz_type; sz_type p = l.find(' '); if(p == std::string::npos) { std::cerr << "Test " << l << " is wrong\n"; continue; } std::string type(l, 0, p); sz_type end_filename = l.find(' ', p+1); std::string file, args; if(end_filename == std::string::npos) { file = l.substr(p+1, std::string::npos); } else { file = l.substr(p+1, end_filename-(p+1)); args = l.substr(end_filename+1, std::string::npos); } std::cout << "*** " << file << " ***\n\n"; out << "\n" << "" << file << "\n" << "" << type << "\n"; previous_results_type::const_iterator prev_iter = previous_results.find(file); std::string previous = (prev_iter == previous_results.end() ? std::string("") : prev_iter->second); std::string::size_type i = 0; for(ForwardIterator it = firstcompiler; it != lastcompiler; ++it, ++i) { std::cout << "** " << it->name << "\n"; std::pair result = run_test(type, it->compile_only_command, it->compile_link_command, boostpath, file, args); if(result.first == unknown_type) { std::cerr << "Unknown test type " << type << ", skipped\n"; continue; } bool pass = result.first == result.second; char prev = (i < previous.size() ? previous[i] : ' '); bool changed = highlight_diff && ((prev == 'F' && pass) || (prev == 'P' && !pass) || prev == ' '); out << "" << (changed ? "" : "") << (pass ? pass_string : fail_string) << (changed ? "" : "") << "" << std::endl; std::cout << (result.first == result.second ? "Pass" : "Fail") << "\n\n"; } out << "\n"; } } int main(int argc, char * argv[]) { configuration config = parse_command_line(argv+1, argv+argc); std::list compilers; read_compiler_configuration(config.compiler_config_file, std::back_inserter(compilers)); std::string host = get_host(); for(std::list::iterator it = compilers.begin(); it != compilers.end(); ) { if(it->os == host && (config.compiler == "*" || it->identifier == config.compiler)) { replace_environment(it->compile_only_command); replace_environment(it->compile_link_command); ++it; } else { it = compilers.erase(it); } } if(compilers.empty()) std::cerr << "You do not have any compatible compilers defined." << std::endl; // if explicit test requested, write temporary file for do_tests if(config.test != "") { std::ofstream tmp((config.test_config_file="boosttmp.tmp").c_str()); tmp << config.test << std::endl; } previous_results_type previous_results; if(config.highlight_differences) { std::ifstream in(config.html_output.c_str()); previous_results = read_previous_results(in); } std::ofstream out( config.html_output.c_str() ); char run_date[100]; std::time_t ct; std::time(&ct); std::strftime(run_date, sizeof(run_date), "%d %b %Y %H:%M GMT", std::gmtime(&ct)); out << "\n\n\nCompiler Status: " + host + "\n\n\n" << "\n" << "\n\n" << "\n" << "\n\n
\n" << "

Compiler Status: " + host + "

\n" << "\n" << "

System Configuration: " << get_system_configuration() << "
\n" << "Run Date: " << run_date << "

\n" << "
\n" << "

\n\n" << "\n"; do_tests(out, compilers.begin(), compilers.end(), config.test_config_file, config.boostpath, previous_results, config.highlight_differences); out << "

\n

\n"; if(host == "linux") out << "Notes: The tests were run on a GNU libc 2.2.4 system which has improved\n" << "wide character support compared to 2.1.x and earlier versions."; else if(host == "irix" || host == "tru64") out << "Note: For the 'clib' configuration, the missing new-style C\n" << "library headers <cXXX> have been supplied.\n"; out << "

\n\n" << std::endl; return 0; }