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