12bfd8b5bSAlexander V. Chernikov // vim: ts=2 sw=2 et
22bfd8b5bSAlexander V. Chernikov
3924226fbSAlexander V. Chernikov #include <format>
4924226fbSAlexander V. Chernikov #include <iostream>
5513ce835SAlexander V. Chernikov #include <map>
6924226fbSAlexander V. Chernikov #include <string>
7924226fbSAlexander V. Chernikov #include <vector>
8924226fbSAlexander V. Chernikov #include <stdlib.h>
9924226fbSAlexander V. Chernikov #include <unistd.h>
10924226fbSAlexander V. Chernikov
11924226fbSAlexander V. Chernikov class Handler {
12924226fbSAlexander V. Chernikov private:
13924226fbSAlexander V. Chernikov const std::string kPytestName = "pytest";
14924226fbSAlexander V. Chernikov const std::string kCleanupSuffix = ":cleanup";
15924226fbSAlexander V. Chernikov const std::string kPythonPathEnv = "PYTHONPATH";
16513ce835SAlexander V. Chernikov const std::string kAtfVar = "_ATF_VAR_";
17924226fbSAlexander V. Chernikov public:
18924226fbSAlexander V. Chernikov // Test listing requested
19924226fbSAlexander V. Chernikov bool flag_list = false;
20924226fbSAlexander V. Chernikov // Output debug data (will break listing)
21924226fbSAlexander V. Chernikov bool flag_debug = false;
22924226fbSAlexander V. Chernikov // Cleanup for the test requested
23924226fbSAlexander V. Chernikov bool flag_cleanup = false;
24924226fbSAlexander V. Chernikov // Test source directory (provided by ATF)
25924226fbSAlexander V. Chernikov std::string src_dir;
26924226fbSAlexander V. Chernikov // Path to write test status to (provided by ATF)
27924226fbSAlexander V. Chernikov std::string dst_file;
28924226fbSAlexander V. Chernikov // Path to add to PYTHONPATH (provided by the schebang args)
29924226fbSAlexander V. Chernikov std::string python_path;
30924226fbSAlexander V. Chernikov // Path to the script (provided by the schebang wrapper)
31924226fbSAlexander V. Chernikov std::string script_path;
32924226fbSAlexander V. Chernikov // Name of the test to run (provided by ATF)
33924226fbSAlexander V. Chernikov std::string test_name;
34924226fbSAlexander V. Chernikov // kv pairs (provided by ATF)
35513ce835SAlexander V. Chernikov std::map<std::string,std::string> kv_map;
36924226fbSAlexander V. Chernikov // our binary name
37924226fbSAlexander V. Chernikov std::string binary_name;
38924226fbSAlexander V. Chernikov
ToVector(int argc,char ** argv)39924226fbSAlexander V. Chernikov static std::vector<std::string> ToVector(int argc, char **argv) {
40924226fbSAlexander V. Chernikov std::vector<std::string> ret;
41924226fbSAlexander V. Chernikov
42924226fbSAlexander V. Chernikov for (int i = 0; i < argc; i++) {
43924226fbSAlexander V. Chernikov ret.emplace_back(std::string(argv[i]));
44924226fbSAlexander V. Chernikov }
45924226fbSAlexander V. Chernikov return ret;
46924226fbSAlexander V. Chernikov }
47924226fbSAlexander V. Chernikov
PrintVector(std::string prefix,const std::vector<std::string> & vec)48924226fbSAlexander V. Chernikov static void PrintVector(std::string prefix, const std::vector<std::string> &vec) {
49924226fbSAlexander V. Chernikov std::cerr << prefix << ": ";
50924226fbSAlexander V. Chernikov for (auto &val: vec) {
51924226fbSAlexander V. Chernikov std::cerr << "'" << val << "' ";
52924226fbSAlexander V. Chernikov }
53924226fbSAlexander V. Chernikov std::cerr << std::endl;
54924226fbSAlexander V. Chernikov }
55924226fbSAlexander V. Chernikov
Usage(std::string msg,bool exit_with_error)56924226fbSAlexander V. Chernikov void Usage(std::string msg, bool exit_with_error) {
57924226fbSAlexander V. Chernikov std::cerr << binary_name << ": ERROR: " << msg << "." << std::endl;
58924226fbSAlexander V. Chernikov std::cerr << binary_name << ": See atf-test-program(1) for usage details." << std::endl;
59924226fbSAlexander V. Chernikov exit(exit_with_error != 0);
60924226fbSAlexander V. Chernikov }
61924226fbSAlexander V. Chernikov
62924226fbSAlexander V. Chernikov // Parse args received from the OS. There can be multiple valid options:
63924226fbSAlexander V. Chernikov // * with schebang args (#!/binary -P/path):
64924226fbSAlexander V. Chernikov // atf_wrap '-P /path' /path/to/script -l
65924226fbSAlexander V. Chernikov // * without schebang args
66924226fbSAlexander V. Chernikov // atf_wrap /path/to/script -l
67924226fbSAlexander V. Chernikov // Running test:
68924226fbSAlexander V. Chernikov // atf_wrap '-P /path' /path/to/script -r /path1 -s /path2 -vk1=v1 testname
Parse(int argc,char ** argv)69924226fbSAlexander V. Chernikov void Parse(int argc, char **argv) {
70924226fbSAlexander V. Chernikov if (flag_debug) {
71924226fbSAlexander V. Chernikov PrintVector("IN", ToVector(argc, argv));
72924226fbSAlexander V. Chernikov }
73924226fbSAlexander V. Chernikov // getopt() skips the first argument (as it is typically binary name)
74924226fbSAlexander V. Chernikov // it is possible to have either '-P\s*/path' followed by the script name
75924226fbSAlexander V. Chernikov // or just the script name. Parse kernel-provided arg manually and adjust
76924226fbSAlexander V. Chernikov // array to make getopt work
77924226fbSAlexander V. Chernikov
78924226fbSAlexander V. Chernikov binary_name = std::string(argv[0]);
79924226fbSAlexander V. Chernikov argc--; argv++;
80924226fbSAlexander V. Chernikov // parse -P\s*path from the kernel.
81924226fbSAlexander V. Chernikov if (argc > 0 && !strncmp(argv[0], "-P", 2)) {
82924226fbSAlexander V. Chernikov char *path = &argv[0][2];
83924226fbSAlexander V. Chernikov while (*path == ' ')
84924226fbSAlexander V. Chernikov path++;
85924226fbSAlexander V. Chernikov python_path = std::string(path);
86924226fbSAlexander V. Chernikov argc--; argv++;
87924226fbSAlexander V. Chernikov }
88924226fbSAlexander V. Chernikov
89924226fbSAlexander V. Chernikov // The next argument is a script name. Copy and keep argc/argv the same
90924226fbSAlexander V. Chernikov // Show usage for empty args
91924226fbSAlexander V. Chernikov if (argc == 0) {
92924226fbSAlexander V. Chernikov Usage("Must provide a test case name", true);
93924226fbSAlexander V. Chernikov }
94924226fbSAlexander V. Chernikov script_path = std::string(argv[0]);
95924226fbSAlexander V. Chernikov
96924226fbSAlexander V. Chernikov int c;
97924226fbSAlexander V. Chernikov while ((c = getopt(argc, argv, "lr:s:v:")) != -1) {
98924226fbSAlexander V. Chernikov switch (c) {
99924226fbSAlexander V. Chernikov case 'l':
100924226fbSAlexander V. Chernikov flag_list = true;
101924226fbSAlexander V. Chernikov break;
102924226fbSAlexander V. Chernikov case 's':
103924226fbSAlexander V. Chernikov src_dir = std::string(optarg);
104924226fbSAlexander V. Chernikov break;
105924226fbSAlexander V. Chernikov case 'r':
106924226fbSAlexander V. Chernikov dst_file = std::string(optarg);
107924226fbSAlexander V. Chernikov break;
108924226fbSAlexander 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 }
117924226fbSAlexander V. Chernikov break;
118924226fbSAlexander V. Chernikov default:
119924226fbSAlexander V. Chernikov Usage("Unknown option -" + std::string(1, static_cast<char>(c)), true);
120924226fbSAlexander V. Chernikov }
121924226fbSAlexander V. Chernikov }
122924226fbSAlexander V. Chernikov argc -= optind;
123924226fbSAlexander V. Chernikov argv += optind;
124924226fbSAlexander V. Chernikov
125924226fbSAlexander V. Chernikov if (flag_list) {
126924226fbSAlexander V. Chernikov return;
127924226fbSAlexander V. Chernikov }
128924226fbSAlexander V. Chernikov // There should be just one argument with the test name
129924226fbSAlexander V. Chernikov if (argc != 1) {
130924226fbSAlexander V. Chernikov Usage("Must provide a test case name", true);
131924226fbSAlexander V. Chernikov }
132924226fbSAlexander V. Chernikov test_name = std::string(argv[0]);
133924226fbSAlexander V. Chernikov if (test_name.size() > kCleanupSuffix.size() &&
134924226fbSAlexander V. Chernikov std::equal(kCleanupSuffix.rbegin(), kCleanupSuffix.rend(), test_name.rbegin())) {
135924226fbSAlexander V. Chernikov test_name = test_name.substr(0, test_name.size() - kCleanupSuffix.size());
136924226fbSAlexander V. Chernikov flag_cleanup = true;
137924226fbSAlexander V. Chernikov }
138924226fbSAlexander V. Chernikov }
139924226fbSAlexander V. Chernikov
BuildArgs()140924226fbSAlexander 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"};
143924226fbSAlexander V. Chernikov
144*9f23cbd6SKristof Provost args.push_back("--confcutdir=" + python_path);
145*9f23cbd6SKristof Provost
146924226fbSAlexander V. Chernikov if (flag_list) {
147924226fbSAlexander V. Chernikov args.push_back("--co");
148924226fbSAlexander V. Chernikov args.push_back(script_path);
149924226fbSAlexander V. Chernikov return args;
150924226fbSAlexander V. Chernikov }
151924226fbSAlexander V. Chernikov if (flag_cleanup) {
152924226fbSAlexander V. Chernikov args.push_back("--atf-cleanup");
153924226fbSAlexander 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
158924226fbSAlexander V. Chernikov if (!src_dir.empty()) {
1599c42645aSAlexander V. Chernikov args.push_back("--atf-source-dir=" + src_dir);
160924226fbSAlexander V. Chernikov }
161924226fbSAlexander V. Chernikov if (!dst_file.empty()) {
1629c42645aSAlexander V. Chernikov args.push_back("--atf-file=" + dst_file);
163924226fbSAlexander V. Chernikov }
164924226fbSAlexander V. Chernikov // Create nodeid from the test path &name
165924226fbSAlexander V. Chernikov args.push_back(script_path + "::" + test_name);
166924226fbSAlexander V. Chernikov return args;
167924226fbSAlexander V. Chernikov }
168924226fbSAlexander V. Chernikov
SetPythonPath()169513ce835SAlexander V. Chernikov void SetPythonPath() {
170924226fbSAlexander V. Chernikov if (!python_path.empty()) {
171924226fbSAlexander V. Chernikov char *env_path = getenv(kPythonPathEnv.c_str());
172924226fbSAlexander V. Chernikov if (env_path != nullptr) {
173924226fbSAlexander V. Chernikov python_path = python_path + ":" + std::string(env_path);
174924226fbSAlexander V. Chernikov }
175924226fbSAlexander V. Chernikov setenv(kPythonPathEnv.c_str(), python_path.c_str(), 1);
176924226fbSAlexander V. Chernikov }
177924226fbSAlexander V. Chernikov }
178924226fbSAlexander 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) {
190924226fbSAlexander V. Chernikov if (flag_debug) {
191924226fbSAlexander V. Chernikov PrintVector("OUT", args);
192924226fbSAlexander V. Chernikov }
193924226fbSAlexander V. Chernikov // allocate array with final NULL
194924226fbSAlexander V. Chernikov char **arr = new char*[args.size() + 1]();
195924226fbSAlexander V. Chernikov for (unsigned long i = 0; i < args.size(); i++) {
196924226fbSAlexander V. Chernikov // work around 'char *const *'
197924226fbSAlexander V. Chernikov arr[i] = strdup(args[i].c_str());
198924226fbSAlexander 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 }
212924226fbSAlexander V. Chernikov }
213924226fbSAlexander V. Chernikov
Process()214924226fbSAlexander V. Chernikov int Process() {
215924226fbSAlexander V. Chernikov SetEnv();
2162bfd8b5bSAlexander V. Chernikov if (!Run(kPytestName, BuildArgs())) {
2172bfd8b5bSAlexander V. Chernikov ReportError();
2182bfd8b5bSAlexander V. Chernikov }
2192bfd8b5bSAlexander V. Chernikov return 0;
220924226fbSAlexander V. Chernikov }
221924226fbSAlexander V. Chernikov };
222924226fbSAlexander V. Chernikov
223924226fbSAlexander V. Chernikov
main(int argc,char ** argv)224924226fbSAlexander V. Chernikov int main(int argc, char **argv) {
225924226fbSAlexander V. Chernikov Handler handler;
226924226fbSAlexander V. Chernikov
227924226fbSAlexander V. Chernikov handler.Parse(argc, argv);
228924226fbSAlexander V. Chernikov return handler.Process();
229924226fbSAlexander V. Chernikov }
230