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