diff options
author | Craig Scott <craig.scott@crascit.com> | 2016-09-07 04:04:07 (GMT) |
---|---|---|
committer | Brad King <brad.king@kitware.com> | 2016-09-20 18:37:38 (GMT) |
commit | 73f47c9e46d42513cd7eeb08e27c4d1a424949ad (patch) | |
tree | f7f275e022e3c6debf4450015d1ae40e46fb56a3 /Source/CTest/cmCTestTestHandler.cxx | |
parent | 6b8812c27e6df1d10fa4bfc30cb3eadd08d7966b (diff) | |
download | CMake-73f47c9e46d42513cd7eeb08e27c4d1a424949ad.zip CMake-73f47c9e46d42513cd7eeb08e27c4d1a424949ad.tar.gz CMake-73f47c9e46d42513cd7eeb08e27c4d1a424949ad.tar.bz2 |
CTest: Add support for test fixtures
Add new test properties:
* FIXTURES_SETUP
* FIXTURES_CLEANUP
* FIXTURES_REQUIRED
to specify the roles and dependencies of tests providing/using
test fixtures.
Diffstat (limited to 'Source/CTest/cmCTestTestHandler.cxx')
-rw-r--r-- | Source/CTest/cmCTestTestHandler.cxx | 187 |
1 files changed, 187 insertions, 0 deletions
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; |