1 #include <algorithm> 2 #include <array> 3 #include <cassert> 4 #include <fstream> 5 #include <iostream> 6 #include <memory> 7 #include <numeric> 8 #include <regex> 9 #include <string> 10 #include <vector> 11 12 std::string escape_arg(const std::string& arg) { 13 if (arg.empty() == false && 14 arg.find_first_of(" \t\n\v\"") == arg.npos) { 15 return arg; 16 } 17 18 std::string escaped; 19 escaped.push_back('"'); 20 for (auto it = arg.begin(); ; ++it) { 21 int num_backslashes = 0; 22 23 while (it != arg.end() && *it == '\\') { 24 ++it; 25 ++num_backslashes; 26 } 27 28 if (it == arg.end()) { 29 escaped.append(num_backslashes * 2, '\\'); 30 break; 31 } else if (*it == '"') { 32 escaped.append(num_backslashes * 2 + 1, '\\'); 33 escaped.push_back(*it); 34 } else { 35 escaped.append(num_backslashes, '\\'); 36 escaped.push_back(*it); 37 } 38 } 39 escaped.push_back('"'); 40 41 return escaped; 42 } 43 44 45 void create_empty_file(std::string const& path) { 46 std::ofstream ofs(path); 47 ofs << '\n'; 48 } 49 50 const std::string separator = "--sep--"; 51 const std::string logfile_prefix = "--log-file="; 52 53 bool starts_with(std::string const& str, std::string const& pref) { 54 return str.find(pref) == 0; 55 } 56 57 int parse_log_file_arg(std::string const& arg) { 58 assert(starts_with(arg, logfile_prefix) && "Attempting to parse incorrect arg!"); 59 auto fname = arg.substr(logfile_prefix.size()); 60 create_empty_file(fname); 61 std::regex regex("MemoryChecker\\.(\\d+)\\.log", std::regex::icase); 62 std::smatch match; 63 if (std::regex_search(fname, match, regex)) { 64 return std::stoi(match[1]); 65 } else { 66 throw std::domain_error("Couldn't find desired expression in string: " + fname); 67 } 68 } 69 70 std::string catch_path(std::string path) { 71 auto start = path.find("catch"); 72 // try capitalized instead 73 if (start == std::string::npos) { 74 start = path.find("Catch"); 75 } 76 if (start == std::string::npos) { 77 throw std::domain_error("Couldn't find Catch's base path"); 78 } 79 auto end = path.find_first_of("\\/", start); 80 return path.substr(0, end); 81 } 82 83 std::string windowsify_path(std::string path) { 84 for (auto& c : path) { 85 if (c == '/') { 86 c = '\\'; 87 } 88 } 89 return path; 90 } 91 92 void exec_cmd(std::string const& cmd, int log_num, std::string const& path) { 93 std::array<char, 128> buffer; 94 #if defined(_WIN32) 95 // cmd has already been escaped outside this function. 96 auto real_cmd = "OpenCppCoverage --export_type binary:cov-report" + std::to_string(log_num) 97 + ".bin --quiet " + "--sources " + escape_arg(path) + " --cover_children -- " + cmd; 98 std::cout << "=== Marker ===: Cmd: " << real_cmd << '\n'; 99 std::shared_ptr<FILE> pipe(_popen(real_cmd.c_str(), "r"), _pclose); 100 #else // Just for testing, in the real world we will always work under WIN32 101 (void)log_num; (void)path; 102 std::shared_ptr<FILE> pipe(popen(cmd.c_str(), "r"), pclose); 103 #endif 104 105 if (!pipe) { 106 throw std::runtime_error("popen() failed!"); 107 } 108 while (!feof(pipe.get())) { 109 if (fgets(buffer.data(), 128, pipe.get()) != nullptr) { 110 std::cout << buffer.data(); 111 } 112 } 113 } 114 115 // argv should be: 116 // [0]: our path 117 // [1]: "--log-file=<path>" 118 // [2]: "--sep--" 119 // [3]+: the actual command 120 121 int main(int argc, char** argv) { 122 std::vector<std::string> args(argv, argv + argc); 123 auto sep = std::find(begin(args), end(args), separator); 124 assert(sep - begin(args) == 2 && "Structure differs from expected!"); 125 126 auto num = parse_log_file_arg(args[1]); 127 128 auto cmdline = std::accumulate(++sep, end(args), std::string{}, [] (const std::string& lhs, const std::string& rhs) { 129 return lhs + ' ' + escape_arg(rhs); 130 }); 131 132 try { 133 return exec_cmd(cmdline, num, windowsify_path(catch_path(args[0]))); 134 } catch (std::exception const& ex) { 135 std::cerr << "Helper failed with: '" << ex.what() << "'\n"; 136 return 12; 137 } 138 } 139