cmTestGenerator.cxx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
  2. file Copyright.txt or https://cmake.org/licensing for details. */
  3. #include "cmTestGenerator.h"
  4. #include <algorithm>
  5. #include <cstddef> // IWYU pragma: keep
  6. #include <iterator>
  7. #include <memory>
  8. #include <ostream>
  9. #include <string>
  10. #include <utility>
  11. #include <vector>
  12. #include "cmGeneratorExpression.h"
  13. #include "cmGeneratorTarget.h"
  14. #include "cmList.h"
  15. #include "cmListFileCache.h"
  16. #include "cmLocalGenerator.h"
  17. #include "cmMakefile.h"
  18. #include "cmMessageType.h"
  19. #include "cmOutputConverter.h"
  20. #include "cmPolicies.h"
  21. #include "cmPropertyMap.h"
  22. #include "cmRange.h"
  23. #include "cmStateTypes.h"
  24. #include "cmStringAlgorithms.h"
  25. #include "cmSystemTools.h"
  26. #include "cmTest.h"
  27. #include "cmValue.h"
  28. namespace /* anonymous */
  29. {
  30. bool needToQuoteTestName(const cmMakefile& mf, const std::string& name)
  31. {
  32. // Determine if policy CMP0110 is set to NEW.
  33. switch (mf.GetPolicyStatus(cmPolicies::CMP0110)) {
  34. case cmPolicies::WARN:
  35. // Only warn if a forbidden character is used in the name.
  36. if (name.find_first_of("$[] #;\t\n\"\\") != std::string::npos) {
  37. mf.IssueMessage(
  38. MessageType::AUTHOR_WARNING,
  39. cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0110),
  40. "\nThe following name given to add_test() is invalid if "
  41. "CMP0110 is not set or set to OLD:\n `",
  42. name, "´\n"));
  43. }
  44. CM_FALLTHROUGH;
  45. case cmPolicies::OLD:
  46. // OLD behavior is to not quote the test's name.
  47. return false;
  48. case cmPolicies::NEW:
  49. default:
  50. // NEW behavior is to quote the test's name.
  51. return true;
  52. }
  53. }
  54. std::size_t countMaxConsecutiveEqualSigns(const std::string& name)
  55. {
  56. std::size_t max = 0;
  57. auto startIt = find(name.begin(), name.end(), '=');
  58. auto endIt = startIt;
  59. for (; startIt != name.end(); startIt = find(endIt, name.end(), '=')) {
  60. endIt =
  61. find_if_not(startIt + 1, name.end(), [](char c) { return c == '='; });
  62. max =
  63. std::max(max, static_cast<std::size_t>(std::distance(startIt, endIt)));
  64. }
  65. return max;
  66. }
  67. } // End: anonymous namespace
  68. cmTestGenerator::cmTestGenerator(
  69. cmTest* test, std::vector<std::string> const& configurations)
  70. : cmScriptGenerator("CTEST_CONFIGURATION_TYPE", configurations)
  71. , Test(test)
  72. {
  73. this->ActionsPerConfig = !test->GetOldStyle();
  74. this->TestGenerated = false;
  75. this->LG = nullptr;
  76. }
  77. cmTestGenerator::~cmTestGenerator() = default;
  78. void cmTestGenerator::Compute(cmLocalGenerator* lg)
  79. {
  80. this->LG = lg;
  81. }
  82. bool cmTestGenerator::TestsForConfig(const std::string& config)
  83. {
  84. return this->GeneratesForConfig(config);
  85. }
  86. cmTest* cmTestGenerator::GetTest() const
  87. {
  88. return this->Test;
  89. }
  90. void cmTestGenerator::GenerateScriptConfigs(std::ostream& os, Indent indent)
  91. {
  92. // Create the tests.
  93. this->cmScriptGenerator::GenerateScriptConfigs(os, indent);
  94. }
  95. void cmTestGenerator::GenerateScriptActions(std::ostream& os, Indent indent)
  96. {
  97. if (this->ActionsPerConfig) {
  98. // This is the per-config generation in a single-configuration
  99. // build generator case. The superclass will call our per-config
  100. // method.
  101. this->cmScriptGenerator::GenerateScriptActions(os, indent);
  102. } else {
  103. // This is an old-style test, so there is only one config.
  104. // assert(this->Test->GetOldStyle());
  105. this->GenerateOldStyle(os, indent);
  106. }
  107. }
  108. void cmTestGenerator::GenerateScriptForConfig(std::ostream& os,
  109. const std::string& config,
  110. Indent indent)
  111. {
  112. this->TestGenerated = true;
  113. // Set up generator expression evaluation context.
  114. cmGeneratorExpression ge(*this->Test->GetMakefile()->GetCMakeInstance(),
  115. this->Test->GetBacktrace());
  116. // Determine if policy CMP0110 is set to NEW.
  117. const bool quote_test_name =
  118. needToQuoteTestName(*this->Test->GetMakefile(), this->Test->GetName());
  119. // Determine the number of equal-signs needed for quoting test name with
  120. // [==[...]==] syntax.
  121. const std::string equalSigns(
  122. 1 + countMaxConsecutiveEqualSigns(this->Test->GetName()), '=');
  123. // Start the test command.
  124. if (quote_test_name) {
  125. os << indent << "add_test([" << equalSigns << "[" << this->Test->GetName()
  126. << "]" << equalSigns << "] ";
  127. } else {
  128. os << indent << "add_test(" << this->Test->GetName() << " ";
  129. }
  130. // Evaluate command line arguments
  131. cmList argv{
  132. this->EvaluateCommandLineArguments(this->Test->GetCommand(), ge, config),
  133. // Expand arguments if COMMAND_EXPAND_LISTS is set
  134. this->Test->GetCommandExpandLists() ? cmList::ExpandElements::Yes
  135. : cmList::ExpandElements::No,
  136. cmList::EmptyElements::Yes
  137. };
  138. // Expanding lists on an empty command may have left it empty
  139. if (argv.empty()) {
  140. argv.emplace_back();
  141. }
  142. // Check whether the command executable is a target whose name is to
  143. // be translated.
  144. std::string exe = argv[0];
  145. cmGeneratorTarget* target = this->LG->FindGeneratorTargetToUse(exe);
  146. if (target && target->GetType() == cmStateEnums::EXECUTABLE) {
  147. // Use the target file on disk.
  148. exe = target->GetFullPath(config);
  149. auto addLauncher = [this, &config, &ge, &os,
  150. target](std::string const& propertyName) {
  151. cmValue launcher = target->GetProperty(propertyName);
  152. if (!cmNonempty(launcher)) {
  153. return;
  154. }
  155. const auto propVal = ge.Parse(*launcher)->Evaluate(this->LG, config);
  156. cmList launcherWithArgs(propVal, cmList::ExpandElements::Yes,
  157. this->Test->GetCMP0178() == cmPolicies::NEW
  158. ? cmList::EmptyElements::Yes
  159. : cmList::EmptyElements::No);
  160. if (!launcherWithArgs.empty() && !launcherWithArgs[0].empty()) {
  161. if (this->Test->GetCMP0178() == cmPolicies::WARN) {
  162. cmList argsWithEmptyValuesPreserved(
  163. propVal, cmList::ExpandElements::Yes, cmList::EmptyElements::Yes);
  164. if (launcherWithArgs != argsWithEmptyValuesPreserved) {
  165. this->Test->GetMakefile()->IssueMessage(
  166. MessageType::AUTHOR_WARNING,
  167. cmStrCat("The ", propertyName, " property of target '",
  168. target->GetName(),
  169. "' contains empty list items. Those empty items are "
  170. "being silently discarded to preserve backward "
  171. "compatibility.\n",
  172. cmPolicies::GetPolicyWarning(cmPolicies::CMP0178)));
  173. }
  174. }
  175. std::string launcherExe(launcherWithArgs[0]);
  176. cmSystemTools::ConvertToUnixSlashes(launcherExe);
  177. os << cmOutputConverter::EscapeForCMake(launcherExe) << " ";
  178. for (std::string const& arg :
  179. cmMakeRange(launcherWithArgs).advance(1)) {
  180. if (arg.empty()) {
  181. os << "\"\" ";
  182. } else {
  183. os << cmOutputConverter::EscapeForCMake(arg) << " ";
  184. }
  185. }
  186. }
  187. };
  188. // Prepend with the test launcher if specified.
  189. addLauncher("TEST_LAUNCHER");
  190. // Prepend with the emulator when cross compiling if required.
  191. if (!this->GetTest()->GetCMP0158IsNew() ||
  192. this->LG->GetMakefile()->IsOn("CMAKE_CROSSCOMPILING")) {
  193. addLauncher("CROSSCOMPILING_EMULATOR");
  194. }
  195. } else {
  196. // Use the command name given.
  197. cmSystemTools::ConvertToUnixSlashes(exe);
  198. }
  199. // Generate the command line with full escapes.
  200. os << cmOutputConverter::EscapeForCMake(exe);
  201. for (auto const& arg : cmMakeRange(argv).advance(1)) {
  202. os << " " << cmOutputConverter::EscapeForCMake(arg);
  203. }
  204. // Finish the test command.
  205. os << ")\n";
  206. // Output properties for the test.
  207. if (quote_test_name) {
  208. os << indent << "set_tests_properties([" << equalSigns << "["
  209. << this->Test->GetName() << "]" << equalSigns << "] PROPERTIES ";
  210. } else {
  211. os << indent << "set_tests_properties(" << this->Test->GetName()
  212. << " PROPERTIES ";
  213. }
  214. for (auto const& i : this->Test->GetProperties().GetList()) {
  215. os << " " << i.first << " "
  216. << cmOutputConverter::EscapeForCMake(
  217. ge.Parse(i.second)->Evaluate(this->LG, config));
  218. }
  219. this->GenerateInternalProperties(os);
  220. os << ")\n";
  221. }
  222. void cmTestGenerator::GenerateScriptNoConfig(std::ostream& os, Indent indent)
  223. {
  224. // Determine if policy CMP0110 is set to NEW.
  225. const bool quote_test_name =
  226. needToQuoteTestName(*this->Test->GetMakefile(), this->Test->GetName());
  227. // Determine the number of equal-signs needed for quoting test name with
  228. // [==[...]==] syntax.
  229. const std::string equalSigns(
  230. 1 + countMaxConsecutiveEqualSigns(this->Test->GetName()), '=');
  231. if (quote_test_name) {
  232. os << indent << "add_test([" << equalSigns << "[" << this->Test->GetName()
  233. << "]" << equalSigns << "] NOT_AVAILABLE)\n";
  234. } else {
  235. os << indent << "add_test(" << this->Test->GetName()
  236. << " NOT_AVAILABLE)\n";
  237. }
  238. }
  239. bool cmTestGenerator::NeedsScriptNoConfig() const
  240. {
  241. return (this->TestGenerated && // test generated for at least one config
  242. this->ActionsPerConfig && // test is config-aware
  243. this->Configurations.empty() && // test runs in all configs
  244. !this->ConfigurationTypes->empty()); // config-dependent command
  245. }
  246. void cmTestGenerator::GenerateOldStyle(std::ostream& fout, Indent indent)
  247. {
  248. this->TestGenerated = true;
  249. // Determine if policy CMP0110 is set to NEW.
  250. const bool quote_test_name =
  251. needToQuoteTestName(*this->Test->GetMakefile(), this->Test->GetName());
  252. // Determine the number of equal-signs needed for quoting test name with
  253. // [==[...]==] syntax.
  254. const std::string equalSigns(
  255. 1 + countMaxConsecutiveEqualSigns(this->Test->GetName()), '=');
  256. // Get the test command line to be executed.
  257. std::vector<std::string> const& command = this->Test->GetCommand();
  258. std::string exe = command[0];
  259. cmSystemTools::ConvertToUnixSlashes(exe);
  260. if (quote_test_name) {
  261. fout << indent << "add_test([" << equalSigns << "["
  262. << this->Test->GetName() << "]" << equalSigns << "] \"" << exe
  263. << "\"";
  264. } else {
  265. fout << indent << "add_test(" << this->Test->GetName() << " \"" << exe
  266. << "\"";
  267. }
  268. for (std::string const& arg : cmMakeRange(command).advance(1)) {
  269. // Just double-quote all arguments so they are re-parsed
  270. // correctly by the test system.
  271. fout << " \"";
  272. for (char c : arg) {
  273. // Escape quotes within arguments. We should escape
  274. // backslashes too but we cannot because it makes the result
  275. // inconsistent with previous behavior of this command.
  276. if (c == '"') {
  277. fout << '\\';
  278. }
  279. fout << c;
  280. }
  281. fout << '"';
  282. }
  283. fout << ")\n";
  284. // Output properties for the test.
  285. if (quote_test_name) {
  286. fout << indent << "set_tests_properties([" << equalSigns << "["
  287. << this->Test->GetName() << "]" << equalSigns << "] PROPERTIES ";
  288. } else {
  289. fout << indent << "set_tests_properties(" << this->Test->GetName()
  290. << " PROPERTIES ";
  291. }
  292. for (auto const& i : this->Test->GetProperties().GetList()) {
  293. fout << " " << i.first << " "
  294. << cmOutputConverter::EscapeForCMake(i.second);
  295. }
  296. this->GenerateInternalProperties(fout);
  297. fout << ")\n";
  298. }
  299. void cmTestGenerator::GenerateInternalProperties(std::ostream& os)
  300. {
  301. cmListFileBacktrace bt = this->Test->GetBacktrace();
  302. if (bt.Empty()) {
  303. return;
  304. }
  305. os << " "
  306. << "_BACKTRACE_TRIPLES"
  307. << " \"";
  308. bool prependTripleSeparator = false;
  309. while (!bt.Empty()) {
  310. const auto& entry = bt.Top();
  311. if (prependTripleSeparator) {
  312. os << ";";
  313. }
  314. os << entry.FilePath << ";" << entry.Line << ";" << entry.Name;
  315. bt = bt.Pop();
  316. prependTripleSeparator = true;
  317. }
  318. os << '"';
  319. }
  320. std::vector<std::string> cmTestGenerator::EvaluateCommandLineArguments(
  321. const std::vector<std::string>& argv, cmGeneratorExpression& ge,
  322. const std::string& config) const
  323. {
  324. // Evaluate executable name and arguments
  325. auto evaluatedRange =
  326. cmMakeRange(argv).transform([&](const std::string& arg) {
  327. return ge.Parse(arg)->Evaluate(this->LG, config);
  328. });
  329. return { evaluatedRange.begin(), evaluatedRange.end() };
  330. }