Path: blob/main/libexec/atf/atf-pytest-wrapper/atf_pytest_wrapper.cpp
34923 views
// vim: ts=2 sw=2 et12#include <format>3#include <iostream>4#include <map>5#include <string>6#include <vector>7#include <stdlib.h>8#include <unistd.h>910class Handler {11private:12const std::string kPytestName = "pytest";13const std::string kCleanupSuffix = ":cleanup";14const std::string kPythonPathEnv = "PYTHONPATH";15const std::string kAtfVar = "_ATF_VAR_";16public:17// Test listing requested18bool flag_list = false;19// Output debug data (will break listing)20bool flag_debug = false;21// Cleanup for the test requested22bool flag_cleanup = false;23// Test source directory (provided by ATF)24std::string src_dir;25// Path to write test status to (provided by ATF)26std::string dst_file;27// Path to add to PYTHONPATH (provided by the schebang args)28std::string python_path;29// Path to the script (provided by the schebang wrapper)30std::string script_path;31// Name of the test to run (provided by ATF)32std::string test_name;33// kv pairs (provided by ATF)34std::map<std::string,std::string> kv_map;35// our binary name36std::string binary_name;3738static std::vector<std::string> ToVector(int argc, char **argv) {39std::vector<std::string> ret;4041for (int i = 0; i < argc; i++) {42ret.emplace_back(std::string(argv[i]));43}44return ret;45}4647static void PrintVector(std::string prefix, const std::vector<std::string> &vec) {48std::cerr << prefix << ": ";49for (auto &val: vec) {50std::cerr << "'" << val << "' ";51}52std::cerr << std::endl;53}5455void Usage(std::string msg, bool exit_with_error) {56std::cerr << binary_name << ": ERROR: " << msg << "." << std::endl;57std::cerr << binary_name << ": See atf-test-program(1) for usage details." << std::endl;58exit(exit_with_error != 0);59}6061// Parse args received from the OS. There can be multiple valid options:62// * with schebang args (#!/binary -P/path):63// atf_wrap '-P /path' /path/to/script -l64// * without schebang args65// atf_wrap /path/to/script -l66// Running test:67// atf_wrap '-P /path' /path/to/script -r /path1 -s /path2 -vk1=v1 testname68void Parse(int argc, char **argv) {69if (flag_debug) {70PrintVector("IN", ToVector(argc, argv));71}72// getopt() skips the first argument (as it is typically binary name)73// it is possible to have either '-P\s*/path' followed by the script name74// or just the script name. Parse kernel-provided arg manually and adjust75// array to make getopt work7677binary_name = std::string(argv[0]);78argc--; argv++;79// parse -P\s*path from the kernel.80if (argc > 0 && !strncmp(argv[0], "-P", 2)) {81char *path = &argv[0][2];82while (*path == ' ')83path++;84python_path = std::string(path);85argc--; argv++;86}8788// The next argument is a script name. Copy and keep argc/argv the same89// Show usage for empty args90if (argc == 0) {91Usage("Must provide a test case name", true);92}93script_path = std::string(argv[0]);9495int c;96while ((c = getopt(argc, argv, "lr:s:v:")) != -1) {97switch (c) {98case 'l':99flag_list = true;100break;101case 's':102src_dir = std::string(optarg);103break;104case 'r':105dst_file = std::string(optarg);106break;107case 'v':108{109std::string kv = std::string(optarg);110size_t splitter = kv.find("=");111if (splitter == std::string::npos) {112Usage("Unknown variable: " + kv, true);113}114kv_map[kv.substr(0, splitter)] = kv.substr(splitter + 1);115}116break;117default:118Usage("Unknown option -" + std::string(1, static_cast<char>(c)), true);119}120}121argc -= optind;122argv += optind;123124if (flag_list) {125return;126}127// There should be just one argument with the test name128if (argc != 1) {129Usage("Must provide a test case name", true);130}131test_name = std::string(argv[0]);132if (test_name.size() > kCleanupSuffix.size() &&133std::equal(kCleanupSuffix.rbegin(), kCleanupSuffix.rend(), test_name.rbegin())) {134test_name = test_name.substr(0, test_name.size() - kCleanupSuffix.size());135flag_cleanup = true;136}137}138139std::vector<std::string> BuildArgs() {140std::vector<std::string> args = {"pytest", "-vv", "-p",141"no:cacheprovider", "-s", "--atf"};142143args.push_back("--confcutdir=" + python_path);144145if (flag_list) {146args.push_back("--co");147args.push_back(script_path);148return args;149}150if (flag_cleanup) {151args.push_back("--atf-cleanup");152}153// workaround pytest parser bug:154// https://github.com/pytest-dev/pytest/issues/3097155// use '--arg=value' format instead of '--arg value' for all156// path-like options157if (!src_dir.empty()) {158args.push_back("--atf-source-dir=" + src_dir);159}160if (!dst_file.empty()) {161args.push_back("--atf-file=" + dst_file);162}163// Create nodeid from the test path &name164args.push_back(script_path + "::" + test_name);165return args;166}167168void SetPythonPath() {169if (!python_path.empty()) {170char *env_path = getenv(kPythonPathEnv.c_str());171if (env_path != nullptr) {172python_path = python_path + ":" + std::string(env_path);173}174setenv(kPythonPathEnv.c_str(), python_path.c_str(), 1);175}176}177178void SetEnv() {179SetPythonPath();180181// Pass ATF kv pairs as env variables to avoid dealing with182// pytest parser183for (auto [k, v]: kv_map) {184setenv((kAtfVar + k).c_str(), v.c_str(), 1);185}186}187188bool Run(std::string binary, std::vector<std::string> args) {189if (flag_debug) {190PrintVector("OUT", args);191}192// allocate array with final NULL193char **arr = new char*[args.size() + 1]();194for (unsigned long i = 0; i < args.size(); i++) {195// work around 'char *const *'196arr[i] = strdup(args[i].c_str());197}198return execvp(binary.c_str(), arr) == 0;199}200201void ReportError() {202if (flag_list) {203std::cout << "Content-Type: application/X-atf-tp; version=\"1\"";204std::cout << std::endl << std::endl;205std::cout << "ident: __test_cases_list_"<< kPytestName << "_binary_" <<206"not_found__" << std::endl;207} else {208std::cout << "execvp(" << kPytestName << ") failed: " <<209std::strerror(errno) << std::endl;210}211}212213int Process() {214SetEnv();215if (!Run(kPytestName, BuildArgs())) {216ReportError();217}218return 0;219}220};221222223int main(int argc, char **argv) {224Handler handler;225226handler.Parse(argc, argv);227return handler.Process();228}229230231