tests: Preserve empty arguments in test command lines

This will now preserve empty values in the TEST_LAUNCHER and
CROSSCOMPILING_EMULATOR target properties for tests added by:

- The add_test() command.
- The ExternalData_Add_Test() command from the ExternalData module.
- The gtest_add_tests() or gtest_discover_tests() commands from the
  GoogleTest module.

For the gtest_add_tests() and gtest_discover_tests() commands,
empty elements in the values passed after the EXTRA_ARGS keyword
are also now preserved.

Policy CMP0178 is added to provide backward compatibility with the
old behavior where empty values were silently discarded from the
above cases.

Fixes: #26337
This commit is contained in:
Craig Scott
2024-09-30 21:13:13 +10:00
parent 9f1703530b
commit fc7aa3cd69
27 changed files with 512 additions and 80 deletions
+45 -3
View File
@@ -2,14 +2,19 @@
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmAddTestCommand.h"
#include <algorithm>
#include <cm/memory>
#include "cmExecutionStatus.h"
#include "cmMakefile.h"
#include "cmPolicies.h"
#include "cmStringAlgorithms.h"
#include "cmTest.h"
#include "cmTestGenerator.h"
static std::string const keywordCMP0178 = "__CMP0178";
static bool cmAddTestCommandHandleNameMode(
std::vector<std::string> const& args, cmExecutionStatus& status);
@@ -29,8 +34,30 @@ bool cmAddTestCommand(std::vector<std::string> const& args,
}
cmMakefile& mf = status.GetMakefile();
cmPolicies::PolicyStatus cmp0178;
// If the __CMP0178 keyword is present, it is always at the end
auto endOfCommandIter =
std::find(args.begin() + 2, args.end(), keywordCMP0178);
if (endOfCommandIter != args.end()) {
auto cmp0178Iter = endOfCommandIter + 1;
if (cmp0178Iter == args.end()) {
status.SetError(cmStrCat(keywordCMP0178, " keyword missing value"));
return false;
}
if (*cmp0178Iter == "NEW") {
cmp0178 = cmPolicies::PolicyStatus::NEW;
} else if (*cmp0178Iter == "OLD") {
cmp0178 = cmPolicies::PolicyStatus::OLD;
} else {
cmp0178 = cmPolicies::PolicyStatus::WARN;
}
} else {
cmp0178 = mf.GetPolicyStatus(cmPolicies::CMP0178);
}
// Collect the command with arguments.
std::vector<std::string> command(args.begin() + 1, args.end());
std::vector<std::string> command(args.begin() + 1, endOfCommandIter);
// Create the test but add a generator only the first time it is
// seen. This preserves behavior from before test generators.
@@ -46,6 +73,7 @@ bool cmAddTestCommand(std::vector<std::string> const& args,
} else {
test = mf.CreateTest(args[0]);
test->SetOldStyle(true);
test->SetCMP0178(cmp0178);
mf.AddTestGenerator(cm::make_unique<cmTestGenerator>(test));
}
test->SetCommand(command);
@@ -56,11 +84,14 @@ bool cmAddTestCommand(std::vector<std::string> const& args,
bool cmAddTestCommandHandleNameMode(std::vector<std::string> const& args,
cmExecutionStatus& status)
{
cmMakefile& mf = status.GetMakefile();
std::string name;
std::vector<std::string> configurations;
std::string working_directory;
std::vector<std::string> command;
bool command_expand_lists = false;
cmPolicies::PolicyStatus cmp0178 = mf.GetPolicyStatus(cmPolicies::CMP0178);
// Read the arguments.
enum Doing
@@ -69,6 +100,7 @@ bool cmAddTestCommandHandleNameMode(std::vector<std::string> const& args,
DoingCommand,
DoingConfigs,
DoingWorkingDirectory,
DoingCmp0178,
DoingNone
};
Doing doing = DoingName;
@@ -91,6 +123,8 @@ bool cmAddTestCommandHandleNameMode(std::vector<std::string> const& args,
return false;
}
doing = DoingWorkingDirectory;
} else if (args[i] == keywordCMP0178) {
doing = DoingCmp0178;
} else if (args[i] == "COMMAND_EXPAND_LISTS") {
if (command_expand_lists) {
status.SetError(" may be given at most one COMMAND_EXPAND_LISTS.");
@@ -108,6 +142,15 @@ bool cmAddTestCommandHandleNameMode(std::vector<std::string> const& args,
} else if (doing == DoingWorkingDirectory) {
working_directory = args[i];
doing = DoingNone;
} else if (doing == DoingCmp0178) {
if (args[i] == "NEW") {
cmp0178 = cmPolicies::PolicyStatus::NEW;
} else if (args[i] == "OLD") {
cmp0178 = cmPolicies::PolicyStatus::OLD;
} else {
cmp0178 = cmPolicies::PolicyStatus::WARN;
}
doing = DoingNone;
} else {
status.SetError(cmStrCat(" given unknown argument:\n ", args[i], "\n"));
return false;
@@ -126,8 +169,6 @@ bool cmAddTestCommandHandleNameMode(std::vector<std::string> const& args,
return false;
}
cmMakefile& mf = status.GetMakefile();
// Require a unique test name within the directory.
if (mf.GetTest(name)) {
status.SetError(cmStrCat(" given test NAME \"", name,
@@ -138,6 +179,7 @@ bool cmAddTestCommandHandleNameMode(std::vector<std::string> const& args,
// Add the test.
cmTest* test = mf.CreateTest(name);
test->SetOldStyle(false);
test->SetCMP0178(cmp0178);
test->SetCommand(command);
if (!working_directory.empty()) {
test->SetProperty("WORKING_DIRECTORY", working_directory);
+2
View File
@@ -543,6 +543,8 @@ class cmMakefile;
SELECT(POLICY, CMP0176, "execute_process() ENCODING is UTF-8 by default.", \
3, 31, 0, cmPolicies::WARN) \
SELECT(POLICY, CMP0177, "install() DESTINATION paths are normalized.", 3, \
31, 0, cmPolicies::WARN) \
SELECT(POLICY, CMP0178, "Test command lines preserve empty arguments.", 3, \
31, 0, cmPolicies::WARN)
#define CM_SELECT_ID(F, A1, A2, A3, A4, A5, A6) F(A1)
+1
View File
@@ -10,6 +10,7 @@
cmTest::cmTest(cmMakefile* mf)
: Backtrace(mf->GetBacktrace())
, PolicyStatusCMP0158(mf->GetPolicyStatus(cmPolicies::CMP0158))
, PolicyStatusCMP0178(mf->GetPolicyStatus(cmPolicies::CMP0178))
{
this->Makefile = mf;
this->OldStyle = true;
+12 -1
View File
@@ -61,12 +61,22 @@ public:
bool GetOldStyle() const { return this->OldStyle; }
void SetOldStyle(bool b) { this->OldStyle = b; }
/** Get/Set if CMP0158 policy is NEW */
/** Get if CMP0158 policy is NEW */
bool GetCMP0158IsNew() const
{
return this->PolicyStatusCMP0158 == cmPolicies::NEW;
}
/** Get/Set the CMP0178 policy setting */
cmPolicies::PolicyStatus GetCMP0178() const
{
return this->PolicyStatusCMP0178;
}
void SetCMP0178(cmPolicies::PolicyStatus p)
{
this->PolicyStatusCMP0178 = p;
}
/** Set/Get whether lists in command lines should be expanded. */
bool GetCommandExpandLists() const;
void SetCommandExpandLists(bool b);
@@ -82,4 +92,5 @@ private:
cmMakefile* Makefile;
cmListFileBacktrace Backtrace;
cmPolicies::PolicyStatus PolicyStatusCMP0158;
cmPolicies::PolicyStatus PolicyStatusCMP0178;
};
+24 -3
View File
@@ -174,15 +174,36 @@ void cmTestGenerator::GenerateScriptForConfig(std::ostream& os,
if (!cmNonempty(launcher)) {
return;
}
cmList launcherWithArgs{ ge.Parse(*launcher)->Evaluate(this->LG,
config) };
const auto propVal = ge.Parse(*launcher)->Evaluate(this->LG, config);
cmList launcherWithArgs(propVal, cmList::ExpandElements::Yes,
this->Test->GetCMP0178() == cmPolicies::NEW
? cmList::EmptyElements::Yes
: cmList::EmptyElements::No);
if (!launcherWithArgs.empty() && !launcherWithArgs[0].empty()) {
if (this->Test->GetCMP0178() == cmPolicies::WARN) {
cmList argsWithEmptyValuesPreserved(
propVal, cmList::ExpandElements::Yes, cmList::EmptyElements::Yes);
if (launcherWithArgs != argsWithEmptyValuesPreserved) {
this->Test->GetMakefile()->IssueMessage(
MessageType::AUTHOR_WARNING,
cmStrCat("The ", propertyName, " property of target '",
target->GetName(),
"' contains empty list items. Those empty items are "
"being silently discarded to preserve backward "
"compatibility.\n",
cmPolicies::GetPolicyWarning(cmPolicies::CMP0178)));
}
}
std::string launcherExe(launcherWithArgs[0]);
cmSystemTools::ConvertToUnixSlashes(launcherExe);
os << cmOutputConverter::EscapeForCMake(launcherExe) << " ";
for (std::string const& arg :
cmMakeRange(launcherWithArgs).advance(1)) {
os << cmOutputConverter::EscapeForCMake(arg) << " ";
if (arg.empty()) {
os << "\"\" ";
} else {
os << cmOutputConverter::EscapeForCMake(arg) << " ";
}
}
}
};