diff options
author | Alexander V. Chernikov <melifaro@FreeBSD.org> | 2022-06-26 11:46:37 +0000 |
---|---|---|
committer | Alexander V. Chernikov <melifaro@FreeBSD.org> | 2022-06-26 13:25:47 +0000 |
commit | 924226fba12cc9a228c73b956e1b7fa24c60b055 (patch) | |
tree | f527a7bfd80ed54f3619695151ae6a680392896b /libexec | |
parent | fdfeaa66b58a49ee1bb40f9d447c610402231f20 (diff) | |
download | src-924226fba12cc9a228c73b956e1b7fa24c60b055.tar.gz src-924226fba12cc9a228c73b956e1b7fa24c60b055.zip |
testing: move atf-pytest-wrapper to /usr/libexec
Move pytest wrapper to the collection of the other atf wrappers
in libexec. It solves the problem of combining bits & pieces from
bsd.test.mk and bgs.prog.mk to address "test binary, but not the
suite binary".
Reviewed by: kp
Differential Revision: https://reviews.freebsd.org/D35604
MFC after: 2 weeks
Diffstat (limited to 'libexec')
-rw-r--r-- | libexec/atf/Makefile | 2 | ||||
-rw-r--r-- | libexec/atf/atf-pytest-wrapper/Makefile | 10 | ||||
-rw-r--r-- | libexec/atf/atf-pytest-wrapper/atf_pytest_wrapper.cpp | 192 |
3 files changed, 203 insertions, 1 deletions
diff --git a/libexec/atf/Makefile b/libexec/atf/Makefile index db7554d9ddaa..77819aeff697 100644 --- a/libexec/atf/Makefile +++ b/libexec/atf/Makefile @@ -25,6 +25,6 @@ # # $FreeBSD$ -SUBDIR= atf-check atf-sh tests +SUBDIR= atf-check atf-pytest-wrapper atf-sh tests .include <bsd.subdir.mk> diff --git a/libexec/atf/atf-pytest-wrapper/Makefile b/libexec/atf/atf-pytest-wrapper/Makefile new file mode 100644 index 000000000000..e4dc0f8a5fd1 --- /dev/null +++ b/libexec/atf/atf-pytest-wrapper/Makefile @@ -0,0 +1,10 @@ +.include <src.opts.mk> +.include <bsd.init.mk> + +PACKAGE= tests +PROG_CXX= atf_pytest_wrapper +SRCS= atf_pytest_wrapper.cpp +CXXSTD= c++17 +MAN= + +.include <bsd.prog.mk> diff --git a/libexec/atf/atf-pytest-wrapper/atf_pytest_wrapper.cpp b/libexec/atf/atf-pytest-wrapper/atf_pytest_wrapper.cpp new file mode 100644 index 000000000000..11fd3c47d507 --- /dev/null +++ b/libexec/atf/atf-pytest-wrapper/atf_pytest_wrapper.cpp @@ -0,0 +1,192 @@ +#include <format> +#include <iostream> +#include <string> +#include <vector> +#include <stdlib.h> +#include <unistd.h> + +class Handler { + private: + const std::string kPytestName = "pytest"; + const std::string kCleanupSuffix = ":cleanup"; + const std::string kPythonPathEnv = "PYTHONPATH"; + public: + // Test listing requested + bool flag_list = false; + // Output debug data (will break listing) + bool flag_debug = false; + // Cleanup for the test requested + bool flag_cleanup = false; + // Test source directory (provided by ATF) + std::string src_dir; + // Path to write test status to (provided by ATF) + std::string dst_file; + // Path to add to PYTHONPATH (provided by the schebang args) + std::string python_path; + // Path to the script (provided by the schebang wrapper) + std::string script_path; + // Name of the test to run (provided by ATF) + std::string test_name; + // kv pairs (provided by ATF) + std::vector<std::string> kv_list; + // our binary name + std::string binary_name; + + static std::vector<std::string> ToVector(int argc, char **argv) { + std::vector<std::string> ret; + + for (int i = 0; i < argc; i++) { + ret.emplace_back(std::string(argv[i])); + } + return ret; + } + + static void PrintVector(std::string prefix, const std::vector<std::string> &vec) { + std::cerr << prefix << ": "; + for (auto &val: vec) { + std::cerr << "'" << val << "' "; + } + std::cerr << std::endl; + } + + void Usage(std::string msg, bool exit_with_error) { + std::cerr << binary_name << ": ERROR: " << msg << "." << std::endl; + std::cerr << binary_name << ": See atf-test-program(1) for usage details." << std::endl; + exit(exit_with_error != 0); + } + + // Parse args received from the OS. There can be multiple valid options: + // * with schebang args (#!/binary -P/path): + // atf_wrap '-P /path' /path/to/script -l + // * without schebang args + // atf_wrap /path/to/script -l + // Running test: + // atf_wrap '-P /path' /path/to/script -r /path1 -s /path2 -vk1=v1 testname + void Parse(int argc, char **argv) { + if (flag_debug) { + PrintVector("IN", ToVector(argc, argv)); + } + // getopt() skips the first argument (as it is typically binary name) + // it is possible to have either '-P\s*/path' followed by the script name + // or just the script name. Parse kernel-provided arg manually and adjust + // array to make getopt work + + binary_name = std::string(argv[0]); + argc--; argv++; + // parse -P\s*path from the kernel. + if (argc > 0 && !strncmp(argv[0], "-P", 2)) { + char *path = &argv[0][2]; + while (*path == ' ') + path++; + python_path = std::string(path); + argc--; argv++; + } + + // The next argument is a script name. Copy and keep argc/argv the same + // Show usage for empty args + if (argc == 0) { + Usage("Must provide a test case name", true); + } + script_path = std::string(argv[0]); + + int c; + while ((c = getopt(argc, argv, "lr:s:v:")) != -1) { + switch (c) { + case 'l': + flag_list = true; + break; + case 's': + src_dir = std::string(optarg); + break; + case 'r': + dst_file = std::string(optarg); + break; + case 'v': + kv_list.emplace_back(std::string(optarg)); + break; + default: + Usage("Unknown option -" + std::string(1, static_cast<char>(c)), true); + } + } + argc -= optind; + argv += optind; + + if (flag_list) { + return; + } + // There should be just one argument with the test name + if (argc != 1) { + Usage("Must provide a test case name", true); + } + test_name = std::string(argv[0]); + if (test_name.size() > kCleanupSuffix.size() && + std::equal(kCleanupSuffix.rbegin(), kCleanupSuffix.rend(), test_name.rbegin())) { + test_name = test_name.substr(0, test_name.size() - kCleanupSuffix.size()); + flag_cleanup = true; + } + } + + std::vector<std::string> BuildArgs() { + std::vector<std::string> args = {"pytest", "-p", "no:cacheprovider", "-s", "--atf"}; + + if (flag_list) { + args.push_back("--co"); + args.push_back(script_path); + return args; + } + if (flag_cleanup) { + args.push_back("--atf-cleanup"); + } + if (!src_dir.empty()) { + args.push_back("--atf-source-dir"); + args.push_back(src_dir); + } + if (!dst_file.empty()) { + args.push_back("--atf-file"); + args.push_back(dst_file); + } + for (auto &pair: kv_list) { + args.push_back("--atf-var"); + args.push_back(pair); + } + // Create nodeid from the test path &name + args.push_back(script_path + "::" + test_name); + return args; + } + + void SetEnv() { + if (!python_path.empty()) { + char *env_path = getenv(kPythonPathEnv.c_str()); + if (env_path != nullptr) { + python_path = python_path + ":" + std::string(env_path); + } + setenv(kPythonPathEnv.c_str(), python_path.c_str(), 1); + } + } + + int Run(std::string binary, std::vector<std::string> args) { + if (flag_debug) { + PrintVector("OUT", args); + } + // allocate array with final NULL + char **arr = new char*[args.size() + 1](); + for (unsigned long i = 0; i < args.size(); i++) { + // work around 'char *const *' + arr[i] = strdup(args[i].c_str()); + } + return (execvp(binary.c_str(), arr) != 0); + } + + int Process() { + SetEnv(); + return Run(kPytestName, BuildArgs()); + } +}; + + +int main(int argc, char **argv) { + Handler handler; + + handler.Parse(argc, argv); + return handler.Process(); +} |