| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373 |
- /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
- file Copyright.txt or https://cmake.org/licensing for details. */
- #include "cmTestGenerator.h"
- #include <algorithm>
- #include <cstddef> // IWYU pragma: keep
- #include <iterator>
- #include <memory>
- #include <ostream>
- #include <string>
- #include <utility>
- #include <vector>
- #include "cmGeneratorExpression.h"
- #include "cmGeneratorTarget.h"
- #include "cmList.h"
- #include "cmListFileCache.h"
- #include "cmLocalGenerator.h"
- #include "cmMakefile.h"
- #include "cmMessageType.h"
- #include "cmOutputConverter.h"
- #include "cmPolicies.h"
- #include "cmPropertyMap.h"
- #include "cmRange.h"
- #include "cmStateTypes.h"
- #include "cmStringAlgorithms.h"
- #include "cmSystemTools.h"
- #include "cmTest.h"
- #include "cmValue.h"
- namespace /* anonymous */
- {
- bool needToQuoteTestName(const cmMakefile& mf, const std::string& name)
- {
- // Determine if policy CMP0110 is set to NEW.
- switch (mf.GetPolicyStatus(cmPolicies::CMP0110)) {
- case cmPolicies::WARN:
- // Only warn if a forbidden character is used in the name.
- if (name.find_first_of("$[] #;\t\n\"\\") != std::string::npos) {
- mf.IssueMessage(
- MessageType::AUTHOR_WARNING,
- cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0110),
- "\nThe following name given to add_test() is invalid if "
- "CMP0110 is not set or set to OLD:\n `",
- name, "´\n"));
- }
- CM_FALLTHROUGH;
- case cmPolicies::OLD:
- // OLD behavior is to not quote the test's name.
- return false;
- case cmPolicies::REQUIRED_IF_USED:
- case cmPolicies::REQUIRED_ALWAYS:
- case cmPolicies::NEW:
- default:
- // NEW behavior is to quote the test's name.
- return true;
- }
- }
- std::size_t countMaxConsecutiveEqualSigns(const std::string& name)
- {
- std::size_t max = 0;
- auto startIt = find(name.begin(), name.end(), '=');
- auto endIt = startIt;
- for (; startIt != name.end(); startIt = find(endIt, name.end(), '=')) {
- endIt =
- find_if_not(startIt + 1, name.end(), [](char c) { return c == '='; });
- max =
- std::max(max, static_cast<std::size_t>(std::distance(startIt, endIt)));
- }
- return max;
- }
- } // End: anonymous namespace
- cmTestGenerator::cmTestGenerator(
- cmTest* test, std::vector<std::string> const& configurations)
- : cmScriptGenerator("CTEST_CONFIGURATION_TYPE", configurations)
- , Test(test)
- {
- this->ActionsPerConfig = !test->GetOldStyle();
- this->TestGenerated = false;
- this->LG = nullptr;
- }
- cmTestGenerator::~cmTestGenerator() = default;
- void cmTestGenerator::Compute(cmLocalGenerator* lg)
- {
- this->LG = lg;
- }
- bool cmTestGenerator::TestsForConfig(const std::string& config)
- {
- return this->GeneratesForConfig(config);
- }
- cmTest* cmTestGenerator::GetTest() const
- {
- return this->Test;
- }
- void cmTestGenerator::GenerateScriptConfigs(std::ostream& os, Indent indent)
- {
- // Create the tests.
- this->cmScriptGenerator::GenerateScriptConfigs(os, indent);
- }
- void cmTestGenerator::GenerateScriptActions(std::ostream& os, Indent indent)
- {
- if (this->ActionsPerConfig) {
- // This is the per-config generation in a single-configuration
- // build generator case. The superclass will call our per-config
- // method.
- this->cmScriptGenerator::GenerateScriptActions(os, indent);
- } else {
- // This is an old-style test, so there is only one config.
- // assert(this->Test->GetOldStyle());
- this->GenerateOldStyle(os, indent);
- }
- }
- void cmTestGenerator::GenerateScriptForConfig(std::ostream& os,
- const std::string& config,
- Indent indent)
- {
- this->TestGenerated = true;
- // Set up generator expression evaluation context.
- cmGeneratorExpression ge(*this->Test->GetMakefile()->GetCMakeInstance(),
- this->Test->GetBacktrace());
- // Determine if policy CMP0110 is set to NEW.
- const bool quote_test_name =
- needToQuoteTestName(*this->Test->GetMakefile(), this->Test->GetName());
- // Determine the number of equal-signs needed for quoting test name with
- // [==[...]==] syntax.
- const std::string equalSigns(
- 1 + countMaxConsecutiveEqualSigns(this->Test->GetName()), '=');
- // Start the test command.
- if (quote_test_name) {
- os << indent << "add_test([" << equalSigns << "[" << this->Test->GetName()
- << "]" << equalSigns << "] ";
- } else {
- os << indent << "add_test(" << this->Test->GetName() << " ";
- }
- // Evaluate command line arguments
- cmList argv{
- this->EvaluateCommandLineArguments(this->Test->GetCommand(), ge, config),
- // Expand arguments if COMMAND_EXPAND_LISTS is set
- this->Test->GetCommandExpandLists() ? cmList::ExpandElements::Yes
- : cmList::ExpandElements::No,
- cmList::EmptyElements::Yes
- };
- // Expanding lists on an empty command may have left it empty
- if (argv.empty()) {
- argv.emplace_back();
- }
- // Check whether the command executable is a target whose name is to
- // be translated.
- std::string exe = argv[0];
- cmGeneratorTarget* target = this->LG->FindGeneratorTargetToUse(exe);
- if (target && target->GetType() == cmStateEnums::EXECUTABLE) {
- // Use the target file on disk.
- exe = target->GetFullPath(config);
- auto addLauncher = [this, &config, &ge, &os,
- target](std::string const& propertyName) {
- cmValue launcher = target->GetProperty(propertyName);
- if (!cmNonempty(launcher)) {
- return;
- }
- 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)) {
- if (arg.empty()) {
- os << "\"\" ";
- } else {
- os << cmOutputConverter::EscapeForCMake(arg) << " ";
- }
- }
- }
- };
- // Prepend with the test launcher if specified.
- addLauncher("TEST_LAUNCHER");
- // Prepend with the emulator when cross compiling if required.
- if (!this->GetTest()->GetCMP0158IsNew() ||
- this->LG->GetMakefile()->IsOn("CMAKE_CROSSCOMPILING")) {
- addLauncher("CROSSCOMPILING_EMULATOR");
- }
- } else {
- // Use the command name given.
- cmSystemTools::ConvertToUnixSlashes(exe);
- }
- // Generate the command line with full escapes.
- os << cmOutputConverter::EscapeForCMake(exe);
- for (auto const& arg : cmMakeRange(argv).advance(1)) {
- os << " " << cmOutputConverter::EscapeForCMake(arg);
- }
- // Finish the test command.
- os << ")\n";
- // Output properties for the test.
- if (quote_test_name) {
- os << indent << "set_tests_properties([" << equalSigns << "["
- << this->Test->GetName() << "]" << equalSigns << "] PROPERTIES ";
- } else {
- os << indent << "set_tests_properties(" << this->Test->GetName()
- << " PROPERTIES ";
- }
- for (auto const& i : this->Test->GetProperties().GetList()) {
- os << " " << i.first << " "
- << cmOutputConverter::EscapeForCMake(
- ge.Parse(i.second)->Evaluate(this->LG, config));
- }
- this->GenerateInternalProperties(os);
- os << ")\n";
- }
- void cmTestGenerator::GenerateScriptNoConfig(std::ostream& os, Indent indent)
- {
- // Determine if policy CMP0110 is set to NEW.
- const bool quote_test_name =
- needToQuoteTestName(*this->Test->GetMakefile(), this->Test->GetName());
- // Determine the number of equal-signs needed for quoting test name with
- // [==[...]==] syntax.
- const std::string equalSigns(
- 1 + countMaxConsecutiveEqualSigns(this->Test->GetName()), '=');
- if (quote_test_name) {
- os << indent << "add_test([" << equalSigns << "[" << this->Test->GetName()
- << "]" << equalSigns << "] NOT_AVAILABLE)\n";
- } else {
- os << indent << "add_test(" << this->Test->GetName()
- << " NOT_AVAILABLE)\n";
- }
- }
- bool cmTestGenerator::NeedsScriptNoConfig() const
- {
- return (this->TestGenerated && // test generated for at least one config
- this->ActionsPerConfig && // test is config-aware
- this->Configurations.empty() && // test runs in all configs
- !this->ConfigurationTypes->empty()); // config-dependent command
- }
- void cmTestGenerator::GenerateOldStyle(std::ostream& fout, Indent indent)
- {
- this->TestGenerated = true;
- // Determine if policy CMP0110 is set to NEW.
- const bool quote_test_name =
- needToQuoteTestName(*this->Test->GetMakefile(), this->Test->GetName());
- // Determine the number of equal-signs needed for quoting test name with
- // [==[...]==] syntax.
- const std::string equalSigns(
- 1 + countMaxConsecutiveEqualSigns(this->Test->GetName()), '=');
- // Get the test command line to be executed.
- std::vector<std::string> const& command = this->Test->GetCommand();
- std::string exe = command[0];
- cmSystemTools::ConvertToUnixSlashes(exe);
- if (quote_test_name) {
- fout << indent << "add_test([" << equalSigns << "["
- << this->Test->GetName() << "]" << equalSigns << "] \"" << exe
- << "\"";
- } else {
- fout << indent << "add_test(" << this->Test->GetName() << " \"" << exe
- << "\"";
- }
- for (std::string const& arg : cmMakeRange(command).advance(1)) {
- // Just double-quote all arguments so they are re-parsed
- // correctly by the test system.
- fout << " \"";
- for (char c : arg) {
- // Escape quotes within arguments. We should escape
- // backslashes too but we cannot because it makes the result
- // inconsistent with previous behavior of this command.
- if (c == '"') {
- fout << '\\';
- }
- fout << c;
- }
- fout << '"';
- }
- fout << ")\n";
- // Output properties for the test.
- if (quote_test_name) {
- fout << indent << "set_tests_properties([" << equalSigns << "["
- << this->Test->GetName() << "]" << equalSigns << "] PROPERTIES ";
- } else {
- fout << indent << "set_tests_properties(" << this->Test->GetName()
- << " PROPERTIES ";
- }
- for (auto const& i : this->Test->GetProperties().GetList()) {
- fout << " " << i.first << " "
- << cmOutputConverter::EscapeForCMake(i.second);
- }
- this->GenerateInternalProperties(fout);
- fout << ")\n";
- }
- void cmTestGenerator::GenerateInternalProperties(std::ostream& os)
- {
- cmListFileBacktrace bt = this->Test->GetBacktrace();
- if (bt.Empty()) {
- return;
- }
- os << " "
- << "_BACKTRACE_TRIPLES"
- << " \"";
- bool prependTripleSeparator = false;
- while (!bt.Empty()) {
- const auto& entry = bt.Top();
- if (prependTripleSeparator) {
- os << ";";
- }
- os << entry.FilePath << ";" << entry.Line << ";" << entry.Name;
- bt = bt.Pop();
- prependTripleSeparator = true;
- }
- os << '"';
- }
- std::vector<std::string> cmTestGenerator::EvaluateCommandLineArguments(
- const std::vector<std::string>& argv, cmGeneratorExpression& ge,
- const std::string& config) const
- {
- // Evaluate executable name and arguments
- auto evaluatedRange =
- cmMakeRange(argv).transform([&](const std::string& arg) {
- return ge.Parse(arg)->Evaluate(this->LG, config);
- });
- return { evaluatedRange.begin(), evaluatedRange.end() };
- }
|