int cmCTest::Run()

in Source/cmCTest.cxx [1851:2654]


int cmCTest::Run(std::vector<std::string> const& args)
{
  char const* ctestExec = "ctest";
  bool cmakeAndTest = false;
  bool processSteps = false;
  bool SRArgumentSpecified = false;
  std::vector<std::pair<std::string, bool>> runScripts;

  // copy the command line
  cm::append(this->Impl->InitialCommandLineArguments, args);

  // check if a test preset was specified

  bool listPresets =
    find(args.begin(), args.end(), "--list-presets") != args.end();
  auto it =
    std::find_if(args.begin(), args.end(), [](std::string const& arg) -> bool {
      return arg == "--preset" || cmHasLiteralPrefix(arg, "--preset=");
    });
  if (listPresets || it != args.end()) {
    std::string errormsg;
    bool success;

    if (listPresets) {
      // If listing presets we don't need a presetName
      success = this->SetArgsFromPreset("", listPresets);
    } else {
      if (cmHasLiteralPrefix(*it, "--preset=")) {
        auto const& presetName = it->substr(9);
        success = this->SetArgsFromPreset(presetName, listPresets);
      } else if (++it != args.end()) {
        auto const& presetName = *it;
        success = this->SetArgsFromPreset(presetName, listPresets);
      } else {
        cmSystemTools::Error("'--preset' requires an argument");
        success = false;
      }
    }

    if (listPresets) {
      return success ? 0 : 1;
    }

    if (!success) {
      return 1;
    }
  }

  auto const dashD = [this, &processSteps](std::string const& targ) -> bool {
    // AddTestsForDashboard parses the dashboard type and converts it
    // into the separate stages
    if (this->AddTestsForDashboardType(targ)) {
      processSteps = true;
      return true;
    }
    if (this->AddVariableDefinition(targ)) {
      return true;
    }
    this->ErrorMessageUnknownDashDValue(targ);
    return false;
  };
  auto const dashT = [this, &processSteps,
                      ctestExec](std::string const& action) -> bool {
    if (!this->SetTest(action, false)) {
      cmCTestLog(this, ERROR_MESSAGE,
                 "CTest -T called with incorrect option: " << action << '\n');
      /* clang-format off */
      cmCTestLog(this, ERROR_MESSAGE,
                 "Available options are:\n"
                 "  " << ctestExec << " -T all\n"
                 "  " << ctestExec << " -T start\n"
                 "  " << ctestExec << " -T update\n"
                 "  " << ctestExec << " -T configure\n"
                 "  " << ctestExec << " -T build\n"
                 "  " << ctestExec << " -T test\n"
                 "  " << ctestExec << " -T coverage\n"
                 "  " << ctestExec << " -T memcheck\n"
                 "  " << ctestExec << " -T notes\n"
                 "  " << ctestExec << " -T submit\n");
      /* clang-format on */
      return false;
    }
    processSteps = true;
    return true;
  };
  auto const dashM = [this, &processSteps,
                      ctestExec](std::string const& model) -> bool {
    if (cmSystemTools::LowerCase(model) == "nightly"_s) {
      this->SetTestModel(cmCTest::NIGHTLY);
    } else if (cmSystemTools::LowerCase(model) == "continuous"_s) {
      this->SetTestModel(cmCTest::CONTINUOUS);
    } else if (cmSystemTools::LowerCase(model) == "experimental"_s) {
      this->SetTestModel(cmCTest::EXPERIMENTAL);
    } else {
      cmCTestLog(this, ERROR_MESSAGE,
                 "CTest -M called with incorrect option: " << model << '\n');
      /* clang-format off */
           cmCTestLog(this, ERROR_MESSAGE,
                      "Available options are:\n"
                      "  " << ctestExec << " -M Continuous\n"
                      "  " << ctestExec << " -M Experimental\n"
                      "  " << ctestExec << " -M Nightly\n");
      /* clang-format on */
      return false;
    }
    processSteps = true;
    return true;
  };
  auto const dashSP =
    [&runScripts, &SRArgumentSpecified](std::string const& script) -> bool {
    // -SR is an internal argument, -SP should be ignored when it is passed
    if (!SRArgumentSpecified) {
      runScripts.emplace_back(cmSystemTools::ToNormalizedPathOnDisk(script),
                              false);
    }
    return true;
  };
  auto const dashSR =
    [&runScripts, &SRArgumentSpecified](std::string const& script) -> bool {
    // -SR should be processed only once
    if (!SRArgumentSpecified) {
      SRArgumentSpecified = true;
      runScripts.emplace_back(cmSystemTools::ToNormalizedPathOnDisk(script),
                              true);
    }
    return true;
  };
  auto const dash_S =
    [&runScripts, &SRArgumentSpecified](std::string const& script) -> bool {
    // -SR is an internal argument, -S should be ignored when it is passed
    if (!SRArgumentSpecified) {
      runScripts.emplace_back(cmSystemTools::ToNormalizedPathOnDisk(script),
                              true);
    }
    return true;
  };
  auto const dashJ = [this](cm::string_view arg,
                            std::string const& j) -> bool {
    cm::optional<size_t> parallelLevel;
    // No value or an empty value tells ctest to choose a default.
    if (!j.empty()) {
      // A non-empty value must be a non-negative integer.
      unsigned long plevel = 0;
      if (!cmStrToULong(j, &plevel)) {
        cmSystemTools::Error(
          cmStrCat('\'', arg, "' given invalid value '", j, '\''));
        return false;
      }
      parallelLevel = plevel;
    }
    this->SetParallelLevel(parallelLevel);
    this->Impl->ParallelLevelSetInCli = true;
    return true;
  };
  auto const dashC = [this](std::string const& config) -> bool {
    this->SetConfigType(config);
    return true;
  };
  auto const dashGroup = [this](std::string const& group) -> bool {
    this->Impl->SpecificGroup = group;
    return true;
  };
  auto const dashQ = [this](std::string const&) -> bool {
    this->Impl->Quiet = true;
    return true;
  };
  auto const dashV = [this](std::string const&) -> bool {
    this->Impl->Verbose = true;
    return true;
  };
  auto const dashVV = [this](std::string const&) -> bool {
    this->Impl->ExtraVerbose = true;
    this->Impl->Verbose = true;
    return true;
  };
  auto const dashO = [this](std::string const& log) -> bool {
    this->SetOutputLogFileName(log);
    return true;
  };
  auto const dashW = [this](std::string const& width) -> bool {
    this->Impl->MaxTestNameWidth = atoi(width.c_str());
    return true;
  };
  auto const dashA = [this, &processSteps](std::string const& notes) -> bool {
    processSteps = true;
    this->SetTest("Notes");
    this->SetNotesFiles(notes);
    return true;
  };
  auto const dashI = [this](std::string const& tests) -> bool {
    this->Impl->TestOptions.TestsToRunInformation = tests;
    return true;
  };
  auto const dashU = [this](std::string const&) -> bool {
    this->Impl->TestOptions.UseUnion = true;
    return true;
  };
  auto const dashR = [this](std::string const& expr) -> bool {
    this->Impl->TestOptions.IncludeRegularExpression = expr;
    return true;
  };
  auto const dashE = [this](std::string const& expr) -> bool {
    this->Impl->TestOptions.ExcludeRegularExpression = expr;
    return true;
  };
  auto const dashL = [this](std::string const& expr) -> bool {
    this->Impl->TestOptions.LabelRegularExpression.push_back(expr);
    return true;
  };
  auto const dashLE = [this](std::string const& expr) -> bool {
    this->Impl->TestOptions.ExcludeLabelRegularExpression.push_back(expr);
    return true;
  };
  auto const dashFA = [this](std::string const& expr) -> bool {
    this->Impl->TestOptions.ExcludeFixtureRegularExpression = expr;
    return true;
  };
  auto const dashFS = [this](std::string const& expr) -> bool {
    this->Impl->TestOptions.ExcludeFixtureSetupRegularExpression = expr;
    return true;
  };
  auto const dashFC = [this](std::string const& expr) -> bool {
    this->Impl->TestOptions.ExcludeFixtureCleanupRegularExpression = expr;
    return true;
  };

  using CommandArgument =
    cmCommandLineArgument<bool(std::string const& value)>;

  auto const arguments = std::vector<CommandArgument>{
    CommandArgument{ "--dashboard", CommandArgument::Values::One, dashD },
    CommandArgument{ "-D",
                     "-D must be followed by dashboard mode or VAR=VALUE.",
                     CommandArgument::Values::One, dashD },
    CommandArgument{
      "-D", "-D must be followed by dashboard mode or VAR=VALUE.",
      CommandArgument::Values::One, CommandArgument::RequiresSeparator::No,
      [this](std::string const& def) -> bool {
        // Unsuccessful parsing of VAR=VALUE has historically
        // been ignored.
        this->AddVariableDefinition(def);
        return true;
      } },
    CommandArgument{ "-T", CommandArgument::Values::One, dashT },
    CommandArgument{ "--test-action", CommandArgument::Values::One, dashT },
    CommandArgument{ "-M", CommandArgument::Values::One, dashM },
    CommandArgument{ "--test-model", CommandArgument::Values::One, dashM },
    CommandArgument{ "--extra-submit", CommandArgument::Values::One,
                     [this, &processSteps](std::string const& extra) -> bool {
                       processSteps = true;
                       this->SetTest("Submit");
                       return this->SubmitExtraFiles(extra);
                     } },
    CommandArgument{
      "--build-and-test", "--build-and-test must have source and binary dir",
      CommandArgument::Values::Two,
      [this, &cmakeAndTest](std::string const& dirs) -> bool {
        cmakeAndTest = true;
        cmList dirList{ dirs };
        if (dirList.size() != 2) {
          return false;
        }
        this->Impl->BuildAndTest.SourceDir =
          cmSystemTools::ToNormalizedPathOnDisk(dirList[0]);
        this->Impl->BuildAndTest.BinaryDir =
          cmSystemTools::ToNormalizedPathOnDisk(dirList[1]);
        cmSystemTools::MakeDirectory(this->Impl->BuildAndTest.BinaryDir);
        return true;
      } },
    CommandArgument{ "--build-target", CommandArgument::Values::One,
                     [this](std::string const& t) -> bool {
                       this->Impl->BuildAndTest.BuildTargets.emplace_back(t);
                       return true;
                     } },
    CommandArgument{ "--build-noclean", CommandArgument::Values::Zero,
                     [this](std::string const&) -> bool {
                       this->Impl->BuildAndTest.BuildNoClean = true;
                       return true;
                     } },
    CommandArgument{ "--build-nocmake", CommandArgument::Values::Zero,
                     [this](std::string const&) -> bool {
                       this->Impl->BuildAndTest.BuildNoCMake = true;
                       return true;
                     } },
    CommandArgument{ "--build-two-config", CommandArgument::Values::Zero,
                     [this](std::string const&) -> bool {
                       this->Impl->BuildAndTest.BuildTwoConfig = true;
                       return true;
                     } },
    CommandArgument{ "--build-run-dir", CommandArgument::Values::One,
                     [this](std::string const& dir) -> bool {
                       this->Impl->BuildAndTest.BuildRunDir = dir;
                       return true;
                     } },
    CommandArgument{ "--build-exe-dir", CommandArgument::Values::One,
                     [this](std::string const& dir) -> bool {
                       this->Impl->BuildAndTest.ExecutableDirectory = dir;
                       return true;
                     } },
    CommandArgument{ "--test-timeout", CommandArgument::Values::One,
                     [this](std::string const& t) -> bool {
                       this->Impl->BuildAndTest.Timeout =
                         cmDuration(atof(t.c_str()));
                       return true;
                     } },
    CommandArgument{ "--build-generator", CommandArgument::Values::One,
                     [this](std::string const& g) -> bool {
                       this->Impl->BuildAndTest.BuildGenerator = g;
                       return true;
                     } },
    CommandArgument{ "--build-generator-platform",
                     CommandArgument::Values::One,
                     [this](std::string const& p) -> bool {
                       this->Impl->BuildAndTest.BuildGeneratorPlatform = p;
                       return true;
                     } },
    CommandArgument{ "--build-generator-toolset", CommandArgument::Values::One,
                     [this](std::string const& t) -> bool {
                       this->Impl->BuildAndTest.BuildGeneratorToolset = t;
                       return true;
                     } },
    CommandArgument{ "--build-project", CommandArgument::Values::One,
                     [this](std::string const& p) -> bool {
                       this->Impl->BuildAndTest.BuildProject = p;
                       return true;
                     } },
    CommandArgument{ "--build-makeprogram", CommandArgument::Values::One,
                     [this](std::string const& p) -> bool {
                       this->Impl->BuildAndTest.BuildMakeProgram = p;
                       return true;
                     } },
    CommandArgument{ "--build-config-sample", CommandArgument::Values::One,
                     [this](std::string const& s) -> bool {
                       this->Impl->BuildAndTest.ConfigSample = s;
                       return true;
                     } },
    CommandArgument{ "-SP", CommandArgument::Values::One, dashSP },
    CommandArgument{ "--script-new-process", CommandArgument::Values::One,
                     dashSP },
    CommandArgument{ "-SR", CommandArgument::Values::One, dashSR },
    CommandArgument{ "--script-run", CommandArgument::Values::One, dashSR },
    CommandArgument{ "-S", CommandArgument::Values::One, dash_S },
    CommandArgument{ "--script", CommandArgument::Values::One, dash_S },
    CommandArgument{ "-F", CommandArgument::Values::Zero,
                     [this](std::string const&) -> bool {
                       this->Impl->Failover = true;
                       return true;
                     } },
    CommandArgument{
      "-j", CommandArgument::Values::ZeroOrOne,
      [&dashJ](std::string const& j) -> bool { return dashJ("-j"_s, j); } },
    CommandArgument{ "--parallel", CommandArgument::Values::ZeroOrOne,
                     [&dashJ](std::string const& j) -> bool {
                       return dashJ("--parallel"_s, j);
                     } },
    CommandArgument{ "-j", CommandArgument::Values::One,
                     CommandArgument::RequiresSeparator::No,
                     [this](std::string const& j) -> bool {
                       // The value must be a non-negative integer.
                       unsigned long plevel = 0;
                       if (!cmStrToULong(j, &plevel)) {
                         cmSystemTools::Error(
                           cmStrCat("'-j' given invalid value '", j, '\''));
                         return false;
                       }
                       this->SetParallelLevel(plevel);
                       this->Impl->ParallelLevelSetInCli = true;
                       return true;
                     } },
    CommandArgument{
      "--repeat-until-fail", "'--repeat-until-fail' requires an argument",
      CommandArgument::Values::One,
      [this](std::string const& r) -> bool {
        if (this->Impl->RepeatMode != cmCTest::Repeat::Never) {
          cmSystemTools::Error("At most one '--repeat' option may be used.");
          return false;
        }
        long repeat = 1;
        if (!cmStrToLong(r, &repeat)) {
          cmSystemTools::Error(cmStrCat(
            "'--repeat-until-fail' given non-integer value '", r, '\''));
          return false;
        }
        this->Impl->RepeatCount = static_cast<int>(repeat);
        if (repeat > 1) {
          this->Impl->RepeatMode = cmCTest::Repeat::UntilFail;
        }
        return true;
      } },
    CommandArgument{
      "--repeat", CommandArgument::Values::One,
      [this](std::string const& r) -> bool {
        if (this->Impl->RepeatMode != cmCTest::Repeat::Never) {
          cmSystemTools::Error("At most one '--repeat' option may be used.");
          return false;
        }
        cmsys::RegularExpression repeatRegex(
          "^(until-fail|until-pass|after-timeout):([0-9]+)$");
        if (repeatRegex.find(r)) {
          std::string const& count = repeatRegex.match(2);
          unsigned long n = 1;
          cmStrToULong(count, &n); // regex guarantees success
          this->Impl->RepeatCount = static_cast<int>(n);
          if (this->Impl->RepeatCount > 1) {
            std::string const& mode = repeatRegex.match(1);
            if (mode == "until-fail") {
              this->Impl->RepeatMode = cmCTest::Repeat::UntilFail;
            } else if (mode == "until-pass") {
              this->Impl->RepeatMode = cmCTest::Repeat::UntilPass;
            } else if (mode == "after-timeout") {
              this->Impl->RepeatMode = cmCTest::Repeat::AfterTimeout;
            }
          }
        } else {
          cmSystemTools::Error(
            cmStrCat("'--repeat' given invalid value '", r, '\''));
          return false;
        }
        return true;
      } },
    CommandArgument{ "--test-load", CommandArgument::Values::One,
                     [this](std::string const& l) -> bool {
                       unsigned long load;
                       if (cmStrToULong(l, &load)) {
                         this->SetTestLoad(load);
                       } else {
                         cmCTestLog(
                           this, WARNING,
                           "Invalid value for 'Test Load' : " << l << '\n');
                       }
                       return true;
                     } },
    CommandArgument{ "--no-compress-output", CommandArgument::Values::Zero,
                     [this](std::string const&) -> bool {
                       this->Impl->CompressTestOutput = false;
                       return true;
                     } },
    CommandArgument{ "--print-labels", CommandArgument::Values::Zero,
                     [this](std::string const&) -> bool {
                       this->Impl->PrintLabels = true;
                       return true;
                     } },
    CommandArgument{ "--http1.0", CommandArgument::Values::Zero,
                     [this](std::string const&) -> bool {
                       this->Impl->UseHTTP10 = true;
                       return true;
                     } },
    CommandArgument{ "--timeout", CommandArgument::Values::One,
                     [this](std::string const& t) -> bool {
                       auto timeout = cmDuration(atof(t.c_str()));
                       this->Impl->GlobalTimeout = timeout;
                       return true;
                     } },
    CommandArgument{ "--stop-time", CommandArgument::Values::One,
                     [this](std::string const& t) -> bool {
                       this->SetStopTime(t);
                       return true;
                     } },
    CommandArgument{ "--stop-on-failure", CommandArgument::Values::Zero,
                     [this](std::string const&) -> bool {
                       this->Impl->StopOnFailure = true;
                       return true;
                     } },
    CommandArgument{ "-C", CommandArgument::Values::One, dashC },
    CommandArgument{ "--build-config", CommandArgument::Values::One, dashC },
    CommandArgument{ "--debug", CommandArgument::Values::Zero,
                     [this](std::string const&) -> bool {
                       this->Impl->Debug = true;
                       return true;
                     } },
    CommandArgument{ "--group", CommandArgument::Values::One, dashGroup },
    // This is an undocumented / deprecated option.
    // "Track" has been renamed to "Group".
    CommandArgument{ "--track", CommandArgument::Values::One, dashGroup },
    CommandArgument{ "--show-line-numbers", CommandArgument::Values::Zero,
                     [](std::string const&) -> bool {
                       // Silently ignore this never-documented and now-removed
                       // option.
                       return true;
                     } },
    CommandArgument{ "--no-label-summary", CommandArgument::Values::Zero,
                     [this](std::string const&) -> bool {
                       this->Impl->LabelSummary = false;
                       return true;
                     } },
    CommandArgument{ "--no-subproject-summary", CommandArgument::Values::Zero,
                     [this](std::string const&) -> bool {
                       this->Impl->SubprojectSummary = false;
                       return true;
                     } },
    CommandArgument{ "--progress", CommandArgument::Values::Zero,
                     [this](std::string const&) -> bool {
                       this->Impl->TestProgressOutput = true;
                       return true;
                     } },
    CommandArgument{ "-Q", CommandArgument::Values::Zero, dashQ },
    CommandArgument{ "--quiet", CommandArgument::Values::Zero, dashQ },
    CommandArgument{ "-V", CommandArgument::Values::Zero, dashV },
    CommandArgument{ "--verbose", CommandArgument::Values::Zero, dashV },
    CommandArgument{ "-VV", CommandArgument::Values::Zero, dashVV },
    CommandArgument{ "--extra-verbose", CommandArgument::Values::Zero,
                     dashVV },
    CommandArgument{ "--output-on-failure", CommandArgument::Values::Zero,
                     [this](std::string const&) -> bool {
                       this->Impl->OutputTestOutputOnTestFailure = true;
                       return true;
                     } },
    CommandArgument{ "--test-output-size-passed", CommandArgument::Values::One,
                     [this](std::string const& sz) -> bool {
                       long outputSize;
                       if (cmStrToLong(sz, &outputSize)) {
                         this->Impl->TestOptions.OutputSizePassed =
                           static_cast<int>(outputSize);
                       } else {
                         cmCTestLog(
                           this, WARNING,
                           "Invalid value for '--test-output-size-passed': "
                             << sz << "\n");
                       }
                       return true;
                     } },
    CommandArgument{ "--test-output-size-failed", CommandArgument::Values::One,
                     [this](std::string const& sz) -> bool {
                       long outputSize;
                       if (cmStrToLong(sz, &outputSize)) {
                         this->Impl->TestOptions.OutputSizeFailed =
                           static_cast<int>(outputSize);
                       } else {
                         cmCTestLog(
                           this, WARNING,
                           "Invalid value for '--test-output-size-failed': "
                             << sz << "\n");
                       }
                       return true;
                     } },
    CommandArgument{
      "--test-output-truncation", CommandArgument::Values::One,
      [this](std::string const& mode) -> bool {
        if (!SetTruncationMode(this->Impl->TestOptions.OutputTruncation,
                               mode)) {
          cmSystemTools::Error(
            cmStrCat("Invalid value for '--test-output-truncation': ", mode));
          return false;
        }
        return true;
      } },
    CommandArgument{ "--show-only", CommandArgument::Values::ZeroOrOne,
                     [this](std::string const& format) -> bool {
                       this->Impl->ShowOnly = true;
                       // Check if a specific format is requested.
                       // Defaults to human readable text.
                       if (format == "json-v1") {
                         // Force quiet mode so the only output
                         // is the json object model.
                         this->Impl->Quiet = true;
                         this->Impl->OutputAsJson = true;
                         this->Impl->OutputAsJsonVersion = 1;
                       } else if (format == "human") {
                       } else if (!format.empty()) {
                         cmSystemTools::Error(
                           cmStrCat("'--show-only=' given unknown value '",
                                    format, '\''));
                         return false;
                       }
                       return true;
                     } },
    CommandArgument{ "-N", CommandArgument::Values::Zero,
                     [this](std::string const&) -> bool {
                       this->Impl->ShowOnly = true;
                       return true;
                     } },
    CommandArgument{ "-O", CommandArgument::Values::One, dashO },
    CommandArgument{ "--output-log", CommandArgument::Values::One, dashO },
    CommandArgument{ "--tomorrow-tag", CommandArgument::Values::Zero,
                     [this](std::string const&) -> bool {
                       this->Impl->TomorrowTag = true;
                       return true;
                     } },
    CommandArgument{ "--force-new-ctest-process",
                     CommandArgument::Values::Zero,
                     [](std::string const&) -> bool {
                       // Silently ignore now-removed option.
                       return true;
                     } },
    CommandArgument{ "-W", CommandArgument::Values::One, dashW },
    CommandArgument{ "--max-width", CommandArgument::Values::One, dashW },
    CommandArgument{ "--interactive-debug-mode", CommandArgument::Values::One,
                     [this](std::string const& idm) -> bool {
                       this->Impl->InteractiveDebugMode = cmIsOn(idm);
                       return true;
                     } },
    CommandArgument{ "--http-header", CommandArgument::Values::One,
                     [this](std::string const& h) -> bool {
                       this->Impl->CommandLineHttpHeaders.push_back(h);
                       return true;
                     } },
    CommandArgument{ "--submit-index", CommandArgument::Values::One,
                     [this](std::string const& index) -> bool {
                       this->Impl->SubmitIndex = atoi(index.c_str());
                       if (this->Impl->SubmitIndex < 0) {
                         this->Impl->SubmitIndex = 0;
                       }
                       return true;
                     } },
    CommandArgument{ "--overwrite", CommandArgument::Values::One,
                     [this](std::string const& opt) -> bool {
                       this->AddCTestConfigurationOverwrite(opt);
                       return true;
                     } },
    CommandArgument{ "-A", CommandArgument::Values::One, dashA },
    CommandArgument{ "--add-notes", CommandArgument::Values::One, dashA },
    CommandArgument{ "--test-dir", "'--test-dir' requires an argument",
                     CommandArgument::Values::One,
                     [this](std::string const& dir) -> bool {
                       this->Impl->TestDir = dir;
                       return true;
                     } },
    CommandArgument{ "--output-junit", CommandArgument::Values::One,
                     [this](std::string const& file) -> bool {
                       this->SetOutputJUnitFileName(file);
                       return true;
                     } },
    CommandArgument{ "--no-tests", CommandArgument::Values::One,
                     [this](std::string const& action) -> bool {
                       if (action == "error"_s) {
                         this->Impl->NoTestsMode = cmCTest::NoTests::Error;
                       } else if (action == "ignore"_s) {
                         this->Impl->NoTestsMode = cmCTest::NoTests::Ignore;
                       } else {
                         cmSystemTools::Error(
                           cmStrCat("'--no-tests=' given unknown value '",
                                    action, '\''));
                         return false;
                       }
                       this->Impl->NoTestsModeSetInCli = true;
                       return true;
                     } },
    CommandArgument{ "-I", CommandArgument::Values::One, dashI },
    CommandArgument{ "--tests-information", CommandArgument::Values::One,
                     dashI },
    CommandArgument{ "-U", CommandArgument::Values::One, dashU },
    CommandArgument{ "--union", CommandArgument::Values::One, dashU },
    CommandArgument{ "-R", CommandArgument::Values::One, dashR },
    CommandArgument{ "--tests-regex", CommandArgument::Values::One, dashR },
    CommandArgument{ "-E", CommandArgument::Values::One, dashE },
    CommandArgument{ "--exclude-regex", CommandArgument::Values::One, dashE },
    CommandArgument{ "-L", CommandArgument::Values::One, dashL },
    CommandArgument{ "--label-regex", CommandArgument::Values::One, dashL },
    CommandArgument{ "-LE", CommandArgument::Values::One, dashLE },
    CommandArgument{ "--label-exclude", CommandArgument::Values::One, dashLE },
    CommandArgument{ "-FA", CommandArgument::Values::One, dashFA },
    CommandArgument{ "--fixture-exclude-any", CommandArgument::Values::One,
                     dashFA },
    CommandArgument{ "-FS", CommandArgument::Values::One, dashFS },
    CommandArgument{ "--fixture-exclude-setup", CommandArgument::Values::One,
                     dashFS },
    CommandArgument{ "-FC", CommandArgument::Values::One, dashFC },
    CommandArgument{ "--fixture-exclude-cleanup", CommandArgument::Values::One,
                     dashFC },
    CommandArgument{ "--resource-spec-file", CommandArgument::Values::One,
                     [this](std::string const& file) -> bool {
                       this->Impl->TestOptions.ResourceSpecFile = file;
                       return true;
                     } },
    CommandArgument{ "--tests-from-file", CommandArgument::Values::One,
                     [this](std::string const& file) -> bool {
                       this->Impl->TestOptions.TestListFile = file;
                       return true;
                     } },
    CommandArgument{ "--exclude-from-file", CommandArgument::Values::One,
                     [this](std::string const& file) -> bool {
                       this->Impl->TestOptions.ExcludeTestListFile = file;
                       return true;
                     } },
    CommandArgument{ "--schedule-random", CommandArgument::Values::Zero,
                     [this](std::string const&) -> bool {
                       this->Impl->TestOptions.ScheduleRandom = true;
                       return true;
                     } },
    CommandArgument{ "--rerun-failed", CommandArgument::Values::Zero,
                     [this](std::string const&) -> bool {
                       this->Impl->TestOptions.RerunFailed = true;
                       return true;
                     } },
  };

  // process the command line arguments
  for (size_t i = 1; i < args.size(); ++i) {
    std::string const& arg = args[i];
    bool matched = false;
    for (auto const& m : arguments) {
      if (m.matches(arg)) {
        matched = true;
        if (!m.parse(arg, i, args)) {
          return 1;
        }
        break;
      }
    }
    if (!matched && arg == "--build-options"_s) {
      matched = true;
      while (i + 1 < args.size() && args[i + 1] != "--build-target"_s &&
             args[i + 1] != "--test-command"_s) {
        ++i;
        this->Impl->BuildAndTest.BuildOptions.emplace_back(args[i]);
      }
    }
    if (!matched && arg == "--test-command"_s && i + 1 < args.size()) {
      matched = true;
      ++i;
      this->Impl->BuildAndTest.TestCommand = args[i];
      while (i + 1 < args.size()) {
        ++i;
        this->Impl->BuildAndTest.TestCommandArgs.emplace_back(args[i]);
      }
    }
    if (!matched && cmHasLiteralPrefix(arg, "-") &&
        !cmHasLiteralPrefix(arg, "--preset")) {
      cmSystemTools::Error(cmStrCat("Unknown argument: ", arg));
      cmSystemTools::Error("Run 'ctest --help' for all supported options.");
      return 1;
    }
  }

  // handle CTEST_PARALLEL_LEVEL environment variable
  if (!this->Impl->ParallelLevelSetInCli) {
    if (cm::optional<std::string> parallelEnv =
          cmSystemTools::GetEnvVar("CTEST_PARALLEL_LEVEL")) {
      if (parallelEnv->empty() ||
          parallelEnv->find_first_not_of(" \t") == std::string::npos) {
        // An empty value tells ctest to choose a default.
        this->SetParallelLevel(cm::nullopt);
      } else {
        // A non-empty value must be a non-negative integer.
        // Otherwise, ignore it.
        unsigned long plevel = 0;
        if (cmStrToULong(*parallelEnv, &plevel)) {
          this->SetParallelLevel(plevel);
        }
      }
    }
  }

  // handle CTEST_NO_TESTS_ACTION environment variable
  if (!this->Impl->NoTestsModeSetInCli) {
    std::string action;
    if (cmSystemTools::GetEnv("CTEST_NO_TESTS_ACTION", action) &&
        !action.empty()) {
      if (action == "error"_s) {
        this->Impl->NoTestsMode = cmCTest::NoTests::Error;
      } else if (action == "ignore"_s) {
        this->Impl->NoTestsMode = cmCTest::NoTests::Ignore;
      } else {
        cmCTestLog(this, ERROR_MESSAGE,
                   "Unknown value for CTEST_NO_TESTS_ACTION: '" << action
                                                                << '\'');
        return 1;
      }
    }
  }

  // TestProgressOutput only supported if console supports it and not logging
  // to a file
  this->Impl->TestProgressOutput = this->Impl->TestProgressOutput &&
    !this->Impl->OutputLogFile && this->ProgressOutputSupportedByConsole();
#ifdef _WIN32
  if (this->Impl->TestProgressOutput) {
    // Disable output line buffering so we can print content without
    // a newline.
    std::setvbuf(stdout, nullptr, _IONBF, 0);
  }
#endif

  // now what should cmake do? if --build-and-test was specified then
  // we run the build and test handler and return
  if (cmakeAndTest) {
    return this->RunCMakeAndTest();
  }

  // -S, -SP, and/or -SP was specified
  if (!runScripts.empty()) {
    return this->RunScripts(runScripts);
  }

  // Establish the working directory.
  std::string const currDir = cmSystemTools::GetLogicalWorkingDirectory();
  std::string workDir = currDir;
  if (!this->Impl->TestDir.empty()) {
    workDir = cmSystemTools::ToNormalizedPathOnDisk(this->Impl->TestDir);
  }
  cmWorkingDirectory changeDir(workDir);
  if (changeDir.Failed()) {
    cmCTestLog(this, ERROR_MESSAGE, changeDir.GetError() << std::endl);
    return 1;
  }
  this->Impl->BinaryDir = workDir;

  // -D, -T, and/or -M was specified
  if (processSteps) {
    return this->ProcessSteps();
  }

  return this->ExecuteTests(args);
}