Coverage Report

Created: 2026-10-28 06:38

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmDiscoverTestsCommand.cxx
Line
Count
Source
1
/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
2
   file LICENSE.rst or https://cmake.org/licensing for details.  */
3
#include "cmDiscoverTestsCommand.h"
4
5
#include <cstddef>
6
#include <map>
7
#include <ostream>
8
#include <utility>
9
#include <vector>
10
11
#include <cm/memory>
12
#include <cm/string_view>
13
#include <cmext/string_view>
14
15
#include "cmArgumentParser.h"
16
#include "cmArgumentParserTypes.h"
17
#include "cmExecutionStatus.h"
18
#include "cmGeneratorExpression.h"
19
#include "cmGeneratorTarget.h"
20
#include "cmGlobalGenerator.h"
21
#include "cmListFileCache.h"
22
#include "cmLocalGenerator.h"
23
#include "cmMakefile.h"
24
#include "cmPolicies.h"
25
#include "cmScriptGenerator.h"
26
#include "cmStringAlgorithms.h"
27
#include "cmTestDiscovery.h"
28
#include "cmTestGenerator.h"
29
30
namespace {
31
32
struct Arguments : cmTestDiscoveryArgs
33
{
34
  bool CommandExpandLists = false;
35
  ArgumentParser::MaybeEmpty<std::vector<std::string>> Configurations;
36
};
37
38
class DiscoveryGenerator : public cmTestGenerator
39
{
40
public:
41
  DiscoveryGenerator(Arguments arguments, cmListFileBacktrace backtrace)
42
0
    : cmTestGenerator(nullptr, arguments.Configurations)
43
0
    , Args{ std::move(arguments) }
44
0
    , Backtrace{ std::move(backtrace) }
45
0
  {
46
0
  }
47
48
  void Compute(cmLocalGenerator* lg) override
49
0
  {
50
0
    this->cmTestGenerator::Compute(lg);
51
0
    this->BuildDependenciesByConfig.clear();
52
0
    if (!lg->GetMakefile()->IsOn("CMAKE_TESTING_ENABLED")) {
53
0
      return;
54
0
    }
55
0
    bool const filterConfigs =
56
0
      !lg->GetMakefile()
57
0
         ->GetGeneratorConfigs(cmMakefile::OnlyMultiConfig)
58
0
         .empty();
59
0
    for (std::string const& config : lg->GetMakefile()->GetGeneratorConfigs(
60
0
           cmMakefile::IncludeEmptyConfig)) {
61
0
      if (filterConfigs && !this->GeneratesForConfig(config)) {
62
0
        continue;
63
0
      }
64
0
      cmTestGenerator::BuildDependencies& deps =
65
0
        this->BuildDependenciesByConfig[config];
66
0
      if (!cmTestGenerator::EvaluateBuildDependencies(
67
0
            lg, config, this->Backtrace, "discover_tests", this->Args.Command,
68
0
            this->Args.BuildDepends, deps, lg->GetMakefile())) {
69
0
        deps = {};
70
0
        continue;
71
0
      }
72
0
      for (cmGeneratorTarget* target : deps.Targets) {
73
0
        lg->AddDirectoryTestPrepDependency(config,
74
0
                                           { target->GetName(), target });
75
0
      }
76
0
      for (cmTestGenerator::BuildDependencies::FileDependency const& file :
77
0
           deps.Files) {
78
0
        lg->AddDirectoryTestPrepDependency(
79
0
          config, { file.Path, nullptr, file.Owner, file.Generated });
80
0
      }
81
0
    }
82
0
  }
83
84
private:
85
  void GenerateProperties(std::ostream& os, Indent indent,
86
                          std::vector<std::string> const& props,
87
                          std::string const& config, cmGeneratorExpression& ge)
88
0
  {
89
0
    for (std::size_t i = 0; i < props.size(); i += 2) {
90
0
      os << '\n'
91
0
         << indent << Quote(props[i]) << ' '
92
0
         << Quote(ge.Parse(props[i + 1])->Evaluate(this->LG, config));
93
0
    }
94
0
  }
95
96
  void GenerateScriptForConfig(std::ostream& os, std::string const& config,
97
                               Indent indent) override
98
0
  {
99
    // Set up generator expression evaluation context.
100
0
    cmGeneratorExpression ge(*this->LG->GetMakefile()->GetCMakeInstance(),
101
0
                             this->Backtrace);
102
103
0
    std::vector<std::string> const buildDepends =
104
0
      this->LG->GetGlobalGenerator()->GetTestBuildDependencyPaths(
105
0
        config, this->BuildDependenciesByConfig.at(config));
106
107
0
    auto const in = indent.Next();
108
0
    os << indent << "discover_tests(COMMAND ";
109
0
    this->GenerateCommand(os, this->Args.Command, config,
110
0
                          this->Args.CommandExpandLists, ge);
111
0
    os << '\n' << in << "DISCOVERY_ARGS";
112
0
    for (auto const& arg : this->Args.DiscoveryArgs) {
113
0
      os << ' ' << Quote(arg);
114
0
    }
115
0
    os << '\n' << in << "DISCOVERY_MATCH " << Quote(this->Args.DiscoveryMatch);
116
0
    if (!this->Args.DiscoveryProperties.empty()) {
117
0
      os << '\n' << in << "DISCOVERY_PROPERTIES";
118
0
      this->GenerateProperties(os, in.Next(), this->Args.DiscoveryProperties,
119
0
                               config, ge);
120
0
    }
121
0
    os << '\n' << in << "TEST_NAME " << Quote(this->Args.TestName);
122
0
    os << '\n' << in << "TEST_ARGS";
123
0
    for (auto const& arg : this->Args.TestArgs) {
124
0
      os << ' ' << Quote(arg);
125
0
    }
126
0
    os << '\n' << in << "TEST_PROPERTIES";
127
0
    this->GenerateProperties(os, in.Next(), this->Args.TestProperties, config,
128
0
                             ge);
129
0
    os << '\n' << in.Next();
130
0
    this->GenerateBacktrace(os, this->Backtrace);
131
0
    if (!buildDepends.empty()) {
132
0
      os << '\n' << in << "BUILD_DEPENDS";
133
0
      for (std::string const& dep : buildDepends) {
134
0
        os << ' ' << Quote(dep);
135
0
      }
136
0
    }
137
0
    os << '\n' << in << ")\n";
138
0
  }
139
140
  Arguments Args;
141
  cmListFileBacktrace Backtrace;
142
  std::map<std::string, cmTestGenerator::BuildDependencies>
143
    BuildDependenciesByConfig;
144
};
145
146
bool SetsFixtureRepeatMode(std::vector<std::string> const& properties)
147
0
{
148
0
  for (std::size_t i = 0; i < properties.size(); i += 2) {
149
0
    if (properties[i] == "FIXTURE_REPEAT_MODE"_s ||
150
0
        properties[i] == "_CMAKE_DEFAULT_FIXTURE_REPEAT_MODE"_s) {
151
0
      return true;
152
0
    }
153
0
  }
154
0
  return false;
155
0
}
156
157
} // namespace
158
159
bool cmDiscoverTestsCommand(std::vector<std::string> const& args,
160
                            cmExecutionStatus& status)
161
0
{
162
0
  static auto const parser =
163
0
    cmArgumentParser<Arguments>{ cmTestDiscoveryParser<Arguments>() }
164
0
      .Bind("COMMAND_EXPAND_LISTS"_s, &Arguments::CommandExpandLists)
165
0
      .Bind("CONFIGURATIONS"_s, &Arguments::Configurations);
166
167
0
  auto unparsed = std::vector<std::string>{};
168
0
  Arguments arguments = parser.Parse(args, &unparsed);
169
0
  if (arguments.MaybeReportError(status.GetMakefile())) {
170
0
    return true;
171
0
  }
172
173
0
  if (!unparsed.empty()) {
174
0
    status.SetError(
175
0
      cmStrCat(" given unknown argument \"", unparsed.front(), "\"."));
176
0
    return false;
177
0
  }
178
179
0
  if (arguments.DiscoveryProperties.size() % 2 != 0) {
180
0
    status.SetError(" DISCOVERY_PROPERTIES must be key-value pairs.");
181
0
    return false;
182
0
  }
183
184
0
  if (arguments.TestProperties.size() % 2 != 0) {
185
0
    status.SetError(" TEST_PROPERTIES must be key-value pairs.");
186
0
    return false;
187
0
  }
188
189
0
  cmMakefile& mf = status.GetMakefile();
190
191
  // The discovered tests are created while ctest runs, too late for the
192
  // policy to reach them.  Only NEW needs carrying through.  With nothing
193
  // recorded, ctest already uses the EACH_TEST_SEPARATELY behavior of
194
  // CMake 4.4 and below.
195
0
  if (mf.GetPolicyStatus(cmPolicies::CMP0224) == cmPolicies::NEW &&
196
0
      !SetsFixtureRepeatMode(arguments.TestProperties)) {
197
0
    arguments.TestProperties.emplace_back(
198
0
      "_CMAKE_DEFAULT_FIXTURE_REPEAT_MODE");
199
0
    arguments.TestProperties.emplace_back("AROUND_EACH_REPEAT");
200
0
  }
201
202
0
  mf.AddTestGenerator(cm::make_unique<DiscoveryGenerator>(std::move(arguments),
203
0
                                                          mf.GetBacktrace()));
204
0
  return true;
205
0
}