1 // vim: ts=2 sw=2 et 2 3 #include <format> 4 #include <iostream> 5 #include <map> 6 #include <string> 7 #include <vector> 8 #include <stdlib.h> 9 #include <unistd.h> 10 11 class Handler { 12 private: 13 const std::string kPytestName = "pytest"; 14 const std::string kCleanupSuffix = ":cleanup"; 15 const std::string kPythonPathEnv = "PYTHONPATH"; 16 const std::string kAtfVar = "_ATF_VAR_"; 17 public: 18 // Test listing requested 19 bool flag_list = false; 20 // Output debug data (will break listing) 21 bool flag_debug = false; 22 // Cleanup for the test requested 23 bool flag_cleanup = false; 24 // Test source directory (provided by ATF) 25 std::string src_dir; 26 // Path to write test status to (provided by ATF) 27 std::string dst_file; 28 // Path to add to PYTHONPATH (provided by the schebang args) 29 std::string python_path; 30 // Path to the script (provided by the schebang wrapper) 31 std::string script_path; 32 // Name of the test to run (provided by ATF) 33 std::string test_name; 34 // kv pairs (provided by ATF) 35 std::map<std::string,std::string> kv_map; 36 // our binary name 37 std::string binary_name; 38 39 static std::vector<std::string> ToVector(int argc, char **argv) { 40 std::vector<std::string> ret; 41 42 for (int i = 0; i < argc; i++) { 43 ret.emplace_back(std::string(argv[i])); 44 } 45 return ret; 46 } 47 48 static void PrintVector(std::string prefix, const std::vector<std::string> &vec) { 49 std::cerr << prefix << ": "; 50 for (auto &val: vec) { 51 std::cerr << "'" << val << "' "; 52 } 53 std::cerr << std::endl; 54 } 55 56 void Usage(std::string msg, bool exit_with_error) { 57 std::cerr << binary_name << ": ERROR: " << msg << "." << std::endl; 58 std::cerr << binary_name << ": See atf-test-program(1) for usage details." << std::endl; 59 exit(exit_with_error != 0); 60 } 61 62 // Parse args received from the OS. There can be multiple valid options: 63 // * with schebang args (#!/binary -P/path): 64 // atf_wrap '-P /path' /path/to/script -l 65 // * without schebang args 66 // atf_wrap /path/to/script -l 67 // Running test: 68 // atf_wrap '-P /path' /path/to/script -r /path1 -s /path2 -vk1=v1 testname 69 void Parse(int argc, char **argv) { 70 if (flag_debug) { 71 PrintVector("IN", ToVector(argc, argv)); 72 } 73 // getopt() skips the first argument (as it is typically binary name) 74 // it is possible to have either '-P\s*/path' followed by the script name 75 // or just the script name. Parse kernel-provided arg manually and adjust 76 // array to make getopt work 77 78 binary_name = std::string(argv[0]); 79 argc--; argv++; 80 // parse -P\s*path from the kernel. 81 if (argc > 0 && !strncmp(argv[0], "-P", 2)) { 82 char *path = &argv[0][2]; 83 while (*path == ' ') 84 path++; 85 python_path = std::string(path); 86 argc--; argv++; 87 } 88 89 // The next argument is a script name. Copy and keep argc/argv the same 90 // Show usage for empty args 91 if (argc == 0) { 92 Usage("Must provide a test case name", true); 93 } 94 script_path = std::string(argv[0]); 95 96 int c; 97 while ((c = getopt(argc, argv, "lr:s:v:")) != -1) { 98 switch (c) { 99 case 'l': 100 flag_list = true; 101 break; 102 case 's': 103 src_dir = std::string(optarg); 104 break; 105 case 'r': 106 dst_file = std::string(optarg); 107 break; 108 case 'v': 109 { 110 std::string kv = std::string(optarg); 111 size_t splitter = kv.find("="); 112 if (splitter == std::string::npos) { 113 Usage("Unknown variable: " + kv, true); 114 } 115 kv_map[kv.substr(0, splitter)] = kv.substr(splitter + 1); 116 } 117 break; 118 default: 119 Usage("Unknown option -" + std::string(1, static_cast<char>(c)), true); 120 } 121 } 122 argc -= optind; 123 argv += optind; 124 125 if (flag_list) { 126 return; 127 } 128 // There should be just one argument with the test name 129 if (argc != 1) { 130 Usage("Must provide a test case name", true); 131 } 132 test_name = std::string(argv[0]); 133 if (test_name.size() > kCleanupSuffix.size() && 134 std::equal(kCleanupSuffix.rbegin(), kCleanupSuffix.rend(), test_name.rbegin())) { 135 test_name = test_name.substr(0, test_name.size() - kCleanupSuffix.size()); 136 flag_cleanup = true; 137 } 138 } 139 140 std::vector<std::string> BuildArgs() { 141 std::vector<std::string> args = {"pytest", "-vv", "-p", 142 "no:cacheprovider", "-s", "--atf"}; 143 144 if (flag_list) { 145 args.push_back("--co"); 146 args.push_back(script_path); 147 return args; 148 } 149 if (flag_cleanup) { 150 args.push_back("--atf-cleanup"); 151 } 152 // workaround pytest parser bug: 153 // https://github.com/pytest-dev/pytest/issues/3097 154 // use '--arg=value' format instead of '--arg value' for all 155 // path-like options 156 if (!src_dir.empty()) { 157 args.push_back("--atf-source-dir=" + src_dir); 158 } 159 if (!dst_file.empty()) { 160 args.push_back("--atf-file=" + dst_file); 161 } 162 // Create nodeid from the test path &name 163 args.push_back(script_path + "::" + test_name); 164 return args; 165 } 166 167 void SetPythonPath() { 168 if (!python_path.empty()) { 169 char *env_path = getenv(kPythonPathEnv.c_str()); 170 if (env_path != nullptr) { 171 python_path = python_path + ":" + std::string(env_path); 172 } 173 setenv(kPythonPathEnv.c_str(), python_path.c_str(), 1); 174 } 175 } 176 177 void SetEnv() { 178 SetPythonPath(); 179 180 // Pass ATF kv pairs as env variables to avoid dealing with 181 // pytest parser 182 for (auto [k, v]: kv_map) { 183 setenv((kAtfVar + k).c_str(), v.c_str(), 1); 184 } 185 } 186 187 bool Run(std::string binary, std::vector<std::string> args) { 188 if (flag_debug) { 189 PrintVector("OUT", args); 190 } 191 // allocate array with final NULL 192 char **arr = new char*[args.size() + 1](); 193 for (unsigned long i = 0; i < args.size(); i++) { 194 // work around 'char *const *' 195 arr[i] = strdup(args[i].c_str()); 196 } 197 return execvp(binary.c_str(), arr) == 0; 198 } 199 200 void ReportError() { 201 if (flag_list) { 202 std::cout << "Content-Type: application/X-atf-tp; version=\"1\""; 203 std::cout << std::endl << std::endl; 204 std::cout << "ident: __test_cases_list_"<< kPytestName << "_binary_" << 205 "not_found__" << std::endl; 206 } else { 207 std::cout << "execvp(" << kPytestName << ") failed: " << 208 std::strerror(errno) << std::endl; 209 } 210 } 211 212 int Process() { 213 SetEnv(); 214 if (!Run(kPytestName, BuildArgs())) { 215 ReportError(); 216 } 217 return 0; 218 } 219 }; 220 221 222 int main(int argc, char **argv) { 223 Handler handler; 224 225 handler.Parse(argc, argv); 226 return handler.Process(); 227 } 228