From 463517fddd431e8f80e0c3f88749cd1840848fa3 Mon Sep 17 00:00:00 2001 From: Martin Duffy Date: Tue, 18 Aug 2026 10:56:44 -0400 Subject: [PATCH] ctest: Support Test Dependencies for discover_test modules - Update discover_tests to work with test_prep targets and ctest --out-of-date - Extend behavior to gtest_discover_tests - Don't treat non-built targets as always out of date - Support imported targets in BUILD_DEPENDS Issue: #28052, #28070 --- Help/command/add_test.rst | 2 + Help/command/discover_tests.rst | 20 ++ Help/generator/FASTBuild.rst | 27 ++ Help/manual/cmake-properties.7.rst | 1 + Help/manual/ctest.1.rst | 13 +- Help/prop_dir/CMAKE_TEST_BUILD_DEPENDS.rst | 15 + Help/variable/CMAKE_TEST_BUILD_DEPENDS.rst | 3 + Modules/GoogleTest.cmake | 4 + Modules/GoogleTestAddTests.cmake | 1 + Source/CTest/cmCTestDiscoverTests.cxx | 7 +- Source/CTest/cmCTestRunTest.cxx | 15 + Source/CTest/cmCTestTestHandler.cxx | 16 +- Source/CTest/cmCTestTestHandler.h | 1 + Source/cmDiscoverTestsCommand.cxx | 51 ++++ Source/cmGlobalFastbuildGenerator.cxx | 16 + Source/cmGlobalGenerator.cxx | 17 +- Source/cmGlobalGenerator.h | 3 + Source/cmGlobalNinjaGenerator.cxx | 26 +- Source/cmGlobalUnixMakefileGenerator3.cxx | 81 ++++- Source/cmGlobalUnixMakefileGenerator3.h | 3 + Source/cmGlobalXCodeGenerator.cxx | 24 ++ Source/cmGlobalXCodeGenerator.h | 3 + Source/cmLocalGenerator.cxx | 48 ++- Source/cmLocalGenerator.h | 32 ++ Source/cmTestDiscovery.h | 2 + Source/cmTestGenerator.cxx | 52 ++-- Source/cmTestGenerator.h | 10 +- Tests/RunCMake/CMakeLists.txt | 1 + .../CTestCommandLine/RunCMakeTest.cmake | 7 + .../CTestCommandLine/out-of-date-stdout.txt | 2 +- .../GoogleTest/GoogleTestBuildDepends.cmake | 16 + Tests/RunCMake/GoogleTest/RunCMakeTest.cmake | 30 ++ Tests/RunCMake/TestDependency/AddTest.cmake | 25 ++ Tests/RunCMake/TestDependency/CMakeLists.txt | 16 + .../DEFAULT.cmake} | 0 .../DirectoryGeneratedFile.cmake | 13 + .../TestDependency/DiscoverTests.cmake | 17 ++ .../DiscoverTestsBuildDepends.cmake | 20 ++ .../DiscoverTestsCommands.cmake | 32 ++ .../DiscoverTestsGeneratedFile.cmake | 19 ++ .../TestDependency/DiscoverTestsUtility.cmake | 19 ++ .../GoogleTest-POST_BUILD.cmake | 16 + .../TestDependency/GoogleTest-PRE_TEST.cmake | 16 + .../MissingBuildDependency.cmake | 13 + .../OFF.cmake} | 0 .../ON-byproduct-stderr.txt} | 2 +- .../ON-byproduct.cmake} | 2 +- .../ON-colon-name-stderr.txt} | 2 +- .../ON-colon-name.cmake} | 0 .../ON-file-generate-config.cmake} | 2 +- .../RunCMake/TestDependency/ON-imported.cmake | 16 + .../ON-interface.cmake} | 2 +- .../ON-invalid-test-name-result.txt} | 0 .../ON-invalid-test-name-stderr.txt} | 4 +- .../ON-invalid-test-name.cmake} | 0 .../ON-orphan-stderr.txt} | 2 +- .../ON-orphan.cmake} | 0 .../ON.cmake} | 0 .../TestDependency/ObjectLibrary.cmake | 18 ++ .../TestDependency/RunCMakeTest.cmake | 289 ++++++++++++++++++ .../TestDependency.cmake | 4 +- .../TestDependencySubdir/CMakeLists.txt | 0 .../build-check.cmake} | 0 .../check-discovered-build-depends.cmake | 33 ++ .../check-targets.cmake} | 2 +- .../RunCMake/TestDependency/fake_discovery.c | 13 + .../TestDependency/fake_gtest_post_build.cpp | 58 ++++ .../shared/fake_discovery_wrapper.cmake | 10 + Tests/RunCMake/add_test/RunCMakeTest.cmake | 92 ------ 69 files changed, 1146 insertions(+), 160 deletions(-) create mode 100644 Help/prop_dir/CMAKE_TEST_BUILD_DEPENDS.rst create mode 100644 Tests/RunCMake/GoogleTest/GoogleTestBuildDepends.cmake create mode 100644 Tests/RunCMake/TestDependency/AddTest.cmake create mode 100644 Tests/RunCMake/TestDependency/CMakeLists.txt rename Tests/RunCMake/{add_test/TestDependency-DEFAULT.cmake => TestDependency/DEFAULT.cmake} (100%) create mode 100644 Tests/RunCMake/TestDependency/DirectoryGeneratedFile.cmake create mode 100644 Tests/RunCMake/TestDependency/DiscoverTests.cmake create mode 100644 Tests/RunCMake/TestDependency/DiscoverTestsBuildDepends.cmake create mode 100644 Tests/RunCMake/TestDependency/DiscoverTestsCommands.cmake create mode 100644 Tests/RunCMake/TestDependency/DiscoverTestsGeneratedFile.cmake create mode 100644 Tests/RunCMake/TestDependency/DiscoverTestsUtility.cmake create mode 100644 Tests/RunCMake/TestDependency/GoogleTest-POST_BUILD.cmake create mode 100644 Tests/RunCMake/TestDependency/GoogleTest-PRE_TEST.cmake create mode 100644 Tests/RunCMake/TestDependency/MissingBuildDependency.cmake rename Tests/RunCMake/{add_test/TestDependency-OFF.cmake => TestDependency/OFF.cmake} (100%) rename Tests/RunCMake/{add_test/TestDependency-ON-byproduct-stderr.txt => TestDependency/ON-byproduct-stderr.txt} (81%) rename Tests/RunCMake/{add_test/TestDependency-ON-byproduct.cmake => TestDependency/ON-byproduct.cmake} (89%) rename Tests/RunCMake/{add_test/TestDependency-ON-colon-name-stderr.txt => TestDependency/ON-colon-name-stderr.txt} (77%) rename Tests/RunCMake/{add_test/TestDependency-ON-colon-name.cmake => TestDependency/ON-colon-name.cmake} (100%) rename Tests/RunCMake/{add_test/TestDependency-ON-file-generate-config.cmake => TestDependency/ON-file-generate-config.cmake} (88%) create mode 100644 Tests/RunCMake/TestDependency/ON-imported.cmake rename Tests/RunCMake/{add_test/TestDependency-ON-interface.cmake => TestDependency/ON-interface.cmake} (88%) rename Tests/RunCMake/{add_test/TestDependency-ON-invalid-test-name-result.txt => TestDependency/ON-invalid-test-name-result.txt} (100%) rename Tests/RunCMake/{add_test/TestDependency-ON-invalid-test-name-stderr.txt => TestDependency/ON-invalid-test-name-stderr.txt} (60%) rename Tests/RunCMake/{add_test/TestDependency-ON-invalid-test-name.cmake => TestDependency/ON-invalid-test-name.cmake} (100%) rename Tests/RunCMake/{add_test/TestDependency-ON-orphan-stderr.txt => TestDependency/ON-orphan-stderr.txt} (82%) rename Tests/RunCMake/{add_test/TestDependency-ON-orphan.cmake => TestDependency/ON-orphan.cmake} (100%) rename Tests/RunCMake/{add_test/TestDependency-ON.cmake => TestDependency/ON.cmake} (100%) create mode 100644 Tests/RunCMake/TestDependency/ObjectLibrary.cmake create mode 100644 Tests/RunCMake/TestDependency/RunCMakeTest.cmake rename Tests/RunCMake/{add_test => TestDependency}/TestDependency.cmake (92%) rename Tests/RunCMake/{add_test => TestDependency}/TestDependencySubdir/CMakeLists.txt (100%) rename Tests/RunCMake/{add_test/TestDependency-build-check.cmake => TestDependency/build-check.cmake} (100%) create mode 100644 Tests/RunCMake/TestDependency/check-discovered-build-depends.cmake rename Tests/RunCMake/{add_test/TestDependency-check-targets.cmake => TestDependency/check-targets.cmake} (91%) create mode 100644 Tests/RunCMake/TestDependency/fake_discovery.c create mode 100644 Tests/RunCMake/TestDependency/fake_gtest_post_build.cpp create mode 100644 Tests/RunCMake/TestDependency/shared/fake_discovery_wrapper.cmake diff --git a/Help/command/add_test.rst b/Help/command/add_test.rst index c630932e26..e952f5b9a0 100644 --- a/Help/command/add_test.rst +++ b/Help/command/add_test.rst @@ -117,6 +117,8 @@ directory the test is created in. ``COMMAND`` also enable the :option:`--out-of-date ` behavior of :manual:`ctest(1)`. + Tests with missing build dependencies will not be run. + ``COMMAND_EXPAND_LISTS`` .. versionadded:: 3.16 diff --git a/Help/command/discover_tests.rst b/Help/command/discover_tests.rst index 2b46a80d4c..6cd303e020 100644 --- a/Help/command/discover_tests.rst +++ b/Help/command/discover_tests.rst @@ -16,6 +16,7 @@ Register tests with names and properties discovered at test time by TEST_NAME TEST_ARGS ... [TEST_PROPERTIES [ ]...] + [BUILD_DEPENDS ...] ) This command configures test discovery rather than defining a single test at @@ -94,6 +95,25 @@ the provided regular expression and replacement strings. they do not set :prop_test:`FIXTURE_REPEAT_MODE`, policy :policy:`CMP0224` determines the behavior when the :ctest-option:`--repeat` option is used. +``BUILD_DEPENDS`` + Specify a list of targets or files that must be built before the test + discovery can run. + + When the :variable:`CMAKE_TEST_BUILD_DEPENDS` variable is enabled with the + :ref:`Ninja Generators`, :generator:`FASTBuild`, or :ref:`Makefile + Generators`, these dependencies are built by the ``test_prep/all`` target. + If ``COMMAND`` names an executable target created by + :command:`add_executable`, that target is also added automatically. + + Individual ``test_prep/`` targets are not created for discovered + tests because their names are not known at generation time. + + Build dependencies added by this argument, and an executable target + named by ``COMMAND``, also enable the + :ctest-option:`--out-of-date` behavior of :manual:`ctest(1)`. + + Tests with missing build dependencies will not be run. + CTest executes the discovery step to obtain the list of tests and then runs each discovered test using the command-line produced by ``COMMAND`` together with ``TEST_ARGS``. The pass/fail behavior of each discovered test follows diff --git a/Help/generator/FASTBuild.rst b/Help/generator/FASTBuild.rst index e42ab369bc..43c87b7253 100644 --- a/Help/generator/FASTBuild.rst +++ b/Help/generator/FASTBuild.rst @@ -29,6 +29,32 @@ This writes a FASTBuild configuration file named ``fbuild.bff`` into These IDE files can be generated by building ``xcode`` or ``solution`` targets and will build using FASTBuild as the backend. +Built-in Targets +---------------- + +.. versionadded:: 4.5 + + When the :variable:`CMAKE_TEST_BUILD_DEPENDS` variable is enabled, the + generated build file provides the following targets: + + ``test_prep/`` + Builds all known build dependencies for the test named ```` + added by :command:`add_test`, including the executable target invoked by + the test, targets referenced by generator expressions in the test command, + and explicit ``BUILD_DEPENDS`` entries. + + Tests whose names are not valid target names are excluded. If multiple + tests in different directories share the same name, their dependencies are + merged into one ``test_prep/`` target. + + Individual ``test_prep/`` targets are not created for tests + discovered at test time, because their names are not known during generation. + + ``test_prep/all`` + Depends on every generated ``test_prep/`` target. Also builds + dependencies recorded by the :prop_dir:`CMAKE_TEST_BUILD_DEPENDS` directory + property. + Caching ------- @@ -70,6 +96,7 @@ The following variables can be used to configure this generator: * :variable:`CMAKE_FASTBUILD_IDE_ARGS` * :variable:`CMAKE_FASTBUILD_TRACK_BYPRODUCTS_AS_OUTPUT` * :variable:`CMAKE_FASTBUILD_VERBOSE_GENERATOR` +* :variable:`CMAKE_TEST_BUILD_DEPENDS` Target Properties ----------------- diff --git a/Help/manual/cmake-properties.7.rst b/Help/manual/cmake-properties.7.rst index 629336a621..15d3802a2d 100644 --- a/Help/manual/cmake-properties.7.rst +++ b/Help/manual/cmake-properties.7.rst @@ -72,6 +72,7 @@ Properties on Directories /prop_dir/CACHE_VARIABLES /prop_dir/CLEAN_NO_CUSTOM /prop_dir/CMAKE_CONFIGURE_DEPENDS + /prop_dir/CMAKE_TEST_BUILD_DEPENDS /prop_dir/COMPILE_DEFINITIONS /prop_dir/COMPILE_OPTIONS /prop_dir/DEFINITIONS diff --git a/Help/manual/ctest.1.rst b/Help/manual/ctest.1.rst index c82f7f2242..6979cf0d67 100644 --- a/Help/manual/ctest.1.rst +++ b/Help/manual/ctest.1.rst @@ -616,13 +616,16 @@ The options for running tests are: respect to their recorded build dependencies. Build dependencies include executables and targets in generator expressions as part of the test ``COMMAND``, as well as the outputs of targets or files added as explicit - dependencies with the ``BUILD_DEPENDS`` argument of :command:`add_test`. + dependencies with the ``BUILD_DEPENDS`` argument of :command:`add_test` or + :command:`discover_tests`. Tests discovered by + :command:`gtest_discover_tests` also record their test executable as a build + dependency. A test is selected to run when any of its recorded build dependencies are - newer than the test's last-run timestamp, or when the test has not been - run before. Tests without any known build dependencies, including any tests - not added by the :command:`add_test` command, are excluded when this argument - is provided. + newer than the test's last-run timestamp, when any dependency is missing, or + when the test has not been run before. Selected tests with missing build + dependencies are reported as ``Not Run``. Tests without any known build + dependencies are excluded when this argument is provided. View Help ========= diff --git a/Help/prop_dir/CMAKE_TEST_BUILD_DEPENDS.rst b/Help/prop_dir/CMAKE_TEST_BUILD_DEPENDS.rst new file mode 100644 index 0000000000..98d9fda59e --- /dev/null +++ b/Help/prop_dir/CMAKE_TEST_BUILD_DEPENDS.rst @@ -0,0 +1,15 @@ +CMAKE_TEST_BUILD_DEPENDS +------------------------ + +.. versionadded:: 4.5 + +List of directory-scoped build time dependencies for tests. + +This property holds a +:ref:`semicolon-separated list ` of targets and/or files +that should be built by ``test_prep/all`` when +:variable:`CMAKE_TEST_BUILD_DEPENDS` is enabled. + +Projects may append to this property directly with :command:`set_property` or +:command:`set_directory_properties` to extend ``test_prep/all`` with +directory-scoped build dependencies. diff --git a/Help/variable/CMAKE_TEST_BUILD_DEPENDS.rst b/Help/variable/CMAKE_TEST_BUILD_DEPENDS.rst index e3a2e96724..b32639a632 100644 --- a/Help/variable/CMAKE_TEST_BUILD_DEPENDS.rst +++ b/Help/variable/CMAKE_TEST_BUILD_DEPENDS.rst @@ -18,6 +18,9 @@ target for all such tests. Building these targets ensures the test executable, targets referenced by test command generator expressions, and explicit ``BUILD_DEPENDS`` entries are up-to-date before the test runs. +Modules may also extend ``test_prep/all`` with directory-scoped dependencies +via the :prop_dir:`CMAKE_TEST_BUILD_DEPENDS` directory property. + Tests whose names are not valid target names are excluded from this behavior. If multiple tests in different directories share the same name, their dependencies are merged into a single ``test_prep/`` target. diff --git a/Modules/GoogleTest.cmake b/Modules/GoogleTest.cmake index 8b144892f3..826316d166 100644 --- a/Modules/GoogleTest.cmake +++ b/Modules/GoogleTest.cmake @@ -740,6 +740,10 @@ function(gtest_discover_tests target) ) file(GENERATE OUTPUT "${discovery_file}" CONTENT "${discovery_content}") + set_property(DIRECTORY APPEND PROPERTY CMAKE_TEST_BUILD_DEPENDS + "${target}" + ) + set(ctest_include_content) if(arg_DISCOVERY_MODE STREQUAL "POST_BUILD") # Make sure that TEST_LAUNCHER and CROSSCOMPILING_EMULATOR appear on the diff --git a/Modules/GoogleTestAddTests.cmake b/Modules/GoogleTestAddTests.cmake index 98a9c7ddfb..d2ff644d03 100644 --- a/Modules/GoogleTestAddTests.cmake +++ b/Modules/GoogleTestAddTests.cmake @@ -97,6 +97,7 @@ macro(write_test_to_file) "set_tests_properties(${guarded_testname}\n" " PROPERTIES\n" " ${maybe_DISABLED}\n" + " _CMAKE_TEST_BUILD_DEPENDS [==[${arg_TEST_EXECUTABLE}]==]\n" " ${maybe_LOCATION}\n" " WORKING_DIRECTORY [==[${arg_TEST_WORKING_DIR}]==]\n" " SKIP_REGULAR_EXPRESSION [==[\\[ SKIPPED \\]]==]\n" diff --git a/Source/CTest/cmCTestDiscoverTests.cxx b/Source/CTest/cmCTestDiscoverTests.cxx index 754850dc8d..0be43b4a57 100644 --- a/Source/CTest/cmCTestDiscoverTests.cxx +++ b/Source/CTest/cmCTestDiscoverTests.cxx @@ -161,11 +161,16 @@ bool cmCTestDiscoverTests(cmTestDiscoveryArgs const& args, } auto testProperties = std::vector{ testName, "PROPERTIES" }; - testProperties.reserve(2 + args.TestProperties.size()); + testProperties.reserve(2 + args.TestProperties.size() + + (args.BuildDepends.empty() ? 0 : 2)); for (std::size_t i = 0; i < args.TestProperties.size(); i += 2) { testProperties.push_back(args.TestProperties[i]); testProperties.push_back(replace(args.TestProperties[i + 1])); } + if (!args.BuildDepends.empty()) { + testProperties.push_back("_CMAKE_TEST_BUILD_DEPENDS"); + testProperties.push_back(cmList::to_string(args.BuildDepends)); + } if (!handler->SetTestsProperties(testProperties)) { return false; } diff --git a/Source/CTest/cmCTestRunTest.cxx b/Source/CTest/cmCTestRunTest.cxx index 2e59c04d8d..d0aa7ad63d 100644 --- a/Source/CTest/cmCTestRunTest.cxx +++ b/Source/CTest/cmCTestRunTest.cxx @@ -30,6 +30,7 @@ #include "cmInstrumentation.h" #include "cmInstrumentationQuery.h" #include "cmJSONState.h" +#include "cmList.h" #include "cmProcess.h" #include "cmStringAlgorithms.h" #include "cmSystemTools.h" @@ -668,6 +669,20 @@ bool cmCTestRunTest::StartTest(size_t completed, size_t total) this->TestResult.Status = cmCTestTestHandler::NOT_RUN; return false; } + // log and return if we did not find any other test dependencies + for (std::string const& dep : cmList{ this->TestProperties->BuildDepends }) { + if (!dep.empty() && !cmSystemTools::FileExists(dep)) { + std::string const msg = "Unable to find build dependency: " + dep; + *this->TestHandler->LogFile << msg << std::endl; + cmCTestLog(this->CTest, ERROR_MESSAGE, msg, '\n'); + this->TestResult.Output = msg; + this->TestResult.FullCommandLine.clear(); + this->TestResult.Environment.clear(); + this->TestResult.CompletionStatus = "Build Dependencies Missing"; + this->TestResult.Status = cmCTestTestHandler::NOT_RUN; + return false; + } + } this->StartTime = this->CTest->CurrentTime(); if (this->CTest->GetInstrumentation().HasQuery()) { this->CTest->GetInstrumentation().GetPreTestStats(); diff --git a/Source/CTest/cmCTestTestHandler.cxx b/Source/CTest/cmCTestTestHandler.cxx index d7073e92f9..2afde1e73f 100644 --- a/Source/CTest/cmCTestTestHandler.cxx +++ b/Source/CTest/cmCTestTestHandler.cxx @@ -995,6 +995,10 @@ bool cmCTestTestHandler::ComputeTestList() } } + if (this->TestOptions.OutOfDateOnly && !tp.IsOutOfDate) { + continue; + } + if (this->TestsToRunByName) { if (this->TestsToRunByName->find(tp.Name) == this->TestsToRunByName->end()) { @@ -1057,11 +1061,11 @@ bool cmCTestTestHandler::ComputeTestListForRerunFailed() void cmCTestTestHandler::ComputeOutOfDateTests() { - ListOfTests finalList; std::string const stampDir = this->CTest->GetStampDir(); cmSystemTools::MakeDirectory(stampDir); for (cmCTestTestProperties& tp : this->TestList) { + tp.IsOutOfDate = false; if (tp.BuildDepends.empty()) { continue; } @@ -1069,7 +1073,7 @@ void cmCTestTestHandler::ComputeOutOfDateTests() std::string const stampFile = cmStrCat(stampDir, '/', tp.GetStampFile()); if (!cmSystemTools::FileExists(stampFile)) { - finalList.push_back(tp); + tp.IsOutOfDate = true; continue; } @@ -1080,8 +1084,7 @@ void cmCTestTestHandler::ComputeOutOfDateTests() continue; } if (!cmSystemTools::FileExists(dep)) { - // If any dependencies don't exist, skip the test - outOfDate = false; + outOfDate = true; break; } int result = 0; @@ -1091,11 +1094,8 @@ void cmCTestTestHandler::ComputeOutOfDateTests() outOfDate = true; } } - if (outOfDate) { - finalList.push_back(tp); - } + tp.IsOutOfDate = outOfDate; } - this->TestList = finalList; } void cmCTestTestHandler::UpdateForFixtures(ListOfTests& tests) const diff --git a/Source/CTest/cmCTestTestHandler.h b/Source/CTest/cmCTestTestHandler.h index 87ffd334de..c7eebdcae9 100644 --- a/Source/CTest/cmCTestTestHandler.h +++ b/Source/CTest/cmCTestTestHandler.h @@ -191,6 +191,7 @@ public: std::map CustomProperties; std::unordered_map RawProperties; bool IsInBasedOnREOptions = true; + bool IsOutOfDate = true; bool WillFail = false; std::string WillFailRaw; bool Disabled = false; diff --git a/Source/cmDiscoverTestsCommand.cxx b/Source/cmDiscoverTestsCommand.cxx index 6bab2a1925..8612dde3de 100644 --- a/Source/cmDiscoverTestsCommand.cxx +++ b/Source/cmDiscoverTestsCommand.cxx @@ -3,6 +3,7 @@ #include "cmDiscoverTestsCommand.h" #include +#include #include #include #include @@ -15,6 +16,8 @@ #include "cmArgumentParserTypes.h" #include "cmExecutionStatus.h" #include "cmGeneratorExpression.h" +#include "cmGeneratorTarget.h" +#include "cmGlobalGenerator.h" #include "cmListFileCache.h" #include "cmLocalGenerator.h" #include "cmMakefile.h" @@ -42,6 +45,42 @@ public: { } + void Compute(cmLocalGenerator* lg) override + { + this->cmTestGenerator::Compute(lg); + this->BuildDependenciesByConfig.clear(); + if (!lg->GetMakefile()->IsOn("CMAKE_TESTING_ENABLED")) { + return; + } + bool const filterConfigs = + !lg->GetMakefile() + ->GetGeneratorConfigs(cmMakefile::OnlyMultiConfig) + .empty(); + for (std::string const& config : lg->GetMakefile()->GetGeneratorConfigs( + cmMakefile::IncludeEmptyConfig)) { + if (filterConfigs && !this->GeneratesForConfig(config)) { + continue; + } + cmTestGenerator::BuildDependencies& deps = + this->BuildDependenciesByConfig[config]; + if (!cmTestGenerator::EvaluateBuildDependencies( + lg, config, this->Backtrace, "discover_tests", this->Args.Command, + this->Args.BuildDepends, deps, lg->GetMakefile())) { + deps = {}; + continue; + } + for (cmGeneratorTarget* target : deps.Targets) { + lg->AddDirectoryTestPrepDependency(config, + { target->GetName(), target }); + } + for (cmTestGenerator::BuildDependencies::FileDependency const& file : + deps.Files) { + lg->AddDirectoryTestPrepDependency( + config, { file.Path, nullptr, file.Owner, file.Generated }); + } + } + } + private: void GenerateProperties(std::ostream& os, Indent indent, std::vector const& props, @@ -61,6 +100,10 @@ private: cmGeneratorExpression ge(*this->LG->GetMakefile()->GetCMakeInstance(), this->Backtrace); + std::vector const buildDepends = + this->LG->GetGlobalGenerator()->GetTestBuildDependencyPaths( + config, this->BuildDependenciesByConfig.at(config)); + auto const in = indent.Next(); os << indent << "discover_tests(COMMAND "; this->GenerateCommand(os, this->Args.Command, config, @@ -85,11 +128,19 @@ private: ge); os << '\n' << in.Next(); this->GenerateBacktrace(os, this->Backtrace); + if (!buildDepends.empty()) { + os << '\n' << in << "BUILD_DEPENDS"; + for (std::string const& dep : buildDepends) { + os << ' ' << Quote(dep); + } + } os << '\n' << in << ")\n"; } Arguments Args; cmListFileBacktrace Backtrace; + std::map + BuildDependenciesByConfig; }; bool SetsFixtureRepeatMode(std::vector const& properties) diff --git a/Source/cmGlobalFastbuildGenerator.cxx b/Source/cmGlobalFastbuildGenerator.cxx index 26fc1799cd..cbe3abae0e 100644 --- a/Source/cmGlobalFastbuildGenerator.cxx +++ b/Source/cmGlobalFastbuildGenerator.cxx @@ -1625,6 +1625,22 @@ void cmGlobalFastbuildGenerator::WriteTestPrepTargets() testPrepTarget.Dependencies.emplace(depFile.Path); } } + cmLocalGenerator::DirectoryTestPrepTarget directoryTarget; + if (localGen->GetDirectoryTestPrepTarget( + directoryTarget, + localGen->GetMakefile()->GetSafeDefinition("CMAKE_BUILD_TYPE"))) { + TestPrepTarget& testPrepTarget = testPrepTargets[directoryTarget.Name]; + testPrepTarget.Comment = std::move(directoryTarget.Comment); + + for (cmLocalGenerator::DirectoryTestPrepDependency const& dep : + directoryTarget.Dependencies) { + if (dep.Target) { + testPrepTarget.Dependencies.emplace(dep.Target->GetName()); + } else { + testPrepTarget.Dependencies.emplace(dep.Raw); + } + } + } } FastbuildAliasNode allAliasNode; diff --git a/Source/cmGlobalGenerator.cxx b/Source/cmGlobalGenerator.cxx index dc6d42267e..132a790342 100644 --- a/Source/cmGlobalGenerator.cxx +++ b/Source/cmGlobalGenerator.cxx @@ -71,6 +71,7 @@ #include "cmSystemTools.h" #include "cmTarget.h" #include "cmTargetExport.h" +#include "cmTestGenerator.h" #include "cmUnreachable.h" #include "cmValue.h" #include "cmVersion.h" @@ -1870,6 +1871,14 @@ bool cmGlobalGenerator::Compute() localGen->ComputeTargetManifest(); } + // Register test preparation dependencies after custom-command outputs are + // known, but before generators compute their build graphs. + for (auto const& localGen : this->LocalGenerators) { + for (auto const& tester : localGen->GetMakefile()->GetTestGenerators()) { + tester->Compute(localGen.get()); + } + } + // Compute the inter-target dependencies. if (!this->ComputeTargetDepends()) { return false; @@ -3248,11 +3257,17 @@ std::vector cmGlobalGenerator::GetTestBuildDependencyPaths( } continue; } - uniqueDeps.insert(target->GetFullPath(config)); + uniqueDeps.insert(this->GetTestBuildDependencyPath(target, config)); } return { uniqueDeps.begin(), uniqueDeps.end() }; } +std::string cmGlobalGenerator::GetTestBuildDependencyPath( + cmGeneratorTarget const* target, std::string const& config) const +{ + return target->GetFullPath(config); +} + // If the file has no extension it's either a raw executable or might // be a direct reference to a binary within a framework (bad practice!). // This is where we change the path to point to the framework directory. diff --git a/Source/cmGlobalGenerator.h b/Source/cmGlobalGenerator.h index 313cb06655..04689baae4 100644 --- a/Source/cmGlobalGenerator.h +++ b/Source/cmGlobalGenerator.h @@ -796,6 +796,9 @@ public: cmXcFrameworkPlist const& content); protected: + virtual std::string GetTestBuildDependencyPath( + cmGeneratorTarget const* target, std::string const& config) const; + /** Get all targets produced under the given root, plus the transitive closure of targets on which they depend, possibly from other dirs. */ TargetDependSet GetTargetsForProject( diff --git a/Source/cmGlobalNinjaGenerator.cxx b/Source/cmGlobalNinjaGenerator.cxx index 2f45a76d72..33fb178364 100644 --- a/Source/cmGlobalNinjaGenerator.cxx +++ b/Source/cmGlobalNinjaGenerator.cxx @@ -1308,7 +1308,7 @@ void cmGlobalNinjaGenerator::WriteTestPrepTargets() std::map testPrepTargets; for (auto const& localGen : this->LocalGenerators) { - auto* lg = static_cast(localGen.get()); + cmLocalGenerator* lg = localGen.get(); auto const& testGenerators = lg->GetMakefile()->GetTestGenerators(); for (auto const& tester : testGenerators) { cmTestGenerator::BuildDependencies testDeps; @@ -1332,6 +1332,27 @@ void cmGlobalNinjaGenerator::WriteTestPrepTargets() this->ConvertToNinjaPath(file.Path)); } } + + cmLocalGenerator::DirectoryTestPrepTarget directoryTarget; + if (!lg->GetDirectoryTestPrepTarget(directoryTarget, config)) { + continue; + } + + std::string const depName = + this->ConvertToNinjaPath(directoryTarget.Name); + TestPrepTarget& testPrepTarget = testPrepTargets[depName]; + testPrepTarget.Comment = std::move(directoryTarget.Comment); + + for (cmLocalGenerator::DirectoryTestPrepDependency const& dep : + directoryTarget.Dependencies) { + if (dep.Target) { + this->AppendTargetOutputs(dep.Target, testPrepTarget.ExplicitDeps, + config, DependOnTargetArtifact); + } else { + testPrepTarget.ExplicitDeps.push_back( + this->ConvertToNinjaPath(dep.Raw)); + } + } } std::vector allDeps; @@ -1369,7 +1390,8 @@ void cmGlobalNinjaGenerator::WriteTestPrepTargets() } } } else { - writeConfig(std::string(), *this->GetCommonFileStream()); + writeConfig(this->Makefiles.front()->GetSafeDefinition("CMAKE_BUILD_TYPE"), + *this->GetCommonFileStream()); } } diff --git a/Source/cmGlobalUnixMakefileGenerator3.cxx b/Source/cmGlobalUnixMakefileGenerator3.cxx index 703b6501f2..948a3bd5ee 100644 --- a/Source/cmGlobalUnixMakefileGenerator3.cxx +++ b/Source/cmGlobalUnixMakefileGenerator3.cxx @@ -115,8 +115,11 @@ void cmGlobalUnixMakefileGenerator3::Generate() this->ClangTidyExportFixesDirs.clear(); this->ClangTidyExportFixesFiles.clear(); + this->TestPrepTargets.clear(); + this->TestPrepEnabled = false; + // Compute the "test_prep/" targets before generating the local makefiles - // so their convenience rules can be written into the top-level Makefile. + // so they can be emitted in a single pass. this->ComputeTestPrepTargets(); // first do superclass method @@ -1064,10 +1067,12 @@ void cmGlobalUnixMakefileGenerator3::ComputeTestPrepTargets() for (cmTestGenerator::BuildDependencies::FileDependency const& file : deps.Files) { - if (file.Owner) { + if (cmGeneratorTarget* owner = file.Owner + ? file.Owner + : this->FindOutputOwningTarget(file.Path)) { // The file is the primary output of one build-system target; build // that target to produce the file. - rules.push_back(targetAllRule(file.Owner)); + rules.push_back(targetAllRule(owner)); } else if (file.Generated) { // The file is generated but cannot be attributed to a single owning // target (e.g. a byproduct or an ambiguous/shared output), so the @@ -1088,6 +1093,8 @@ void cmGlobalUnixMakefileGenerator3::ComputeTestPrepTargets() // file that already exists) and needs no build rule. } } + + this->AddDirectoryTestPrepTargets(lg.get(), this->TestPrepTargets); } // Sort and de-duplicate each rule list (as the Ninja generator does). @@ -1098,6 +1105,45 @@ void cmGlobalUnixMakefileGenerator3::ComputeTestPrepTargets() } } +void cmGlobalUnixMakefileGenerator3::AddDirectoryTestPrepTargets( + cmLocalGenerator* lg, + std::map>& testPrepTargets) +{ + cmLocalGenerator::DirectoryTestPrepTarget directoryTarget; + if (!lg->GetDirectoryTestPrepTarget( + directoryTarget, + lg->GetMakefile()->GetSafeDefinition("CMAKE_BUILD_TYPE"))) { + return; + } + + std::vector& rules = testPrepTargets[directoryTarget.Name]; + + for (cmLocalGenerator::DirectoryTestPrepDependency const& dep : + directoryTarget.Dependencies) { + cmGeneratorTarget* target = dep.Target ? dep.Target : dep.Owner; + if (!target) { + target = this->FindOutputOwningTarget(dep.Raw); + } + if (target) { + auto* lg3 = static_cast( + target->GetLocalGenerator()); + rules.push_back( + cmStrCat(lg3->GetRelativeTargetDirectory(target), "/all")); + } else if (dep.Generated) { + lg->GetMakefile()->IssueMessage( + MessageType::WARNING, + cmStrCat("Directory test prep dependency\n ", dep.Raw, + "\nis generated but is not the unique output of a build " + "target, so the \"", + directoryTarget.Name, + "\" target cannot build it with this generator. Depend " + "on the target that produces it (for example one created " + "with add_custom_target) instead."), + lg->GetMakefile()->GetBacktrace()); + } + } +} + void cmGlobalUnixMakefileGenerator3::WriteTestPrepRules( std::ostream& makefileStream, cmLocalUnixMakefileGenerator3& rootLG) { @@ -1142,24 +1188,29 @@ void cmGlobalUnixMakefileGenerator3::WriteTestPrepConvenienceRules( std::vector depends; std::vector commands; - auto writeForward = [&](std::string const& prepName) { - depends.clear(); - if (regenerate) { - depends.emplace_back("cmake_check_build_system"); - } - commands.clear(); - commands.push_back(lg.GetRecursiveMakeCall(makefile2, prepName)); - lg.WriteMakeRule(ruleFileStream, "Build the dependencies of a test.", - prepName, depends, commands, true); - }; lg.WriteDivider(ruleFileStream); ruleFileStream << "# Convenience rules to build test dependencies.\n\n"; for (auto const& entry : this->TestPrepTargets) { - writeForward(entry.first); + depends.clear(); + if (regenerate) { + depends.emplace_back("cmake_check_build_system"); + } + commands.clear(); + commands.push_back(lg.GetRecursiveMakeCall(makefile2, entry.first)); + lg.WriteMakeRule(ruleFileStream, "Build the dependencies of a test.", + entry.first, depends, commands, true); } - writeForward("test_prep/all"); + + depends.clear(); + if (regenerate) { + depends.emplace_back("cmake_check_build_system"); + } + commands.clear(); + commands.push_back(lg.GetRecursiveMakeCall(makefile2, "test_prep/all")); + lg.WriteMakeRule(ruleFileStream, "Build the dependencies of a test.", + "test_prep/all", depends, commands, true); } void cmGlobalUnixMakefileGenerator3::WriteHelpRule( diff --git a/Source/cmGlobalUnixMakefileGenerator3.h b/Source/cmGlobalUnixMakefileGenerator3.h index 76ea0b9678..64df08bd7b 100644 --- a/Source/cmGlobalUnixMakefileGenerator3.h +++ b/Source/cmGlobalUnixMakefileGenerator3.h @@ -330,5 +330,8 @@ private: // rules that must be built to prepare the named test. Populated by // ComputeTestPrepTargets when CMAKE_TEST_BUILD_DEPENDS is enabled. std::map> TestPrepTargets; + void AddDirectoryTestPrepTargets( + cmLocalGenerator* lg, + std::map>& testPrepTargets); bool TestPrepEnabled = false; }; diff --git a/Source/cmGlobalXCodeGenerator.cxx b/Source/cmGlobalXCodeGenerator.cxx index 66a4d14627..e16f781f6f 100644 --- a/Source/cmGlobalXCodeGenerator.cxx +++ b/Source/cmGlobalXCodeGenerator.cxx @@ -5248,6 +5248,30 @@ void cmGlobalXCodeGenerator::OutputXCodeProject( root->GetBinaryDirectory()); } +std::string cmGlobalXCodeGenerator::GetTestBuildDependencyPath( + cmGeneratorTarget const* target, std::string const& config) const +{ + // Replicates the CMake-language logic that computes _test_bundle_dir in + // xctest_add_test() in Modules/FindXCTest.cmake. + if (!target->IsImported() && target->IsXCTestOnApple() && + this->XcodeVersion >= 73) { + cmValue testeeName = target->GetProperty("XCTEST_TESTEE"); + cmGeneratorTarget const* testee = testeeName + ? target->GetLocalGenerator()->FindGeneratorTargetToUse(*testeeName) + : nullptr; + if (testee && testee->IsAppBundleOnApple()) { + std::string path = testee->GetMacContentDirectory( + config, cmStateEnums::RuntimeBinaryArtifact); + if (!(this->XcodeBuildSystem == BuildSystem::Twelve && + this->XcodeVersion < 125 && this->SystemName != "Darwin"_s)) { + path += "/PlugIns"; + } + return cmStrCat(path, '/', target->GetFullName(config)); + } + } + return this->cmGlobalGenerator::GetTestBuildDependencyPath(target, config); +} + bool cmGlobalXCodeGenerator::OutputXCodeSharedSchemes( std::string const& xcProjDir, cmLocalGenerator* root) { diff --git a/Source/cmGlobalXCodeGenerator.h b/Source/cmGlobalXCodeGenerator.h index 908dc7bf89..8b02a2b334 100644 --- a/Source/cmGlobalXCodeGenerator.h +++ b/Source/cmGlobalXCodeGenerator.h @@ -143,6 +143,9 @@ public: }; protected: + std::string GetTestBuildDependencyPath( + cmGeneratorTarget const* target, std::string const& config) const override; + void AddExtraIDETargets() override; void Generate() override; diff --git a/Source/cmLocalGenerator.cxx b/Source/cmLocalGenerator.cxx index 5baa37b033..db0e1c9bf5 100644 --- a/Source/cmLocalGenerator.cxx +++ b/Source/cmLocalGenerator.cxx @@ -243,6 +243,53 @@ cmLocalGenerator::CreateRulePlaceholderExpander(cmBuildStep buildStep) const : cmRulePlaceholderExpander::UseShortPaths::No); } +void cmLocalGenerator::AddDirectoryTestPrepDependency( + std::string const& config, DirectoryTestPrepDependency dependency) +{ + this->DiscoveryTestPrepDependencies[config].push_back(std::move(dependency)); +} + +bool cmLocalGenerator::GetDirectoryTestPrepTarget(DirectoryTestPrepTarget& out, + std::string const& config) +{ + cmValue const directoryTestPrepDependencies = + this->GetStateSnapshot().GetDirectory().GetProperty( + "CMAKE_TEST_BUILD_DEPENDS"); + std::string const directoryId = + cmCryptoHash(cmCryptoHash::AlgoMD5) + .HashString(this->GetCurrentBinaryDirectory()) + .substr(0, 8); + out.Name = cmStrCat("test_prep/directory_", directoryId); + out.Comment = + cmStrCat("Build extra dependencies for test prep in directory ", + this->GetCurrentBinaryDirectory()); + out.Dependencies.clear(); + auto const discovery = this->DiscoveryTestPrepDependencies.find(config); + if (discovery != this->DiscoveryTestPrepDependencies.end()) { + out.Dependencies = discovery->second; + } + + cmList buildDepends; + if (directoryTestPrepDependencies) { + buildDepends.assign( + cmList{ *directoryTestPrepDependencies, cmList::EmptyElements::Yes }); + } + cmTestGenerator::BuildDependencies deps; + cmTestGenerator::EvaluateBuildDependencies( + this, config, this->Makefile->GetBacktrace(), "directory_test_prep", {}, + std::vector(buildDepends.begin(), buildDepends.end()), deps, + this->Makefile); + for (cmGeneratorTarget* target : deps.Targets) { + out.Dependencies.push_back({ target->GetName(), target }); + } + for (cmTestGenerator::BuildDependencies::FileDependency const& file : + deps.Files) { + out.Dependencies.push_back( + { file.Path, nullptr, file.Owner, file.Generated }); + } + return !out.Dependencies.empty(); +} + cmLocalGenerator::~cmLocalGenerator() = default; void cmLocalGenerator::IssueMessage(MessageType type, std::string const& text, @@ -485,7 +532,6 @@ void cmLocalGenerator::GenerateTestFiles() // Ask each test generator to write its code. for (auto const& tester : this->Makefile->GetTestGenerators()) { - tester->Compute(this); tester->Generate(fout, config, configurationTypes); } using vec_t = std::vector; diff --git a/Source/cmLocalGenerator.h b/Source/cmLocalGenerator.h index 0dfc86144b..4af8a1006b 100644 --- a/Source/cmLocalGenerator.h +++ b/Source/cmLocalGenerator.h @@ -78,6 +78,31 @@ struct cmSourcesWithOutput class cmLocalGenerator : public cmOutputConverter { public: + struct DirectoryTestPrepDependency + { + DirectoryTestPrepDependency(std::string raw, cmGeneratorTarget* target, + cmGeneratorTarget* owner = nullptr, + bool generated = false) + : Raw(std::move(raw)) + , Target(target) + , Owner(owner) + , Generated(generated) + { + } + + std::string Raw; + cmGeneratorTarget* Target; + cmGeneratorTarget* Owner; + bool Generated; + }; + + struct DirectoryTestPrepTarget + { + std::string Name; + std::string Comment; + std::vector Dependencies; + }; + cmLocalGenerator(cmGlobalGenerator* gg, cmMakefile* makefile); ~cmLocalGenerator() override; @@ -135,6 +160,11 @@ public: CreateRulePlaceholderExpander( cmBuildStep buildStep = cmBuildStep::Compile) const; + void AddDirectoryTestPrepDependency(std::string const& config, + DirectoryTestPrepDependency dependency); + bool GetDirectoryTestPrepTarget(DirectoryTestPrepTarget& out, + std::string const& config); + std::string GetExeExportFlags(std::string const& linkLanguage, cmGeneratorTarget& tgt) const; @@ -708,6 +738,8 @@ protected: #endif private: + std::map> + DiscoveryTestPrepDependencies; /** * See LinearGetSourceFileWithOutput for background information */ diff --git a/Source/cmTestDiscovery.h b/Source/cmTestDiscovery.h index a8d899ee35..4f6c587dc7 100644 --- a/Source/cmTestDiscovery.h +++ b/Source/cmTestDiscovery.h @@ -12,6 +12,7 @@ struct cmTestDiscoveryArgs : ArgumentParser::ParseResult ArgumentParser::NonEmpty> DiscoveryArgs; ArgumentParser::NonEmpty DiscoveryMatch; ArgumentParser::MaybeEmpty> DiscoveryProperties; + ArgumentParser::MaybeEmpty> BuildDepends; ArgumentParser::NonEmpty TestName; ArgumentParser::NonEmpty> TestArgs; ArgumentParser::MaybeEmpty> TestProperties; @@ -26,6 +27,7 @@ auto cmTestDiscoveryParser() -> cmArgumentParser .Bind("DISCOVERY_ARGS"_s, &Args::DiscoveryArgs) .Bind("DISCOVERY_MATCH"_s, &Args::DiscoveryMatch) .Bind("DISCOVERY_PROPERTIES"_s, &Args::DiscoveryProperties) + .Bind("BUILD_DEPENDS"_s, &Args::BuildDepends) .Bind("TEST_NAME"_s, &Args::TestName) .Bind("TEST_ARGS"_s, &Args::TestArgs) .Bind("TEST_PROPERTIES"_s, &Args::TestProperties); diff --git a/Source/cmTestGenerator.cxx b/Source/cmTestGenerator.cxx index b0b56d4ea5..6e0753fbff 100644 --- a/Source/cmTestGenerator.cxx +++ b/Source/cmTestGenerator.cxx @@ -17,7 +17,6 @@ #include "cmListFileCache.h" #include "cmLocalGenerator.h" #include "cmMakefile.h" -#include "cmMessageType.h" #include "cmPolicies.h" #include "cmPropertyMap.h" #include "cmRange.h" @@ -114,24 +113,29 @@ cmTest* cmTestGenerator::GetTest() const return this->Test; } -bool cmTestGenerator::GetBuildDependencies(cmLocalGenerator* lg, - std::string const& config, - BuildDependencies& info) +bool cmTestGenerator::EvaluateBuildDependencies( + cmLocalGenerator* lg, std::string const& config, + cmListFileBacktrace const& backtrace, std::string const& testName, + std::vector const& command, + std::vector const& buildDepends, BuildDependencies& info, + cmMakefile* mf) { - if (this->Test == nullptr || - !cmGeneratorExpression::IsValidTargetName(this->Test->GetName()) || - cmGlobalGenerator::IsReservedTarget(this->Test->GetName())) { + if (!cmGeneratorExpression::IsValidTargetName(testName) || + cmGlobalGenerator::IsReservedTarget(testName)) { return false; } std::set dependencies; // Get dependencies from generator expressions - cmGeneratorExpression ge(*this->Test->GetMakefile()->GetCMakeInstance(), - this->Test->GetBacktrace()); - for (std::string const& arg : this->Test->GetCommand()) { + cmGeneratorExpression ge(*mf->GetCMakeInstance(), backtrace); + std::string exe; + for (std::string const& arg : command) { auto parsed = ge.Parse(arg); - parsed->Evaluate(lg, config); + std::string const& evaluated = parsed->Evaluate(lg, config); + if (exe.empty()) { + exe = evaluated; + } for (cmGeneratorTarget* dep : parsed->GetTargets()) { if (dep && !dep->IsImported()) { dependencies.insert(dep); @@ -140,8 +144,7 @@ bool cmTestGenerator::GetBuildDependencies(cmLocalGenerator* lg, } // Add target executed by test - if (!this->Test->GetCommand().empty()) { - std::string exe = this->Test->GetCommand().front(); + if (!exe.empty()) { cmGeneratorTarget* target = lg->FindGeneratorTargetToUse(exe); if (target && target->GetType() == cm::TargetType::EXECUTABLE && !target->IsImported()) { @@ -150,7 +153,7 @@ bool cmTestGenerator::GetBuildDependencies(cmLocalGenerator* lg, } // Add dependencies from BUILD_DEPENDS keyword - for (auto const& depName : this->Test->GetDependencies()) { + for (std::string const& depName : buildDepends) { if (depName.empty()) { continue; } @@ -164,14 +167,6 @@ bool cmTestGenerator::GetBuildDependencies(cmLocalGenerator* lg, info.Files.push_back(std::move(file)); continue; } - if (depTarget->IsImported()) { - lg->GetMakefile()->IssueMessage( - MessageType::FATAL_ERROR, - cmStrCat("Test \"", this->Test->GetName(), "\" DEPENDS target \"", - depName, "\" which is imported and cannot be built."), - this->Test->GetBacktrace()); - return false; - } dependencies.insert(depTarget); } @@ -183,6 +178,19 @@ bool cmTestGenerator::GetBuildDependencies(cmLocalGenerator* lg, return true; } +bool cmTestGenerator::GetBuildDependencies(cmLocalGenerator* lg, + std::string const& config, + BuildDependencies& info) +{ + if (this->Test == nullptr) { + return false; + } + return EvaluateBuildDependencies( + lg, config, this->Test->GetBacktrace(), this->Test->GetName(), + this->Test->GetCommand(), this->Test->GetDependencies(), info, + this->Test->GetMakefile()); +} + void cmTestGenerator::GenerateScriptActions(std::ostream& os, Indent indent) { if (this->ActionsPerConfig) { diff --git a/Source/cmTestGenerator.h b/Source/cmTestGenerator.h index d3357a3862..cb9b54ce24 100644 --- a/Source/cmTestGenerator.h +++ b/Source/cmTestGenerator.h @@ -15,6 +15,7 @@ class cmListFileBacktrace; class cmGeneratorExpression; class cmGeneratorTarget; class cmLocalGenerator; +class cmMakefile; class cmTest; /** \class cmTestGenerator @@ -43,6 +44,13 @@ public: std::vector Files; }; + static bool EvaluateBuildDependencies( + cmLocalGenerator* lg, std::string const& config, + cmListFileBacktrace const& backtrace, std::string const& testName, + std::vector const& command, + std::vector const& buildDepends, BuildDependencies& info, + cmMakefile* mf); + cmTestGenerator(cmTest* test, std::vector const& configurations = std::vector()); @@ -51,7 +59,7 @@ public: cmTestGenerator(cmTestGenerator const&) = delete; cmTestGenerator& operator=(cmTestGenerator const&) = delete; - void Compute(cmLocalGenerator* lg); + virtual void Compute(cmLocalGenerator* lg); bool GetBuildDependencies(cmLocalGenerator* lg, std::string const& config, BuildDependencies& deps); diff --git a/Tests/RunCMake/CMakeLists.txt b/Tests/RunCMake/CMakeLists.txt index 65fdcc3199..b40068e600 100644 --- a/Tests/RunCMake/CMakeLists.txt +++ b/Tests/RunCMake/CMakeLists.txt @@ -865,6 +865,7 @@ add_RunCMake_test(string -DCMake_TEST_NO_64BIT_TIME=${CMake_TEST_NO_64BIT_TIME} ) add_RunCMake_test(test_include_dirs) +add_RunCMake_test(TestDependency) add_RunCMake_test(BundleUtilities) if(APPLE) add_RunCMake_test(INSTALL_NAME_DIR) diff --git a/Tests/RunCMake/CTestCommandLine/RunCMakeTest.cmake b/Tests/RunCMake/CTestCommandLine/RunCMakeTest.cmake index 45e3435b82..d0f42e65af 100644 --- a/Tests/RunCMake/CTestCommandLine/RunCMakeTest.cmake +++ b/Tests/RunCMake/CTestCommandLine/RunCMakeTest.cmake @@ -216,8 +216,15 @@ block() set(RunCMake_TEST_BINARY_DIR "${out_of_date_build_dir}") run_cmake(out-of-date-configure) run_cmake_command(out-of-date-build "${CMAKE_COMMAND}" --build "${out_of_date_build_dir}" ${out_of_date_build_config_args}) + # file_gen is created below, so file-test is initially Not Run. + set(RunCMake_TEST_EXPECT_RESULT 8) + set(RunCMake_TEST_EXPECT_stdout "file-test.*Not Run") + set(RunCMake_TEST_EXPECT_stderr "Unable to find build dependency: [^\n]*file_gen") run_cmake_command(out-of-date-initial-test "${CMAKE_CTEST_COMMAND}" ${out_of_date_ctest_config_args} -V --test-dir "${out_of_date_build_dir}") + unset(RunCMake_TEST_EXPECT_RESULT) + unset(RunCMake_TEST_EXPECT_stdout) + unset(RunCMake_TEST_EXPECT_stderr) # Sleep for timestamp compare execute_process(COMMAND "${CMAKE_COMMAND}" -E sleep 1.125) diff --git a/Tests/RunCMake/CTestCommandLine/out-of-date-stdout.txt b/Tests/RunCMake/CTestCommandLine/out-of-date-stdout.txt index c1b25ce896..658342eead 100644 --- a/Tests/RunCMake/CTestCommandLine/out-of-date-stdout.txt +++ b/Tests/RunCMake/CTestCommandLine/out-of-date-stdout.txt @@ -4,4 +4,4 @@ Test project .* .*Start 3: shared-test.* .*Start 4: module-test.* .*Start 5: object-test.* -.*Start 6: file-test.* +.*Start 8: file-test.* diff --git a/Tests/RunCMake/GoogleTest/GoogleTestBuildDepends.cmake b/Tests/RunCMake/GoogleTest/GoogleTestBuildDepends.cmake new file mode 100644 index 0000000000..0e609b47a1 --- /dev/null +++ b/Tests/RunCMake/GoogleTest/GoogleTestBuildDepends.cmake @@ -0,0 +1,16 @@ +enable_language(CXX) +include(GoogleTest) + +enable_testing() + +set(CMAKE_TEST_BUILD_DEPENDS ON) + +add_executable(fake_gtest fake_gtest.cpp) + +gtest_discover_tests( + fake_gtest + TEST_PREFIX PREP: + TEST_FILTER basic* + EXTRA_ARGS how now "\"brown\" cow" + DISCOVERY_MODE POST_BUILD +) diff --git a/Tests/RunCMake/GoogleTest/RunCMakeTest.cmake b/Tests/RunCMake/GoogleTest/RunCMakeTest.cmake index daa08e034b..04e953c297 100644 --- a/Tests/RunCMake/GoogleTest/RunCMakeTest.cmake +++ b/Tests/RunCMake/GoogleTest/RunCMakeTest.cmake @@ -492,6 +492,32 @@ function(run_GoogleTest_discovery_duplicate) unset(RunCMake_TEST_OUTPUT_MERGE) endfunction() +function(run_GoogleTest_build_depends) + set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/GoogleTest-build-depends-build) + set(RunCMake_TEST_NO_CLEAN 1) + if(NOT RunCMake_GENERATOR_IS_MULTI_CONFIG) + set(RunCMake_TEST_OPTIONS -DCMAKE_BUILD_TYPE=Debug) + endif() + file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}") + file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}") + + run_cmake(GoogleTestBuildDepends) + + run_cmake_command(GoogleTest-build-depends-build + ${CMAKE_COMMAND} + --build . + --config Debug + --target test_prep/all + ) + + run_cmake_command(GoogleTest-build-depends-test + ${CMAKE_CTEST_COMMAND} + -C Debug + -R "^PREP:basic\\.case_(foo|bar)$" + --no-label-summary + ) +endfunction() + foreach(DISCOVERY_MODE POST_BUILD PRE_TEST) message(STATUS "Testing ${DISCOVERY_MODE} discovery mode via CMAKE_GTEST_DISCOVER_TESTS_DISCOVERY_MODE global override...") run_GoogleTest(${DISCOVERY_MODE}) @@ -546,3 +572,7 @@ if (NOT RunCMake_GENERATOR MATCHES "(Borland|NMake|Watcom)") endif() run_GoogleTest_discovery_duplicate() + +if(RunCMake_GENERATOR MATCHES "Ninja|FASTBuild|Makefiles") + run_GoogleTest_build_depends() +endif() diff --git a/Tests/RunCMake/TestDependency/AddTest.cmake b/Tests/RunCMake/TestDependency/AddTest.cmake new file mode 100644 index 0000000000..c6450ba364 --- /dev/null +++ b/Tests/RunCMake/TestDependency/AddTest.cmake @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 4.3) +project(TestDependencyAddTest C) + +enable_testing() +set(CMAKE_TEST_BUILD_DEPENDS ON) + +add_executable(fake_discovery fake_discovery.c) +# Ensure the timestamp check uses the artifact path, not the target name. +set_target_properties(fake_discovery PROPERTIES OUTPUT_NAME renamed_discovery) + +add_test(NAME ADD_TEST.command_genex + COMMAND $ fake_discovery case_foo) +set_tests_properties(ADD_TEST.command_genex PROPERTIES + LABELS label_one) + +add_test(NAME ADD_TEST.command_target + COMMAND "$<1:fake_discovery>" case_bar) +set_tests_properties(ADD_TEST.command_target PROPERTIES + LABELS label_two) + +add_test(NAME ADD_TEST.build_depends + COMMAND ${CMAKE_COMMAND} -E true + BUILD_DEPENDS fake_discovery) +set_tests_properties(ADD_TEST.build_depends PROPERTIES + LABELS label_three) diff --git a/Tests/RunCMake/TestDependency/CMakeLists.txt b/Tests/RunCMake/TestDependency/CMakeLists.txt new file mode 100644 index 0000000000..0ef5b1df35 --- /dev/null +++ b/Tests/RunCMake/TestDependency/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 4.3) + +project(TestDependencyRunCMake NONE) + +if(NOT DEFINED RunCMake_TEST) + message(FATAL_ERROR "RunCMake_TEST not set") +endif() + +include("${CMAKE_CURRENT_LIST_DIR}/${RunCMake_TEST}.cmake") + +foreach(target IN ITEMS fake_discovery fake_gtest) + if(TARGET ${target}) + file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${target}-$.txt" + CONTENT "$") + endif() +endforeach() diff --git a/Tests/RunCMake/add_test/TestDependency-DEFAULT.cmake b/Tests/RunCMake/TestDependency/DEFAULT.cmake similarity index 100% rename from Tests/RunCMake/add_test/TestDependency-DEFAULT.cmake rename to Tests/RunCMake/TestDependency/DEFAULT.cmake diff --git a/Tests/RunCMake/TestDependency/DirectoryGeneratedFile.cmake b/Tests/RunCMake/TestDependency/DirectoryGeneratedFile.cmake new file mode 100644 index 0000000000..d573a9550d --- /dev/null +++ b/Tests/RunCMake/TestDependency/DirectoryGeneratedFile.cmake @@ -0,0 +1,13 @@ +project(TestDependencyDirectoryGeneratedFile NONE) +enable_testing() +set(CMAKE_TEST_BUILD_DEPENDS ON) + +set(output "${CMAKE_CURRENT_BINARY_DIR}/directory-generated.txt") +add_custom_command(OUTPUT "${output}" + COMMAND "${CMAKE_COMMAND}" -E touch "${output}" + VERBATIM) +add_custom_target(directory_generated_owner DEPENDS "${output}") +set_property(DIRECTORY PROPERTY CMAKE_TEST_BUILD_DEPENDS "${output}") + +add_test(NAME DirectoryGeneratedFileBuilt + COMMAND "${CMAKE_COMMAND}" -E compare_files "${output}" "${output}") diff --git a/Tests/RunCMake/TestDependency/DiscoverTests.cmake b/Tests/RunCMake/TestDependency/DiscoverTests.cmake new file mode 100644 index 0000000000..d647c3a3fd --- /dev/null +++ b/Tests/RunCMake/TestDependency/DiscoverTests.cmake @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 4.3) +project(TestDependencyDiscoverGeneric C) + +enable_testing() +set(CMAKE_TEST_BUILD_DEPENDS ON) + +add_executable(fake_discovery fake_discovery.c) + +discover_tests( + COMMAND fake_discovery + DISCOVERY_ARGS --list_tests + DISCOVERY_MATCH "^([^,]+),([^,]+)$" + TEST_NAME "DISCOVER.\\1" + TEST_ARGS "\\1" + TEST_PROPERTIES + LABELS "\\2" +) diff --git a/Tests/RunCMake/TestDependency/DiscoverTestsBuildDepends.cmake b/Tests/RunCMake/TestDependency/DiscoverTestsBuildDepends.cmake new file mode 100644 index 0000000000..6834fc40a7 --- /dev/null +++ b/Tests/RunCMake/TestDependency/DiscoverTestsBuildDepends.cmake @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 4.3) +project(TestDependencyDiscoverGenericBuildDepends C) + +enable_testing() +set(CMAKE_TEST_BUILD_DEPENDS ON) + +add_executable(fake_discovery fake_discovery.c) + +discover_tests( + COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_SOURCE_DIR}/shared/fake_discovery_wrapper.cmake + "$" + DISCOVERY_ARGS --list_tests + DISCOVERY_MATCH "^([^,]+),([^,]+)$" + TEST_NAME "DISCOVER.\\1" + TEST_ARGS "\\1" + TEST_PROPERTIES + LABELS "\\2" + BUILD_DEPENDS + fake_discovery +) diff --git a/Tests/RunCMake/TestDependency/DiscoverTestsCommands.cmake b/Tests/RunCMake/TestDependency/DiscoverTestsCommands.cmake new file mode 100644 index 0000000000..d738f23304 --- /dev/null +++ b/Tests/RunCMake/TestDependency/DiscoverTestsCommands.cmake @@ -0,0 +1,32 @@ +project(TestDependencyDiscoverCommands C) +enable_testing() +set(CMAKE_TEST_BUILD_DEPENDS ON) + +add_executable(command_genex EXCLUDE_FROM_ALL fake_discovery.c) +add_executable(command_wrapper EXCLUDE_FROM_ALL fake_discovery.c) +add_executable(command_name EXCLUDE_FROM_ALL fake_discovery.c) + +discover_tests(COMMAND "$" + DISCOVERY_ARGS --list_tests + DISCOVERY_MATCH "^([^,]+),([^,]+)$" + TEST_NAME "GENEX.\\1" + TEST_ARGS "\\1") +discover_tests(COMMAND "${CMAKE_COMMAND}" -P + "${CMAKE_CURRENT_SOURCE_DIR}/shared/fake_discovery_wrapper.cmake" + "$" + DISCOVERY_ARGS --list_tests + DISCOVERY_MATCH "^([^,]+),([^,]+)$" + TEST_NAME "WRAPPER.\\1" + TEST_ARGS "\\1") +discover_tests(COMMAND command_forward + DISCOVERY_ARGS --list_tests + DISCOVERY_MATCH "^([^,]+),([^,]+)$" + TEST_NAME "FORWARD.\\1" + TEST_ARGS "\\1") +discover_tests(COMMAND "$<1:command_name>" + DISCOVERY_ARGS --list_tests + DISCOVERY_MATCH "^([^,]+),([^,]+)$" + TEST_NAME "NAME.\\1" + TEST_ARGS "\\1") + +add_executable(command_forward EXCLUDE_FROM_ALL fake_discovery.c) diff --git a/Tests/RunCMake/TestDependency/DiscoverTestsGeneratedFile.cmake b/Tests/RunCMake/TestDependency/DiscoverTestsGeneratedFile.cmake new file mode 100644 index 0000000000..04145c9e31 --- /dev/null +++ b/Tests/RunCMake/TestDependency/DiscoverTestsGeneratedFile.cmake @@ -0,0 +1,19 @@ +project(TestDependencyDiscoverGeneratedFile C) +enable_testing() +set(CMAKE_TEST_BUILD_DEPENDS ON) + +add_executable(fake_discovery EXCLUDE_FROM_ALL fake_discovery.c) +set(output "${CMAKE_CURRENT_BINARY_DIR}/generated.txt") +add_custom_command(OUTPUT "${output}" + COMMAND "${CMAKE_COMMAND}" -E touch "${output}" + VERBATIM) +add_custom_target(generated_owner DEPENDS "${output}") + +discover_tests(COMMAND fake_discovery + DISCOVERY_ARGS --list_tests + DISCOVERY_MATCH "^([^,]+),([^,]+)$" + TEST_NAME "GENERATED.\\1" + TEST_ARGS "\\1" + BUILD_DEPENDS "${output}") +add_test(NAME GeneratedFileBuilt + COMMAND "${CMAKE_COMMAND}" -E compare_files "${output}" "${output}") diff --git a/Tests/RunCMake/TestDependency/DiscoverTestsUtility.cmake b/Tests/RunCMake/TestDependency/DiscoverTestsUtility.cmake new file mode 100644 index 0000000000..7f4aecb92f --- /dev/null +++ b/Tests/RunCMake/TestDependency/DiscoverTestsUtility.cmake @@ -0,0 +1,19 @@ +project(TestDependencyDiscoverUtility C) +enable_testing() +set(CMAKE_TEST_BUILD_DEPENDS ON) + +add_executable(fake_discovery EXCLUDE_FROM_ALL fake_discovery.c) +add_custom_target(discovery_utility + COMMAND "${CMAKE_COMMAND}" -E touch "${CMAKE_CURRENT_BINARY_DIR}/utility-built.txt" + VERBATIM) + +discover_tests(COMMAND fake_discovery + DISCOVERY_ARGS --list_tests + DISCOVERY_MATCH "^([^,]+),([^,]+)$" + TEST_NAME "UTILITY.\\1" + TEST_ARGS "\\1" + BUILD_DEPENDS discovery_utility) +add_test(NAME UtilityBuilt + COMMAND "${CMAKE_COMMAND}" -E compare_files + "${CMAKE_CURRENT_BINARY_DIR}/utility-built.txt" + "${CMAKE_CURRENT_BINARY_DIR}/utility-built.txt") diff --git a/Tests/RunCMake/TestDependency/GoogleTest-POST_BUILD.cmake b/Tests/RunCMake/TestDependency/GoogleTest-POST_BUILD.cmake new file mode 100644 index 0000000000..b2fe1ec6a4 --- /dev/null +++ b/Tests/RunCMake/TestDependency/GoogleTest-POST_BUILD.cmake @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 4.3) +project(TestDependencyDiscoverPostBuild CXX) + +include(GoogleTest) + +enable_testing() +set(CMAKE_TEST_BUILD_DEPENDS ON) + +add_executable(fake_gtest fake_gtest_post_build.cpp) + +gtest_discover_tests( + fake_gtest + TEST_PREFIX POST: + TEST_FILTER basic* + DISCOVERY_MODE POST_BUILD +) diff --git a/Tests/RunCMake/TestDependency/GoogleTest-PRE_TEST.cmake b/Tests/RunCMake/TestDependency/GoogleTest-PRE_TEST.cmake new file mode 100644 index 0000000000..61a10cae37 --- /dev/null +++ b/Tests/RunCMake/TestDependency/GoogleTest-PRE_TEST.cmake @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 4.3) +project(TestDependencyGoogleTestPreTest CXX) + +include(GoogleTest) + +enable_testing() +set(CMAKE_TEST_BUILD_DEPENDS ON) + +add_executable(fake_gtest fake_gtest_post_build.cpp) + +gtest_discover_tests( + fake_gtest + TEST_PREFIX PRE: + TEST_FILTER basic* + DISCOVERY_MODE PRE_TEST +) diff --git a/Tests/RunCMake/TestDependency/MissingBuildDependency.cmake b/Tests/RunCMake/TestDependency/MissingBuildDependency.cmake new file mode 100644 index 0000000000..9da832f14f --- /dev/null +++ b/Tests/RunCMake/TestDependency/MissingBuildDependency.cmake @@ -0,0 +1,13 @@ +enable_testing() +set(CMAKE_TEST_BUILD_DEPENDS ON) + +add_test(NAME Direct + COMMAND "${CMAKE_COMMAND}" -E true + BUILD_DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/dependency.txt") +discover_tests( + COMMAND "${CMAKE_COMMAND}" -E echo + DISCOVERY_ARGS Discovered + DISCOVERY_MATCH "Discovered" + TEST_NAME Discovered + TEST_ARGS Discovered + BUILD_DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/dependency.txt") diff --git a/Tests/RunCMake/add_test/TestDependency-OFF.cmake b/Tests/RunCMake/TestDependency/OFF.cmake similarity index 100% rename from Tests/RunCMake/add_test/TestDependency-OFF.cmake rename to Tests/RunCMake/TestDependency/OFF.cmake diff --git a/Tests/RunCMake/add_test/TestDependency-ON-byproduct-stderr.txt b/Tests/RunCMake/TestDependency/ON-byproduct-stderr.txt similarity index 81% rename from Tests/RunCMake/add_test/TestDependency-ON-byproduct-stderr.txt rename to Tests/RunCMake/TestDependency/ON-byproduct-stderr.txt index 6030ece186..c42de80999 100644 --- a/Tests/RunCMake/add_test/TestDependency-ON-byproduct-stderr.txt +++ b/Tests/RunCMake/TestDependency/ON-byproduct-stderr.txt @@ -1,4 +1,4 @@ -CMake Warning at TestDependency-ON-byproduct\.cmake:[0-9]+ \(add_test\): +CMake Warning at ON-byproduct\.cmake:[0-9]+ \(add_test\): Test "ByproductTest" BUILD_DEPENDS file .*/TestDependencyByproduct-built\.txt diff --git a/Tests/RunCMake/add_test/TestDependency-ON-byproduct.cmake b/Tests/RunCMake/TestDependency/ON-byproduct.cmake similarity index 89% rename from Tests/RunCMake/add_test/TestDependency-ON-byproduct.cmake rename to Tests/RunCMake/TestDependency/ON-byproduct.cmake index c578d4c510..89ba63b5a7 100644 --- a/Tests/RunCMake/add_test/TestDependency-ON-byproduct.cmake +++ b/Tests/RunCMake/TestDependency/ON-byproduct.cmake @@ -5,7 +5,7 @@ project(TestDependencyByproduct C) enable_testing() -add_executable(TestDependencyByproductExe main.c) +add_executable(TestDependencyByproductExe ../add_test/main.c) add_custom_command(TARGET TestDependencyByproductExe POST_BUILD COMMAND "${CMAKE_COMMAND}" -E touch diff --git a/Tests/RunCMake/add_test/TestDependency-ON-colon-name-stderr.txt b/Tests/RunCMake/TestDependency/ON-colon-name-stderr.txt similarity index 77% rename from Tests/RunCMake/add_test/TestDependency-ON-colon-name-stderr.txt rename to Tests/RunCMake/TestDependency/ON-colon-name-stderr.txt index 822804e781..37182d1e9e 100644 --- a/Tests/RunCMake/add_test/TestDependency-ON-colon-name-stderr.txt +++ b/Tests/RunCMake/TestDependency/ON-colon-name-stderr.txt @@ -1,4 +1,4 @@ -CMake Warning at TestDependency-ON-colon-name\.cmake:[0-9]+ \(add_test\): +CMake Warning at ON-colon-name\.cmake:[0-9]+ \(add_test\): Test "Foo::Bar" has a name containing ':', which cannot be used as a Makefile build target\. No "test_prep/" target will be generated for it\. Use the Ninja generator or rename the test to build its dependencies with a diff --git a/Tests/RunCMake/add_test/TestDependency-ON-colon-name.cmake b/Tests/RunCMake/TestDependency/ON-colon-name.cmake similarity index 100% rename from Tests/RunCMake/add_test/TestDependency-ON-colon-name.cmake rename to Tests/RunCMake/TestDependency/ON-colon-name.cmake diff --git a/Tests/RunCMake/add_test/TestDependency-ON-file-generate-config.cmake b/Tests/RunCMake/TestDependency/ON-file-generate-config.cmake similarity index 88% rename from Tests/RunCMake/add_test/TestDependency-ON-file-generate-config.cmake rename to Tests/RunCMake/TestDependency/ON-file-generate-config.cmake index 60eb52eb42..5ec294681c 100644 --- a/Tests/RunCMake/add_test/TestDependency-ON-file-generate-config.cmake +++ b/Tests/RunCMake/TestDependency/ON-file-generate-config.cmake @@ -5,7 +5,7 @@ project(TestDependencyFileGenerateConfig C) enable_testing() -add_executable(TestDependencyGenexFileGenerate main.c) +add_executable(TestDependencyGenexFileGenerate ../add_test/main.c) set(rc_file "${CMAKE_CURRENT_BINARY_DIR}/gen/TestDependencyGenexFileGenerate/$/version.rc") diff --git a/Tests/RunCMake/TestDependency/ON-imported.cmake b/Tests/RunCMake/TestDependency/ON-imported.cmake new file mode 100644 index 0000000000..f217d5a773 --- /dev/null +++ b/Tests/RunCMake/TestDependency/ON-imported.cmake @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 4.3) +set(CMAKE_TEST_BUILD_DEPENDS ON) + +project(TestDependencyImported C) + +enable_testing() + +add_executable(TestDependencyImportedExe ../add_test/main.c) + +add_executable(TestDependencyImportedTool IMPORTED) +set_target_properties(TestDependencyImportedTool PROPERTIES + IMPORTED_LOCATION "${CMAKE_COMMAND}") + +add_test(NAME ImportedTest + COMMAND TestDependencyImportedExe + BUILD_DEPENDS TestDependencyImportedTool) diff --git a/Tests/RunCMake/add_test/TestDependency-ON-interface.cmake b/Tests/RunCMake/TestDependency/ON-interface.cmake similarity index 88% rename from Tests/RunCMake/add_test/TestDependency-ON-interface.cmake rename to Tests/RunCMake/TestDependency/ON-interface.cmake index 48ccc0abcc..66f26bc842 100644 --- a/Tests/RunCMake/add_test/TestDependency-ON-interface.cmake +++ b/Tests/RunCMake/TestDependency/ON-interface.cmake @@ -5,7 +5,7 @@ project(TestDependencyInterface C) enable_testing() -add_executable(TestDependencyInterfaceExe main.c) +add_executable(TestDependencyInterfaceExe ../add_test/main.c) # Header-only INTERFACE library is not part of the build system and must be # filtered out of the test_prep dependencies so that no dead diff --git a/Tests/RunCMake/add_test/TestDependency-ON-invalid-test-name-result.txt b/Tests/RunCMake/TestDependency/ON-invalid-test-name-result.txt similarity index 100% rename from Tests/RunCMake/add_test/TestDependency-ON-invalid-test-name-result.txt rename to Tests/RunCMake/TestDependency/ON-invalid-test-name-result.txt diff --git a/Tests/RunCMake/add_test/TestDependency-ON-invalid-test-name-stderr.txt b/Tests/RunCMake/TestDependency/ON-invalid-test-name-stderr.txt similarity index 60% rename from Tests/RunCMake/add_test/TestDependency-ON-invalid-test-name-stderr.txt rename to Tests/RunCMake/TestDependency/ON-invalid-test-name-stderr.txt index 576b6b12e0..405311e403 100644 --- a/Tests/RunCMake/add_test/TestDependency-ON-invalid-test-name-stderr.txt +++ b/Tests/RunCMake/TestDependency/ON-invalid-test-name-stderr.txt @@ -1,5 +1,5 @@ -CMake Error at TestDependency-ON-invalid-test-name\.cmake:[0-9]+ \(add_test\): +CMake Error at ON-invalid-test-name\.cmake:[0-9]+ \(add_test\): add_test Cannot set build dependencies for a test with NAME "Target Build Test Invalid Name" which is not a valid target name\. Call Stack \(most recent call first\): - CMakeLists\.txt:3 \(include\) + CMakeLists\.txt:[0-9]+ \(include\) diff --git a/Tests/RunCMake/add_test/TestDependency-ON-invalid-test-name.cmake b/Tests/RunCMake/TestDependency/ON-invalid-test-name.cmake similarity index 100% rename from Tests/RunCMake/add_test/TestDependency-ON-invalid-test-name.cmake rename to Tests/RunCMake/TestDependency/ON-invalid-test-name.cmake diff --git a/Tests/RunCMake/add_test/TestDependency-ON-orphan-stderr.txt b/Tests/RunCMake/TestDependency/ON-orphan-stderr.txt similarity index 82% rename from Tests/RunCMake/add_test/TestDependency-ON-orphan-stderr.txt rename to Tests/RunCMake/TestDependency/ON-orphan-stderr.txt index 2bb78b30a2..abd42dab7d 100644 --- a/Tests/RunCMake/add_test/TestDependency-ON-orphan-stderr.txt +++ b/Tests/RunCMake/TestDependency/ON-orphan-stderr.txt @@ -1,4 +1,4 @@ -CMake Warning at TestDependency-ON-orphan\.cmake:[0-9]+ \(add_test\): +CMake Warning at ON-orphan\.cmake:[0-9]+ \(add_test\): Test "OrphanTest" BUILD_DEPENDS file .*/TestDependencyOrphan-built\.txt diff --git a/Tests/RunCMake/add_test/TestDependency-ON-orphan.cmake b/Tests/RunCMake/TestDependency/ON-orphan.cmake similarity index 100% rename from Tests/RunCMake/add_test/TestDependency-ON-orphan.cmake rename to Tests/RunCMake/TestDependency/ON-orphan.cmake diff --git a/Tests/RunCMake/add_test/TestDependency-ON.cmake b/Tests/RunCMake/TestDependency/ON.cmake similarity index 100% rename from Tests/RunCMake/add_test/TestDependency-ON.cmake rename to Tests/RunCMake/TestDependency/ON.cmake diff --git a/Tests/RunCMake/TestDependency/ObjectLibrary.cmake b/Tests/RunCMake/TestDependency/ObjectLibrary.cmake new file mode 100644 index 0000000000..b6ffbbaf9a --- /dev/null +++ b/Tests/RunCMake/TestDependency/ObjectLibrary.cmake @@ -0,0 +1,18 @@ +project(TestDependencyObjectLibrary C) +enable_testing() +set(CMAKE_TEST_BUILD_DEPENDS ON) + +add_executable(fake_discovery EXCLUDE_FROM_ALL fake_discovery.c) +add_library(dependency_objects OBJECT EXCLUDE_FROM_ALL fake_discovery.c) +file(GENERATE + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/dependency_objects-$.txt" + CONTENT "$") + +add_test(NAME OBJECT.direct COMMAND "${CMAKE_COMMAND}" -E true + BUILD_DEPENDS dependency_objects) +discover_tests(COMMAND fake_discovery + DISCOVERY_ARGS --list_tests + DISCOVERY_MATCH "^([^,]+),([^,]+)$" + TEST_NAME "OBJECT.\\1" + TEST_ARGS "\\1" + BUILD_DEPENDS dependency_objects) diff --git a/Tests/RunCMake/TestDependency/RunCMakeTest.cmake b/Tests/RunCMake/TestDependency/RunCMakeTest.cmake new file mode 100644 index 0000000000..04cb62f3f8 --- /dev/null +++ b/Tests/RunCMake/TestDependency/RunCMakeTest.cmake @@ -0,0 +1,289 @@ +include(RunCMake) + +# Verify test_prep behavior +function(run_testdependency_case CASE_NAME EXPECT_PRESENT) + set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${CASE_NAME}-build) + + # 1. Configure + run_cmake(${CASE_NAME}) + set(RunCMake_TEST_NO_CLEAN 1) + + # 2. Verify build targets exist + run_cmake_command(${CASE_NAME}-check + ${CMAKE_COMMAND} + -DRunCMake_TEST_BINARY_DIR=${RunCMake_TEST_BINARY_DIR} + -Dexpect_present=${EXPECT_PRESENT} + -P ${RunCMake_SOURCE_DIR}/check-targets.cmake) + + # If build targets enabled... + if (EXPECT_PRESENT) + + # Build test_prep target + run_cmake_command(${CASE_NAME}-build + ${CMAKE_COMMAND} --build . ${TestDependency_BUILD_CONFIG_ARG} + --target test_prep/TargetBuildTest) + + # Verify building test_prep target built test dependencies + run_cmake_command(${CASE_NAME}-build-check + ${CMAKE_COMMAND} + -DRunCMake_TEST_BINARY_DIR=${RunCMake_TEST_BINARY_DIR} + -P ${RunCMake_SOURCE_DIR}/build-check.cmake) + endif() + unset(RunCMake_TEST_NO_CLEAN) +endfunction() + +# Ensure generated test file includes BUILD_DEPENDs +function(check_discovered_build_depends CASE_NAME TESTS_FILE) + set(RunCMake_TEST_NO_CLEAN 1) + run_cmake_command(${CASE_NAME}-build-depends + ${CMAKE_COMMAND} + -Dtests_file=${TESTS_FILE} + -Ddependency_manifest=${TestDependency_DEPENDENCY_MANIFEST} + -P ${RunCMake_SOURCE_DIR}/check-discovered-build-depends.cmake) + unset(RunCMake_TEST_NO_CLEAN) +endfunction() + +# Find discovered test file for GTest +function(find_discovered_tests_file OUT_VAR BINARY_DIR TARGET_NAME) + file(GLOB discovered_tests_files + "${BINARY_DIR}/${TARGET_NAME}*_tests.cmake") + list(FILTER discovered_tests_files EXCLUDE REGEX [[.*_include\.cmake$]]) + list(LENGTH discovered_tests_files discovered_tests_files_length) + if(NOT discovered_tests_files_length EQUAL 1) + message(FATAL_ERROR + "Expected exactly one discovered tests file for ${TARGET_NAME}, got: ${discovered_tests_files}") + endif() + set(${OUT_VAR} "${discovered_tests_files}" PARENT_SCOPE) +endfunction() + +# Verify ctest --out-of-date behavior +function(run_discover_out_of_date_case CASE_NAME TARGET_NAME EXPECTED_TESTS_REGEX) + cmake_parse_arguments(arg + "ADD_TEST;GTEST" + "" + "" + ${ARGN}) + + set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${CASE_NAME}-build) + + # 1. Configure + run_cmake(${CASE_NAME}) + set(RunCMake_TEST_NO_CLEAN 1) + + # 2. Build dependencies needed for the tests. + run_cmake_command(${CASE_NAME}-build + ${CMAKE_COMMAND} --build . ${TestDependency_BUILD_CONFIG_ARG} + --target test_prep/all) + + # 3. Run ctest once, including any PRE_TEST discovery. + # Ensure test stamps are newer than build dependencies even on filesystems + # with one-second timestamp resolution. Equal timestamps are out of date. + run_cmake_command(${CASE_NAME}-build-sleep + ${CMAKE_COMMAND} -E sleep 1.1) + set(RunCMake_TEST_EXPECT_stderr ".*") + run_cmake_command(${CASE_NAME}-initial + ${CMAKE_CTEST_COMMAND} ${TestDependency_CTEST_CONFIG_ARG}) + + # 4. Check that generated files include dependencies. + if(arg_GTEST) + find_discovered_tests_file(discovered_tests_file + ${RunCMake_TEST_BINARY_DIR} ${TARGET_NAME}) + check_discovered_build_depends(${CASE_NAME} ${discovered_tests_file}) + else() + set(discovered_tests_file + "${RunCMake_TEST_BINARY_DIR}/CTestTestfile.cmake") + check_discovered_build_depends(${CASE_NAME} ${discovered_tests_file}) + endif() + + if(TestDependency_DEPENDENCY_MANIFEST) + set(RunCMake_TEST_EXPECT_stdout "Total Tests: 0") + run_cmake_command(${CASE_NAME}-up-to-date + ${CMAKE_CTEST_COMMAND} ${TestDependency_CTEST_CONFIG_ARG} --out-of-date -N) + unset(RunCMake_TEST_EXPECT_stdout) + endif() + + # 5. Touch the actual generated artifact, after the last test timestamp. + file(READ "${RunCMake_TEST_BINARY_DIR}/${TARGET_NAME}-${TestDependency_CONFIG}.txt" + dependent_files) + foreach(dependent_file IN LISTS dependent_files) + if(NOT EXISTS "${dependent_file}") + message(FATAL_ERROR "Dependency was not built: ${dependent_file}") + endif() + endforeach() + run_cmake_command(${CASE_NAME}-sleep + ${CMAKE_COMMAND} -E sleep 1.1) + run_cmake_command(${CASE_NAME}-touch + ${CMAKE_COMMAND} -E touch ${dependent_files}) + + # 6. Run ctest --out-of-date and ensure test reruns + set(RunCMake_TEST_EXPECT_stdout + "${EXPECTED_TESTS_REGEX}") + run_cmake_command(${CASE_NAME}-out-of-date + ${CMAKE_CTEST_COMMAND} ${TestDependency_CTEST_CONFIG_ARG} --out-of-date) + unset(RunCMake_TEST_NO_CLEAN) + unset(RunCMake_TEST_EXPECT_stderr) + unset(RunCMake_TEST_EXPECT_stdout) +endfunction() + +if(RunCMake_GENERATOR MATCHES "Ninja|FASTBuild|Makefiles") + block() + # Use the same configuration for generated manifests, builds, and CTest, + # including single-config generators whose default build type varies. + set(TestDependency_CONFIG Debug) + set(TestDependency_BUILD_CONFIG_ARG --config ${TestDependency_CONFIG}) + set(TestDependency_CTEST_CONFIG_ARG -C ${TestDependency_CONFIG}) + if(NOT RunCMake_GENERATOR_IS_MULTI_CONFIG) + list(APPEND RunCMake_TEST_OPTIONS -DCMAKE_BUILD_TYPE=${TestDependency_CONFIG}) + endif() + + # Don't run tests with missing dependencies + block() + set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/MissingBuildDependency-build) + run_cmake(MissingBuildDependency) + set(RunCMake_TEST_NO_CLEAN 1) + block() + set(RunCMake_TEST_EXPECT_RESULT 8) + set(RunCMake_TEST_EXPECT_stdout "Direct[^\n]*Not Run.*Discovered[^\n]*Not Run") + set(RunCMake_TEST_EXPECT_stderr "Unable to find build dependency: [^\n]*dependency.txt") + run_cmake_command(MissingBuildDependency-test + ${CMAKE_CTEST_COMMAND} ${TestDependency_CTEST_CONFIG_ARG}) + run_cmake_command(MissingBuildDependency-out-of-date + ${CMAKE_CTEST_COMMAND} ${TestDependency_CTEST_CONFIG_ARG} --out-of-date) + endblock() + file(WRITE "${RunCMake_TEST_BINARY_DIR}/dependency.txt" "ready\n") + set(RunCMake_TEST_EXPECT_stdout "100% tests passed out of 2") + run_cmake_command(MissingBuildDependency-available + ${CMAKE_CTEST_COMMAND} ${TestDependency_CTEST_CONFIG_ARG} --out-of-date) + # Missing dependencies make tests out of date even with existing stamps. + file(REMOVE "${RunCMake_TEST_BINARY_DIR}/dependency.txt") + set(RunCMake_TEST_EXPECT_RESULT 8) + set(RunCMake_TEST_EXPECT_stdout "Direct[^\n]*Not Run.*Discovered[^\n]*Not Run") + set(RunCMake_TEST_EXPECT_stderr "Unable to find build dependency: [^\n]*dependency.txt") + run_cmake_command(MissingBuildDependency-removed + ${CMAKE_CTEST_COMMAND} ${TestDependency_CTEST_CONFIG_ARG} --out-of-date) + endblock() + + # "Failure" cases, do not generate test dependencies + block() + run_testdependency_case(DEFAULT FALSE) + run_testdependency_case(OFF FALSE) + run_cmake(ON-invalid-test-name) + endblock() + + # A header-only INTERFACE library dependency is filtered out by the + # generator-independent dependency resolution, so building the + # test_prep target must not fail on a missing rule. + block() + set(RunCMake_TEST_BINARY_DIR + ${RunCMake_BINARY_DIR}/ON-interface-build) + run_cmake(ON-interface) + set(RunCMake_TEST_NO_CLEAN 1) + set(RunCMake_TEST_OUTPUT_MERGE 1) + run_cmake_command(ON-interface-build + ${CMAKE_COMMAND} --build . ${TestDependency_BUILD_CONFIG_ARG} + --target test_prep/InterfaceTest) + unset(RunCMake_TEST_OUTPUT_MERGE) + endblock() + + # Imported dependencies are accepted without requiring a build rule. + block() + set(RunCMake_TEST_BINARY_DIR + ${RunCMake_BINARY_DIR}/ON-imported-build) + run_cmake(ON-imported) + set(RunCMake_TEST_NO_CLEAN 1) + set(RunCMake_TEST_OUTPUT_MERGE 1) + run_cmake_command(ON-imported-build + ${CMAKE_COMMAND} --build . ${TestDependency_BUILD_CONFIG_ARG} + --target test_prep/ImportedTest) + unset(RunCMake_TEST_OUTPUT_MERGE) + run_cmake_command(ON-imported-test + ${CMAKE_CTEST_COMMAND} ${TestDependency_CTEST_CONFIG_ARG} + -R "^ImportedTest$") + endblock() + + # Exceptions for Makefile generator + if(RunCMake_GENERATOR MATCHES Makefiles) + # Diagnostics specific to the Makefile generators. + block() + # A ':' in a test name cannot be expressed as a Makefile target. + set(RunCMake_TEST_BINARY_DIR + ${RunCMake_BINARY_DIR}/ON-colon-name-build) + run_cmake(ON-colon-name) + endblock() + block() + # A byproduct file cannot be built through a single owning target. + set(RunCMake_TEST_BINARY_DIR + ${RunCMake_BINARY_DIR}/ON-byproduct-build) + run_cmake(ON-byproduct) + endblock() + block() + # A custom-command output owned by no target cannot be built. + set(RunCMake_TEST_BINARY_DIR + ${RunCMake_BINARY_DIR}/ON-orphan-build) + run_cmake(ON-orphan) + endblock() + endif() + + # Verify successful behavior + block() + foreach(case IN ITEMS DiscoverTestsUtility DiscoverTestsCommands + DiscoverTestsGeneratedFile DirectoryGeneratedFile) + set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${case}-build) + run_cmake(${case}) + set(RunCMake_TEST_NO_CLEAN 1) + run_cmake_command(${case}-build + ${CMAKE_COMMAND} --build . ${TestDependency_BUILD_CONFIG_ARG} + --target test_prep/all) + set(RunCMake_TEST_EXPECT_stdout "100% tests passed") + run_cmake_command(${case}-test + ${CMAKE_CTEST_COMMAND} ${TestDependency_CTEST_CONFIG_ARG}) + unset(RunCMake_TEST_EXPECT_stdout) + unset(RunCMake_TEST_NO_CLEAN) + endforeach() + + if(RunCMake_GENERATOR STREQUAL "Ninja Multi-Config") + set(RunCMake_TEST_BINARY_DIR + ${RunCMake_BINARY_DIR}/ON-file-generate-config-build) + run_cmake(ON-file-generate-config) + set(RunCMake_TEST_NO_CLEAN 1) + run_cmake_command(ON-file-generate-config-build + ${CMAKE_COMMAND} --build . --config Debug + --target test_prep/FileGenerateConfigTest) + run_cmake_command(ON-file-generate-config-test + ${CMAKE_CTEST_COMMAND} -C Debug -R "^FileGenerateConfigTest$") + unset(RunCMake_TEST_NO_CLEAN) + endif() + + block() + set(TestDependency_DEPENDENCY_MANIFEST + "${RunCMake_BINARY_DIR}/ObjectLibrary-build/dependency_objects-${TestDependency_CONFIG}.txt") + run_discover_out_of_date_case(ObjectLibrary dependency_objects + "OBJECT\\.direct.*OBJECT\\.case_foo.*OBJECT\\.case_bar") + endblock() + + # Verify test_depends + run_testdependency_case(ON TRUE) + + # Verify test_depends + --out-of-date for add_test + run_discover_out_of_date_case(AddTest fake_discovery + "ADD_TEST\\.command_genex.*ADD_TEST\\.command_target.*ADD_TEST\\.build_depends" + ADD_TEST) + + # Verify test_depends + --out-of-date for discover_tests + run_discover_out_of_date_case(DiscoverTests fake_discovery + "DISCOVER\\.case_foo.*DISCOVER\\.case_bar") + + # Verify explicit BUILD_DEPENDS for discover_tests + run_discover_out_of_date_case(DiscoverTestsBuildDepends fake_discovery + "DISCOVER\\.case_foo.*DISCOVER\\.case_bar") + + # Verify test_depends + --out-of-date for gtest_discover_tests + run_discover_out_of_date_case(GoogleTest-PRE_TEST fake_gtest + "PRE:basic\\.case_foo.*PRE:basic\\.case_bar" + GTEST) + run_discover_out_of_date_case(GoogleTest-POST_BUILD fake_gtest + "POST:basic\\.case_foo.*POST:basic\\.case_bar" + GTEST) + endblock() + endblock() +endif() diff --git a/Tests/RunCMake/add_test/TestDependency.cmake b/Tests/RunCMake/TestDependency/TestDependency.cmake similarity index 92% rename from Tests/RunCMake/add_test/TestDependency.cmake rename to Tests/RunCMake/TestDependency/TestDependency.cmake index 312dd8a030..d2a2207ba4 100644 --- a/Tests/RunCMake/add_test/TestDependency.cmake +++ b/Tests/RunCMake/TestDependency/TestDependency.cmake @@ -2,7 +2,7 @@ project(TestDependency C) enable_testing() -add_executable(TestDependencyExe main.c) +add_executable(TestDependencyExe ../add_test/main.c) add_custom_command(TARGET TestDependencyExe POST_BUILD COMMAND "${CMAKE_COMMAND}" -E touch @@ -10,7 +10,7 @@ add_custom_command(TARGET TestDependencyExe POST_BUILD BYPRODUCTS "${CMAKE_CURRENT_BINARY_DIR}/TestDependencyExe-built.txt") -add_executable(TestDependencyGenex main.c) +add_executable(TestDependencyGenex ../add_test/main.c) add_custom_command(TARGET TestDependencyGenex POST_BUILD COMMAND "${CMAKE_COMMAND}" -E touch diff --git a/Tests/RunCMake/add_test/TestDependencySubdir/CMakeLists.txt b/Tests/RunCMake/TestDependency/TestDependencySubdir/CMakeLists.txt similarity index 100% rename from Tests/RunCMake/add_test/TestDependencySubdir/CMakeLists.txt rename to Tests/RunCMake/TestDependency/TestDependencySubdir/CMakeLists.txt diff --git a/Tests/RunCMake/add_test/TestDependency-build-check.cmake b/Tests/RunCMake/TestDependency/build-check.cmake similarity index 100% rename from Tests/RunCMake/add_test/TestDependency-build-check.cmake rename to Tests/RunCMake/TestDependency/build-check.cmake diff --git a/Tests/RunCMake/TestDependency/check-discovered-build-depends.cmake b/Tests/RunCMake/TestDependency/check-discovered-build-depends.cmake new file mode 100644 index 0000000000..8b1eec63ea --- /dev/null +++ b/Tests/RunCMake/TestDependency/check-discovered-build-depends.cmake @@ -0,0 +1,33 @@ +if(NOT DEFINED tests_file) + message(FATAL_ERROR "tests_file not set") +endif() + +if(NOT EXISTS "${tests_file}") + message(FATAL_ERROR "Discovered tests file not found: ${tests_file}") +endif() + +file(READ "${tests_file}" content) + +if(NOT content MATCHES "_CMAKE_TEST_BUILD_DEPENDS" + AND NOT content MATCHES "BUILD_DEPENDS") + message(FATAL_ERROR + "Expected discovered tests to contain BUILD_DEPENDS metadata.") +endif() + +if(dependency_manifest) + file(READ "${dependency_manifest}" dependencies) + file(STRINGS "${tests_file}" metadata REGEX "BUILD_DEPENDS") + foreach(dependency IN LISTS dependencies) + set(count 0) + foreach(line IN LISTS metadata) + string(FIND "${line}" "${dependency}" pos) + if(NOT pos EQUAL -1) + math(EXPR count "${count} + 1") + endif() + endforeach() + # Both add_test and discover_tests must record the object, not its target name. + if(count LESS 2) + message(FATAL_ERROR "Missing object file dependency metadata: ${dependency}") + endif() + endforeach() +endif() diff --git a/Tests/RunCMake/add_test/TestDependency-check-targets.cmake b/Tests/RunCMake/TestDependency/check-targets.cmake similarity index 91% rename from Tests/RunCMake/add_test/TestDependency-check-targets.cmake rename to Tests/RunCMake/TestDependency/check-targets.cmake index b0c2ae615a..011cf5405d 100644 --- a/Tests/RunCMake/add_test/TestDependency-check-targets.cmake +++ b/Tests/RunCMake/TestDependency/check-targets.cmake @@ -16,7 +16,7 @@ file(GLOB build_files LIST_DIRECTORIES false set(found FALSE) foreach(build_file IN LISTS build_files) file(READ "${build_file}" content) - if(content MATCHES "test_prep.all|test_prep.TargetBuildTest") + if(content MATCHES "test_prep.all|test_prep.TargetBuildTest|test_prep.directory_") set(found TRUE) break() endif() diff --git a/Tests/RunCMake/TestDependency/fake_discovery.c b/Tests/RunCMake/TestDependency/fake_discovery.c new file mode 100644 index 0000000000..867f5bb1e5 --- /dev/null +++ b/Tests/RunCMake/TestDependency/fake_discovery.c @@ -0,0 +1,13 @@ +#include +#include + +int main(int argc, char** argv) +{ + if (argc >= 2 && strcmp(argv[1], "--list_tests") == 0) { + puts("case_foo,label_one"); + puts("case_bar,label_two"); + return 0; + } + + return 0; +} diff --git a/Tests/RunCMake/TestDependency/fake_gtest_post_build.cpp b/Tests/RunCMake/TestDependency/fake_gtest_post_build.cpp new file mode 100644 index 0000000000..6916b5c313 --- /dev/null +++ b/Tests/RunCMake/TestDependency/fake_gtest_post_build.cpp @@ -0,0 +1,58 @@ +#include +#include +#include + +int main(int argc, char** argv) +{ + if (argc >= 2 && std::string(argv[1]) == "--gtest_list_tests") { + bool saw_json = false; + bool saw_basic_filter = false; + for (int i = 2; i < argc; ++i) { + std::string const arg = argv[i]; + if (arg.find("--gtest_output=json:") == 0) { + saw_json = true; + } else if (arg == "--gtest_filter=basic*") { + saw_basic_filter = true; + } else { + return 1; + } + } + + if (saw_json && argc >= 3) { + for (int i = 2; i < argc; ++i) { + std::string const arg = argv[i]; + if (arg.find("--gtest_output=json:") == 0) { + std::string const json_path = arg.substr(20); + std::ofstream json(json_path.c_str()); + json << "{\n" + " \"name\": \"AllTests\",\n" + " \"testsuites\": [\n" + " {\n" + " \"name\": \"basic\",\n" + " \"tests\": 2,\n" + " \"testsuite\": [\n" + " { \"name\": \"case_foo\", \"file\": \"file1.cpp\", " + "\"line\": 1 },\n" + " { \"name\": \"case_bar\", \"file\": \"file1.cpp\", " + "\"line\": 2 }\n" + " ]\n" + " }\n" + " ]\n" + "}\n"; + break; + } + } + } + + if (!saw_basic_filter && argc >= 3) { + return 1; + } + + std::cout << "basic.\n"; + std::cout << " case_foo\n"; + std::cout << " case_bar\n"; + return 0; + } + + return 0; +} diff --git a/Tests/RunCMake/TestDependency/shared/fake_discovery_wrapper.cmake b/Tests/RunCMake/TestDependency/shared/fake_discovery_wrapper.cmake new file mode 100644 index 0000000000..c2ee8ad59a --- /dev/null +++ b/Tests/RunCMake/TestDependency/shared/fake_discovery_wrapper.cmake @@ -0,0 +1,10 @@ +set(args) +math(EXPR last_arg_index "${CMAKE_ARGC} - 1") +foreach(arg_index RANGE 4 ${last_arg_index}) + list(APPEND args "${CMAKE_ARGV${arg_index}}") +endforeach() + +execute_process( + COMMAND "${CMAKE_ARGV3}" ${args} + COMMAND_ERROR_IS_FATAL ANY +) diff --git a/Tests/RunCMake/add_test/RunCMakeTest.cmake b/Tests/RunCMake/add_test/RunCMakeTest.cmake index 6437082360..e61e29417a 100644 --- a/Tests/RunCMake/add_test/RunCMakeTest.cmake +++ b/Tests/RunCMake/add_test/RunCMakeTest.cmake @@ -61,95 +61,3 @@ block() run_cmake_command(TestLauncher-test ${CMAKE_CTEST_COMMAND} -C Debug -V) endblock() unset(RunCMake_TEST_OPTIONS) - -function(run_testdependency_case CASE_NAME EXPECT_PRESENT) - set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/TestDependency-${CASE_NAME}-build) - run_cmake(TestDependency-${CASE_NAME}) - set(RunCMake_TEST_NO_CLEAN 1) - run_cmake_command(TestDependency-${CASE_NAME}-check - ${CMAKE_COMMAND} - -DRunCMake_TEST_BINARY_DIR=${RunCMake_TEST_BINARY_DIR} - -Dexpect_present=${EXPECT_PRESENT} - -P ${RunCMake_SOURCE_DIR}/TestDependency-check-targets.cmake) - unset(RunCMake_TEST_NO_CLEAN) -endfunction() - -if(RunCMake_GENERATOR MATCHES "Ninja|FASTBuild|Makefiles") - block() - if(RunCMake_GENERATOR_IS_MULTI_CONFIG) - set(TestDependency_BUILD_CONFIG_ARG --config Debug) - else() - set(TestDependency_BUILD_CONFIG_ARG) - endif() - - run_testdependency_case(DEFAULT FALSE) - run_testdependency_case(OFF FALSE) - run_cmake(TestDependency-ON-invalid-test-name) - - block() - # A header-only INTERFACE library dependency is filtered out by the - # generator-independent dependency resolution, so building the - # test_prep target must not fail on a missing rule. - set(RunCMake_TEST_BINARY_DIR - ${RunCMake_BINARY_DIR}/TestDependency-ON-interface-build) - run_cmake(TestDependency-ON-interface) - set(RunCMake_TEST_NO_CLEAN 1) - set(RunCMake_TEST_OUTPUT_MERGE 1) - run_cmake_command(TestDependency-ON-interface-build - ${CMAKE_COMMAND} --build . ${TestDependency_BUILD_CONFIG_ARG} - --target test_prep/InterfaceTest) - endblock() - - if(RunCMake_GENERATOR MATCHES Makefiles) - # Diagnostics specific to the Makefile generators. - block() - # A ':' in a test name cannot be expressed as a Makefile target. - set(RunCMake_TEST_BINARY_DIR - ${RunCMake_BINARY_DIR}/TestDependency-ON-colon-name-build) - run_cmake(TestDependency-ON-colon-name) - endblock() - block() - # A byproduct file cannot be built through a single owning target. - set(RunCMake_TEST_BINARY_DIR - ${RunCMake_BINARY_DIR}/TestDependency-ON-byproduct-build) - run_cmake(TestDependency-ON-byproduct) - endblock() - block() - # A custom-command output owned by no target cannot be built. - set(RunCMake_TEST_BINARY_DIR - ${RunCMake_BINARY_DIR}/TestDependency-ON-orphan-build) - run_cmake(TestDependency-ON-orphan) - endblock() - endif() - - set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/TestDependency-ON-build) - run_testdependency_case(ON TRUE) - - if(RunCMake_GENERATOR MATCHES "Ninja" AND RunCMake_GENERATOR_IS_MULTI_CONFIG) - block() - set(RunCMake_TEST_BINARY_DIR - ${RunCMake_BINARY_DIR}/TestDependency-ON-file-generate-config-build) - run_cmake(TestDependency-ON-file-generate-config) - set(RunCMake_TEST_NO_CLEAN 1) - run_cmake_command(TestDependency-ON-file-generate-config-build - ${CMAKE_COMMAND} --build . --config Debug - --target test_prep/FileGenerateConfigTest) - endblock() - endif() - - set(RunCMake_TEST_OUTPUT_MERGE 1) - set(RunCMake_TEST_NO_CLEAN 1) - run_cmake_command(TestDependency-ON-all - ${CMAKE_COMMAND} --build . ${TestDependency_BUILD_CONFIG_ARG} - --target test_prep/all) - run_cmake_command(TestDependency-ON-build - ${CMAKE_COMMAND} --build . ${TestDependency_BUILD_CONFIG_ARG} - --target test_prep/TargetBuildTest) - run_cmake_command(TestDependency-ON-build-check - ${CMAKE_COMMAND} - -DRunCMake_TEST_BINARY_DIR=${RunCMake_TEST_BINARY_DIR} - -P ${RunCMake_SOURCE_DIR}/TestDependency-build-check.cmake) - unset(RunCMake_TEST_OUTPUT_MERGE) - unset(RunCMake_TEST_NO_CLEAN) - endblock() -endif()