git: 513ce835b558 - main - testing: pass ATF vars to pytest via env instead of arguments.

From: Alexander V. Chernikov <melifaro_at_FreeBSD.org>
Date: Tue, 28 Jun 2022 12:20:27 UTC
The branch main has been updated by melifaro:

URL: https://cgit.FreeBSD.org/src/commit/?id=513ce835b55831d343185e03a51efa2901405ac8

commit 513ce835b55831d343185e03a51efa2901405ac8
Author:     Alexander V. Chernikov <melifaro@FreeBSD.org>
AuthorDate: 2022-06-28 10:49:41 +0000
Commit:     Alexander V. Chernikov <melifaro@FreeBSD.org>
CommitDate: 2022-06-28 12:20:16 +0000

    testing: pass ATF vars to pytest via env instead of arguments.
    
    This change is a continuation of 9c42645a1e4d workaround.
    Apparently pytest argument parser is not happy when parsing values
     with spaces or just more than one --atf-var argument.
    Switch wrapper to send these kv pairs as env variables. Specifically,
     use _ATF_VAR_key=value format to distinguish from the other vars.
    
    Add the `atf_vars` fixture returning all passed kv pairs as a dict.
    
    Reviewed by:    lwhsu
    Differential Revision: https://reviews.freebsd.org/D35625
    MFC after:      2 weeks
---
 .../atf/atf-pytest-wrapper/atf_pytest_wrapper.cpp  | 29 ++++++++++++++++------
 tests/atf_python/atf_pytest.py                     |  6 +++++
 tests/conftest.py                                  |  7 +++++-
 3 files changed, 34 insertions(+), 8 deletions(-)

diff --git a/libexec/atf/atf-pytest-wrapper/atf_pytest_wrapper.cpp b/libexec/atf/atf-pytest-wrapper/atf_pytest_wrapper.cpp
index bc7eec3b851d..6baa85999070 100644
--- a/libexec/atf/atf-pytest-wrapper/atf_pytest_wrapper.cpp
+++ b/libexec/atf/atf-pytest-wrapper/atf_pytest_wrapper.cpp
@@ -1,5 +1,6 @@
 #include <format>
 #include <iostream>
+#include <map>
 #include <string>
 #include <vector>
 #include <stdlib.h>
@@ -10,6 +11,7 @@ class Handler {
     const std::string kPytestName = "pytest";
     const std::string kCleanupSuffix = ":cleanup";
     const std::string kPythonPathEnv = "PYTHONPATH";
+    const std::string kAtfVar = "_ATF_VAR_";
   public:
     // Test listing requested
     bool flag_list = false;
@@ -28,7 +30,7 @@ class Handler {
     // Name of the test to run (provided by ATF)
     std::string test_name;
     // kv pairs (provided by ATF)
-    std::vector<std::string> kv_list;
+    std::map<std::string,std::string> kv_map;
     // our binary name
     std::string binary_name;
 
@@ -102,7 +104,14 @@ class Handler {
           dst_file = std::string(optarg);
           break;
         case 'v':
-          kv_list.emplace_back(std::string(optarg));
+	  {
+	    std::string kv = std::string(optarg);
+	    size_t splitter = kv.find("=");
+	    if (splitter == std::string::npos) {
+	      Usage("Unknown variable: " + kv, true);
+	    }
+	    kv_map[kv.substr(0, splitter)] = kv.substr(splitter + 1);
+	  }
           break;
         default:
           Usage("Unknown option -" + std::string(1, static_cast<char>(c)), true);
@@ -147,16 +156,12 @@ class Handler {
       if (!dst_file.empty()) {
         args.push_back("--atf-file=" + 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() {
+    void SetPythonPath() {
       if (!python_path.empty()) {
         char *env_path = getenv(kPythonPathEnv.c_str());
         if (env_path != nullptr) {
@@ -166,6 +171,16 @@ class Handler {
       }
     }
 
+    void SetEnv() {
+      SetPythonPath();
+
+      // Pass ATF kv pairs as env variables to avoid dealing with
+      // pytest parser
+      for (auto [k, v]: kv_map) {
+	setenv((kAtfVar + k).c_str(), v.c_str(), 1);
+      }
+    }
+
     int Run(std::string binary, std::vector<std::string> args) {
       if (flag_debug) {
         PrintVector("OUT", args);
diff --git a/tests/atf_python/atf_pytest.py b/tests/atf_python/atf_pytest.py
index 89c0e3a515b9..f72122fb740e 100644
--- a/tests/atf_python/atf_pytest.py
+++ b/tests/atf_python/atf_pytest.py
@@ -6,6 +6,7 @@ from typing import NamedTuple
 from typing import Tuple
 
 import pytest
+import os
 
 
 class ATFCleanupItem(pytest.Item):
@@ -216,3 +217,8 @@ class ATFHandler(object):
                 line = "{}: {}".format(test.state, test.reason)
             with open(path, mode="w") as f:
                 print(line, file=f)
+
+    @staticmethod
+    def get_atf_vars() -> Dict[str, str]:
+        px = "_ATF_VAR_"
+        return {k[len(px):]: v for k, v in os.environ.items() if k.startswith(px)}
diff --git a/tests/conftest.py b/tests/conftest.py
index 193d2adfb5e0..65c8bf5f0d01 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,5 +1,6 @@
 import pytest
 from atf_python.atf_pytest import ATFHandler
+from typing import Dict
 
 
 PLUGIN_ENABLED = False
@@ -17,7 +18,6 @@ def pytest_addoption(parser):
     """Add file output"""
     # Add meta-values
     group = parser.getgroup("general", "Running and selection options")
-    group.addoption("--atf-var", dest="atf_vars", action="append", default=[])
     group.addoption(
         "--atf-source-dir",
         type=str,
@@ -46,6 +46,11 @@ def pytest_addoption(parser):
     )
 
 
+@pytest.fixture(autouse=True, scope="session")
+def atf_vars() -> Dict[str, str]:
+    return ATFHandler.get_atf_vars()
+
+
 @pytest.mark.trylast
 def pytest_configure(config):
     if config.option.help: