cmTestGenerator.cxx 10 KB

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