xref: /freebsd/libexec/atf/atf-pytest-wrapper/atf_pytest_wrapper.cpp (revision 9c42645a1e4d6695730187aef0f1494394503624)
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