xref: /src/libexec/atf/atf-pytest-wrapper/atf_pytest_wrapper.cpp (revision 9f23cbd6cae82fd77edfad7173432fa8dccd0a95)
12bfd8b5bSAlexander V. Chernikov // vim: ts=2 sw=2 et
22bfd8b5bSAlexander V. Chernikov 
38eb2bee6SAlexander V. Chernikov #include <format>
48eb2bee6SAlexander V. Chernikov #include <iostream>
5513ce835SAlexander V. Chernikov #include <map>
68eb2bee6SAlexander V. Chernikov #include <string>
78eb2bee6SAlexander V. Chernikov #include <vector>
88eb2bee6SAlexander V. Chernikov #include <stdlib.h>
98eb2bee6SAlexander V. Chernikov #include <unistd.h>
108eb2bee6SAlexander V. Chernikov 
118eb2bee6SAlexander V. Chernikov class Handler {
128eb2bee6SAlexander V. Chernikov   private:
138eb2bee6SAlexander V. Chernikov     const std::string kPytestName = "pytest";
148eb2bee6SAlexander V. Chernikov     const std::string kCleanupSuffix = ":cleanup";
158eb2bee6SAlexander V. Chernikov     const std::string kPythonPathEnv = "PYTHONPATH";
16513ce835SAlexander V. Chernikov     const std::string kAtfVar = "_ATF_VAR_";
178eb2bee6SAlexander V. Chernikov   public:
188eb2bee6SAlexander V. Chernikov     // Test listing requested
198eb2bee6SAlexander V. Chernikov     bool flag_list = false;
208eb2bee6SAlexander V. Chernikov     // Output debug data (will break listing)
218eb2bee6SAlexander V. Chernikov     bool flag_debug = false;
228eb2bee6SAlexander V. Chernikov     // Cleanup for the test requested
238eb2bee6SAlexander V. Chernikov     bool flag_cleanup = false;
248eb2bee6SAlexander V. Chernikov     // Test source directory (provided by ATF)
258eb2bee6SAlexander V. Chernikov     std::string src_dir;
268eb2bee6SAlexander V. Chernikov     // Path to write test status to (provided by ATF)
278eb2bee6SAlexander V. Chernikov     std::string dst_file;
288eb2bee6SAlexander V. Chernikov     // Path to add to PYTHONPATH (provided by the schebang args)
298eb2bee6SAlexander V. Chernikov     std::string python_path;
308eb2bee6SAlexander V. Chernikov     // Path to the script (provided by the schebang wrapper)
318eb2bee6SAlexander V. Chernikov     std::string script_path;
328eb2bee6SAlexander V. Chernikov     // Name of the test to run (provided by ATF)
338eb2bee6SAlexander V. Chernikov     std::string test_name;
348eb2bee6SAlexander V. Chernikov     // kv pairs (provided by ATF)
35513ce835SAlexander V. Chernikov     std::map<std::string,std::string> kv_map;
368eb2bee6SAlexander V. Chernikov     // our binary name
378eb2bee6SAlexander V. Chernikov     std::string binary_name;
388eb2bee6SAlexander V. Chernikov 
ToVector(int argc,char ** argv)398eb2bee6SAlexander V. Chernikov     static std::vector<std::string> ToVector(int argc, char **argv) {
408eb2bee6SAlexander V. Chernikov       std::vector<std::string> ret;
418eb2bee6SAlexander V. Chernikov 
428eb2bee6SAlexander V. Chernikov       for (int i = 0; i < argc; i++) {
438eb2bee6SAlexander V. Chernikov         ret.emplace_back(std::string(argv[i]));
448eb2bee6SAlexander V. Chernikov       }
458eb2bee6SAlexander V. Chernikov       return ret;
468eb2bee6SAlexander V. Chernikov     }
478eb2bee6SAlexander V. Chernikov 
PrintVector(std::string prefix,const std::vector<std::string> & vec)488eb2bee6SAlexander V. Chernikov     static void PrintVector(std::string prefix, const std::vector<std::string> &vec) {
498eb2bee6SAlexander V. Chernikov       std::cerr << prefix << ": ";
508eb2bee6SAlexander V. Chernikov       for (auto &val: vec) {
518eb2bee6SAlexander V. Chernikov         std::cerr << "'" << val << "' ";
528eb2bee6SAlexander V. Chernikov       }
538eb2bee6SAlexander V. Chernikov       std::cerr << std::endl;
548eb2bee6SAlexander V. Chernikov     }
558eb2bee6SAlexander V. Chernikov 
Usage(std::string msg,bool exit_with_error)568eb2bee6SAlexander V. Chernikov     void Usage(std::string msg, bool exit_with_error) {
578eb2bee6SAlexander V. Chernikov       std::cerr << binary_name << ": ERROR: " << msg << "." << std::endl;
588eb2bee6SAlexander V. Chernikov       std::cerr << binary_name << ": See atf-test-program(1) for usage details." << std::endl;
598eb2bee6SAlexander V. Chernikov       exit(exit_with_error != 0);
608eb2bee6SAlexander V. Chernikov     }
618eb2bee6SAlexander V. Chernikov 
628eb2bee6SAlexander V. Chernikov     // Parse args received from the OS. There can be multiple valid options:
638eb2bee6SAlexander V. Chernikov     // * with schebang args (#!/binary -P/path):
648eb2bee6SAlexander V. Chernikov     // atf_wrap '-P /path' /path/to/script -l
658eb2bee6SAlexander V. Chernikov     // * without schebang args
668eb2bee6SAlexander V. Chernikov     // atf_wrap /path/to/script -l
678eb2bee6SAlexander V. Chernikov     // Running test:
688eb2bee6SAlexander V. Chernikov     // atf_wrap '-P /path' /path/to/script -r /path1 -s /path2 -vk1=v1 testname
Parse(int argc,char ** argv)698eb2bee6SAlexander V. Chernikov     void Parse(int argc, char **argv) {
708eb2bee6SAlexander V. Chernikov       if (flag_debug) {
718eb2bee6SAlexander V. Chernikov         PrintVector("IN", ToVector(argc, argv));
728eb2bee6SAlexander V. Chernikov       }
738eb2bee6SAlexander V. Chernikov       // getopt() skips the first argument (as it is typically binary name)
748eb2bee6SAlexander V. Chernikov       // it is possible to have either '-P\s*/path' followed by the script name
758eb2bee6SAlexander V. Chernikov       // or just the script name. Parse kernel-provided arg manually and adjust
768eb2bee6SAlexander V. Chernikov       // array to make getopt work
778eb2bee6SAlexander V. Chernikov 
788eb2bee6SAlexander V. Chernikov       binary_name = std::string(argv[0]);
798eb2bee6SAlexander V. Chernikov       argc--; argv++;
808eb2bee6SAlexander V. Chernikov       // parse -P\s*path from the kernel.
818eb2bee6SAlexander V. Chernikov       if (argc > 0 && !strncmp(argv[0], "-P", 2)) {
828eb2bee6SAlexander V. Chernikov         char *path = &argv[0][2];
838eb2bee6SAlexander V. Chernikov         while (*path == ' ')
848eb2bee6SAlexander V. Chernikov           path++;
858eb2bee6SAlexander V. Chernikov         python_path = std::string(path);
868eb2bee6SAlexander V. Chernikov         argc--; argv++;
878eb2bee6SAlexander V. Chernikov       }
888eb2bee6SAlexander V. Chernikov 
898eb2bee6SAlexander V. Chernikov       // The next argument is a script name. Copy and keep argc/argv the same
908eb2bee6SAlexander V. Chernikov       // Show usage for empty args
918eb2bee6SAlexander V. Chernikov       if (argc == 0) {
928eb2bee6SAlexander V. Chernikov         Usage("Must provide a test case name", true);
938eb2bee6SAlexander V. Chernikov       }
948eb2bee6SAlexander V. Chernikov       script_path = std::string(argv[0]);
958eb2bee6SAlexander V. Chernikov 
968eb2bee6SAlexander V. Chernikov       int c;
978eb2bee6SAlexander V. Chernikov       while ((c = getopt(argc, argv, "lr:s:v:")) != -1) {
988eb2bee6SAlexander V. Chernikov         switch (c) {
998eb2bee6SAlexander V. Chernikov           case 'l':
1008eb2bee6SAlexander V. Chernikov             flag_list = true;
1018eb2bee6SAlexander V. Chernikov             break;
1028eb2bee6SAlexander V. Chernikov           case 's':
1038eb2bee6SAlexander V. Chernikov             src_dir = std::string(optarg);
1048eb2bee6SAlexander V. Chernikov             break;
1058eb2bee6SAlexander V. Chernikov           case 'r':
1068eb2bee6SAlexander V. Chernikov             dst_file = std::string(optarg);
1078eb2bee6SAlexander V. Chernikov             break;
1088eb2bee6SAlexander V. Chernikov           case 'v':
109513ce835SAlexander V. Chernikov             {
110513ce835SAlexander V. Chernikov               std::string kv = std::string(optarg);
111513ce835SAlexander V. Chernikov               size_t splitter = kv.find("=");
112513ce835SAlexander V. Chernikov               if (splitter == std::string::npos) {
113513ce835SAlexander V. Chernikov                 Usage("Unknown variable: " + kv, true);
114513ce835SAlexander V. Chernikov               }
115513ce835SAlexander V. Chernikov               kv_map[kv.substr(0, splitter)] = kv.substr(splitter + 1);
116513ce835SAlexander V. Chernikov             }
1178eb2bee6SAlexander V. Chernikov             break;
1188eb2bee6SAlexander V. Chernikov           default:
1198eb2bee6SAlexander V. Chernikov             Usage("Unknown option -" + std::string(1, static_cast<char>(c)), true);
1208eb2bee6SAlexander V. Chernikov         }
1218eb2bee6SAlexander V. Chernikov       }
1228eb2bee6SAlexander V. Chernikov       argc -= optind;
1238eb2bee6SAlexander V. Chernikov       argv += optind;
1248eb2bee6SAlexander V. Chernikov 
1258eb2bee6SAlexander V. Chernikov       if (flag_list) {
1268eb2bee6SAlexander V. Chernikov         return;
1278eb2bee6SAlexander V. Chernikov       }
1288eb2bee6SAlexander V. Chernikov       // There should be just one argument with the test name
1298eb2bee6SAlexander V. Chernikov       if (argc != 1) {
1308eb2bee6SAlexander V. Chernikov         Usage("Must provide a test case name", true);
1318eb2bee6SAlexander V. Chernikov       }
1328eb2bee6SAlexander V. Chernikov       test_name = std::string(argv[0]);
1338eb2bee6SAlexander V. Chernikov       if (test_name.size() > kCleanupSuffix.size() &&
1348eb2bee6SAlexander V. Chernikov           std::equal(kCleanupSuffix.rbegin(), kCleanupSuffix.rend(), test_name.rbegin())) {
1358eb2bee6SAlexander V. Chernikov         test_name = test_name.substr(0, test_name.size() - kCleanupSuffix.size());
1368eb2bee6SAlexander V. Chernikov         flag_cleanup = true;
1378eb2bee6SAlexander V. Chernikov       }
1388eb2bee6SAlexander V. Chernikov     }
1398eb2bee6SAlexander V. Chernikov 
BuildArgs()1408eb2bee6SAlexander V. Chernikov     std::vector<std::string> BuildArgs() {
141058ac3e8SJose Luis Duran       std::vector<std::string> args = {"pytest", "-vv", "-p",
142058ac3e8SJose Luis Duran         "no:cacheprovider", "-s", "--atf"};
1438eb2bee6SAlexander V. Chernikov 
144*9f23cbd6SKristof Provost       args.push_back("--confcutdir=" + python_path);
145*9f23cbd6SKristof Provost 
1468eb2bee6SAlexander V. Chernikov       if (flag_list) {
1478eb2bee6SAlexander V. Chernikov         args.push_back("--co");
1488eb2bee6SAlexander V. Chernikov         args.push_back(script_path);
1498eb2bee6SAlexander V. Chernikov         return args;
1508eb2bee6SAlexander V. Chernikov       }
1518eb2bee6SAlexander V. Chernikov       if (flag_cleanup) {
1528eb2bee6SAlexander V. Chernikov         args.push_back("--atf-cleanup");
1538eb2bee6SAlexander V. Chernikov       }
1549c42645aSAlexander V. Chernikov       // workaround pytest parser bug:
1559c42645aSAlexander V. Chernikov       // https://github.com/pytest-dev/pytest/issues/3097
1569c42645aSAlexander V. Chernikov       // use '--arg=value' format instead of '--arg value' for all
1579c42645aSAlexander V. Chernikov       // path-like options
1588eb2bee6SAlexander V. Chernikov       if (!src_dir.empty()) {
1599c42645aSAlexander V. Chernikov         args.push_back("--atf-source-dir=" + src_dir);
1608eb2bee6SAlexander V. Chernikov       }
1618eb2bee6SAlexander V. Chernikov       if (!dst_file.empty()) {
1629c42645aSAlexander V. Chernikov         args.push_back("--atf-file=" + dst_file);
1638eb2bee6SAlexander V. Chernikov       }
1648eb2bee6SAlexander V. Chernikov       // Create nodeid from the test path &name
1658eb2bee6SAlexander V. Chernikov       args.push_back(script_path + "::" + test_name);
1668eb2bee6SAlexander V. Chernikov       return args;
1678eb2bee6SAlexander V. Chernikov     }
1688eb2bee6SAlexander V. Chernikov 
SetPythonPath()169513ce835SAlexander V. Chernikov     void SetPythonPath() {
1708eb2bee6SAlexander V. Chernikov       if (!python_path.empty()) {
1718eb2bee6SAlexander V. Chernikov         char *env_path = getenv(kPythonPathEnv.c_str());
1728eb2bee6SAlexander V. Chernikov         if (env_path != nullptr) {
1738eb2bee6SAlexander V. Chernikov           python_path = python_path + ":" + std::string(env_path);
1748eb2bee6SAlexander V. Chernikov         }
1758eb2bee6SAlexander V. Chernikov         setenv(kPythonPathEnv.c_str(), python_path.c_str(), 1);
1768eb2bee6SAlexander V. Chernikov       }
1778eb2bee6SAlexander V. Chernikov     }
1788eb2bee6SAlexander V. Chernikov 
SetEnv()179513ce835SAlexander V. Chernikov     void SetEnv() {
180513ce835SAlexander V. Chernikov       SetPythonPath();
181513ce835SAlexander V. Chernikov 
182513ce835SAlexander V. Chernikov       // Pass ATF kv pairs as env variables to avoid dealing with
183513ce835SAlexander V. Chernikov       // pytest parser
184513ce835SAlexander V. Chernikov       for (auto [k, v]: kv_map) {
185513ce835SAlexander V. Chernikov         setenv((kAtfVar + k).c_str(), v.c_str(), 1);
186513ce835SAlexander V. Chernikov       }
187513ce835SAlexander V. Chernikov     }
188513ce835SAlexander V. Chernikov 
Run(std::string binary,std::vector<std::string> args)1892bfd8b5bSAlexander V. Chernikov     bool Run(std::string binary, std::vector<std::string> args) {
1908eb2bee6SAlexander V. Chernikov       if (flag_debug) {
1918eb2bee6SAlexander V. Chernikov         PrintVector("OUT", args);
1928eb2bee6SAlexander V. Chernikov       }
1938eb2bee6SAlexander V. Chernikov       // allocate array with final NULL
1948eb2bee6SAlexander V. Chernikov       char **arr = new char*[args.size() + 1]();
1958eb2bee6SAlexander V. Chernikov       for (unsigned long i = 0; i < args.size(); i++) {
1968eb2bee6SAlexander V. Chernikov         // work around 'char *const *'
1978eb2bee6SAlexander V. Chernikov         arr[i] = strdup(args[i].c_str());
1988eb2bee6SAlexander V. Chernikov       }
1992bfd8b5bSAlexander V. Chernikov       return execvp(binary.c_str(), arr) == 0;
2002bfd8b5bSAlexander V. Chernikov     }
2012bfd8b5bSAlexander V. Chernikov 
ReportError()2022bfd8b5bSAlexander V. Chernikov     void ReportError() {
2032bfd8b5bSAlexander V. Chernikov       if (flag_list) {
2042bfd8b5bSAlexander V. Chernikov         std::cout << "Content-Type: application/X-atf-tp; version=\"1\"";
2052bfd8b5bSAlexander V. Chernikov         std::cout << std::endl << std::endl;
2062bfd8b5bSAlexander V. Chernikov         std::cout << "ident: __test_cases_list_"<< kPytestName << "_binary_" <<
2072bfd8b5bSAlexander V. Chernikov           "not_found__" << std::endl;
2082bfd8b5bSAlexander V. Chernikov       } else {
2092bfd8b5bSAlexander V. Chernikov         std::cout << "execvp(" << kPytestName << ") failed: " <<
2102bfd8b5bSAlexander V. Chernikov           std::strerror(errno) << std::endl;
2112bfd8b5bSAlexander V. Chernikov       }
2128eb2bee6SAlexander V. Chernikov     }
2138eb2bee6SAlexander V. Chernikov 
Process()2148eb2bee6SAlexander V. Chernikov     int Process() {
2158eb2bee6SAlexander V. Chernikov       SetEnv();
2162bfd8b5bSAlexander V. Chernikov       if (!Run(kPytestName, BuildArgs())) {
2172bfd8b5bSAlexander V. Chernikov         ReportError();
2182bfd8b5bSAlexander V. Chernikov       }
2192bfd8b5bSAlexander V. Chernikov       return 0;
2208eb2bee6SAlexander V. Chernikov     }
2218eb2bee6SAlexander V. Chernikov };
2228eb2bee6SAlexander V. Chernikov 
2238eb2bee6SAlexander V. Chernikov 
main(int argc,char ** argv)2248eb2bee6SAlexander V. Chernikov int main(int argc, char **argv) {
2258eb2bee6SAlexander V. Chernikov   Handler handler;
2268eb2bee6SAlexander V. Chernikov 
2278eb2bee6SAlexander V. Chernikov   handler.Parse(argc, argv);
2288eb2bee6SAlexander V. Chernikov   return handler.Process();
2298eb2bee6SAlexander V. Chernikov }
230