diff options
author | Ben Boeckel <ben.boeckel@kitware.com> | 2021-07-02 13:54:38 (GMT) |
---|---|---|
committer | Ben Boeckel <ben.boeckel@kitware.com> | 2021-07-09 12:45:18 (GMT) |
commit | de4f1f26b0a3217f7caf842c50857b1ac90d9b7d (patch) | |
tree | 655178cd8c11efe1ef3fff26acca0a351b69cc34 /Source/CTest | |
parent | 4c757fa3c8a8d5bc32b8d169b2ebfb7781f3ea0c (diff) | |
download | CMake-de4f1f26b0a3217f7caf842c50857b1ac90d9b7d.zip CMake-de4f1f26b0a3217f7caf842c50857b1ac90d9b7d.tar.gz CMake-de4f1f26b0a3217f7caf842c50857b1ac90d9b7d.tar.bz2 |
CTest: add an ENVIRONMENT_MODIFICATION property
This property allows projects to modify environment variables at test
time rather than trying to guess what the state should be based on what
is present at configure time. Of particular interest is the ability to
use a `PATH` present at test time while adding entries known to be
necessary for the test itself.
There are multiple operations provided to modify variables, including:
- setting and unsetting
- appending and prepending as:
- strings
- path lists
- CMake lists
Additionally, a `reset` action is provided to cancel any prior
modifications to that particular variable in the case of incremental
additions to the test property.
Diffstat (limited to 'Source/CTest')
-rw-r--r-- | Source/CTest/cmCTestMultiProcessHandler.cxx | 5 | ||||
-rw-r--r-- | Source/CTest/cmCTestRunTest.cxx | 145 | ||||
-rw-r--r-- | Source/CTest/cmCTestRunTest.h | 1 | ||||
-rw-r--r-- | Source/CTest/cmCTestTestHandler.cxx | 2 | ||||
-rw-r--r-- | Source/CTest/cmCTestTestHandler.h | 1 |
5 files changed, 151 insertions, 3 deletions
diff --git a/Source/CTest/cmCTestMultiProcessHandler.cxx b/Source/CTest/cmCTestMultiProcessHandler.cxx index 86a8e00..d90c4a6 100644 --- a/Source/CTest/cmCTestMultiProcessHandler.cxx +++ b/Source/CTest/cmCTestMultiProcessHandler.cxx @@ -1026,6 +1026,11 @@ static Json::Value DumpCTestProperties( properties.append(DumpCTestProperty( "ENVIRONMENT", DumpToJsonArray(testProperties.Environment))); } + if (!testProperties.EnvironmentModification.empty()) { + properties.append(DumpCTestProperty( + "ENVIRONMENT_MODIFICATION", + DumpToJsonArray(testProperties.EnvironmentModification))); + } if (!testProperties.ErrorRegularExpressions.empty()) { properties.append(DumpCTestProperty( "FAIL_REGULAR_EXPRESSION", diff --git a/Source/CTest/cmCTestRunTest.cxx b/Source/CTest/cmCTestRunTest.cxx index 4e9b0f0..20f0ed3 100644 --- a/Source/CTest/cmCTestRunTest.cxx +++ b/Source/CTest/cmCTestRunTest.cxx @@ -8,12 +8,16 @@ #include <cstdint> #include <cstdio> #include <cstring> +#include <functional> #include <iomanip> #include <ratio> #include <sstream> #include <utility> #include <cm/memory> +#include <cm/optional> +#include <cm/string_view> +#include <cmext/string_view> #include "cmsys/RegularExpression.hxx" @@ -640,6 +644,7 @@ bool cmCTestRunTest::StartTest(size_t completed, size_t total) return this->ForkProcess(timeout, this->TestProperties->ExplicitTimeout, &this->TestProperties->Environment, + &this->TestProperties->EnvironmentModification, &this->TestProperties->Affinity); } @@ -696,6 +701,17 @@ void cmCTestRunTest::ComputeArguments() cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, this->Index << ": " << env << std::endl); } + if (!this->TestProperties->EnvironmentModification.empty()) { + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + this->Index << ": " + << "Environment variable modifications: " + << std::endl); + } + for (std::string const& envmod : + this->TestProperties->EnvironmentModification) { + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + this->Index << ": " << envmod << std::endl); + } } void cmCTestRunTest::ParseOutputForMeasurements() @@ -719,9 +735,11 @@ void cmCTestRunTest::ParseOutputForMeasurements() } } -bool cmCTestRunTest::ForkProcess(cmDuration testTimeOut, bool explicitTimeout, - std::vector<std::string>* environment, - std::vector<size_t>* affinity) +bool cmCTestRunTest::ForkProcess( + cmDuration testTimeOut, bool explicitTimeout, + std::vector<std::string>* environment, + std::vector<std::string>* environment_modification, + std::vector<size_t>* affinity) { this->TestProcess->SetId(this->Index); this->TestProcess->SetWorkingDirectory(this->TestProperties->Directory); @@ -770,6 +788,127 @@ bool cmCTestRunTest::ForkProcess(cmDuration testTimeOut, bool explicitTimeout, } } + if (environment_modification && !environment_modification->empty()) { + std::map<std::string, cm::optional<std::string>> env_application; + +#ifdef _WIN32 + char path_sep = ';'; +#else + char path_sep = ':'; +#endif + + auto apply_diff = + [&env_application](const std::string& name, + std::function<void(std::string&)> const& apply) { + auto entry = env_application.find(name); + std::string output; + if (entry != env_application.end() && entry->second) { + output = *entry->second; + } + apply(output); + entry->second = output; + }; + + bool err_occurred = false; + + for (auto const& envmod : *environment_modification) { + // Split on `=` + auto const eq_loc = envmod.find_first_of('='); + if (eq_loc == std::string::npos) { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Error: Missing `=` after the variable name in: " + << envmod << std::endl); + err_occurred = true; + continue; + } + auto const name = envmod.substr(0, eq_loc); + + // Split value on `:` + auto const op_value_start = eq_loc + 1; + auto const colon_loc = envmod.find_first_of(':', op_value_start); + if (colon_loc == std::string::npos) { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Error: Missing `:` after the operation in: " << envmod + << std::endl); + err_occurred = true; + continue; + } + auto const op = + envmod.substr(op_value_start, colon_loc - op_value_start); + + auto const value_start = colon_loc + 1; + auto const value = envmod.substr(value_start); + + // Determine what to do with the operation. + if (op == "reset"_s) { + auto entry = env_application.find(name); + if (entry != env_application.end()) { + env_application.erase(entry); + } + } else if (op == "set"_s) { + env_application[name] = value; + } else if (op == "unset"_s) { + env_application[name] = {}; + } else if (op == "string_append"_s) { + apply_diff(name, [&value](std::string& output) { output += value; }); + } else if (op == "string_prepend"_s) { + apply_diff(name, + [&value](std::string& output) { output.insert(0, value); }); + } else if (op == "path_list_append"_s) { + apply_diff(name, [&value, path_sep](std::string& output) { + if (!output.empty()) { + output += path_sep; + } + output += value; + }); + } else if (op == "path_list_prepend"_s) { + apply_diff(name, [&value, path_sep](std::string& output) { + if (!output.empty()) { + output.insert(output.begin(), path_sep); + } + output.insert(0, value); + }); + } else if (op == "cmake_list_append"_s) { + apply_diff(name, [&value](std::string& output) { + if (!output.empty()) { + output += ';'; + } + output += value; + }); + } else if (op == "cmake_list_prepend"_s) { + apply_diff(name, [&value](std::string& output) { + if (!output.empty()) { + output.insert(output.begin(), ';'); + } + output.insert(0, value); + }); + } else { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Error: Unrecognized environment manipulation argument: " + << op << std::endl); + err_occurred = true; + continue; + } + } + + if (err_occurred) { + return false; + } + + for (auto const& env_apply : env_application) { + if (env_apply.second) { + auto const env_update = + cmStrCat(env_apply.first, '=', *env_apply.second); + cmSystemTools::PutEnv(env_update); + envMeasurement << env_update << std::endl; + } else { + cmSystemTools::UnsetEnv(env_apply.first.c_str()); + // Signify that this variable is being actively unset + envMeasurement << "#" << env_apply.first << "=" << std::endl; + } + } + } + if (this->UseAllocatedResources) { std::vector<std::string> envLog; this->SetupResourcesEnvironment(&envLog); diff --git a/Source/CTest/cmCTestRunTest.h b/Source/CTest/cmCTestRunTest.h index a4981c3..2082156 100644 --- a/Source/CTest/cmCTestRunTest.h +++ b/Source/CTest/cmCTestRunTest.h @@ -113,6 +113,7 @@ private: void ExeNotFound(std::string exe); bool ForkProcess(cmDuration testTimeOut, bool explicitTimeout, std::vector<std::string>* environment, + std::vector<std::string>* environment_modification, std::vector<size_t>* affinity); void WriteLogOutputTop(size_t completed, size_t total); // Run post processing of the process output for MemCheck diff --git a/Source/CTest/cmCTestTestHandler.cxx b/Source/CTest/cmCTestTestHandler.cxx index 72b86c1..c29c124 100644 --- a/Source/CTest/cmCTestTestHandler.cxx +++ b/Source/CTest/cmCTestTestHandler.cxx @@ -2245,6 +2245,8 @@ bool cmCTestTestHandler::SetTestsProperties( cmExpandList(val, rt.Depends); } else if (key == "ENVIRONMENT"_s) { cmExpandList(val, rt.Environment); + } else if (key == "ENVIRONMENT_MODIFICATION"_s) { + cmExpandList(val, rt.EnvironmentModification); } else if (key == "LABELS"_s) { std::vector<std::string> Labels = cmExpandedList(val); rt.Labels.insert(rt.Labels.end(), Labels.begin(), Labels.end()); diff --git a/Source/CTest/cmCTestTestHandler.h b/Source/CTest/cmCTestTestHandler.h index 1b42647..f2da320 100644 --- a/Source/CTest/cmCTestTestHandler.h +++ b/Source/CTest/cmCTestTestHandler.h @@ -151,6 +151,7 @@ public: // return code of test which will mark test as "not run" int SkipReturnCode; std::vector<std::string> Environment; + std::vector<std::string> EnvironmentModification; std::vector<std::string> Labels; std::set<std::string> LockedResources; std::set<std::string> FixturesSetup; |