1924226fbSAlexander V. Chernikov #include <format> 2924226fbSAlexander V. Chernikov #include <iostream> 3924226fbSAlexander V. Chernikov #include <string> 4924226fbSAlexander V. Chernikov #include <vector> 5924226fbSAlexander V. Chernikov #include <stdlib.h> 6924226fbSAlexander V. Chernikov #include <unistd.h> 7924226fbSAlexander V. Chernikov 8924226fbSAlexander V. Chernikov class Handler { 9924226fbSAlexander V. Chernikov private: 10924226fbSAlexander V. Chernikov const std::string kPytestName = "pytest"; 11924226fbSAlexander V. Chernikov const std::string kCleanupSuffix = ":cleanup"; 12924226fbSAlexander V. Chernikov const std::string kPythonPathEnv = "PYTHONPATH"; 13924226fbSAlexander V. Chernikov public: 14924226fbSAlexander V. Chernikov // Test listing requested 15924226fbSAlexander V. Chernikov bool flag_list = false; 16924226fbSAlexander V. Chernikov // Output debug data (will break listing) 17924226fbSAlexander V. Chernikov bool flag_debug = false; 18924226fbSAlexander V. Chernikov // Cleanup for the test requested 19924226fbSAlexander V. Chernikov bool flag_cleanup = false; 20924226fbSAlexander V. Chernikov // Test source directory (provided by ATF) 21924226fbSAlexander V. Chernikov std::string src_dir; 22924226fbSAlexander V. Chernikov // Path to write test status to (provided by ATF) 23924226fbSAlexander V. Chernikov std::string dst_file; 24924226fbSAlexander V. Chernikov // Path to add to PYTHONPATH (provided by the schebang args) 25924226fbSAlexander V. Chernikov std::string python_path; 26924226fbSAlexander V. Chernikov // Path to the script (provided by the schebang wrapper) 27924226fbSAlexander V. Chernikov std::string script_path; 28924226fbSAlexander V. Chernikov // Name of the test to run (provided by ATF) 29924226fbSAlexander V. Chernikov std::string test_name; 30924226fbSAlexander V. Chernikov // kv pairs (provided by ATF) 31924226fbSAlexander V. Chernikov std::vector<std::string> kv_list; 32924226fbSAlexander V. Chernikov // our binary name 33924226fbSAlexander V. Chernikov std::string binary_name; 34924226fbSAlexander V. Chernikov 35924226fbSAlexander V. Chernikov static std::vector<std::string> ToVector(int argc, char **argv) { 36924226fbSAlexander V. Chernikov std::vector<std::string> ret; 37924226fbSAlexander V. Chernikov 38924226fbSAlexander V. Chernikov for (int i = 0; i < argc; i++) { 39924226fbSAlexander V. Chernikov ret.emplace_back(std::string(argv[i])); 40924226fbSAlexander V. Chernikov } 41924226fbSAlexander V. Chernikov return ret; 42924226fbSAlexander V. Chernikov } 43924226fbSAlexander V. Chernikov 44924226fbSAlexander V. Chernikov static void PrintVector(std::string prefix, const std::vector<std::string> &vec) { 45924226fbSAlexander V. Chernikov std::cerr << prefix << ": "; 46924226fbSAlexander V. Chernikov for (auto &val: vec) { 47924226fbSAlexander V. Chernikov std::cerr << "'" << val << "' "; 48924226fbSAlexander V. Chernikov } 49924226fbSAlexander V. Chernikov std::cerr << std::endl; 50924226fbSAlexander V. Chernikov } 51924226fbSAlexander V. Chernikov 52924226fbSAlexander V. Chernikov void Usage(std::string msg, bool exit_with_error) { 53924226fbSAlexander V. Chernikov std::cerr << binary_name << ": ERROR: " << msg << "." << std::endl; 54924226fbSAlexander V. Chernikov std::cerr << binary_name << ": See atf-test-program(1) for usage details." << std::endl; 55924226fbSAlexander V. Chernikov exit(exit_with_error != 0); 56924226fbSAlexander V. Chernikov } 57924226fbSAlexander V. Chernikov 58924226fbSAlexander V. Chernikov // Parse args received from the OS. There can be multiple valid options: 59924226fbSAlexander V. Chernikov // * with schebang args (#!/binary -P/path): 60924226fbSAlexander V. Chernikov // atf_wrap '-P /path' /path/to/script -l 61924226fbSAlexander V. Chernikov // * without schebang args 62924226fbSAlexander V. Chernikov // atf_wrap /path/to/script -l 63924226fbSAlexander V. Chernikov // Running test: 64924226fbSAlexander V. Chernikov // atf_wrap '-P /path' /path/to/script -r /path1 -s /path2 -vk1=v1 testname 65924226fbSAlexander V. Chernikov void Parse(int argc, char **argv) { 66924226fbSAlexander V. Chernikov if (flag_debug) { 67924226fbSAlexander V. Chernikov PrintVector("IN", ToVector(argc, argv)); 68924226fbSAlexander V. Chernikov } 69924226fbSAlexander V. Chernikov // getopt() skips the first argument (as it is typically binary name) 70924226fbSAlexander V. Chernikov // it is possible to have either '-P\s*/path' followed by the script name 71924226fbSAlexander V. Chernikov // or just the script name. Parse kernel-provided arg manually and adjust 72924226fbSAlexander V. Chernikov // array to make getopt work 73924226fbSAlexander V. Chernikov 74924226fbSAlexander V. Chernikov binary_name = std::string(argv[0]); 75924226fbSAlexander V. Chernikov argc--; argv++; 76924226fbSAlexander V. Chernikov // parse -P\s*path from the kernel. 77924226fbSAlexander V. Chernikov if (argc > 0 && !strncmp(argv[0], "-P", 2)) { 78924226fbSAlexander V. Chernikov char *path = &argv[0][2]; 79924226fbSAlexander V. Chernikov while (*path == ' ') 80924226fbSAlexander V. Chernikov path++; 81924226fbSAlexander V. Chernikov python_path = std::string(path); 82924226fbSAlexander V. Chernikov argc--; argv++; 83924226fbSAlexander V. Chernikov } 84924226fbSAlexander V. Chernikov 85924226fbSAlexander V. Chernikov // The next argument is a script name. Copy and keep argc/argv the same 86924226fbSAlexander V. Chernikov // Show usage for empty args 87924226fbSAlexander V. Chernikov if (argc == 0) { 88924226fbSAlexander V. Chernikov Usage("Must provide a test case name", true); 89924226fbSAlexander V. Chernikov } 90924226fbSAlexander V. Chernikov script_path = std::string(argv[0]); 91924226fbSAlexander V. Chernikov 92924226fbSAlexander V. Chernikov int c; 93924226fbSAlexander V. Chernikov while ((c = getopt(argc, argv, "lr:s:v:")) != -1) { 94924226fbSAlexander V. Chernikov switch (c) { 95924226fbSAlexander V. Chernikov case 'l': 96924226fbSAlexander V. Chernikov flag_list = true; 97924226fbSAlexander V. Chernikov break; 98924226fbSAlexander V. Chernikov case 's': 99924226fbSAlexander V. Chernikov src_dir = std::string(optarg); 100924226fbSAlexander V. Chernikov break; 101924226fbSAlexander V. Chernikov case 'r': 102924226fbSAlexander V. Chernikov dst_file = std::string(optarg); 103924226fbSAlexander V. Chernikov break; 104924226fbSAlexander V. Chernikov case 'v': 105924226fbSAlexander V. Chernikov kv_list.emplace_back(std::string(optarg)); 106924226fbSAlexander V. Chernikov break; 107924226fbSAlexander V. Chernikov default: 108924226fbSAlexander V. Chernikov Usage("Unknown option -" + std::string(1, static_cast<char>(c)), true); 109924226fbSAlexander V. Chernikov } 110924226fbSAlexander V. Chernikov } 111924226fbSAlexander V. Chernikov argc -= optind; 112924226fbSAlexander V. Chernikov argv += optind; 113924226fbSAlexander V. Chernikov 114924226fbSAlexander V. Chernikov if (flag_list) { 115924226fbSAlexander V. Chernikov return; 116924226fbSAlexander V. Chernikov } 117924226fbSAlexander V. Chernikov // There should be just one argument with the test name 118924226fbSAlexander V. Chernikov if (argc != 1) { 119924226fbSAlexander V. Chernikov Usage("Must provide a test case name", true); 120924226fbSAlexander V. Chernikov } 121924226fbSAlexander V. Chernikov test_name = std::string(argv[0]); 122924226fbSAlexander V. Chernikov if (test_name.size() > kCleanupSuffix.size() && 123924226fbSAlexander V. Chernikov std::equal(kCleanupSuffix.rbegin(), kCleanupSuffix.rend(), test_name.rbegin())) { 124924226fbSAlexander V. Chernikov test_name = test_name.substr(0, test_name.size() - kCleanupSuffix.size()); 125924226fbSAlexander V. Chernikov flag_cleanup = true; 126924226fbSAlexander V. Chernikov } 127924226fbSAlexander V. Chernikov } 128924226fbSAlexander V. Chernikov 129924226fbSAlexander V. Chernikov std::vector<std::string> BuildArgs() { 130924226fbSAlexander V. Chernikov std::vector<std::string> args = {"pytest", "-p", "no:cacheprovider", "-s", "--atf"}; 131924226fbSAlexander V. Chernikov 132924226fbSAlexander V. Chernikov if (flag_list) { 133924226fbSAlexander V. Chernikov args.push_back("--co"); 134924226fbSAlexander V. Chernikov args.push_back(script_path); 135924226fbSAlexander V. Chernikov return args; 136924226fbSAlexander V. Chernikov } 137924226fbSAlexander V. Chernikov if (flag_cleanup) { 138924226fbSAlexander V. Chernikov args.push_back("--atf-cleanup"); 139924226fbSAlexander V. Chernikov } 140*9c42645aSAlexander V. Chernikov // workaround pytest parser bug: 141*9c42645aSAlexander V. Chernikov // https://github.com/pytest-dev/pytest/issues/3097 142*9c42645aSAlexander V. Chernikov // use '--arg=value' format instead of '--arg value' for all 143*9c42645aSAlexander V. Chernikov // path-like options 144924226fbSAlexander V. Chernikov if (!src_dir.empty()) { 145*9c42645aSAlexander V. Chernikov args.push_back("--atf-source-dir=" + src_dir); 146924226fbSAlexander V. Chernikov } 147924226fbSAlexander V. Chernikov if (!dst_file.empty()) { 148*9c42645aSAlexander V. Chernikov args.push_back("--atf-file=" + dst_file); 149924226fbSAlexander V. Chernikov } 150924226fbSAlexander V. Chernikov for (auto &pair: kv_list) { 151924226fbSAlexander V. Chernikov args.push_back("--atf-var"); 152924226fbSAlexander V. Chernikov args.push_back(pair); 153924226fbSAlexander V. Chernikov } 154924226fbSAlexander V. Chernikov // Create nodeid from the test path &name 155924226fbSAlexander V. Chernikov args.push_back(script_path + "::" + test_name); 156924226fbSAlexander V. Chernikov return args; 157924226fbSAlexander V. Chernikov } 158924226fbSAlexander V. Chernikov 159924226fbSAlexander V. Chernikov void SetEnv() { 160924226fbSAlexander V. Chernikov if (!python_path.empty()) { 161924226fbSAlexander V. Chernikov char *env_path = getenv(kPythonPathEnv.c_str()); 162924226fbSAlexander V. Chernikov if (env_path != nullptr) { 163924226fbSAlexander V. Chernikov python_path = python_path + ":" + std::string(env_path); 164924226fbSAlexander V. Chernikov } 165924226fbSAlexander V. Chernikov setenv(kPythonPathEnv.c_str(), python_path.c_str(), 1); 166924226fbSAlexander V. Chernikov } 167924226fbSAlexander V. Chernikov } 168924226fbSAlexander V. Chernikov 169924226fbSAlexander V. Chernikov int Run(std::string binary, std::vector<std::string> args) { 170924226fbSAlexander V. Chernikov if (flag_debug) { 171924226fbSAlexander V. Chernikov PrintVector("OUT", args); 172924226fbSAlexander V. Chernikov } 173924226fbSAlexander V. Chernikov // allocate array with final NULL 174924226fbSAlexander V. Chernikov char **arr = new char*[args.size() + 1](); 175924226fbSAlexander V. Chernikov for (unsigned long i = 0; i < args.size(); i++) { 176924226fbSAlexander V. Chernikov // work around 'char *const *' 177924226fbSAlexander V. Chernikov arr[i] = strdup(args[i].c_str()); 178924226fbSAlexander V. Chernikov } 179924226fbSAlexander V. Chernikov return (execvp(binary.c_str(), arr) != 0); 180924226fbSAlexander V. Chernikov } 181924226fbSAlexander V. Chernikov 182924226fbSAlexander V. Chernikov int Process() { 183924226fbSAlexander V. Chernikov SetEnv(); 184924226fbSAlexander V. Chernikov return Run(kPytestName, BuildArgs()); 185924226fbSAlexander V. Chernikov } 186924226fbSAlexander V. Chernikov }; 187924226fbSAlexander V. Chernikov 188924226fbSAlexander V. Chernikov 189924226fbSAlexander V. Chernikov int main(int argc, char **argv) { 190924226fbSAlexander V. Chernikov Handler handler; 191924226fbSAlexander V. Chernikov 192924226fbSAlexander V. Chernikov handler.Parse(argc, argv); 193924226fbSAlexander V. Chernikov return handler.Process(); 194924226fbSAlexander V. Chernikov } 195