1
0
mirror of https://github.com/Kitware/CMake.git synced 2025-10-23 09:37:51 +08:00

ctest: Add support for running under a make job server on POSIX systems

Share job slots with the job server by acquiring a token before running
each test, and releasing the token when the test finishes.
This commit is contained in:
Brad King
2023-11-15 13:55:35 -05:00
parent 5396f4a9a3
commit 80fe56c481
13 changed files with 136 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
test:
+ctest -j 8

View File

@@ -1841,6 +1841,31 @@ fixture in their :prop_test:`FIXTURES_REQUIRED`, and a resource spec file may
not be specified with the ``--resource-spec-file`` argument or the
:variable:`CTEST_RESOURCE_SPEC_FILE` variable.
.. _`ctest-job-server-integration`:
Job Server Integration
======================
.. versionadded:: 3.29
On POSIX systems, when running under the context of a `Job Server`_,
CTest shares its job slots. This is independent of the :prop_test:`PROCESSORS`
test property, which still counts against CTest's :option:`-j <ctest -j>`
parallel level. CTest acquires exactly one token from the job server before
running each test, and returns it when the test finishes.
For example, consider the ``Makefile``:
.. literalinclude:: CTEST_EXAMPLE_MAKEFILE_JOB_SERVER.make
:language: make
When invoked via ``make -j 2 test``, ``ctest`` connects to the job server,
acquires a token for each test, and runs at most 2 tests concurrently.
On Windows systems, job server integration is not yet implemented.
.. _`Job Server`: https://www.gnu.org/software/make/manual/html_node/Job-Slots.html
See Also
========

View File

@@ -0,0 +1,5 @@
ctest-jobserver-client
----------------------
* :manual:`ctest(1)` now supports :ref:`job server integration
<ctest-job-server-integration>` on POSIX systems.

View File

@@ -40,6 +40,7 @@
#include "cmRange.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
#include "cmUVJobServerClient.h"
#include "cmWorkingDirectory.h"
namespace cmsys {
@@ -130,10 +131,19 @@ void cmCTestMultiProcessHandler::InitializeLoop()
this->Loop.init();
this->StartNextTestsOnIdle_.init(*this->Loop, this);
this->StartNextTestsOnTimer_.init(*this->Loop, this);
this->JobServerClient = cmUVJobServerClient::Connect(
*this->Loop, /*onToken=*/[this]() { this->JobServerReceivedToken(); },
/*onDisconnect=*/nullptr);
if (this->JobServerClient) {
cmCTestLog(this->CTest, OUTPUT,
"Connected to MAKE jobserver" << std::endl);
}
}
void cmCTestMultiProcessHandler::FinalizeLoop()
{
this->JobServerClient.reset();
this->StartNextTestsOnTimer_.reset();
this->StartNextTestsOnIdle_.reset();
this->Loop.reset();
@@ -461,6 +471,26 @@ std::string cmCTestMultiProcessHandler::GetName(int test)
void cmCTestMultiProcessHandler::StartTest(int test)
{
if (this->JobServerClient) {
// There is a job server. Request a token and queue the test to run
// when a token is received. Note that if we do not get a token right
// away it's possible that the system load will be higher when the
// token is received and we may violate the test-load limit. However,
// this is unlikely because if we do not get a token right away, some
// other job that's currently running must finish before we get one.
this->JobServerClient->RequestToken();
this->JobServerQueuedTests.emplace_back(test);
} else {
// There is no job server. Start the test now.
this->StartTestProcess(test);
}
}
void cmCTestMultiProcessHandler::JobServerReceivedToken()
{
assert(!this->JobServerQueuedTests.empty());
int test = this->JobServerQueuedTests.front();
this->JobServerQueuedTests.pop_front();
this->StartTestProcess(test);
}
@@ -692,6 +722,9 @@ void cmCTestMultiProcessHandler::FinishTestProcess(
runner.reset();
if (this->JobServerClient) {
this->JobServerClient->ReleaseToken();
}
this->StartNextTestsOnIdle();
}

View File

@@ -19,6 +19,7 @@
#include "cmCTestResourceSpec.h"
#include "cmCTestTestHandler.h"
#include "cmUVHandlePtr.h"
#include "cmUVJobServerClient.h"
struct cmCTestBinPackerAllocation;
class cmCTestRunTest;
@@ -204,6 +205,15 @@ protected:
cmCTestResourceAllocator ResourceAllocator;
std::vector<cmCTestTestHandler::cmCTestTestResult>* TestResults;
size_t ParallelLevel; // max number of process that can be run at once
// 'make' jobserver client. If connected, we acquire a token
// for each test before running its process.
cm::optional<cmUVJobServerClient> JobServerClient;
// List of tests that are queued to run when a token is available.
std::list<int> JobServerQueuedTests;
// Callback invoked when a token is received.
void JobServerReceivedToken();
unsigned long TestLoad;
unsigned long FakeLoadForTesting;
cm::uv_loop_ptr Loop;

View File

@@ -0,0 +1,9 @@
Test project [^
]*/Tests/RunCMake/Make/CTestJobServer-build
Start [0-9]+: test[0-9]+
Start [0-9]+: test[0-9]+
Start [0-9]+: test[0-9]+
Start [0-9]+: test[0-9]+
Start [0-9]+: test[0-9]+
Start [0-9]+: test[0-9]+
1/6 Test #[0-9]+: test[0-9]+ ............................ Passed +[0-9.]+ sec

View File

@@ -0,0 +1 @@
No tests were found!!!

View File

@@ -0,0 +1,3 @@
Test project [^
]*/Tests/RunCMake/Make/CTestJobServer-build
Connected to MAKE jobserver

View File

@@ -0,0 +1,6 @@
Test project [^
]*/Tests/RunCMake/Make/CTestJobServer-build
Connected to MAKE jobserver
Start [0-9]+: test[0-9]+
Start [0-9]+: test[0-9]+
1/6 Test #[0-9]+: test[0-9]+ ............................ Passed +[0-9.]+ sec

View File

@@ -0,0 +1,7 @@
Test project [^
]*/Tests/RunCMake/Make/CTestJobServer-build
Connected to MAKE jobserver
Start [0-9]+: test[0-9]+
Start [0-9]+: test[0-9]+
Start [0-9]+: test[0-9]+
1/6 Test #[0-9]+: test[0-9]+ ............................ Passed +[0-9.]+ sec

View File

@@ -0,0 +1,4 @@
enable_testing()
foreach(i RANGE 1 6)
add_test(NAME test${i} COMMAND ${CMAKE_COMMAND} -E true)
endforeach()

View File

@@ -0,0 +1,11 @@
NoPipe:
env MAKEFLAGS= $(CMAKE_CTEST_COMMAND) -j6
.PHONY: NoPipe
NoTests:
+$(CMAKE_CTEST_COMMAND) -j6 -R NoTests
.PHONY: NoTests
Tests:
+$(CMAKE_CTEST_COMMAND) -j6
.PHONY: Tests

View File

@@ -79,9 +79,29 @@ function(detect_jobserver_present)
run_cmake_command(DetectJobServer-present-parallel-build ${CMAKE_COMMAND} --build . -j4)
endfunction()
function(run_make_rule case rule job_count)
run_cmake_command(${case}-${rule}-j${job_count}
${RunCMake_MAKE_PROGRAM} -f "${RunCMake_SOURCE_DIR}/${case}.make" ${rule} -j${job_count}
CMAKE_COMMAND="${CMAKE_COMMAND}" CMAKE_CTEST_COMMAND="${CMAKE_CTEST_COMMAND}"
)
endfunction()
function(run_CTestJobServer)
set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/CTestJobServer-build)
run_cmake(CTestJobServer)
set(RunCMake_TEST_NO_CLEAN 1)
run_make_rule(CTestJobServer NoPipe 2)
run_make_rule(CTestJobServer NoTests 2)
run_make_rule(CTestJobServer Tests 2)
run_make_rule(CTestJobServer Tests 3)
endfunction()
# Jobservers are currently only supported by GNU makes, except MSYS2 make
if(MAKE_IS_GNU AND NOT RunCMake_GENERATOR MATCHES "MSYS Makefiles")
detect_jobserver_present()
if(UNIX)
run_CTestJobServer()
endif()
endif()
if(MAKE_IS_GNU)