discover_tests: Add CTest command implementation

This commit is contained in:
Daniel Pfeifer authored and Brad King committed 2026-03-26 10:36:21 -04:00
1 parent d718a979b7
commit 3f1fce6b48
5 files changed
+245

No files matched your search

+1
View File
@@ -1143,6 +1143,7 @@ add_library(
CTest/cmCTestCoverageCommand.cxx
CTest/cmCTestCoverageHandler.cxx
CTest/cmCTestCurl.cxx
CTest/cmCTestDiscoverTests.cxx
CTest/cmParseMumpsCoverage.cxx
CTest/cmParseCacheCoverage.cxx
CTest/cmParseGTMCoverage.cxx
+163
View File
@@ -0,0 +1,163 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file LICENSE.rst or https://cmake.org/licensing for details. */
#include "cmCTestDiscoverTests.h"
#include <algorithm>
#include <cstddef>
#include <cstdlib>
#include <iterator>
#include <string>
#include <vector>
#include <cmsys/RegularExpression.hxx>
#include "cmArgumentParserTypes.h"
#include "cmCTestTestHandler.h"
#include "cmDuration.h"
#include "cmEnvironment.h"
#include "cmExecutionStatus.h"
#include "cmList.h"
#include "cmProcessOutput.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
#include "cmTestDiscovery.h"
namespace {
struct DiscoveryProperties
{
DiscoveryProperties(std::vector<std::string> const& props);
std::vector<std::string> Environment;
std::vector<std::string> EnvironmentModification;
cmDuration Timeout = cmDuration(5.0);
std::string WorkingDirectory;
};
DiscoveryProperties::DiscoveryProperties(std::vector<std::string> const& props)
{
for (std::size_t i = 0; i < props.size(); i += 2) {
auto const& key = props[i];
auto const& val = props[i + 1];
if (key == "ENVIRONMENT") {
cmExpandList(val, this->Environment);
} else if (key == "ENVIRONMENT_MODIFICATION") {
cmExpandList(val, this->EnvironmentModification);
} else if (key == "TIMEOUT") {
this->Timeout = cmDuration(atof(val.c_str()));
} else if (key == "WORKING_DIRECTORY") {
this->WorkingDirectory = val;
}
}
}
std::string AddAnchors(std::string str)
{
if (str.empty() || str.front() != '^') {
str.insert(str.begin(), '^');
}
if (str.size() < 2 || str.back() != '$') {
str.push_back('$');
}
return str;
}
class RegexReplacer
{
public:
RegexReplacer(cmsys::RegularExpression& regex)
: RegEx(regex)
{
}
std::string operator()(std::string str)
{
for (int i = 0; i <= this->RegEx.num_groups(); ++i) {
cmsys::SystemTools::ReplaceString(str, cmStrCat('\\', i),
this->RegEx.match(i));
}
return str;
}
private:
cmsys::RegularExpression& RegEx;
};
} // namespace
bool cmCTestDiscoverTests(cmTestDiscoveryArgs const& args,
cmCTestTestHandler* handler,
cmExecutionStatus& status)
{
cmsys::RegularExpression re;
if (!re.compile(AddAnchors(args.DiscoveryMatch))) {
std::string e = "DISCOVERY_MATCH failed to compile regex \"" +
args.DiscoveryMatch + "\".";
status.SetError(e);
return false;
}
auto const props = DiscoveryProperties{ args.DiscoveryProperties };
auto env = cmEnvironment{ cmSystemTools::GetEnvironmentVariables() };
env.Update(props.Environment);
if (!props.EnvironmentModification.empty()) {
auto diff = cmEnvironmentModification{};
if (!diff.Add(props.EnvironmentModification)) {
return false;
}
diff.ApplyTo(env);
}
auto runCommand = std::vector<std::string>(args.Command);
runCommand.reserve(runCommand.size() + args.DiscoveryArgs.size());
std::copy(args.DiscoveryArgs.begin(), args.DiscoveryArgs.end(),
std::back_inserter(runCommand));
std::string stdOut;
std::string stdErr;
bool const res = cmSystemTools::RunSingleCommand(
runCommand, &stdOut, &stdErr, nullptr, props.WorkingDirectory.c_str(),
cmSystemTools::OUTPUT_NONE, props.Timeout, cmProcessOutput::Auto,
env.GetVariables());
if (!res) {
status.SetError(cmStrCat(" failed to run command: ",
cmSystemTools::PrintSingleCommand(runCommand),
"\n", stdErr));
return false;
}
auto replace = RegexReplacer{ re };
std::vector<std::string> lines;
cmSystemTools::Split(stdOut, lines);
for (auto const& line : lines) {
if (!re.find(line)) {
continue;
}
auto const testName = replace(args.TestName);
auto testArgs = std::vector<std::string>{ testName };
testArgs.reserve(1 + args.Command.size() + args.TestArgs.size());
std::copy(args.Command.begin(), args.Command.end(),
std::back_inserter(testArgs));
std::transform(args.TestArgs.begin(), args.TestArgs.end(),
std::back_inserter(testArgs), replace);
if (!handler->AddTest(testArgs)) {
return false;
}
auto testProperties = std::vector<std::string>{ testName, "PROPERTIES" };
testProperties.reserve(2 + args.TestProperties.size());
for (std::size_t i = 0; i < args.TestProperties.size(); i += 2) {
testProperties.push_back(args.TestProperties[i]);
testProperties.push_back(replace(args.TestProperties[i + 1]));
}
if (!handler->SetTestsProperties(testProperties)) {
return false;
}
}
return true;
}
+13
View File
@@ -0,0 +1,13 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file LICENSE.rst or https://cmake.org/licensing for details. */
#pragma once
#include "cmConfigure.h" // IWYU pragma: keep
struct cmTestDiscoveryArgs;
class cmCTestTestHandler;
class cmExecutionStatus;
bool cmCTestDiscoverTests(cmTestDiscoveryArgs const& args,
cmCTestTestHandler* handler,
cmExecutionStatus& status);
+36
View File
@@ -15,6 +15,7 @@
#include <ratio>
#include <set>
#include <sstream>
#include <string>
#include <utility>
#ifndef _WIN32
@@ -33,7 +34,9 @@
#include "cm_utf8.h"
#include "cmArgumentParser.h"
#include "cmCTest.h"
#include "cmCTestDiscoverTests.h"
#include "cmCTestMultiProcessHandler.h"
#include "cmCTestResourceGroupsLexerHelper.h"
#include "cmCTestTestMeasurementXMLParser.h"
@@ -49,6 +52,7 @@
#include "cmStateSnapshot.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
#include "cmTestDiscovery.h"
#include "cmTimestamp.h"
#include "cmValue.h"
#include "cmWorkingDirectory.h"
@@ -184,6 +188,34 @@ bool cmCTestAddTestCommand::InitialPass(std::vector<std::string> const& args,
return this->TestHandler->AddTest(args);
}
class cmCTestDiscoverTestsCommand : public cmCTestCommand
{
public:
using cmCTestCommand::cmCTestCommand;
bool InitialPass(std::vector<std::string> const& args,
cmExecutionStatus& status) override;
};
bool cmCTestDiscoverTestsCommand::InitialPass(
std::vector<std::string> const& args, cmExecutionStatus& status)
{
using Arguments = cmTestDiscoveryArgs;
static auto const parser = cmTestDiscoveryParser<Arguments>();
auto unparsed = std::vector<std::string>{};
Arguments const arguments = parser.Parse(args, &unparsed);
if (arguments.MaybeReportError(status.GetMakefile())) {
return true;
}
if (!unparsed.empty()) {
status.SetError(" given unknown argument \"" + unparsed.front() + "\".");
return false;
}
return cmCTestDiscoverTests(arguments, this->TestHandler, status);
}
class cmCTestSetTestsPropertiesCommand : public cmCTestCommand
{
public:
@@ -1772,6 +1804,10 @@ bool cmCTestTestHandler::GetListOfTests()
// Add handler for ADD_TEST
cm.GetState()->AddBuiltinCommand("add_test", cmCTestAddTestCommand(this));
// Add handler for DISCOVER_TESTS
cm.GetState()->AddBuiltinCommand("discover_tests",
cmCTestDiscoverTestsCommand(this));
// Add handler for SUBDIRS
cm.GetState()->AddBuiltinCommand("subdirs", cmCTestSubdirCommand);
+32
View File
@@ -0,0 +1,32 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file LICENSE.rst or https://cmake.org/licensing for details. */
#pragma once
#include "cmConfigure.h" // IWYU pragma: keep
#include "cmArgumentParser.h"
struct cmTestDiscoveryArgs : ArgumentParser::ParseResult
{
ArgumentParser::NonEmpty<std::vector<std::string>> Command;
ArgumentParser::NonEmpty<std::vector<std::string>> DiscoveryArgs;
ArgumentParser::NonEmpty<std::string> DiscoveryMatch;
ArgumentParser::MaybeEmpty<std::vector<std::string>> DiscoveryProperties;
ArgumentParser::NonEmpty<std::string> TestName;
ArgumentParser::NonEmpty<std::vector<std::string>> TestArgs;
ArgumentParser::MaybeEmpty<std::vector<std::string>> TestProperties;
};
template <typename Args>
auto cmTestDiscoveryParser() -> cmArgumentParser<Args>
{
static_assert(std::is_base_of<cmTestDiscoveryArgs, Args>::value, "");
return cmArgumentParser<Args>{}
.Bind("COMMAND"_s, &Args::Command)
.Bind("DISCOVERY_ARGS"_s, &Args::DiscoveryArgs)
.Bind("DISCOVERY_MATCH"_s, &Args::DiscoveryMatch)
.Bind("DISCOVERY_PROPERTIES"_s, &Args::DiscoveryProperties)
.Bind("TEST_NAME"_s, &Args::TestName)
.Bind("TEST_ARGS"_s, &Args::TestArgs)
.Bind("TEST_PROPERTIES"_s, &Args::TestProperties);
}