diff options
Diffstat (limited to 'Source')
-rw-r--r-- | Source/CTest/cmCTestMultiProcessHandler.cxx | 83 | ||||
-rw-r--r-- | Source/CTest/cmCTestMultiProcessHandler.h | 45 | ||||
-rw-r--r-- | Source/CTest/cmCTestTestCommand.cxx | 4 | ||||
-rw-r--r-- | Source/CTest/cmCTestTestCommand.h | 4 | ||||
-rw-r--r-- | Source/CTest/cmCTestTestHandler.cxx | 23 | ||||
-rw-r--r-- | Source/cmCTest.cxx | 55 | ||||
-rw-r--r-- | Source/cmCTest.h | 5 | ||||
-rw-r--r-- | Source/ctest.cxx | 6 |
8 files changed, 156 insertions, 69 deletions
diff --git a/Source/CTest/cmCTestMultiProcessHandler.cxx b/Source/CTest/cmCTestMultiProcessHandler.cxx index 4c39d6e..a0bb1ea 100644 --- a/Source/CTest/cmCTestMultiProcessHandler.cxx +++ b/Source/CTest/cmCTestMultiProcessHandler.cxx @@ -43,6 +43,17 @@ #include "cmUVJobServerClient.h" #include "cmWorkingDirectory.h" +namespace { +// For unspecified parallelism, limit to the number of processors, +// but with a minimum greater than 1 so there is some parallelism. +constexpr unsigned long kParallelLevelMinimum = 2u; + +// For "unbounded" parallelism, limit to a very high value. +// Under a job server, parallelism is effectively limited +// only by available job server tokens. +constexpr unsigned long kParallelLevelUnbounded = 0x10000u; +} + namespace cmsys { class RegularExpression; } @@ -66,18 +77,14 @@ private: cmCTestMultiProcessHandler* Handler; }; -cmCTestMultiProcessHandler::cmCTestMultiProcessHandler() +cmCTestMultiProcessHandler::cmCTestMultiProcessHandler( + cmCTest* ctest, cmCTestTestHandler* handler) + : CTest(ctest) + , TestHandler(handler) + , ProcessorsAvailable(cmAffinity::GetProcessorsAvailable()) + , HaveAffinity(this->ProcessorsAvailable.size()) + , ParallelLevelDefault(kParallelLevelMinimum) { - this->ParallelLevel = 1; - this->TestLoad = 0; - this->FakeLoadForTesting = 0; - this->Completed = 0; - this->RunningCount = 0; - this->ProcessorsAvailable = cmAffinity::GetProcessorsAvailable(); - this->HaveAffinity = this->ProcessorsAvailable.size(); - this->HasCycles = false; - this->HasInvalidGeneratedResourceSpec = false; - this->SerialTestRunning = false; } cmCTestMultiProcessHandler::~cmCTestMultiProcessHandler() = default; @@ -103,9 +110,43 @@ bool cmCTestMultiProcessHandler::SetTests(TestMap tests, } // Set the max number of tests that can be run at the same time. -void cmCTestMultiProcessHandler::SetParallelLevel(size_t level) +void cmCTestMultiProcessHandler::SetParallelLevel(cm::optional<size_t> level) +{ + this->ParallelLevel = level; + + if (!this->ParallelLevel) { + // '-j' was given with no value. Limit by number of processors. + cmsys::SystemInformation info; + info.RunCPUCheck(); + unsigned long processorCount = info.GetNumberOfLogicalCPU(); + + if (cm::optional<std::string> fakeProcessorCount = + cmSystemTools::GetEnvVar( + "__CTEST_FAKE_PROCESSOR_COUNT_FOR_TESTING")) { + unsigned long pc = 0; + if (cmStrToULong(*fakeProcessorCount, &pc)) { + processorCount = pc; + } else { + cmSystemTools::Error("Failed to parse fake processor count: " + + *fakeProcessorCount); + } + } + + this->ParallelLevelDefault = + std::max(kParallelLevelMinimum, processorCount); + } +} + +size_t cmCTestMultiProcessHandler::GetParallelLevel() const { - this->ParallelLevel = level < 1 ? 1 : level; + if ((this->ParallelLevel && *this->ParallelLevel == 0) || + (!this->ParallelLevel && this->JobServerClient)) { + return kParallelLevelUnbounded; + } + if (this->ParallelLevel) { + return *this->ParallelLevel; + } + return this->ParallelLevelDefault; } void cmCTestMultiProcessHandler::SetTestLoad(unsigned long load) @@ -452,10 +493,11 @@ void cmCTestMultiProcessHandler::UnlockResources(int index) inline size_t cmCTestMultiProcessHandler::GetProcessorsUsed(int test) { size_t processors = static_cast<int>(this->Properties[test]->Processors); + size_t const parallelLevel = this->GetParallelLevel(); // If processors setting is set higher than the -j // setting, we default to using all of the process slots. - if (processors > this->ParallelLevel) { - processors = this->ParallelLevel; + if (processors > parallelLevel) { + processors = parallelLevel; } // Cap tests that want affinity to the maximum affinity available. if (this->HaveAffinity && processors > this->HaveAffinity && @@ -509,8 +551,9 @@ void cmCTestMultiProcessHandler::StartNextTests() size_t numToStart = 0; - if (this->RunningCount < this->ParallelLevel) { - numToStart = this->ParallelLevel - this->RunningCount; + size_t const parallelLevel = this->GetParallelLevel(); + if (this->RunningCount < parallelLevel) { + numToStart = parallelLevel - this->RunningCount; } if (numToStart == 0) { @@ -524,7 +567,7 @@ void cmCTestMultiProcessHandler::StartNextTests() } bool allTestsFailedTestLoadCheck = false; - size_t minProcessorsRequired = this->ParallelLevel; + size_t minProcessorsRequired = this->GetParallelLevel(); std::string testWithMinProcessors; cmsys::SystemInformation info; @@ -819,7 +862,7 @@ void cmCTestMultiProcessHandler::ReadCostData() this->Properties[index]->PreviousRuns = prev; // When not running in parallel mode, don't use cost data - if (this->ParallelLevel > 1 && this->Properties[index] && + if (this->GetParallelLevel() > 1 && this->Properties[index] && this->Properties[index]->Cost == 0) { this->Properties[index]->Cost = cost; } @@ -848,7 +891,7 @@ int cmCTestMultiProcessHandler::SearchByName(std::string const& name) void cmCTestMultiProcessHandler::CreateTestCostList() { - if (this->ParallelLevel > 1) { + if (this->GetParallelLevel() > 1) { this->CreateParallelTestCostList(); } else { this->CreateSerialTestCostList(); diff --git a/Source/CTest/cmCTestMultiProcessHandler.h b/Source/CTest/cmCTestMultiProcessHandler.h index d66c348..1f6b042 100644 --- a/Source/CTest/cmCTestMultiProcessHandler.h +++ b/Source/CTest/cmCTestMultiProcessHandler.h @@ -58,12 +58,12 @@ public: unsigned int Slots; }; - cmCTestMultiProcessHandler(); + cmCTestMultiProcessHandler(cmCTest* ctest, cmCTestTestHandler* handler); virtual ~cmCTestMultiProcessHandler(); // Set the tests bool SetTests(TestMap tests, PropertiesMap properties); // Set the max number of tests that can be run at the same time. - void SetParallelLevel(size_t); + void SetParallelLevel(cm::optional<size_t> level); void SetTestLoad(unsigned long load); virtual void RunTests(); void PrintOutputAsJson(); @@ -81,13 +81,6 @@ public: this->TestResults = r; } - void SetCTest(cmCTest* ctest) { this->CTest = ctest; } - - void SetTestHandler(cmCTestTestHandler* handler) - { - this->TestHandler = handler; - } - cmCTestTestHandler* GetTestHandler() { return this->TestHandler; } void SetRepeatMode(cmCTest::Repeat mode, int count) @@ -171,22 +164,26 @@ protected: bool InitResourceAllocator(std::string& error); bool CheckGeneratedResourceSpec(); +private: + cmCTest* CTest; + cmCTestTestHandler* TestHandler; + bool UseResourceSpec = false; cmCTestResourceSpec ResourceSpec; std::string ResourceSpecFile; std::string ResourceSpecSetupFixture; cm::optional<std::size_t> ResourceSpecSetupTest; - bool HasInvalidGeneratedResourceSpec; + bool HasInvalidGeneratedResourceSpec = false; // Tests pending selection to start. They may have dependencies. TestMap PendingTests; // List of pending test indexes, ordered by cost. std::list<int> OrderedTests; // Total number of tests we'll be running - size_t Total; + size_t Total = 0; // Number of tests that are complete - size_t Completed; - size_t RunningCount; + size_t Completed = 0; + size_t RunningCount = 0; std::set<size_t> ProcessorsAvailable; size_t HaveAffinity; bool StopTimePassed = false; @@ -204,7 +201,15 @@ protected: ResourceAvailabilityErrors; cmCTestResourceAllocator ResourceAllocator; std::vector<cmCTestTestHandler::cmCTestTestResult>* TestResults; - size_t ParallelLevel; // max number of process that can be run at once + + // Get the maximum number of processors that may be used at once. + size_t GetParallelLevel() const; + + // With no '-j' option, default to serial testing. + cm::optional<size_t> ParallelLevel = 1; + + // Fallback parallelism limit when '-j' is given with no value. + size_t ParallelLevelDefault; // 'make' jobserver client. If connected, we acquire a token // for each test before running its process. @@ -214,16 +219,14 @@ protected: // Callback invoked when a token is received. void JobServerReceivedToken(); - unsigned long TestLoad; - unsigned long FakeLoadForTesting; + unsigned long TestLoad = 0; + unsigned long FakeLoadForTesting = 0; cm::uv_loop_ptr Loop; cm::uv_idle_ptr StartNextTestsOnIdle_; cm::uv_timer_ptr StartNextTestsOnTimer_; - cmCTestTestHandler* TestHandler; - cmCTest* CTest; - bool HasCycles; + bool HasCycles = false; cmCTest::Repeat RepeatMode = cmCTest::Repeat::Never; int RepeatCount = 1; - bool Quiet; - bool SerialTestRunning; + bool Quiet = false; + bool SerialTestRunning = false; }; diff --git a/Source/CTest/cmCTestTestCommand.cxx b/Source/CTest/cmCTestTestCommand.cxx index ed16354..98ce862 100644 --- a/Source/CTest/cmCTestTestCommand.cxx +++ b/Source/CTest/cmCTestTestCommand.cxx @@ -105,8 +105,8 @@ cmCTestGenericHandler* cmCTestTestCommand::InitializeHandler() if (this->StopOnFailure) { handler->SetOption("StopOnFailure", "ON"); } - if (!this->ParallelLevel.empty()) { - handler->SetOption("ParallelLevel", this->ParallelLevel); + if (this->ParallelLevel) { + handler->SetOption("ParallelLevel", *this->ParallelLevel); } if (!this->Repeat.empty()) { handler->SetOption("Repeat", this->Repeat); diff --git a/Source/CTest/cmCTestTestCommand.h b/Source/CTest/cmCTestTestCommand.h index ce054ed..23661c5 100644 --- a/Source/CTest/cmCTestTestCommand.h +++ b/Source/CTest/cmCTestTestCommand.h @@ -8,7 +8,9 @@ #include <utility> #include <cm/memory> +#include <cm/optional> +#include "cmArgumentParserTypes.h" #include "cmCTestHandlerCommand.h" #include "cmCommand.h" @@ -56,7 +58,7 @@ protected: std::string ExcludeFixture; std::string ExcludeFixtureSetup; std::string ExcludeFixtureCleanup; - std::string ParallelLevel; + cm::optional<ArgumentParser::Maybe<std::string>> ParallelLevel; std::string Repeat; std::string ScheduleRandom; std::string StopTime; diff --git a/Source/CTest/cmCTestTestHandler.cxx b/Source/CTest/cmCTestTestHandler.cxx index 957806a..b75df27 100644 --- a/Source/CTest/cmCTestTestHandler.cxx +++ b/Source/CTest/cmCTestTestHandler.cxx @@ -550,9 +550,21 @@ bool cmCTestTestHandler::ProcessOptions() return false; } } - if (this->GetOption("ParallelLevel")) { - this->CTest->SetParallelLevel( - std::stoi(*this->GetOption("ParallelLevel"))); + if (cmValue parallelLevel = this->GetOption("ParallelLevel")) { + if (parallelLevel.IsEmpty()) { + // An empty value tells ctest to choose a default. + this->CTest->SetParallelLevel(cm::nullopt); + } else { + // A non-empty value must be a non-negative integer. + unsigned long plevel = 0; + if (!cmStrToULong(*parallelLevel, &plevel)) { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "ParallelLevel invalid value: " << *parallelLevel + << std::endl); + return false; + } + this->CTest->SetParallelLevel(plevel); + } } if (this->GetOption("StopOnFailure")) { @@ -1360,10 +1372,9 @@ bool cmCTestTestHandler::ProcessDirectory(std::vector<std::string>& passed, this->StartTestTime = std::chrono::system_clock::now(); auto elapsed_time_start = std::chrono::steady_clock::now(); - auto parallel = cm::make_unique<cmCTestMultiProcessHandler>(); - parallel->SetCTest(this->CTest); + auto parallel = + cm::make_unique<cmCTestMultiProcessHandler>(this->CTest, this); parallel->SetParallelLevel(this->CTest->GetParallelLevel()); - parallel->SetTestHandler(this); if (this->RepeatMode != cmCTest::Repeat::Never) { parallel->SetRepeatMode(this->RepeatMode, this->RepeatCount); } else { diff --git a/Source/cmCTest.cxx b/Source/cmCTest.cxx index 7acee4b..1ae3cb5 100644 --- a/Source/cmCTest.cxx +++ b/Source/cmCTest.cxx @@ -179,7 +179,7 @@ struct cmCTest::Private int MaxTestNameWidth = 30; - int ParallelLevel = 1; + cm::optional<size_t> ParallelLevel = 1; bool ParallelLevelSetInCli = false; unsigned long TestLoad = 0; @@ -380,14 +380,14 @@ cmCTest::cmCTest() cmCTest::~cmCTest() = default; -int cmCTest::GetParallelLevel() const +cm::optional<size_t> cmCTest::GetParallelLevel() const { return this->Impl->ParallelLevel; } -void cmCTest::SetParallelLevel(int level) +void cmCTest::SetParallelLevel(cm::optional<size_t> level) { - this->Impl->ParallelLevel = level < 1 ? 1 : level; + this->Impl->ParallelLevel = level; } unsigned long cmCTest::GetTestLoad() const @@ -1892,14 +1892,31 @@ bool cmCTest::HandleCommandLineArguments(size_t& i, std::string arg = args[i]; if (this->CheckArgument(arg, "-F"_s)) { this->Impl->Failover = true; - } else if (this->CheckArgument(arg, "-j"_s, "--parallel") && - i < args.size() - 1) { - i++; - int plevel = atoi(args[i].c_str()); - this->SetParallelLevel(plevel); + } else if (this->CheckArgument(arg, "-j"_s, "--parallel")) { + cm::optional<size_t> parallelLevel; + // No value or an empty value tells ctest to choose a default. + if (i + 1 < args.size() && !cmHasLiteralPrefix(args[i + 1], "-")) { + ++i; + if (!args[i].empty()) { + // A non-empty value must be a non-negative integer. + unsigned long plevel = 0; + if (!cmStrToULong(args[i], &plevel)) { + errormsg = + cmStrCat("'", arg, "' given invalid value '", args[i], "'"); + return false; + } + parallelLevel = plevel; + } + } + this->SetParallelLevel(parallelLevel); this->Impl->ParallelLevelSetInCli = true; } else if (cmHasPrefix(arg, "-j")) { - int plevel = atoi(arg.substr(2).c_str()); + // The value must be a non-negative integer. + unsigned long plevel = 0; + if (!cmStrToULong(arg.substr(2), &plevel)) { + errormsg = cmStrCat("'", arg, "' given invalid value '", args[i], "'"); + return false; + } this->SetParallelLevel(plevel); this->Impl->ParallelLevelSetInCli = true; } @@ -2799,10 +2816,20 @@ int cmCTest::Run(std::vector<std::string>& args, std::string* output) // handle CTEST_PARALLEL_LEVEL environment variable if (!this->Impl->ParallelLevelSetInCli) { - std::string parallel; - if (cmSystemTools::GetEnv("CTEST_PARALLEL_LEVEL", parallel)) { - int plevel = atoi(parallel.c_str()); - this->SetParallelLevel(plevel); + 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); + } + } } } diff --git a/Source/cmCTest.h b/Source/cmCTest.h index 1644d84..2ab810c 100644 --- a/Source/cmCTest.h +++ b/Source/cmCTest.h @@ -12,6 +12,7 @@ #include <string> #include <vector> +#include <cm/optional> #include <cm/string_view> #include "cmDuration.h" @@ -116,8 +117,8 @@ public: cmDuration GetGlobalTimeout() const; /** how many test to run at the same time */ - int GetParallelLevel() const; - void SetParallelLevel(int); + cm::optional<size_t> GetParallelLevel() const; + void SetParallelLevel(cm::optional<size_t> level); unsigned long GetTestLoad() const; void SetTestLoad(unsigned long); diff --git a/Source/ctest.cxx b/Source/ctest.cxx index c1c2071..f6a11b4 100644 --- a/Source/ctest.cxx +++ b/Source/ctest.cxx @@ -48,9 +48,9 @@ const cmDocumentationEntry cmDocumentationOptions[] = { "Truncate 'tail' (default), 'middle' or 'head' of test output once " "maximum output size is reached" }, { "-F", "Enable failover." }, - { "-j <jobs>, --parallel <jobs>", - "Run the tests in parallel using the " - "given number of jobs." }, + { "-j [<level>], --parallel [<level>]", + "Run tests in parallel, " + "optionally limited to a given level of parallelism." }, { "-Q,--quiet", "Make ctest quiet." }, { "-O <file>, --output-log <file>", "Output to log file" }, { "--output-junit <file>", "Output test results to JUnit XML file." }, |