summaryrefslogtreecommitdiffstats
path: root/Source/cmTestGenerator.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'Source/cmTestGenerator.cxx')
-rw-r--r--Source/cmTestGenerator.cxx230
1 files changed, 230 insertions, 0 deletions
diff --git a/Source/cmTestGenerator.cxx b/Source/cmTestGenerator.cxx
new file mode 100644
index 0000000..1142dbd
--- /dev/null
+++ b/Source/cmTestGenerator.cxx
@@ -0,0 +1,230 @@
+/* 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 <memory>
+#include <ostream>
+#include <utility>
+#include <vector>
+
+#include "cmGeneratorExpression.h"
+#include "cmGeneratorTarget.h"
+#include "cmListFileCache.h"
+#include "cmLocalGenerator.h"
+#include "cmOutputConverter.h"
+#include "cmPropertyMap.h"
+#include "cmRange.h"
+#include "cmStateTypes.h"
+#include "cmStringAlgorithms.h"
+#include "cmSystemTools.h"
+#include "cmTest.h"
+
+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->GetBacktrace());
+
+ // Start the test command.
+ os << indent << "add_test(" << this->Test->GetName() << " ";
+
+ // Evaluate command line arguments
+ std::vector<std::string> argv =
+ EvaluateCommandLineArguments(this->Test->GetCommand(), ge, config);
+
+ // Expand arguments if COMMAND_EXPAND_LISTS is set
+ if (this->Test->GetCommandExpandLists()) {
+ argv = cmExpandedLists(argv.begin(), argv.end());
+ // 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);
+
+ // Prepend with the emulator when cross compiling if required.
+ const char* emulator = target->GetProperty("CROSSCOMPILING_EMULATOR");
+ if (emulator != nullptr && *emulator) {
+ std::vector<std::string> emulatorWithArgs = cmExpandedList(emulator);
+ std::string emulatorExe(emulatorWithArgs[0]);
+ cmSystemTools::ConvertToUnixSlashes(emulatorExe);
+ os << cmOutputConverter::EscapeForCMake(emulatorExe) << " ";
+ for (std::string const& arg : cmMakeRange(emulatorWithArgs).advance(1)) {
+ os << cmOutputConverter::EscapeForCMake(arg) << " ";
+ }
+ }
+ } 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.
+ 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)
+{
+ 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;
+
+ // 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);
+ fout << indent;
+ fout << "add_test(";
+ fout << 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.
+ 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() };
+}