From f745dd11abef2c2d8f206362698bc6106791d4ca Mon Sep 17 00:00:00 2001 From: Tyler Yankee Date: Wed, 9 Sep 2026 10:41:12 -0400 Subject: [PATCH] ctest: Respect test envvars set by CTEST_PRESET Note the special attention given to support CTest environment variables from inside a test preset's `environment` field. Fixes: #28079 --- Source/CTest/cmCTestTestCommand.cxx | 42 +++++++++- Source/CTest/cmCTestTestCommand.h | 20 +++++ .../CTestCommandLine/RunCMakeTest.cmake | 78 +++++++++++++++++++ ...esetCLIVarEnvironment-CMakePresets.json.in | 24 ++++++ .../TestPresetCLIVarEnvironment-stdout.txt | 1 + ...estsActionEnvironment-CMakePresets.json.in | 24 ++++++ ...tPresetNoTestsActionEnvironment-result.txt | 1 + ...tPresetNoTestsActionEnvironment-stderr.txt | 1 + ...allelLevelEnvironment-CMakePresets.json.in | 24 ++++++ ...tPresetParallelLevelEnvironment-stdout.txt | 6 ++ 10 files changed, 219 insertions(+), 2 deletions(-) create mode 100644 Tests/RunCMake/CTestCommandLine/TestPresetCLIVarEnvironment-CMakePresets.json.in create mode 100644 Tests/RunCMake/CTestCommandLine/TestPresetCLIVarEnvironment-stdout.txt create mode 100644 Tests/RunCMake/CTestCommandLine/TestPresetNoTestsActionEnvironment-CMakePresets.json.in create mode 100644 Tests/RunCMake/CTestCommandLine/TestPresetNoTestsActionEnvironment-result.txt create mode 100644 Tests/RunCMake/CTestCommandLine/TestPresetNoTestsActionEnvironment-stderr.txt create mode 100644 Tests/RunCMake/CTestCommandLine/TestPresetParallelLevelEnvironment-CMakePresets.json.in create mode 100644 Tests/RunCMake/CTestCommandLine/TestPresetParallelLevelEnvironment-stdout.txt diff --git a/Source/CTest/cmCTestTestCommand.cxx b/Source/CTest/cmCTestTestCommand.cxx index e39fad34ea..bbcc30c7ef 100644 --- a/Source/CTest/cmCTestTestCommand.cxx +++ b/Source/CTest/cmCTestTestCommand.cxx @@ -2,8 +2,10 @@ file LICENSE.rst or https://cmake.org/licensing for details. */ #include "cmCTestTestCommand.h" +#include #include #include +#include #include #include #include @@ -105,13 +107,49 @@ cmCTestTestCommand::ResolveTestPreset(cmMakefile& mf, return cm::optional(std::move(resolved)); } +bool cmCTestTestCommand::ExecuteHandlerCommand(TestArguments& args, + cmExecutionStatus& status) const +{ + cmMakefile& mf = status.GetMakefile(); + + // Resolve the preset once here (rather than letting InitializeHandler() + // resolve it again) so its Environment can be applied for the duration of + // the whole test run, without parsing the presets file (and emitting errors, + // etc.) more than once. + this->CachedPresetResolution.emplace( + ResolveTestPreset(mf, args.Preset, args.PresetsFile, status)); + + cmSystemTools::SaveRestoreEnvironment restoreEnv; + if (*this->CachedPresetResolution && + (*this->CachedPresetResolution)->ExpandedPreset) { + for (auto const& var : + (*this->CachedPresetResolution)->ExpandedPreset->Environment) { + if (var.second) { + cmSystemTools::PutEnv(cmStrCat(var.first, '=', *var.second)); + } + } + } + + // Special case for CTest environment variables specified in a preset, which + // are handled by cmCTest much earlier than here, and would otherwise be + // ignored. + if (!this->CTest->UpdateStateFromEnvironment()) { + return false; + } + + return cmCTestHandlerCommand::ExecuteHandlerCommand(args, status); +} + std::unique_ptr cmCTestTestCommand::InitializeHandler( HandlerArguments& arguments, cmExecutionStatus& status) const { cmMakefile& mf = status.GetMakefile(); auto& args = static_cast(arguments); - auto resolvedPreset = - ResolveTestPreset(mf, args.Preset, args.PresetsFile, status); + + assert(this->CachedPresetResolution); + cm::optional resolvedPreset = + std::move(*this->CachedPresetResolution); + this->CachedPresetResolution.reset(); if (!resolvedPreset) { return nullptr; } diff --git a/Source/CTest/cmCTestTestCommand.h b/Source/CTest/cmCTestTestCommand.h index 1e10ded9df..c5cab859e3 100644 --- a/Source/CTest/cmCTestTestCommand.h +++ b/Source/CTest/cmCTestTestCommand.h @@ -25,6 +25,17 @@ class cmCTestTestCommand : public cmCTestHandlerCommand public: using cmCTestHandlerCommand::cmCTestHandlerCommand; + cmCTestTestCommand(cmCTestTestCommand const& other) + : cmCTestHandlerCommand(other) + { + } + + cmCTestTestCommand& operator=(cmCTestTestCommand const& other) + { + cmCTestHandlerCommand::operator=(other); + return *this; + } + protected: struct TestArguments : HandlerArguments { @@ -54,6 +65,9 @@ protected: std::string PresetsFile; }; + bool ExecuteHandlerCommand(TestArguments& args, + cmExecutionStatus& status) const; + template static auto MakeTestParser() -> cmArgumentParser { @@ -101,6 +115,12 @@ private: cmMakefile& mf, std::string const& presetArg, std::string const& presetsFileArg, cmExecutionStatus& status) const; + // Set by ExecuteHandlerCommand() (resolves the preset once, up + // front) and consumed by InitializeHandler() to avoid parsing the presets + // file (and emitting error messages, etc.) more than once. + mutable cm::optional> + CachedPresetResolution; + virtual std::unique_ptr InitializeActualHandler( HandlerArguments& arguments, cmExecutionStatus& status) const; diff --git a/Tests/RunCMake/CTestCommandLine/RunCMakeTest.cmake b/Tests/RunCMake/CTestCommandLine/RunCMakeTest.cmake index 789231f8b3..25dde30212 100644 --- a/Tests/RunCMake/CTestCommandLine/RunCMakeTest.cmake +++ b/Tests/RunCMake/CTestCommandLine/RunCMakeTest.cmake @@ -869,6 +869,84 @@ block() -V) endblock() +# Environment variables from a test preset selected by -D CTEST_PRESET are +# applied when dashboard mode runs the Test step. +block() + set(src "${RunCMake_BINARY_DIR}/TestPresetCLIVarEnvironment") + set(bin "${RunCMake_BINARY_DIR}/TestPresetCLIVarEnvironment-build") + file(REMOVE_RECURSE "${src}" "${bin}") + file(MAKE_DIRECTORY "${src}" "${bin}") + configure_file("${RunCMake_SOURCE_DIR}/TestPresetCLIVarEnvironment-CMakePresets.json.in" + "${src}/CMakePresets.json" @ONLY) + file(WRITE "${bin}/DartConfiguration.tcl" + "BuildDirectory: ${bin}\n" + "SourceDirectory: ${src}\n") + file(WRITE "${bin}/CTestTestfile.cmake" " +add_test(print-env \"${CMAKE_COMMAND}\" -E environment) +set_tests_properties(print-env PROPERTIES PASS_REGULAR_EXPRESSION \"DASHBOARD_PRESET_ENV=from-preset\") +") + set(RunCMake_TEST_SOURCE_DIR "${src}") + set(RunCMake_TEST_BINARY_DIR "${bin}") + set(RunCMake_TEST_NO_CLEAN 1) + run_cmake_command(TestPresetCLIVarEnvironment + ${CMAKE_CTEST_COMMAND} + -M Experimental + -D "CTEST_PRESET=my-test-preset" + -T Test + -V) +endblock() + +# CTEST_PARALLEL_LEVEL set by a test preset's environment is respected. +block() + set(src "${RunCMake_BINARY_DIR}/TestPresetParallelLevelEnvironment") + set(bin "${RunCMake_BINARY_DIR}/TestPresetParallelLevelEnvironment-build") + file(REMOVE_RECURSE "${src}" "${bin}") + file(MAKE_DIRECTORY "${src}" "${bin}") + configure_file("${RunCMake_SOURCE_DIR}/TestPresetParallelLevelEnvironment-CMakePresets.json.in" + "${src}/CMakePresets.json" @ONLY) + file(WRITE "${bin}/DartConfiguration.tcl" + "BuildDirectory: ${bin}\n" + "SourceDirectory: ${src}\n") + file(WRITE "${bin}/CTestTestfile.cmake" " +foreach(i RANGE 1 6) + add_test(test\${i} \"${CMAKE_COMMAND}\" -E true) +endforeach() +") + set(RunCMake_TEST_SOURCE_DIR "${src}") + set(RunCMake_TEST_BINARY_DIR "${bin}") + set(RunCMake_TEST_NO_CLEAN 1) + # Spoof a number of processors to make these tests predictable. + set(ENV{__CTEST_FAKE_PROCESSOR_COUNT_FOR_TESTING} 1) + run_cmake_command(TestPresetParallelLevelEnvironment + ${CMAKE_CTEST_COMMAND} + -M Experimental + -D "CTEST_PRESET=my-test-preset" + -T Test) + unset(ENV{__CTEST_FAKE_PROCESSOR_COUNT_FOR_TESTING}) +endblock() + +# CTEST_NO_TESTS_ACTION set by a test preset's environment is respected. +block() + set(src "${RunCMake_BINARY_DIR}/TestPresetNoTestsActionEnvironment") + set(bin "${RunCMake_BINARY_DIR}/TestPresetNoTestsActionEnvironment-build") + file(REMOVE_RECURSE "${src}" "${bin}") + file(MAKE_DIRECTORY "${src}" "${bin}") + configure_file("${RunCMake_SOURCE_DIR}/TestPresetNoTestsActionEnvironment-CMakePresets.json.in" + "${src}/CMakePresets.json" @ONLY) + file(WRITE "${bin}/DartConfiguration.tcl" + "BuildDirectory: ${bin}\n" + "SourceDirectory: ${src}\n") + file(WRITE "${bin}/CTestTestfile.cmake" "") + set(RunCMake_TEST_SOURCE_DIR "${src}") + set(RunCMake_TEST_BINARY_DIR "${bin}") + set(RunCMake_TEST_NO_CLEAN 1) + run_cmake_command(TestPresetNoTestsActionEnvironment + ${CMAKE_CTEST_COMMAND} + -M Experimental + -D "CTEST_PRESET=my-test-preset" + -T Test) +endblock() + # Test --output-junit function(run_output_junit) set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/output-junit) diff --git a/Tests/RunCMake/CTestCommandLine/TestPresetCLIVarEnvironment-CMakePresets.json.in b/Tests/RunCMake/CTestCommandLine/TestPresetCLIVarEnvironment-CMakePresets.json.in new file mode 100644 index 0000000000..6f2aa3e980 --- /dev/null +++ b/Tests/RunCMake/CTestCommandLine/TestPresetCLIVarEnvironment-CMakePresets.json.in @@ -0,0 +1,24 @@ +{ + "version": 3, + "cmakeMinimumRequired": { + "major": 3, + "minor": 21, + "patch": 0 + }, + "configurePresets": [ + { + "name": "my-configure-preset", + "generator": "@RunCMake_GENERATOR@", + "binaryDir": "${sourceDir}/build" + } + ], + "testPresets": [ + { + "name": "my-test-preset", + "configurePreset": "my-configure-preset", + "environment": { + "DASHBOARD_PRESET_ENV": "from-preset" + } + } + ] +} diff --git a/Tests/RunCMake/CTestCommandLine/TestPresetCLIVarEnvironment-stdout.txt b/Tests/RunCMake/CTestCommandLine/TestPresetCLIVarEnvironment-stdout.txt new file mode 100644 index 0000000000..4b1b619265 --- /dev/null +++ b/Tests/RunCMake/CTestCommandLine/TestPresetCLIVarEnvironment-stdout.txt @@ -0,0 +1 @@ +DASHBOARD_PRESET_ENV=from-preset diff --git a/Tests/RunCMake/CTestCommandLine/TestPresetNoTestsActionEnvironment-CMakePresets.json.in b/Tests/RunCMake/CTestCommandLine/TestPresetNoTestsActionEnvironment-CMakePresets.json.in new file mode 100644 index 0000000000..002475602e --- /dev/null +++ b/Tests/RunCMake/CTestCommandLine/TestPresetNoTestsActionEnvironment-CMakePresets.json.in @@ -0,0 +1,24 @@ +{ + "version": 3, + "cmakeMinimumRequired": { + "major": 3, + "minor": 21, + "patch": 0 + }, + "configurePresets": [ + { + "name": "my-configure-preset", + "generator": "@RunCMake_GENERATOR@", + "binaryDir": "${sourceDir}/build" + } + ], + "testPresets": [ + { + "name": "my-test-preset", + "configurePreset": "my-configure-preset", + "environment": { + "CTEST_NO_TESTS_ACTION": "error" + } + } + ] +} diff --git a/Tests/RunCMake/CTestCommandLine/TestPresetNoTestsActionEnvironment-result.txt b/Tests/RunCMake/CTestCommandLine/TestPresetNoTestsActionEnvironment-result.txt new file mode 100644 index 0000000000..45a4fb75db --- /dev/null +++ b/Tests/RunCMake/CTestCommandLine/TestPresetNoTestsActionEnvironment-result.txt @@ -0,0 +1 @@ +8 diff --git a/Tests/RunCMake/CTestCommandLine/TestPresetNoTestsActionEnvironment-stderr.txt b/Tests/RunCMake/CTestCommandLine/TestPresetNoTestsActionEnvironment-stderr.txt new file mode 100644 index 0000000000..eafba1c692 --- /dev/null +++ b/Tests/RunCMake/CTestCommandLine/TestPresetNoTestsActionEnvironment-stderr.txt @@ -0,0 +1 @@ +No tests were found!!! diff --git a/Tests/RunCMake/CTestCommandLine/TestPresetParallelLevelEnvironment-CMakePresets.json.in b/Tests/RunCMake/CTestCommandLine/TestPresetParallelLevelEnvironment-CMakePresets.json.in new file mode 100644 index 0000000000..bf4ed1edd9 --- /dev/null +++ b/Tests/RunCMake/CTestCommandLine/TestPresetParallelLevelEnvironment-CMakePresets.json.in @@ -0,0 +1,24 @@ +{ + "version": 3, + "cmakeMinimumRequired": { + "major": 3, + "minor": 21, + "patch": 0 + }, + "configurePresets": [ + { + "name": "my-configure-preset", + "generator": "@RunCMake_GENERATOR@", + "binaryDir": "${sourceDir}/build" + } + ], + "testPresets": [ + { + "name": "my-test-preset", + "configurePreset": "my-configure-preset", + "environment": { + "CTEST_PARALLEL_LEVEL": "3" + } + } + ] +} diff --git a/Tests/RunCMake/CTestCommandLine/TestPresetParallelLevelEnvironment-stdout.txt b/Tests/RunCMake/CTestCommandLine/TestPresetParallelLevelEnvironment-stdout.txt new file mode 100644 index 0000000000..3ec49479b4 --- /dev/null +++ b/Tests/RunCMake/CTestCommandLine/TestPresetParallelLevelEnvironment-stdout.txt @@ -0,0 +1,6 @@ +Test project [^ +]*/Tests/RunCMake/CTestCommandLine/TestPresetParallelLevelEnvironment-build + 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