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