/// Parses a user-provided test filter. /// /// \param str The user-provided string representing a filter for tests. Must /// be of the form <test_program%gt;[:<test_case%gt;]. /// /// \return The parsed filter. /// /// \throw std::runtime_error If the provided filter is invalid. engine::test_filter engine::test_filter::parse(const std::string& str) { if (str.empty()) throw std::runtime_error("Test filter cannot be empty"); const std::string::size_type pos = str.find(':'); if (pos == 0) throw std::runtime_error(F("Program name component in '%s' is empty") % str); if (pos == str.length() - 1) throw std::runtime_error(F("Test case component in '%s' is empty") % str); try { const fs::path test_program_(str.substr(0, pos)); if (test_program_.is_absolute()) throw std::runtime_error(F("Program name '%s' must be relative " "to the test suite, not absolute") % test_program_.str()); if (pos == std::string::npos) { LD(F("Parsed user filter '%s': test program '%s', no test case") % str % test_program_.str()); return test_filter(test_program_, ""); } else { const std::string test_case_(str.substr(pos + 1)); LD(F("Parsed user filter '%s': test program '%s', test case '%s'") % str % test_program_.str() % test_case_); return test_filter(test_program_, test_case_); } } catch (const fs::error& e) { throw std::runtime_error(F("Invalid path in filter '%s': %s") % str % e.what()); } }
/// Executes an external binary and replaces the current process. /// /// This differs from process::exec() in that this function reports errors /// caused by the exec(2) system call to let the caller decide how to handle /// them. /// /// This function must not use any of the logging features so that the output /// of the subprocess is not "polluted" by our own messages. /// /// This function must also not affect the global state of the current process /// as otherwise we would not be able to use vfork(). Only state stored in the /// stack can be touched. /// /// \param program The binary to execute. /// \param args The arguments to pass to the binary, without the program name. /// /// \throw system_error If the exec(2) call fails. void process::exec_unsafe(const fs::path& program, const args_vector& args) { PRE(args.size() < MAX_ARGS); int original_errno = 0; try { const char* argv[MAX_ARGS + 1]; argv[0] = program.c_str(); for (args_vector::size_type i = 0; i < args.size(); i++) argv[1 + i] = args[i].c_str(); argv[1 + args.size()] = NULL; const int ret = ::execv(program.c_str(), (char* const*)(unsigned long)(const void*)argv); original_errno = errno; INV(ret == -1); std::cerr << "Failed to execute " << program << ": " << std::strerror(original_errno) << "\n"; } catch (const std::runtime_error& error) { std::cerr << "Failed to execute " << program << ": " << error.what() << "\n"; std::abort(); } catch (...) { std::cerr << "Failed to execute " << program << "; got unexpected " "exception during exec\n"; std::abort(); } // We must do this here to prevent our exception from being caught by the // generic handlers above. INV(original_errno != 0); throw system_error("Failed to execute " + program.str(), original_errno); }
/// Returns the test suite name for the current directory. /// /// \return The identifier of the current test suite. std::string layout::test_suite_for_path(const fs::path& path) { std::string test_suite; if (path.is_absolute()) test_suite = path.str(); else test_suite = path.to_absolute().str(); PRE(!test_suite.empty() && test_suite[0] == '/'); std::replace(test_suite.begin(), test_suite.end(), '/', '_'); test_suite.erase(0, 1); return test_suite; }
/// Creates a temporary file. /// /// The temporary file is created using mkstemp(3) using the provided template. /// This should be most likely used in conjunction with fs::auto_file. /// /// \param path_template The template for the temporary path, which is a /// basename that is created within the TMPDIR. Must contain the XXXXXX /// pattern, which is atomically replaced by a random unique string. /// /// \return The generated path for the temporary directory. /// /// \throw fs::system_error If the call to mkstemp(3) fails. fs::path fs::mkstemp(const std::string& path_template) { PRE(path_template.find("XXXXXX") != std::string::npos); const fs::path tmpdir(utils::getenv_with_default("TMPDIR", "/tmp")); const fs::path full_template = tmpdir / path_template; utils::auto_array< char > buf(new char[full_template.str().length() + 1]); std::strcpy(buf.get(), full_template.c_str()); if (::mkstemp(buf.get()) == -1) { const int original_errno = errno; throw fs::system_error(F("Cannot create temporary file using template " "%s") % full_template, original_errno); } return fs::path(buf.get()); }
/// Backs up a database for schema migration purposes. /// /// \todo We should probably use the SQLite backup API instead of doing a raw /// file copy. We issue our backup call with the database already open, but /// because it is quiescent, it's OK to do so. /// /// \param source Location of the database to be backed up. /// \param old_version Version of the database's CURRENT schema, used to /// determine the name of the backup file. /// /// \throw error If there is a problem during the backup. void store::detail::backup_database(const fs::path& source, const int old_version) { const fs::path target(F("%s.v%s.backup") % source.str() % old_version); LI(F("Backing up database %s to %s") % source % target); std::ifstream input(source.c_str()); if (!input) throw error(F("Cannot open database file %s") % source); std::ofstream output(target.c_str()); if (!output) throw error(F("Cannot create database backup file %s") % target); char buffer[1024]; while (input.good()) { input.read(buffer, sizeof(buffer)); if (input.good() || input.eof()) output.write(buffer, input.gcount()); } if (!input.good() && !input.eof()) throw error(F("Error while reading input file %s") % source); }
/// Recursively removes a directory. /// /// This operation simulates a "rm -r". No effort is made to forcibly delete /// files and no attention is paid to mount points. /// /// \param directory The directory to remove. /// /// \throw fs::error If there is a problem removing any directory or file. void fs::rm_r(const fs::path& directory) { DIR* dirp = ::opendir(directory.c_str()); if (dirp == NULL) { const int original_errno = errno; throw fs::system_error(F("Failed to open directory %s") % directory.str(), original_errno); } try { ::dirent* dp; while ((dp = ::readdir(dirp)) != NULL) { const std::string name = dp->d_name; if (name == "." || name == "..") continue; const fs::path entry = directory / dp->d_name; const struct ::stat sb = safe_stat(entry); if (S_ISDIR(sb.st_mode)) { LD(F("Descending into %s") % entry); fs::rm_r(entry); } else { LD(F("Removing file %s") % entry); fs::unlink(entry); } } } catch (...) { ::closedir(dirp); throw; } ::closedir(dirp); LD(F("Removing empty directory %s") % directory); fs::rmdir(directory); }