diff options
41 files changed, 1125 insertions, 153 deletions
diff --git a/Help/manual/cmake-properties.7.rst b/Help/manual/cmake-properties.7.rst index 2cb6a1a..271f497 100644 --- a/Help/manual/cmake-properties.7.rst +++ b/Help/manual/cmake-properties.7.rst @@ -304,6 +304,9 @@ Properties on Tests /prop_test/DEPENDS /prop_test/ENVIRONMENT /prop_test/FAIL_REGULAR_EXPRESSION + /prop_test/FIXTURES_CLEANUP + /prop_test/FIXTURES_REQUIRED + /prop_test/FIXTURES_SETUP /prop_test/LABELS /prop_test/MEASUREMENT /prop_test/PASS_REGULAR_EXPRESSION diff --git a/Help/manual/cmake-server.7.rst b/Help/manual/cmake-server.7.rst index 75aa0ee..00ffcd1 100644 --- a/Help/manual/cmake-server.7.rst +++ b/Help/manual/cmake-server.7.rst @@ -49,12 +49,16 @@ Operation Start :manual:`cmake(1)` in the server command mode, supplying the path to the build directory to process:: - cmake -E server + cmake -E server (--debug|--pipe <NAMED_PIPE>) -The server will start up and reply with an hello message on stdout:: +The server will communicate using stdin/stdout (with the ``--debug`` parameter) +or using a named pipe (with the ``--pipe <NAMED_PIPE>`` parameter). + +When connecting to the server (via named pipe or by starting it in ``--debug`` +mode), the server will reply with a hello message:: [== CMake Server ==[ - {"supportedProtocolVersions":[{"major":0,"minor":1}],"type":"hello"} + {"supportedProtocolVersions":[{"major":1,"minor":0}],"type":"hello"} ]== CMake Server ==] Messages sent to and from the process are wrapped in magic strings:: @@ -65,7 +69,8 @@ Messages sent to and from the process are wrapped in magic strings:: } ]== CMake Server ==] -The server is now ready to accept further requests via stdin. +The server is now ready to accept further requests via the named pipe +or stdin. Debugging diff --git a/Help/prop_test/DEPENDS.rst b/Help/prop_test/DEPENDS.rst index ee946d9..89c7553 100644 --- a/Help/prop_test/DEPENDS.rst +++ b/Help/prop_test/DEPENDS.rst @@ -3,4 +3,8 @@ DEPENDS Specifies that this test should only be run after the specified list of tests. -Set this to a list of tests that must finish before this test is run. +Set this to a list of tests that must finish before this test is run. The +results of those tests are not considered, the dependency relationship is +purely for order of execution (i.e. it is really just a *run after* +relationship). Consider using test fixtures with setup tests if a dependency +with successful completion is required (see :prop_test:`FIXTURES_REQUIRED`). diff --git a/Help/prop_test/FIXTURES_CLEANUP.rst b/Help/prop_test/FIXTURES_CLEANUP.rst new file mode 100644 index 0000000..f0a4be0 --- /dev/null +++ b/Help/prop_test/FIXTURES_CLEANUP.rst @@ -0,0 +1,46 @@ +FIXTURES_CLEANUP +---------------- + +Specifies a list of fixtures for which the test is to be treated as a cleanup +test. + +Fixture cleanup tests are ordinary tests with all of the usual test +functionality. Setting the ``FIXTURES_CLEANUP`` property for a test has two +primary effects: + +- CTest will ensure the test executes after all other tests which list any of + the fixtures in its :prop_test:`FIXTURES_REQUIRED` property. + +- If CTest is asked to run only a subset of tests (e.g. using regular + expressions or the ``--rerun-failed`` option) and the cleanup test is not in + the set of tests to run, it will automatically be added if any tests in the + set require any fixture listed in ``FIXTURES_CLEANUP``. + +A cleanup test can have multiple fixtures listed in its ``FIXTURES_CLEANUP`` +property. It will execute only once for the whole CTest run, not once for each +fixture. A fixture can also have more than one cleanup test defined. If there +are multiple cleanup tests for a fixture, projects can control their order with +the usual :prop_test:`DEPENDS` test property if necessary. + +A cleanup test is allowed to require other fixtures, but not any fixture listed +in its ``FIXTURES_CLEANUP`` property. For example: + +.. code-block:: cmake + + # Ok: Dependent fixture is different to cleanup + set_tests_properties(cleanupFoo PROPERTIES + FIXTURES_CLEANUP Foo + FIXTURES_REQUIRED Bar + ) + + # Error: cannot require same fixture as cleanup + set_tests_properties(cleanupFoo PROPERTIES + FIXTURES_CLEANUP Foo + FIXTURES_REQUIRED Foo + ) + +Cleanup tests will execute even if setup or regular tests for that fixture fail +or are skipped. + +See :prop_test:`FIXTURES_REQUIRED` for a more complete discussion of how to use +test fixtures. diff --git a/Help/prop_test/FIXTURES_REQUIRED.rst b/Help/prop_test/FIXTURES_REQUIRED.rst new file mode 100644 index 0000000..e37dfb5 --- /dev/null +++ b/Help/prop_test/FIXTURES_REQUIRED.rst @@ -0,0 +1,94 @@ +FIXTURES_REQUIRED +----------------- + +Specifies a list of fixtures the test requires. Fixture names are case +sensitive. + +Fixtures are a way to attach setup and cleanup tasks to a set of tests. If a +test requires a given fixture, then all tests marked as setup tasks for that +fixture will be executed first (once for the whole set of tests, not once per +test requiring the fixture). After all tests requiring a particular fixture +have completed, CTest will ensure all tests marked as cleanup tasks for that +fixture are then executed. Tests are marked as setup tasks with the +:prop_test:`FIXTURES_SETUP` property and as cleanup tasks with the +:prop_test:`FIXTURES_CLEANUP` property. If any of a fixture's setup tests fail, +all tests listing that fixture in their ``FIXTURES_REQUIRED`` property will not +be executed. The cleanup tests for the fixture will always be executed, even if +some setup tests fail. + +When CTest is asked to execute only a subset of tests (e.g. by the use of +regular expressions or when run with the ``--rerun-failed`` command line +option), it will automatically add any setup or cleanup tests for fixtures +required by any of the tests that are in the execution set. + +Since setup and cleanup tasks are also tests, they can have an ordering +specified by the :prop_test:`DEPENDS` test property just like any other tests. +This can be exploited to implement setup or cleanup using multiple tests for a +single fixture to modularise setup or cleanup logic. + +The concept of a fixture is different to that of a resource specified by +:prop_test:`RESOURCE_LOCK`, but they may be used together. A fixture defines a +set of tests which share setup and cleanup requirements, whereas a resource +lock has the effect of ensuring a particular set of tests do not run in +parallel. Some situations may need both, such as setting up a database, +serialising test access to that database and deleting the database again at the +end. For such cases, tests would populate both ``FIXTURES_REQUIRED`` and +:prop_test:`RESOURCE_LOCK` to combine the two behaviours. Names used for +:prop_test:`RESOURCE_LOCK` have no relationship with names of fixtures, so note +that a resource lock does not imply a fixture and vice versa. + +Consider the following example which represents a database test scenario +similar to that mentioned above: + +.. code-block:: cmake + + add_test(NAME testsDone COMMAND emailResults) + add_test(NAME fooOnly COMMAND testFoo) + add_test(NAME dbOnly COMMAND testDb) + add_test(NAME dbWithFoo COMMAND testDbWithFoo) + add_test(NAME createDB COMMAND initDB) + add_test(NAME setupUsers COMMAND userCreation) + add_test(NAME cleanupDB COMMAND deleteDB) + add_test(NAME cleanupFoo COMMAND removeFoos) + + set_tests_properties(setupUsers PROPERTIES DEPENDS createDB) + + set_tests_properties(createDB PROPERTIES FIXTURES_SETUP DB) + set_tests_properties(setupUsers PROPERTIES FIXTURES_SETUP DB) + set_tests_properties(cleanupDB PROPERTIES FIXTURES_CLEANUP DB) + set_tests_properties(cleanupFoo PROPERTIES FIXTURES_CLEANUP Foo) + set_tests_properties(testsDone PROPERTIES FIXTURES_CLEANUP "DB;Foo") + + set_tests_properties(fooOnly PROPERTIES FIXTURES_REQUIRED Foo) + set_tests_properties(dbOnly PROPERTIES FIXTURES_REQUIRED DB) + set_tests_properties(dbWithFoo PROPERTIES FIXTURES_REQUIRED "DB;Foo") + + set_tests_properties(dbOnly dbWithFoo createDB setupUsers cleanupDB + PROPERTIES RESOURCE_LOCK DbAccess) + +Key points from this example: + +- Two fixtures are defined: ``DB`` and ``Foo``. Tests can require a single + fixture as ``fooOnly`` and ``dbOnly`` do, or they can depend on multiple + fixtures like ``dbWithFoo`` does. + +- A ``DEPENDS`` relationship is set up to ensure ``setupUsers`` happens after + ``createDB``, both of which are setup tests for the ``DB`` fixture and will + therefore be executed before the ``dbOnly`` and ``dbWithFoo`` tests + automatically. + +- No explicit ``DEPENDS`` relationships were needed to make the setup tests run + before or the cleanup tests run after the regular tests. + +- The ``Foo`` fixture has no setup tests defined, only a single cleanup test. + +- ``testsDone`` is a cleanup test for both the ``DB`` and ``Foo`` fixtures. + Therefore, it will only execute once regular tests for both fixtures have + finished (i.e. after ``fooOnly``, ``dbOnly`` and ``dbWithFoo``). No + ``DEPENDS`` relationship was specified for ``testsDone``, so it is free to + run before, after or concurrently with other cleanup tests for either + fixture. + +- The setup and cleanup tests never list the fixtures they are for in their own + ``FIXTURES_REQUIRED`` property, as that would result in a dependency on + themselves and be considered an error. diff --git a/Help/prop_test/FIXTURES_SETUP.rst b/Help/prop_test/FIXTURES_SETUP.rst new file mode 100644 index 0000000..a220215 --- /dev/null +++ b/Help/prop_test/FIXTURES_SETUP.rst @@ -0,0 +1,47 @@ +FIXTURES_SETUP +-------------- + +Specifies a list of fixtures for which the test is to be treated as a setup +test. + +Fixture setup tests are ordinary tests with all of the usual test +functionality. Setting the ``FIXTURES_SETUP`` property for a test has two +primary effects: + +- CTest will ensure the test executes before any other test which lists the + fixture(s) in its :prop_test:`FIXTURES_REQUIRED` property. + +- If CTest is asked to run only a subset of tests (e.g. using regular + expressions or the ``--rerun-failed`` option) and the setup test is not in + the set of tests to run, it will automatically be added if any tests in the + set require any fixture listed in ``FIXTURES_SETUP``. + +A setup test can have multiple fixtures listed in its ``FIXTURES_SETUP`` +property. It will execute only once for the whole CTest run, not once for each +fixture. A fixture can also have more than one setup test defined. If there are +multiple setup tests for a fixture, projects can control their order with the +usual :prop_test:`DEPENDS` test property if necessary. + +A setup test is allowed to require other fixtures, but not any fixture listed +in its ``FIXTURES_SETUP`` property. For example: + +.. code-block:: cmake + + # Ok: dependent fixture is different to setup + set_tests_properties(setupFoo PROPERTIES + FIXTURES_SETUP Foo + FIXTURES_REQUIRED Bar + ) + + # Error: cannot require same fixture as setup + set_tests_properties(setupFoo PROPERTIES + FIXTURES_SETUP Foo + FIXTURES_REQUIRED Foo + ) + +If any of a fixture's setup tests fail, none of the tests listing that fixture +in its :prop_test:`FIXTURES_REQUIRED` property will be run. Cleanup tests will, +however, still be executed. + +See :prop_test:`FIXTURES_REQUIRED` for a more complete discussion of how to use +test fixtures. diff --git a/Help/prop_test/RESOURCE_LOCK.rst b/Help/prop_test/RESOURCE_LOCK.rst index 8c30f01..755e0aa 100644 --- a/Help/prop_test/RESOURCE_LOCK.rst +++ b/Help/prop_test/RESOURCE_LOCK.rst @@ -5,3 +5,6 @@ Specify a list of resources that are locked by this test. If multiple tests specify the same resource lock, they are guaranteed not to run concurrently. + +See also :prop_test:`FIXTURES_REQUIRED` if the resource requires any setup or +cleanup steps. diff --git a/Help/release/dev/test-fixtures.rst b/Help/release/dev/test-fixtures.rst new file mode 100644 index 0000000..a466c46 --- /dev/null +++ b/Help/release/dev/test-fixtures.rst @@ -0,0 +1,8 @@ +test-fixtures +------------- + +* CTest now supports test fixtures through the new :prop_test:`FIXTURES_SETUP`, + :prop_test:`FIXTURES_CLEANUP` and :prop_test:`FIXTURES_REQUIRED` test + properties. When using regular expressions or ``--rerun-failed`` to limit + the tests to be run, a fixture's setup and cleanup tests will automatically + be added to the execution set if any test requires that fixture. diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index a2dead6..5e136e4 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -789,6 +789,7 @@ target_link_libraries(cmake CMakeLib) if(CMake_HAVE_SERVER_MODE) add_library(CMakeServerLib cmServer.cxx cmServer.h + cmServerConnection.cxx cmServerConnection.h cmServerProtocol.cxx cmServerProtocol.h ) target_link_libraries(CMakeServerLib CMakeLib) diff --git a/Source/CMakeVersion.cmake b/Source/CMakeVersion.cmake index d725626..27ca0ca 100644 --- a/Source/CMakeVersion.cmake +++ b/Source/CMakeVersion.cmake @@ -1,5 +1,5 @@ # CMake version number components. set(CMake_VERSION_MAJOR 3) set(CMake_VERSION_MINOR 6) -set(CMake_VERSION_PATCH 20160921) +set(CMake_VERSION_PATCH 20160923) #set(CMake_VERSION_RC 1) diff --git a/Source/CTest/cmCTestMultiProcessHandler.cxx b/Source/CTest/cmCTestMultiProcessHandler.cxx index d65c915..d8e1312 100644 --- a/Source/CTest/cmCTestMultiProcessHandler.cxx +++ b/Source/CTest/cmCTestMultiProcessHandler.cxx @@ -137,6 +137,16 @@ void cmCTestMultiProcessHandler::StartTestProcess(int test) testRun->SetIndex(test); testRun->SetTestProperties(this->Properties[test]); + // Find any failed dependencies for this test. We assume the more common + // scenario has no failed tests, so make it the outer loop. + for (std::vector<std::string>::const_iterator it = this->Failed->begin(); + it != this->Failed->end(); ++it) { + if (this->Properties[test]->RequireSuccessDepends.find(*it) != + this->Properties[test]->RequireSuccessDepends.end()) { + testRun->AddFailedDependency(*it); + } + } + std::string current_dir = cmSystemTools::GetCurrentWorkingDirectory(); cmSystemTools::ChangeDirectory(this->Properties[test]->Directory); diff --git a/Source/CTest/cmCTestRunTest.cxx b/Source/CTest/cmCTestRunTest.cxx index e7e51d5..fb53a7e 100644 --- a/Source/CTest/cmCTestRunTest.cxx +++ b/Source/CTest/cmCTestRunTest.cxx @@ -177,7 +177,8 @@ bool cmCTestRunTest::EndTest(size_t completed, size_t total, bool started) passIt; bool forceFail = false; bool outputTestErrorsToConsole = false; - if (!this->TestProperties->RequiredRegularExpressions.empty()) { + if (!this->TestProperties->RequiredRegularExpressions.empty() && + this->FailedDependencies.empty()) { bool found = false; for (passIt = this->TestProperties->RequiredRegularExpressions.begin(); passIt != this->TestProperties->RequiredRegularExpressions.end(); @@ -201,7 +202,8 @@ bool cmCTestRunTest::EndTest(size_t completed, size_t total, bool started) } reason += "]"; } - if (!this->TestProperties->ErrorRegularExpressions.empty()) { + if (!this->TestProperties->ErrorRegularExpressions.empty() && + this->FailedDependencies.empty()) { for (passIt = this->TestProperties->ErrorRegularExpressions.begin(); passIt != this->TestProperties->ErrorRegularExpressions.end(); ++passIt) { @@ -437,6 +439,23 @@ bool cmCTestRunTest::StartTest(size_t total) this->TestResult.Name = this->TestProperties->Name; this->TestResult.Path = this->TestProperties->Directory; + if (!this->FailedDependencies.empty()) { + this->TestProcess = new cmProcess; + std::string msg = "Failed test dependencies:"; + for (std::set<std::string>::const_iterator it = + this->FailedDependencies.begin(); + it != this->FailedDependencies.end(); ++it) { + msg += " " + *it; + } + *this->TestHandler->LogFile << msg << std::endl; + cmCTestLog(this->CTest, HANDLER_OUTPUT, msg << std::endl); + this->TestResult.Output = msg; + this->TestResult.FullCommandLine = ""; + this->TestResult.CompletionStatus = "Not Run"; + this->TestResult.Status = cmCTestTestHandler::NOT_RUN; + return false; + } + if (args.size() >= 2 && args[1] == "NOT_AVAILABLE") { this->TestProcess = new cmProcess; std::string msg; diff --git a/Source/CTest/cmCTestRunTest.h b/Source/CTest/cmCTestRunTest.h index 0ca434d..bae633d 100644 --- a/Source/CTest/cmCTestRunTest.h +++ b/Source/CTest/cmCTestRunTest.h @@ -49,6 +49,11 @@ public: int GetIndex() { return this->Index; } + void AddFailedDependency(const std::string& failedTest) + { + this->FailedDependencies.insert(failedTest); + } + std::string GetProcessOutput() { return this->ProcessOutput; } bool IsStopTimePassed() { return this->StopTimePassed; } @@ -106,6 +111,7 @@ private: // The test results cmCTestTestHandler::cmCTestTestResult TestResult; int Index; + std::set<std::string> FailedDependencies; std::string StartTime; std::string ActualCommand; std::vector<std::string> Arguments; diff --git a/Source/CTest/cmCTestTestHandler.cxx b/Source/CTest/cmCTestTestHandler.cxx index 479c516..18e24d6 100644 --- a/Source/CTest/cmCTestTestHandler.cxx +++ b/Source/CTest/cmCTestTestHandler.cxx @@ -33,6 +33,7 @@ #include <cmsys/RegularExpression.hxx> #include <functional> #include <iomanip> +#include <iterator> #include <set> #include <sstream> #include <stdio.h> @@ -762,6 +763,9 @@ void cmCTestTestHandler::ComputeTestList() it->Index = cnt; // save the index into the test list for this test finalList.push_back(*it); } + + UpdateForFixtures(finalList); + // Save the total number of tests before exclusions this->TotalNumberOfTests = this->TestList.size(); // Set the TestList to the final list of all test @@ -791,6 +795,8 @@ void cmCTestTestHandler::ComputeTestListForRerunFailed() finalList.push_back(*it); } + UpdateForFixtures(finalList); + // Save the total number of tests before exclusions this->TotalNumberOfTests = this->TestList.size(); @@ -800,6 +806,169 @@ void cmCTestTestHandler::ComputeTestListForRerunFailed() this->UpdateMaxTestNameWidth(); } +void cmCTestTestHandler::UpdateForFixtures(ListOfTests& tests) const +{ + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Updating test list for fixtures" << std::endl, + this->Quiet); + + // Prepare some maps to help us find setup and cleanup tests for + // any given fixture + typedef std::set<ListOfTests::const_iterator> TestIteratorSet; + typedef std::map<std::string, TestIteratorSet> FixtureDependencies; + FixtureDependencies fixtureSetups; + FixtureDependencies fixtureDeps; + + for (ListOfTests::const_iterator it = this->TestList.begin(); + it != this->TestList.end(); ++it) { + const cmCTestTestProperties& p = *it; + + const std::set<std::string>& setups = p.FixturesSetup; + for (std::set<std::string>::const_iterator depsIt = setups.begin(); + depsIt != setups.end(); ++depsIt) { + fixtureSetups[*depsIt].insert(it); + fixtureDeps[*depsIt].insert(it); + } + + const std::set<std::string>& cleanups = p.FixturesCleanup; + for (std::set<std::string>::const_iterator depsIt = cleanups.begin(); + depsIt != cleanups.end(); ++depsIt) { + fixtureDeps[*depsIt].insert(it); + } + } + + // Prepare fast lookup of tests already included in our list of tests + std::set<std::string> addedTests; + for (ListOfTests::const_iterator it = tests.begin(); it != tests.end(); + ++it) { + const cmCTestTestProperties& p = *it; + addedTests.insert(p.Name); + } + + // This is a lookup of fixture name to a list of indices into the + // final tests array for tests which require that fixture. It is + // needed at the end to populate dependencies of the cleanup tests + // in our final list of tests. + std::map<std::string, std::vector<size_t> > fixtureRequirements; + + // Use integer index for iteration because we append to + // the tests vector as we go + size_t fixtureTestsAdded = 0; + std::set<std::string> addedFixtures; + for (size_t i = 0; i < tests.size(); ++i) { + if (tests[i].FixturesRequired.empty()) { + continue; + } + // Must copy the set of fixtures because we may invalidate + // the tests array by appending to it + const std::set<std::string> fixtures = tests[i].FixturesRequired; + for (std::set<std::string>::const_iterator fixturesIt = fixtures.begin(); + fixturesIt != fixtures.end(); ++fixturesIt) { + + const std::string& requiredFixtureName = *fixturesIt; + if (requiredFixtureName.empty()) { + continue; + } + + fixtureRequirements[requiredFixtureName].push_back(i); + + // Add dependencies to this test for all of the setup tests + // associated with the required fixture. If any of those setup + // tests fail, this test should not run. We make the fixture's + // cleanup tests depend on this test case later. + FixtureDependencies::const_iterator setupIt = + fixtureSetups.find(requiredFixtureName); + if (setupIt != fixtureSetups.end()) { + for (TestIteratorSet::const_iterator sIt = setupIt->second.begin(); + sIt != setupIt->second.end(); ++sIt) { + const std::string& setupTestName = (**sIt).Name; + tests[i].RequireSuccessDepends.insert(setupTestName); + if (std::find(tests[i].Depends.begin(), tests[i].Depends.end(), + setupTestName) == tests[i].Depends.end()) { + tests[i].Depends.push_back(setupTestName); + } + } + } + + // Append any fixture setup/cleanup tests to our test list if they + // are not already in it (they could have been in the original + // set of tests passed to us at the outset or have already been + // added from a previously checked test). A fixture isn't required + // to have setup/cleanup tests. + if (!addedFixtures.insert(requiredFixtureName).second) { + // Already added this fixture + continue; + } + FixtureDependencies::const_iterator fixtureIt = + fixtureDeps.find(requiredFixtureName); + if (fixtureIt == fixtureDeps.end()) { + // No setup or cleanup tests for this fixture + continue; + } + + const TestIteratorSet& testIters = fixtureIt->second; + for (TestIteratorSet::const_iterator depsIt = testIters.begin(); + depsIt != testIters.end(); ++depsIt) { + ListOfTests::const_iterator lotIt = *depsIt; + const cmCTestTestProperties& p = *lotIt; + + if (!addedTests.insert(p.Name).second) { + // Already have p in our test list + continue; + } + + // This is a test not yet in our list, so add it and + // update its index to reflect where it was in the original + // full list of all tests (needed to track individual tests + // across ctest runs for re-run failed, etc.) + tests.push_back(p); + tests.back().Index = + 1 + static_cast<int>(std::distance(this->TestList.begin(), lotIt)); + ++fixtureTestsAdded; + + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Added test " + << p.Name << " required by fixture " + << requiredFixtureName << std::endl, + this->Quiet); + } + } + } + + // Now that we have the final list of tests, we can update all cleanup + // tests to depend on those tests which require that fixture + for (ListOfTests::iterator tIt = tests.begin(); tIt != tests.end(); ++tIt) { + cmCTestTestProperties& p = *tIt; + const std::set<std::string>& cleanups = p.FixturesCleanup; + for (std::set<std::string>::const_iterator fIt = cleanups.begin(); + fIt != cleanups.end(); ++fIt) { + const std::string& fixture = *fIt; + std::map<std::string, std::vector<size_t> >::const_iterator cIt = + fixtureRequirements.find(fixture); + if (cIt == fixtureRequirements.end()) { + // No test cases require the fixture this cleanup test is for. + // This cleanup test must have been part of the original test + // list passed in (which is not an error) + continue; + } + + const std::vector<size_t>& indices = cIt->second; + for (std::vector<size_t>::const_iterator indexIt = indices.begin(); + indexIt != indices.end(); ++indexIt) { + const std::string& reqTestName = tests[*indexIt].Name; + if (std::find(p.Depends.begin(), p.Depends.end(), reqTestName) == + p.Depends.end()) { + p.Depends.push_back(reqTestName); + } + } + } + } + + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Added " + << fixtureTestsAdded + << " tests to meet fixture requirements" << std::endl, + this->Quiet); +} + void cmCTestTestHandler::UpdateMaxTestNameWidth() { std::string::size_type max = this->CTest->GetMaxTestNameWidth(); @@ -1829,6 +1998,24 @@ bool cmCTestTestHandler::SetTestsProperties( rtit->LockedResources.insert(lval.begin(), lval.end()); } + if (key == "FIXTURES_SETUP") { + std::vector<std::string> lval; + cmSystemTools::ExpandListArgument(val, lval); + + rtit->FixturesSetup.insert(lval.begin(), lval.end()); + } + if (key == "FIXTURES_CLEANUP") { + std::vector<std::string> lval; + cmSystemTools::ExpandListArgument(val, lval); + + rtit->FixturesCleanup.insert(lval.begin(), lval.end()); + } + if (key == "FIXTURES_REQUIRED") { + std::vector<std::string> lval; + cmSystemTools::ExpandListArgument(val, lval); + + rtit->FixturesRequired.insert(lval.begin(), lval.end()); + } if (key == "TIMEOUT") { rtit->Timeout = atof(val.c_str()); rtit->ExplicitTimeout = true; diff --git a/Source/CTest/cmCTestTestHandler.h b/Source/CTest/cmCTestTestHandler.h index a5e62dc..beaf8e0 100644 --- a/Source/CTest/cmCTestTestHandler.h +++ b/Source/CTest/cmCTestTestHandler.h @@ -139,6 +139,10 @@ public: std::vector<std::string> Environment; std::vector<std::string> Labels; std::set<std::string> LockedResources; + std::set<std::string> FixturesSetup; + std::set<std::string> FixturesCleanup; + std::set<std::string> FixturesRequired; + std::set<std::string> RequireSuccessDepends; }; struct cmCTestTestResult @@ -251,6 +255,11 @@ private: // based on LastTestFailed.log void ComputeTestListForRerunFailed(); + // add required setup/cleanup tests not already in the + // list of tests to be run and update dependencies between + // tests to account for fixture setup/cleanup + void UpdateForFixtures(ListOfTests& tests) const; + void UpdateMaxTestNameWidth(); bool GetValue(const char* tag, std::string& value, std::istream& fin); diff --git a/Source/QtDialog/CMakeSetup.cxx b/Source/QtDialog/CMakeSetup.cxx index 4de4bef..52809b0 100644 --- a/Source/QtDialog/CMakeSetup.cxx +++ b/Source/QtDialog/CMakeSetup.cxx @@ -109,11 +109,13 @@ int main(int argc, char** argv) QTextCodec::setCodecForLocale(utf8_codec); #endif +#if QT_VERSION < 0x050000 // clean out standard Qt paths for plugins, which we don't use anyway // when creating Mac bundles, it potentially causes problems foreach (QString p, QApplication::libraryPaths()) { QApplication::removeLibraryPath(p); } +#endif // tell the cmake library where cmake is QDir cmExecDir(QApplication::applicationDirPath()); diff --git a/Source/cmServer.cxx b/Source/cmServer.cxx index be001a7..56cd7ba 100644 --- a/Source/cmServer.cxx +++ b/Source/cmServer.cxx @@ -13,6 +13,7 @@ #include "cmServer.h" +#include "cmServerConnection.h" #include "cmServerProtocol.h" #include "cmSystemTools.h" #include "cmVersionMacros.h" @@ -40,57 +41,6 @@ static const std::string kMESSAGE_TYPE = "message"; static const std::string kSTART_MAGIC = "[== CMake Server ==["; static const std::string kEND_MAGIC = "]== CMake Server ==]"; -typedef struct -{ - uv_write_t req; - uv_buf_t buf; -} write_req_t; - -void alloc_buffer(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) -{ - (void)handle; - *buf = uv_buf_init(static_cast<char*>(malloc(suggested_size)), - static_cast<unsigned int>(suggested_size)); -} - -void free_write_req(uv_write_t* req) -{ - write_req_t* wr = reinterpret_cast<write_req_t*>(req); - free(wr->buf.base); - free(wr); -} - -void on_stdout_write(uv_write_t* req, int status) -{ - (void)status; - auto server = reinterpret_cast<cmServer*>(req->data); - free_write_req(req); - server->PopOne(); -} - -void write_data(uv_stream_t* dest, std::string content, uv_write_cb cb) -{ - write_req_t* req = static_cast<write_req_t*>(malloc(sizeof(write_req_t))); - req->req.data = dest->data; - req->buf = uv_buf_init(static_cast<char*>(malloc(content.size())), - static_cast<unsigned int>(content.size())); - memcpy(req->buf.base, content.c_str(), content.size()); - uv_write(reinterpret_cast<uv_write_t*>(req), static_cast<uv_stream_t*>(dest), - &req->buf, 1, cb); -} - -void read_stdin(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) -{ - if (nread > 0) { - auto server = reinterpret_cast<cmServer*>(stream->data); - std::string result = std::string(buf->base, buf->base + nread); - server->handleData(result); - } - - if (buf->base) - free(buf->base); -} - class cmServer::DebugInfo { public: @@ -105,9 +55,11 @@ public: uint64_t StartTime; }; -cmServer::cmServer(bool supportExperimental) - : SupportExperimental(supportExperimental) +cmServer::cmServer(cmServerConnection* conn, bool supportExperimental) + : Connection(conn) + , SupportExperimental(supportExperimental) { + this->Connection->SetServer(this); // Register supported protocols: this->RegisterProtocol(new cmServerProtocol1_0); } @@ -118,18 +70,15 @@ cmServer::~cmServer() return; } - uv_close(reinterpret_cast<uv_handle_t*>(this->InputStream), NULL); - uv_close(reinterpret_cast<uv_handle_t*>(this->OutputStream), NULL); - uv_loop_close(this->Loop); - for (cmServerProtocol* p : this->SupportedProtocols) { delete p; } + + delete this->Connection; } void cmServer::PopOne() { - this->Writing = false; if (this->Queue.empty()) { return; } @@ -173,39 +122,6 @@ void cmServer::PopOne() } } -void cmServer::handleData(const std::string& data) -{ - this->DataBuffer += data; - - for (;;) { - auto needle = this->DataBuffer.find('\n'); - - if (needle == std::string::npos) { - return; - } - std::string line = this->DataBuffer.substr(0, needle); - const auto ls = line.size(); - if (ls > 1 && line.at(ls - 1) == '\r') - line.erase(ls - 1, 1); - this->DataBuffer.erase(this->DataBuffer.begin(), - this->DataBuffer.begin() + needle + 1); - if (line == kSTART_MAGIC) { - this->JsonData.clear(); - continue; - } - if (line == kEND_MAGIC) { - this->Queue.push_back(this->JsonData); - this->JsonData.clear(); - if (!this->Writing) { - this->PopOne(); - } - } else { - this->JsonData += line; - this->JsonData += "\n"; - } - } -} - void cmServer::RegisterProtocol(cmServerProtocol* protocol) { if (protocol->IsExperimental() && !this->SupportExperimental) { @@ -245,6 +161,12 @@ void cmServer::PrintHello() const this->WriteJsonObject(hello, nullptr); } +void cmServer::QueueRequest(const std::string& request) +{ + this->Queue.push_back(request); + this->PopOne(); +} + void cmServer::reportProgress(const char* msg, float progress, void* data) { const cmServerRequest* request = static_cast<const cmServerRequest*>(data); @@ -312,43 +234,16 @@ cmServerResponse cmServer::SetProtocolVersion(const cmServerRequest& request) return request.Reply(Json::objectValue); } -bool cmServer::Serve() +bool cmServer::Serve(std::string* errorMessage) { if (this->SupportedProtocols.empty()) { + *errorMessage = + "No protocol versions defined. Maybe you need --experimental?"; return false; } assert(!this->Protocol); - this->Loop = uv_default_loop(); - - if (uv_guess_handle(1) == UV_TTY) { - uv_tty_init(this->Loop, &this->Input.tty, 0, 1); - uv_tty_set_mode(&this->Input.tty, UV_TTY_MODE_NORMAL); - this->Input.tty.data = this; - InputStream = reinterpret_cast<uv_stream_t*>(&this->Input.tty); - - uv_tty_init(this->Loop, &this->Output.tty, 1, 0); - uv_tty_set_mode(&this->Output.tty, UV_TTY_MODE_NORMAL); - this->Output.tty.data = this; - OutputStream = reinterpret_cast<uv_stream_t*>(&this->Output.tty); - } else { - uv_pipe_init(this->Loop, &this->Input.pipe, 0); - uv_pipe_open(&this->Input.pipe, 0); - this->Input.pipe.data = this; - InputStream = reinterpret_cast<uv_stream_t*>(&this->Input.pipe); - - uv_pipe_init(this->Loop, &this->Output.pipe, 0); - uv_pipe_open(&this->Output.pipe, 1); - this->Output.pipe.data = this; - OutputStream = reinterpret_cast<uv_stream_t*>(&this->Output.pipe); - } - - this->PrintHello(); - - uv_read_start(this->InputStream, alloc_buffer, read_stdin); - - uv_run(this->Loop, UV_RUN_DEFAULT); - return true; + return Connection->ProcessEvents(errorMessage); } void cmServer::WriteJsonObject(const Json::Value& jsonValue, @@ -385,10 +280,8 @@ void cmServer::WriteJsonObject(const Json::Value& jsonValue, } } - this->Writing = true; - write_data(this->OutputStream, std::string("\n") + kSTART_MAGIC + - std::string("\n") + result + kEND_MAGIC + std::string("\n"), - on_stdout_write); + Connection->WriteData(std::string("\n") + kSTART_MAGIC + std::string("\n") + + result + kEND_MAGIC + std::string("\n")); } cmServerProtocol* cmServer::FindMatchingProtocol( diff --git a/Source/cmServer.h b/Source/cmServer.h index 38a11bb..dde5333 100644 --- a/Source/cmServer.h +++ b/Source/cmServer.h @@ -24,6 +24,7 @@ #include <string> #include <vector> +class cmServerConnection; class cmServerProtocol; class cmServerRequest; class cmServerResponse; @@ -33,18 +34,18 @@ class cmServer public: class DebugInfo; - cmServer(bool supportExperimental); + cmServer(cmServerConnection* conn, bool supportExperimental); ~cmServer(); - bool Serve(); - - // for callbacks: - void PopOne(); - void handleData(std::string const& data); + bool Serve(std::string* errorMessage); private: void RegisterProtocol(cmServerProtocol* protocol); + // Callbacks from cmServerConnection: + void PopOne(); + void QueueRequest(const std::string& request); + static void reportProgress(const char* msg, float progress, void* data); static void reportMessage(const char* msg, const char* title, bool& cancel, void* data); @@ -69,6 +70,7 @@ private: static cmServerProtocol* FindMatchingProtocol( const std::vector<cmServerProtocol*>& protocols, int major, int minor); + cmServerConnection* Connection = nullptr; const bool SupportExperimental; cmServerProtocol* Protocol = nullptr; @@ -94,4 +96,5 @@ private: mutable bool Writing = false; friend class cmServerRequest; + friend class cmServerConnection; }; diff --git a/Source/cmServerConnection.cxx b/Source/cmServerConnection.cxx new file mode 100644 index 0000000..398e250 --- /dev/null +++ b/Source/cmServerConnection.cxx @@ -0,0 +1,307 @@ +/*============================================================================ + CMake - Cross Platform Makefile Generator + Copyright 2015 Stephen Kelly <steveire@gmail.com> + Copyright 2016 Tobias Hunger <tobias.hunger@qt.io> + + Distributed under the OSI-approved BSD License (the "License"); + see accompanying file Copyright.txt for details. + + This software is distributed WITHOUT ANY WARRANTY; without even the + implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the License for more information. +============================================================================*/ + +#include "cmServerConnection.h" + +#include <cmServer.h> + +#include <assert.h> + +namespace { + +static const std::string kSTART_MAGIC = "[== CMake Server ==["; +static const std::string kEND_MAGIC = "]== CMake Server ==]"; + +struct write_req_t +{ + uv_write_t req; + uv_buf_t buf; +}; + +void on_alloc_buffer(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) +{ + (void)(handle); + char* rawBuffer = new char[suggested_size]; + *buf = uv_buf_init(rawBuffer, static_cast<unsigned int>(suggested_size)); +} + +void on_read(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) +{ + auto conn = reinterpret_cast<cmServerConnection*>(stream->data); + if (nread >= 0) { + conn->ReadData(std::string(buf->base, buf->base + nread)); + } else { + conn->HandleEof(); + } + + delete[](buf->base); +} + +void on_write(uv_write_t* req, int status) +{ + (void)(status); + auto conn = reinterpret_cast<cmServerConnection*>(req->data); + + // Free req and buffer + write_req_t* wr = reinterpret_cast<write_req_t*>(req); + delete[](wr->buf.base); + delete wr; + + conn->ProcessNextRequest(); +} + +void on_new_connection(uv_stream_t* stream, int status) +{ + (void)(status); + auto conn = reinterpret_cast<cmServerConnection*>(stream->data); + conn->Connect(stream); +} + +} // namespace + +class LoopGuard +{ +public: + LoopGuard(cmServerConnection* connection) + : Connection(connection) + { + Connection->mLoop = uv_default_loop(); + } + + ~LoopGuard() + { + uv_loop_close(Connection->mLoop); + Connection->mLoop = nullptr; + } + +private: + cmServerConnection* Connection; +}; + +cmServerConnection::cmServerConnection() +{ +} + +cmServerConnection::~cmServerConnection() +{ +} + +void cmServerConnection::SetServer(cmServer* s) +{ + this->Server = s; +} + +bool cmServerConnection::ProcessEvents(std::string* errorMessage) +{ + assert(this->Server); + errorMessage->clear(); + + this->RawReadBuffer.clear(); + this->RequestBuffer.clear(); + + LoopGuard guard(this); + (void)(guard); + if (!this->mLoop) { + *errorMessage = "Internal Error: Failed to create event loop."; + return false; + } + + if (!DoSetup(errorMessage)) { + return false; + } + + if (uv_run(this->mLoop, UV_RUN_DEFAULT) != 0) { + *errorMessage = "Internal Error: Event loop stopped in unclean state."; + return false; + } + + // These need to be cleaned up by now: + assert(!this->ReadStream); + assert(!this->WriteStream); + + this->RawReadBuffer.clear(); + this->RequestBuffer.clear(); + + return true; +} + +void cmServerConnection::ReadData(const std::string& data) +{ + this->RawReadBuffer += data; + + for (;;) { + auto needle = this->RawReadBuffer.find('\n'); + + if (needle == std::string::npos) { + return; + } + std::string line = this->RawReadBuffer.substr(0, needle); + const auto ls = line.size(); + if (ls > 1 && line.at(ls - 1) == '\r') + line.erase(ls - 1, 1); + this->RawReadBuffer.erase(this->RawReadBuffer.begin(), + this->RawReadBuffer.begin() + + static_cast<long>(needle) + 1); + if (line == kSTART_MAGIC) { + this->RequestBuffer.clear(); + continue; + } + if (line == kEND_MAGIC) { + this->Server->QueueRequest(this->RequestBuffer); + this->RequestBuffer.clear(); + } else { + this->RequestBuffer += line; + this->RequestBuffer += "\n"; + } + } +} + +void cmServerConnection::HandleEof() +{ + this->TearDown(); +} + +void cmServerConnection::WriteData(const std::string& data) +{ + assert(this->WriteStream); + + auto ds = data.size(); + + write_req_t* req = new write_req_t; + req->req.data = this; + req->buf = uv_buf_init(new char[ds], static_cast<unsigned int>(ds)); + memcpy(req->buf.base, data.c_str(), ds); + + uv_write(reinterpret_cast<uv_write_t*>(req), + static_cast<uv_stream_t*>(this->WriteStream), &req->buf, 1, + on_write); +} + +void cmServerConnection::ProcessNextRequest() +{ + Server->PopOne(); +} + +void cmServerConnection::SendGreetings() +{ + Server->PrintHello(); +} + +bool cmServerStdIoConnection::DoSetup(std::string* errorMessage) +{ + (void)(errorMessage); + + if (uv_guess_handle(1) == UV_TTY) { + uv_tty_init(this->Loop(), &this->Input.tty, 0, 1); + uv_tty_set_mode(&this->Input.tty, UV_TTY_MODE_NORMAL); + Input.tty.data = this; + this->ReadStream = reinterpret_cast<uv_stream_t*>(&this->Input.tty); + + uv_tty_init(this->Loop(), &this->Output.tty, 1, 0); + uv_tty_set_mode(&this->Output.tty, UV_TTY_MODE_NORMAL); + Output.tty.data = this; + this->WriteStream = reinterpret_cast<uv_stream_t*>(&this->Output.tty); + } else { + uv_pipe_init(this->Loop(), &this->Input.pipe, 0); + uv_pipe_open(&this->Input.pipe, 0); + Input.pipe.data = this; + this->ReadStream = reinterpret_cast<uv_stream_t*>(&this->Input.pipe); + + uv_pipe_init(this->Loop(), &this->Output.pipe, 0); + uv_pipe_open(&this->Output.pipe, 1); + Output.pipe.data = this; + this->WriteStream = reinterpret_cast<uv_stream_t*>(&this->Output.pipe); + } + + SendGreetings(); + uv_read_start(this->ReadStream, on_alloc_buffer, on_read); + + return true; +} + +void cmServerStdIoConnection::TearDown() +{ + uv_close(reinterpret_cast<uv_handle_t*>(this->ReadStream), nullptr); + this->ReadStream = nullptr; + uv_close(reinterpret_cast<uv_handle_t*>(this->WriteStream), nullptr); + this->WriteStream = nullptr; +} + +cmServerPipeConnection::cmServerPipeConnection(const std::string& name) + : PipeName(name) +{ + this->ServerPipe.data = nullptr; + this->ClientPipe.data = nullptr; +} + +bool cmServerPipeConnection::DoSetup(std::string* errorMessage) +{ + uv_pipe_init(this->Loop(), &this->ServerPipe, 0); + this->ServerPipe.data = this; + + int r; + if ((r = uv_pipe_bind(&this->ServerPipe, this->PipeName.c_str())) != 0) { + *errorMessage = std::string("Internal Error with ") + this->PipeName + + ": " + uv_err_name(r); + return false; + } + auto serverStream = reinterpret_cast<uv_stream_t*>(&this->ServerPipe); + serverStream->data = this; + if ((r = uv_listen(serverStream, 1, on_new_connection)) != 0) { + *errorMessage = std::string("Internal Error with ") + this->PipeName + + ": " + uv_err_name(r); + return false; + } + + return true; +} + +void cmServerPipeConnection::TearDown() +{ + if (this->WriteStream->data) { + uv_close(reinterpret_cast<uv_handle_t*>(this->WriteStream), nullptr); + this->WriteStream->data = nullptr; + } + uv_close(reinterpret_cast<uv_handle_t*>(&this->ServerPipe), nullptr); + + this->WriteStream = nullptr; + this->ReadStream = nullptr; +} + +void cmServerPipeConnection::Connect(uv_stream_t* server) +{ + if (this->ClientPipe.data == this) { + // Accept and close all pipes but the first: + uv_pipe_t rejectPipe; + + uv_pipe_init(this->Loop(), &rejectPipe, 0); + auto rejecter = reinterpret_cast<uv_stream_t*>(&rejectPipe); + uv_accept(server, rejecter); + uv_close(reinterpret_cast<uv_handle_t*>(rejecter), nullptr); + return; + } + + uv_pipe_init(this->Loop(), &this->ClientPipe, 0); + this->ClientPipe.data = this; + auto client = reinterpret_cast<uv_stream_t*>(&this->ClientPipe); + if (uv_accept(server, client) != 0) { + uv_close(reinterpret_cast<uv_handle_t*>(client), nullptr); + return; + } + this->ReadStream = client; + this->WriteStream = client; + + uv_read_start(this->ReadStream, on_alloc_buffer, on_read); + + this->SendGreetings(); +} diff --git a/Source/cmServerConnection.h b/Source/cmServerConnection.h new file mode 100644 index 0000000..fa86e71 --- /dev/null +++ b/Source/cmServerConnection.h @@ -0,0 +1,97 @@ +/*============================================================================ + CMake - Cross Platform Makefile Generator + Copyright 2015 Stephen Kelly <steveire@gmail.com> + Copyright 2016 Tobias Hunger <tobias.hunger@qt.io> + + Distributed under the OSI-approved BSD License (the "License"); + see accompanying file Copyright.txt for details. + + This software is distributed WITHOUT ANY WARRANTY; without even the + implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the License for more information. +============================================================================*/ + +#pragma once + +#include <string> +#include <vector> + +#if defined(CMAKE_BUILD_WITH_CMAKE) +#include "cm_uv.h" +#endif + +class cmServer; +class LoopGuard; + +class cmServerConnection +{ +public: + cmServerConnection(); + virtual ~cmServerConnection(); + + void SetServer(cmServer* s); + + bool ProcessEvents(std::string* errorMessage); + + void ReadData(const std::string& data); + void HandleEof(); + void WriteData(const std::string& data); + void ProcessNextRequest(); + + virtual void Connect(uv_stream_t* server) { (void)(server); } + +protected: + virtual bool DoSetup(std::string* errorMessage) = 0; + virtual void TearDown() = 0; + + void SendGreetings(); + + uv_loop_t* Loop() const { return mLoop; } + +protected: + std::string RawReadBuffer; + std::string RequestBuffer; + + uv_stream_t* ReadStream = nullptr; + uv_stream_t* WriteStream = nullptr; + +private: + uv_loop_t* mLoop = nullptr; + cmServer* Server = nullptr; + + friend class LoopGuard; +}; + +class cmServerStdIoConnection : public cmServerConnection +{ +public: + bool DoSetup(std::string* errorMessage) override; + + void TearDown() override; + +private: + typedef union + { + uv_tty_t tty; + uv_pipe_t pipe; + } InOutUnion; + + InOutUnion Input; + InOutUnion Output; +}; + +class cmServerPipeConnection : public cmServerConnection +{ +public: + cmServerPipeConnection(const std::string& name); + bool DoSetup(std::string* errorMessage) override; + + void TearDown() override; + + void Connect(uv_stream_t* server) override; + +private: + const std::string PipeName; + uv_pipe_t ServerPipe; + uv_pipe_t ClientPipe; +}; diff --git a/Source/cmcmd.cxx b/Source/cmcmd.cxx index 38f00e6..9daed4b 100644 --- a/Source/cmcmd.cxx +++ b/Source/cmcmd.cxx @@ -25,6 +25,7 @@ #if defined(HAVE_SERVER_MODE) && HAVE_SERVER_MODE #include "cmServer.h" +#include "cmServerConnection.h" #endif #if defined(CMAKE_BUILD_WITH_CMAKE) @@ -913,32 +914,49 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string>& args) } return 0; } else if (args[1] == "server") { - if (args.size() > 3) { - cmSystemTools::Error("Too many arguments to start server mode"); - return 1; - } + const std::string pipePrefix = "--pipe="; bool supportExperimental = false; - if (args.size() == 3) { - if (args[2] == "--experimental") { + bool isDebug = false; + std::string pipe; + + for (size_t i = 2; i < args.size(); ++i) { + const std::string& a = args[i]; + + if (a == "--experimental") { supportExperimental = true; + } else if (a == "--debug") { + pipe.clear(); + isDebug = true; + } else if (a.substr(0, pipePrefix.size()) == pipePrefix) { + isDebug = false; + pipe = a.substr(pipePrefix.size()); + if (pipe.empty()) { + cmSystemTools::Error("No pipe given after --pipe="); + return 2; + } } else { cmSystemTools::Error("Unknown argument for server mode"); return 1; } } #if defined(HAVE_SERVER_MODE) && HAVE_SERVER_MODE - cmServer server(supportExperimental); - if (server.Serve()) { + cmServerConnection* conn; + if (isDebug) { + conn = new cmServerStdIoConnection; + } else { + conn = new cmServerPipeConnection(pipe); + } + cmServer server(conn, supportExperimental); + std::string errorMessage; + if (server.Serve(&errorMessage)) { return 0; } else { - cmSystemTools::Error( - "CMake server could not find any supported protocol. " - "Try with \"--experimental\" to enable " - "experimental support."); + cmSystemTools::Error(errorMessage.c_str()); return 1; } #else static_cast<void>(supportExperimental); + static_cast<void>(isDebug); cmSystemTools::Error("CMake was not built with server mode enabled"); return 1; #endif diff --git a/Tests/RunCMake/CMakeLists.txt b/Tests/RunCMake/CMakeLists.txt index a1f35d7..778982f 100644 --- a/Tests/RunCMake/CMakeLists.txt +++ b/Tests/RunCMake/CMakeLists.txt @@ -193,6 +193,7 @@ add_RunCMake_test(ctest_start) add_RunCMake_test(ctest_submit) add_RunCMake_test(ctest_test) add_RunCMake_test(ctest_upload) +add_RunCMake_test(ctest_fixtures) add_RunCMake_test(file) add_RunCMake_test(find_file) add_RunCMake_test(find_library) diff --git a/Tests/RunCMake/CommandLine/E_server-pipe-result.txt b/Tests/RunCMake/CommandLine/E_server-pipe-result.txt new file mode 100644 index 0000000..0cfbf08 --- /dev/null +++ b/Tests/RunCMake/CommandLine/E_server-pipe-result.txt @@ -0,0 +1 @@ +2 diff --git a/Tests/RunCMake/CommandLine/E_server-pipe-stderr.txt b/Tests/RunCMake/CommandLine/E_server-pipe-stderr.txt new file mode 100644 index 0000000..7193ba6 --- /dev/null +++ b/Tests/RunCMake/CommandLine/E_server-pipe-stderr.txt @@ -0,0 +1 @@ +^CMake Error: No pipe given after --pipe=$ diff --git a/Tests/RunCMake/CommandLine/RunCMakeTest.cmake b/Tests/RunCMake/CommandLine/RunCMakeTest.cmake index 9f76ad9..0c4f71c 100644 --- a/Tests/RunCMake/CommandLine/RunCMakeTest.cmake +++ b/Tests/RunCMake/CommandLine/RunCMakeTest.cmake @@ -13,6 +13,7 @@ run_cmake_command(E_capabilities-arg ${CMAKE_COMMAND} -E capabilities --extra-ar run_cmake_command(E_echo_append ${CMAKE_COMMAND} -E echo_append) run_cmake_command(E_rename-no-arg ${CMAKE_COMMAND} -E rename) run_cmake_command(E_server-arg ${CMAKE_COMMAND} -E server --extra-arg) +run_cmake_command(E_server-pipe ${CMAKE_COMMAND} -E server --pipe=) run_cmake_command(E_touch_nocreate-no-arg ${CMAKE_COMMAND} -E touch_nocreate) run_cmake_command(E_time ${CMAKE_COMMAND} -E time ${CMAKE_COMMAND} -E echo "hello world") diff --git a/Tests/RunCMake/ctest_fixtures/CMakeLists.txt.in b/Tests/RunCMake/ctest_fixtures/CMakeLists.txt.in new file mode 100644 index 0000000..ba1c77a --- /dev/null +++ b/Tests/RunCMake/ctest_fixtures/CMakeLists.txt.in @@ -0,0 +1,81 @@ +cmake_minimum_required (VERSION 3.6.2) +project(ctest_fixtures LANGUAGES NONE) +include(CTest) + +macro(passTest testName) + set(someFile "${CMAKE_CURRENT_SOURCE_DIR}/test.cmake") + add_test(NAME ${testName} + COMMAND ${CMAKE_COMMAND} -E compare_files "${someFile}" "${someFile}") +endmacro() + +macro(failTest testName) + set(someFile "${CMAKE_CURRENT_SOURCE_DIR}/test.cmake") + add_test(NAME ${testName} + COMMAND ${CMAKE_COMMAND} -E compare_files "${someFile}" "${someFile}xxx") +endmacro() + +# Intersperse actual tests among setup/cleanup tests so that we don't +# define them in the same order as they need to be executed. Numbers +# at the end of each line correspond to the test numbers ctest will +# use for each test. +passTest(one) # 1 +passTest(setupBoth) # 2 +passTest(setupFoo) # 3 +passTest(setupMeta) # 4 +passTest(cleanupFoo) # 5 +passTest(two) # 6 +passTest(cleanupBar) # 7 +passTest(three) # 8 +failTest(setupFails) # 9 +passTest(wontRun) # 10 +passTest(cyclicSetup) # 11 +passTest(cyclicCleanup) # 12 + +# Define fixture dependencies and ordering +set_tests_properties(setupFoo PROPERTIES FIXTURES_SETUP "Foo") +set_tests_properties(cleanupFoo PROPERTIES FIXTURES_CLEANUP "Foo") + +set_tests_properties(setupBoth PROPERTIES FIXTURES_SETUP "Foo;Bar") +set_tests_properties(cleanupBar PROPERTIES FIXTURES_CLEANUP "Bar") + +set_tests_properties(setupMeta PROPERTIES FIXTURES_SETUP "Meta" + FIXTURES_REQUIRED "Foo;Bar") + +set_tests_properties(setupBoth PROPERTIES DEPENDS setupFoo) + +set_tests_properties(setupFails PROPERTIES FIXTURES_SETUP "Fails") + +set_tests_properties(one PROPERTIES FIXTURES_REQUIRED "Other;Foo") +set_tests_properties(two PROPERTIES FIXTURES_REQUIRED "Bar") +set_tests_properties(three PROPERTIES FIXTURES_REQUIRED "Meta;Bar") +set_tests_properties(wontRun PROPERTIES FIXTURES_REQUIRED "Fails") + +@CASE_CMAKELISTS_CYCLIC_CODE@ + +# These are the cases verified by the main cmake build +# +# Regex: Test case list (in order) +# one 3, 2, 1, 5 +# two 2, 6, 7 +# three 3, 2, 4, 5, 8, 7 +# setupFoo 3 +# wontRun 9, 10 +# cyclicSetup -NA- (configure fails) +# cyclicCleanup -NA- (configure fails) +# +# In the case of asking for just setupFoo, since there are +# no tests using the Foo fixture, we do NOT expect cleanupFoo +# to be executed. It is important not to pull in cleanupFoo +# if setupFoo is explicitly requested and no other test requires +# the Foo fixture, otherwise it would not be possible to run +# just a setup or cleanup test in isolation (likely to be +# needed during initial creation of such test cases). +# +# For the wontRun case, test 9 fails and test 10 should not run. +# The result of the set of tests should be failure, which is +# verified by the main cmake build's tests. +# +# For the two cyclic test cases invoked by the main cmake build, +# FIXTURES_... properties are added to the relevant test at the +# location marked with CASE_CMAKELISTS_CYCLIC_CODE. This creates +# a self-dependency which causes the configure step to fail. diff --git a/Tests/RunCMake/ctest_fixtures/CTestConfig.cmake.in b/Tests/RunCMake/ctest_fixtures/CTestConfig.cmake.in new file mode 100644 index 0000000..9823562 --- /dev/null +++ b/Tests/RunCMake/ctest_fixtures/CTestConfig.cmake.in @@ -0,0 +1 @@ +set(CTEST_PROJECT_NAME "CTestTestFixtures.@CASE_NAME@") diff --git a/Tests/RunCMake/ctest_fixtures/RunCMakeTest.cmake b/Tests/RunCMake/ctest_fixtures/RunCMakeTest.cmake new file mode 100644 index 0000000..f13289a --- /dev/null +++ b/Tests/RunCMake/ctest_fixtures/RunCMakeTest.cmake @@ -0,0 +1,36 @@ +include(RunCTest) + +# Isolate our ctest runs from external environment. +unset(ENV{CTEST_PARALLEL_LEVEL}) +unset(ENV{CTEST_OUTPUT_ON_FAILURE}) + +function(run_ctest_test CASE_NAME) + set(CASE_CTEST_FIXTURES_ARGS "${ARGN}") + run_ctest(${CASE_NAME}) +endfunction() + +#------------------------------------------------------------ +# CMake configure will pass +#------------------------------------------------------------ +run_ctest_test(one INCLUDE one) +run_ctest_test(two INCLUDE two) +run_ctest_test(three INCLUDE three) +run_ctest_test(setupFoo INCLUDE setupFoo) +run_ctest_test(wontRun INCLUDE wontRun) + +#------------------------------------------------------------ +# CMake configure will fail due to cyclic test dependencies +#------------------------------------------------------------ +set(CASE_CMAKELISTS_CYCLIC_CODE [[ + set_tests_properties(cyclicSetup PROPERTIES + FIXTURES_SETUP "Foo" + FIXTURES_REQUIRED "Foo") +]]) +run_ctest(cyclicSetup) + +set(CASE_CMAKELISTS_CYCLIC_CODE [[ + set_tests_properties(cyclicCleanup PROPERTIES + FIXTURES_CLEANUP "Foo" + FIXTURES_REQUIRED "Foo") +]]) +run_ctest(cyclicCleanup) diff --git a/Tests/RunCMake/ctest_fixtures/cyclicCleanup-result.txt b/Tests/RunCMake/ctest_fixtures/cyclicCleanup-result.txt new file mode 100644 index 0000000..b57e2de --- /dev/null +++ b/Tests/RunCMake/ctest_fixtures/cyclicCleanup-result.txt @@ -0,0 +1 @@ +(-1|255) diff --git a/Tests/RunCMake/ctest_fixtures/cyclicCleanup-stderr.txt b/Tests/RunCMake/ctest_fixtures/cyclicCleanup-stderr.txt new file mode 100644 index 0000000..1a45994 --- /dev/null +++ b/Tests/RunCMake/ctest_fixtures/cyclicCleanup-stderr.txt @@ -0,0 +1,3 @@ +Error: a cycle exists in the test dependency graph for the test "cyclicCleanup". +Please fix the cycle and run ctest again. +No tests were found!!! diff --git a/Tests/RunCMake/ctest_fixtures/cyclicCleanup-stdout.txt b/Tests/RunCMake/ctest_fixtures/cyclicCleanup-stdout.txt new file mode 100644 index 0000000..8b7fdf7 --- /dev/null +++ b/Tests/RunCMake/ctest_fixtures/cyclicCleanup-stdout.txt @@ -0,0 +1 @@ +Test project .*/Tests/RunCMake/ctest_fixtures/cyclicCleanup-build$ diff --git a/Tests/RunCMake/ctest_fixtures/cyclicSetup-result.txt b/Tests/RunCMake/ctest_fixtures/cyclicSetup-result.txt new file mode 100644 index 0000000..b57e2de --- /dev/null +++ b/Tests/RunCMake/ctest_fixtures/cyclicSetup-result.txt @@ -0,0 +1 @@ +(-1|255) diff --git a/Tests/RunCMake/ctest_fixtures/cyclicSetup-stderr.txt b/Tests/RunCMake/ctest_fixtures/cyclicSetup-stderr.txt new file mode 100644 index 0000000..2aba6c9 --- /dev/null +++ b/Tests/RunCMake/ctest_fixtures/cyclicSetup-stderr.txt @@ -0,0 +1,3 @@ +Error: a cycle exists in the test dependency graph for the test "cyclicSetup". +Please fix the cycle and run ctest again. +No tests were found!!!$ diff --git a/Tests/RunCMake/ctest_fixtures/cyclicSetup-stdout.txt b/Tests/RunCMake/ctest_fixtures/cyclicSetup-stdout.txt new file mode 100644 index 0000000..22c564d --- /dev/null +++ b/Tests/RunCMake/ctest_fixtures/cyclicSetup-stdout.txt @@ -0,0 +1 @@ +Test project .*/Tests/RunCMake/ctest_fixtures/cyclicSetup-build$ diff --git a/Tests/RunCMake/ctest_fixtures/one-stdout.txt b/Tests/RunCMake/ctest_fixtures/one-stdout.txt new file mode 100644 index 0000000..e2276a4 --- /dev/null +++ b/Tests/RunCMake/ctest_fixtures/one-stdout.txt @@ -0,0 +1,13 @@ +Test project .*/Tests/RunCMake/ctest_fixtures/one-build + Start 3: setupFoo +1/4 Test #3: setupFoo +\.+ +Passed +[0-9.]+ sec + Start 2: setupBoth +2/4 Test #2: setupBoth +\.+ +Passed +[0-9.]+ sec + Start 1: one +3/4 Test #1: one +\.+ +Passed +[0-9.]+ sec + Start 5: cleanupFoo +4/4 Test #5: cleanupFoo +\.+ +Passed +[0-9.]+ sec ++ +100% tests passed, 0 tests failed out of 4 ++ +Total Test time \(real\) = +[0-9.]+ sec$ diff --git a/Tests/RunCMake/ctest_fixtures/setupFoo-stdout.txt b/Tests/RunCMake/ctest_fixtures/setupFoo-stdout.txt new file mode 100644 index 0000000..7e21b34 --- /dev/null +++ b/Tests/RunCMake/ctest_fixtures/setupFoo-stdout.txt @@ -0,0 +1,7 @@ +Test project .*/Tests/RunCMake/ctest_fixtures/setupFoo-build + Start 3: setupFoo +1/1 Test #3: setupFoo +\.+ +Passed +[0-9.]+ sec ++ +100% tests passed, 0 tests failed out of 1 ++ +Total Test time \(real\) = +[0-9.]+ sec$ diff --git a/Tests/RunCMake/ctest_fixtures/test.cmake.in b/Tests/RunCMake/ctest_fixtures/test.cmake.in new file mode 100644 index 0000000..43df172 --- /dev/null +++ b/Tests/RunCMake/ctest_fixtures/test.cmake.in @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.6.2) + +set(CTEST_SITE "test-site") +set(CTEST_BUILD_NAME "test-build-name") +set(CTEST_SOURCE_DIRECTORY "@RunCMake_BINARY_DIR@/@CASE_NAME@") +set(CTEST_BINARY_DIRECTORY "@RunCMake_BINARY_DIR@/@CASE_NAME@-build") +set(CTEST_CMAKE_GENERATOR "@RunCMake_GENERATOR@") +set(CTEST_CMAKE_GENERATOR_PLATFORM "@RunCMake_GENERATOR_PLATFORM@") +set(CTEST_CMAKE_GENERATOR_TOOLSET "@RunCMake_GENERATOR_TOOLSET@") +set(CTEST_BUILD_CONFIGURATION "$ENV{CMAKE_CONFIG_TYPE}") + +set(ctest_fixtures_args "@CASE_CTEST_FIXTURES_ARGS@") + +ctest_start(Experimental) +ctest_configure() +ctest_test(${ctest_fixtures_args}) diff --git a/Tests/RunCMake/ctest_fixtures/three-stdout.txt b/Tests/RunCMake/ctest_fixtures/three-stdout.txt new file mode 100644 index 0000000..f192e61 --- /dev/null +++ b/Tests/RunCMake/ctest_fixtures/three-stdout.txt @@ -0,0 +1,17 @@ +Test project .*/Tests/RunCMake/ctest_fixtures/three-build + Start 3: setupFoo +1/6 Test #3: setupFoo +\.+ +Passed +[0-9.]+ sec + Start 2: setupBoth +2/6 Test #2: setupBoth +\.+ +Passed +[0-9.]+ sec + Start 4: setupMeta +3/6 Test #4: setupMeta +\.+ +Passed +[0-9.]+ sec + Start 5: cleanupFoo +4/6 Test #5: cleanupFoo +\.+ +Passed +[0-9.]+ sec + Start 8: three +5/6 Test #8: three +\.+ +Passed +[0-9.]+ sec + Start 7: cleanupBar +6/6 Test #7: cleanupBar +\.+ +Passed +[0-9.]+ sec ++ +100% tests passed, 0 tests failed out of 6 ++ +Total Test time \(real\) = +[0-9.]+ sec$ diff --git a/Tests/RunCMake/ctest_fixtures/two-stdout.txt b/Tests/RunCMake/ctest_fixtures/two-stdout.txt new file mode 100644 index 0000000..2e667a7 --- /dev/null +++ b/Tests/RunCMake/ctest_fixtures/two-stdout.txt @@ -0,0 +1,11 @@ +Test project .*/Tests/RunCMake/ctest_fixtures/two-build + Start 2: setupBoth +1/3 Test #2: setupBoth +\.+ +Passed +[0-9.]+ sec + Start 6: two +2/3 Test #6: two +\.+ +Passed +[0-9.]+ sec + Start 7: cleanupBar +3/3 Test #7: cleanupBar +\.+ +Passed +[0-9.]+ sec ++ +100% tests passed, 0 tests failed out of 3 ++ +Total Test time \(real\) = +[0-9.]+ sec$ diff --git a/Tests/RunCMake/ctest_fixtures/wontRun-stdout.txt b/Tests/RunCMake/ctest_fixtures/wontRun-stdout.txt new file mode 100644 index 0000000..526ca4b --- /dev/null +++ b/Tests/RunCMake/ctest_fixtures/wontRun-stdout.txt @@ -0,0 +1,14 @@ +Test project .*/Tests/RunCMake/ctest_fixtures/wontRun-build + Start 9: setupFails +1/2 Test #9: setupFails +\.+\*\*\*Failed +[0-9.]+ sec + Start 10: wontRun +Failed test dependencies: setupFails +2/2 Test #10: wontRun +\.+\*\*\*Not Run +[0-9.]+ sec ++ +0% tests passed, 2 tests failed out of 2 ++ +Total Test time \(real\) = +[0-9.]+ sec ++ +The following tests FAILED: +.* +9 - setupFails \(Failed\) +.* +10 - wontRun \(Not Run\)$ diff --git a/Tests/Server/cmakelib.py b/Tests/Server/cmakelib.py index e89b1f0..0f98078 100644 --- a/Tests/Server/cmakelib.py +++ b/Tests/Server/cmakelib.py @@ -79,7 +79,7 @@ def writePayload(cmakeCommand, obj): writeRawData(cmakeCommand, json.dumps(obj)) def initProc(cmakeCommand): - cmakeCommand = subprocess.Popen([cmakeCommand, "-E", "server", "--experimental"], + cmakeCommand = subprocess.Popen([cmakeCommand, "-E", "server", "--experimental", "--debug"], stdin=subprocess.PIPE, stdout=subprocess.PIPE) |