cmCTestMultiProcessHandler.cxx 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628
  1. /*============================================================================
  2. CMake - Cross Platform Makefile Generator
  3. Copyright 2000-2009 Kitware, Inc., Insight Software Consortium
  4. Distributed under the OSI-approved BSD License (the "License");
  5. see accompanying file Copyright.txt for details.
  6. This software is distributed WITHOUT ANY WARRANTY; without even the
  7. implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  8. See the License for more information.
  9. ============================================================================*/
  10. #include "cmCTestMultiProcessHandler.h"
  11. #include "cmProcess.h"
  12. #include "cmStandardIncludes.h"
  13. #include "cmCTest.h"
  14. #include "cmSystemTools.h"
  15. #include <stdlib.h>
  16. #include <stack>
  17. #include <float.h>
  18. cmCTestMultiProcessHandler::cmCTestMultiProcessHandler()
  19. {
  20. this->ParallelLevel = 1;
  21. this->Completed = 0;
  22. this->RunningCount = 0;
  23. }
  24. cmCTestMultiProcessHandler::~cmCTestMultiProcessHandler()
  25. {
  26. }
  27. // Set the tests
  28. void
  29. cmCTestMultiProcessHandler::SetTests(TestMap& tests,
  30. PropertiesMap& properties)
  31. {
  32. this->Tests = tests;
  33. this->Properties = properties;
  34. this->Total = this->Tests.size();
  35. // set test run map to false for all
  36. for(TestMap::iterator i = this->Tests.begin();
  37. i != this->Tests.end(); ++i)
  38. {
  39. this->TestRunningMap[i->first] = false;
  40. this->TestFinishMap[i->first] = false;
  41. }
  42. if(!this->CTest->GetShowOnly())
  43. {
  44. this->ReadCostData();
  45. this->CreateTestCostList();
  46. }
  47. }
  48. // Set the max number of tests that can be run at the same time.
  49. void cmCTestMultiProcessHandler::SetParallelLevel(size_t level)
  50. {
  51. this->ParallelLevel = level < 1 ? 1 : level;
  52. }
  53. //---------------------------------------------------------
  54. void cmCTestMultiProcessHandler::RunTests()
  55. {
  56. this->CheckResume();
  57. if(!this->CheckCycles())
  58. {
  59. return;
  60. }
  61. this->TestHandler->SetMaxIndex(this->FindMaxIndex());
  62. this->StartNextTests();
  63. while(this->Tests.size() != 0)
  64. {
  65. this->CheckOutput();
  66. this->StartNextTests();
  67. }
  68. // let all running tests finish
  69. while(this->CheckOutput())
  70. {
  71. }
  72. this->MarkFinished();
  73. this->UpdateCostData();
  74. }
  75. //---------------------------------------------------------
  76. void cmCTestMultiProcessHandler::StartTestProcess(int test)
  77. {
  78. cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "test " << test << "\n");
  79. this->TestRunningMap[test] = true; // mark the test as running
  80. // now remove the test itself
  81. this->EraseTest(test);
  82. cmCTestRunTest* testRun = new cmCTestRunTest(this->TestHandler);
  83. testRun->SetIndex(test);
  84. testRun->SetTestProperties(this->Properties[test]);
  85. std::string current_dir = cmSystemTools::GetCurrentWorkingDirectory();
  86. cmSystemTools::ChangeDirectory(this->Properties[test]->Directory.c_str());
  87. // Lock the resources we'll be using
  88. this->LockResources(test);
  89. if(testRun->StartTest(this->Total))
  90. {
  91. this->RunningTests.insert(testRun);
  92. }
  93. else
  94. {
  95. this->UnlockResources(test);
  96. this->Completed++;
  97. this->TestFinishMap[test] = true;
  98. this->TestRunningMap[test] = false;
  99. this->RunningCount -= GetProcessorsUsed(test);
  100. testRun->EndTest(this->Completed, this->Total, false);
  101. this->Failed->push_back(this->Properties[test]->Name);
  102. delete testRun;
  103. }
  104. cmSystemTools::ChangeDirectory(current_dir.c_str());
  105. }
  106. //---------------------------------------------------------
  107. void cmCTestMultiProcessHandler::LockResources(int index)
  108. {
  109. for(std::set<std::string>::iterator i =
  110. this->Properties[index]->LockedResources.begin();
  111. i != this->Properties[index]->LockedResources.end(); ++i)
  112. {
  113. this->LockedResources.insert(*i);
  114. }
  115. }
  116. //---------------------------------------------------------
  117. void cmCTestMultiProcessHandler::UnlockResources(int index)
  118. {
  119. for(std::set<std::string>::iterator i =
  120. this->Properties[index]->LockedResources.begin();
  121. i != this->Properties[index]->LockedResources.end(); ++i)
  122. {
  123. this->LockedResources.erase(*i);
  124. }
  125. }
  126. //---------------------------------------------------------
  127. void cmCTestMultiProcessHandler::EraseTest(int test)
  128. {
  129. this->Tests.erase(test);
  130. for(TestCostMap::iterator i = this->TestCosts.begin();
  131. i != this->TestCosts.end(); ++i)
  132. {
  133. if(i->second.find(test) != i->second.end())
  134. {
  135. i->second.erase(test);
  136. return;
  137. }
  138. }
  139. }
  140. //---------------------------------------------------------
  141. inline size_t cmCTestMultiProcessHandler::GetProcessorsUsed(int test)
  142. {
  143. size_t processors =
  144. static_cast<int>(this->Properties[test]->Processors);
  145. //If this is set to run serially, it must run alone.
  146. //Also, if processors setting is set higher than the -j
  147. //setting, we default to using all of the process slots.
  148. if(this->Properties[test]->RunSerial
  149. || processors > this->ParallelLevel)
  150. {
  151. processors = this->ParallelLevel;
  152. }
  153. return processors;
  154. }
  155. //---------------------------------------------------------
  156. bool cmCTestMultiProcessHandler::StartTest(int test)
  157. {
  158. //Check for locked resources
  159. for(std::set<std::string>::iterator i =
  160. this->Properties[test]->LockedResources.begin();
  161. i != this->Properties[test]->LockedResources.end(); ++i)
  162. {
  163. if(this->LockedResources.find(*i) != this->LockedResources.end())
  164. {
  165. return false;
  166. }
  167. }
  168. // copy the depend tests locally because when
  169. // a test is finished it will be removed from the depend list
  170. // and we don't want to be iterating a list while removing from it
  171. TestSet depends = this->Tests[test];
  172. size_t totalDepends = depends.size();
  173. if(totalDepends)
  174. {
  175. for(TestSet::const_iterator i = depends.begin();
  176. i != depends.end(); ++i)
  177. {
  178. // if the test is not already running then start it
  179. if(!this->TestRunningMap[*i])
  180. {
  181. // this test might be finished, but since
  182. // this is a copy of the depend map we might
  183. // still have it
  184. if(!this->TestFinishMap[*i])
  185. {
  186. // only start one test in this function
  187. return this->StartTest(*i);
  188. }
  189. else
  190. {
  191. // the depend has been and finished
  192. totalDepends--;
  193. }
  194. }
  195. }
  196. }
  197. // if there are no depends left then run this test
  198. if(totalDepends == 0)
  199. {
  200. this->StartTestProcess(test);
  201. return true;
  202. }
  203. // This test was not able to start because it is waiting
  204. // on depends to run
  205. return false;
  206. }
  207. //---------------------------------------------------------
  208. void cmCTestMultiProcessHandler::StartNextTests()
  209. {
  210. size_t numToStart = this->ParallelLevel - this->RunningCount;
  211. if(numToStart == 0)
  212. {
  213. return;
  214. }
  215. for(TestCostMap::reverse_iterator i = this->TestCosts.rbegin();
  216. i != this->TestCosts.rend(); ++i)
  217. {
  218. TestSet tests = i->second; //copy the test set
  219. for(TestSet::iterator test = tests.begin();
  220. test != tests.end(); ++test)
  221. {
  222. //in case this test has already been started due to dependency
  223. if(this->TestRunningMap[*test] || this->TestFinishMap[*test])
  224. {
  225. continue;
  226. }
  227. size_t processors = GetProcessorsUsed(*test);
  228. if(processors > numToStart)
  229. {
  230. return;
  231. }
  232. if(this->StartTest(*test))
  233. {
  234. numToStart -= processors;
  235. this->RunningCount += processors;
  236. }
  237. else
  238. {
  239. cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, std::endl
  240. << "Test did not start waiting on depends to finish: "
  241. << *test << "\n");
  242. }
  243. if(numToStart == 0)
  244. {
  245. return;
  246. }
  247. }
  248. }
  249. }
  250. //---------------------------------------------------------
  251. bool cmCTestMultiProcessHandler::CheckOutput()
  252. {
  253. // no more output we are done
  254. if(this->RunningTests.size() == 0)
  255. {
  256. return false;
  257. }
  258. std::vector<cmCTestRunTest*> finished;
  259. std::string out, err;
  260. for(std::set<cmCTestRunTest*>::const_iterator i = this->RunningTests.begin();
  261. i != this->RunningTests.end(); ++i)
  262. {
  263. cmCTestRunTest* p = *i;
  264. if(!p->CheckOutput())
  265. {
  266. finished.push_back(p);
  267. }
  268. }
  269. for( std::vector<cmCTestRunTest*>::iterator i = finished.begin();
  270. i != finished.end(); ++i)
  271. {
  272. this->Completed++;
  273. cmCTestRunTest* p = *i;
  274. int test = p->GetIndex();
  275. if(p->EndTest(this->Completed, this->Total, true))
  276. {
  277. this->Passed->push_back(p->GetTestProperties()->Name);
  278. }
  279. else
  280. {
  281. this->Failed->push_back(p->GetTestProperties()->Name);
  282. }
  283. for(TestMap::iterator j = this->Tests.begin();
  284. j != this->Tests.end(); ++j)
  285. {
  286. j->second.erase(test);
  287. }
  288. this->TestFinishMap[test] = true;
  289. this->TestRunningMap[test] = false;
  290. this->RunningTests.erase(p);
  291. this->WriteCheckpoint(test);
  292. this->UnlockResources(test);
  293. this->RunningCount -= GetProcessorsUsed(test);
  294. delete p;
  295. }
  296. return true;
  297. }
  298. //---------------------------------------------------------
  299. void cmCTestMultiProcessHandler::UpdateCostData()
  300. {
  301. std::string fname = this->CTest->GetCostDataFile();
  302. std::string tmpout = fname + ".tmp";
  303. std::fstream fout;
  304. fout.open(tmpout.c_str(), std::ios::out);
  305. PropertiesMap temp = this->Properties;
  306. if(cmSystemTools::FileExists(fname.c_str()))
  307. {
  308. std::ifstream fin;
  309. fin.open(fname.c_str());
  310. std::string line;
  311. while(std::getline(fin, line))
  312. {
  313. if(line == "---") break;
  314. std::vector<cmsys::String> parts =
  315. cmSystemTools::SplitString(line.c_str(), ' ');
  316. //Format: <name> <previous_runs> <avg_cost>
  317. if(parts.size() < 3) break;
  318. std::string name = parts[0];
  319. int prev = atoi(parts[1].c_str());
  320. float cost = static_cast<float>(atof(parts[2].c_str()));
  321. int index = this->SearchByName(name);
  322. if(index == -1)
  323. {
  324. // This test is not in memory. We just rewrite the entry
  325. fout << name << " " << prev << " " << cost << "\n";
  326. }
  327. else
  328. {
  329. // Update with our new average cost
  330. fout << name << " " << this->Properties[index]->PreviousRuns << " "
  331. << this->Properties[index]->Cost << "\n";
  332. temp.erase(index);
  333. }
  334. }
  335. fin.close();
  336. cmSystemTools::RemoveFile(fname.c_str());
  337. }
  338. // Add all tests not previously listed in the file
  339. for(PropertiesMap::iterator i = temp.begin(); i != temp.end(); ++i)
  340. {
  341. fout << i->second->Name << " " << i->second->PreviousRuns << " "
  342. << i->second->Cost << "\n";
  343. }
  344. // Write list of failed tests
  345. fout << "---\n";
  346. for(std::vector<cmStdString>::iterator i = this->Failed->begin();
  347. i != this->Failed->end(); ++i)
  348. {
  349. fout << i->c_str() << "\n";
  350. }
  351. fout.close();
  352. cmSystemTools::RenameFile(tmpout.c_str(), fname.c_str());
  353. }
  354. //---------------------------------------------------------
  355. void cmCTestMultiProcessHandler::ReadCostData()
  356. {
  357. std::string fname = this->CTest->GetCostDataFile();
  358. if(cmSystemTools::FileExists(fname.c_str(), true))
  359. {
  360. std::ifstream fin;
  361. fin.open(fname.c_str());
  362. std::string line;
  363. while(std::getline(fin, line))
  364. {
  365. if(line == "---") break;
  366. std::vector<cmsys::String> parts =
  367. cmSystemTools::SplitString(line.c_str(), ' ');
  368. // Probably an older version of the file, will be fixed next run
  369. if(parts.size() < 3)
  370. {
  371. fin.close();
  372. return;
  373. }
  374. std::string name = parts[0];
  375. int prev = atoi(parts[1].c_str());
  376. float cost = static_cast<float>(atof(parts[2].c_str()));
  377. int index = this->SearchByName(name);
  378. if(index == -1) continue;
  379. this->Properties[index]->PreviousRuns = prev;
  380. if(this->Properties[index] && this->Properties[index]->Cost == 0)
  381. {
  382. this->Properties[index]->Cost = cost;
  383. }
  384. }
  385. // Next part of the file is the failed tests
  386. while(std::getline(fin, line))
  387. {
  388. if(line != "")
  389. {
  390. this->LastTestsFailed.push_back(line);
  391. }
  392. }
  393. fin.close();
  394. }
  395. }
  396. //---------------------------------------------------------
  397. int cmCTestMultiProcessHandler::SearchByName(std::string name)
  398. {
  399. int index = -1;
  400. for(PropertiesMap::iterator i = this->Properties.begin();
  401. i != this->Properties.end(); ++i)
  402. {
  403. if(i->second->Name == name)
  404. {
  405. index = i->first;
  406. }
  407. }
  408. return index;
  409. }
  410. //---------------------------------------------------------
  411. void cmCTestMultiProcessHandler::CreateTestCostList()
  412. {
  413. for(TestMap::iterator i = this->Tests.begin();
  414. i != this->Tests.end(); ++i)
  415. {
  416. std::string name = this->Properties[i->first]->Name;
  417. if(std::find(this->LastTestsFailed.begin(), this->LastTestsFailed.end(),
  418. name) != this->LastTestsFailed.end())
  419. {
  420. this->TestCosts[FLT_MAX].insert(i->first);
  421. }
  422. else
  423. {
  424. this->TestCosts[this->Properties[i->first]->Cost].insert(i->first);
  425. }
  426. }
  427. }
  428. //---------------------------------------------------------
  429. void cmCTestMultiProcessHandler::WriteCheckpoint(int index)
  430. {
  431. std::string fname = this->CTest->GetBinaryDir()
  432. + "/Testing/Temporary/CTestCheckpoint.txt";
  433. std::fstream fout;
  434. fout.open(fname.c_str(), std::ios::app | std::ios::out);
  435. fout << index << "\n";
  436. fout.close();
  437. }
  438. //---------------------------------------------------------
  439. void cmCTestMultiProcessHandler::MarkFinished()
  440. {
  441. std::string fname = this->CTest->GetBinaryDir()
  442. + "/Testing/Temporary/CTestCheckpoint.txt";
  443. cmSystemTools::RemoveFile(fname.c_str());
  444. }
  445. //---------------------------------------------------------
  446. //For ShowOnly mode
  447. void cmCTestMultiProcessHandler::PrintTestList()
  448. {
  449. this->TestHandler->SetMaxIndex(this->FindMaxIndex());
  450. int count = 0;
  451. for (PropertiesMap::iterator it = this->Properties.begin();
  452. it != this->Properties.end(); ++it)
  453. {
  454. count++;
  455. cmCTestTestHandler::cmCTestTestProperties& p = *it->second;
  456. //push working dir
  457. std::string current_dir = cmSystemTools::GetCurrentWorkingDirectory();
  458. cmSystemTools::ChangeDirectory(p.Directory.c_str());
  459. cmCTestRunTest testRun(this->TestHandler);
  460. testRun.SetIndex(p.Index);
  461. testRun.SetTestProperties(&p);
  462. testRun.ComputeArguments(); //logs the command in verbose mode
  463. if (this->TestHandler->MemCheck)
  464. {
  465. cmCTestLog(this->CTest, HANDLER_OUTPUT, " Memory Check");
  466. }
  467. else
  468. {
  469. cmCTestLog(this->CTest, HANDLER_OUTPUT, " Test");
  470. }
  471. cmOStringStream indexStr;
  472. indexStr << " #" << p.Index << ":";
  473. cmCTestLog(this->CTest, HANDLER_OUTPUT,
  474. std::setw(3 + getNumWidth(this->TestHandler->GetMaxIndex()))
  475. << indexStr.str().c_str());
  476. cmCTestLog(this->CTest, HANDLER_OUTPUT, " ");
  477. cmCTestLog(this->CTest, HANDLER_OUTPUT, p.Name.c_str() << std::endl);
  478. //pop working dir
  479. cmSystemTools::ChangeDirectory(current_dir.c_str());
  480. }
  481. cmCTestLog(this->CTest, HANDLER_OUTPUT, std::endl << "Total Tests: "
  482. << this->Total << std::endl);
  483. }
  484. //---------------------------------------------------------
  485. void cmCTestMultiProcessHandler::CheckResume()
  486. {
  487. std::string fname = this->CTest->GetBinaryDir()
  488. + "/Testing/Temporary/CTestCheckpoint.txt";
  489. if(this->CTest->GetFailover())
  490. {
  491. if(cmSystemTools::FileExists(fname.c_str(), true))
  492. {
  493. *this->TestHandler->LogFile << "Resuming previously interrupted test set"
  494. << std::endl
  495. << "----------------------------------------------------------"
  496. << std::endl;
  497. std::ifstream fin;
  498. fin.open(fname.c_str());
  499. std::string line;
  500. while(std::getline(fin, line))
  501. {
  502. int index = atoi(line.c_str());
  503. this->RemoveTest(index);
  504. }
  505. fin.close();
  506. }
  507. }
  508. else if(cmSystemTools::FileExists(fname.c_str(), true))
  509. {
  510. cmSystemTools::RemoveFile(fname.c_str());
  511. }
  512. }
  513. //---------------------------------------------------------
  514. void cmCTestMultiProcessHandler::RemoveTest(int index)
  515. {
  516. this->EraseTest(index);
  517. this->Properties.erase(index);
  518. this->TestRunningMap[index] = false;
  519. this->TestFinishMap[index] = true;
  520. this->Completed++;
  521. }
  522. //---------------------------------------------------------
  523. int cmCTestMultiProcessHandler::FindMaxIndex()
  524. {
  525. int max = 0;
  526. cmCTestMultiProcessHandler::TestMap::iterator i = this->Tests.begin();
  527. for(; i != this->Tests.end(); ++i)
  528. {
  529. if(i->first > max)
  530. {
  531. max = i->first;
  532. }
  533. }
  534. return max;
  535. }
  536. //Returns true if no cycles exist in the dependency graph
  537. bool cmCTestMultiProcessHandler::CheckCycles()
  538. {
  539. cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
  540. "Checking test dependency graph..." << std::endl);
  541. for(TestMap::iterator it = this->Tests.begin();
  542. it != this->Tests.end(); ++it)
  543. {
  544. //DFS from each element to itself
  545. std::stack<int> s;
  546. std::vector<int> visited;
  547. s.push(it->first);
  548. visited.push_back(it->first);
  549. while(!s.empty())
  550. {
  551. int test = s.top();
  552. s.pop();
  553. for(TestSet::iterator d = this->Tests[test].begin();
  554. d != this->Tests[test].end(); ++d)
  555. {
  556. s.push(*d);
  557. for(std::vector<int>::iterator v = visited.begin();
  558. v != visited.end(); ++v)
  559. {
  560. if(*v == *d)
  561. {
  562. //cycle exists
  563. cmCTestLog(this->CTest, ERROR_MESSAGE, "Error: a cycle exists in "
  564. "the test dependency graph for the test \""
  565. << this->Properties[*d]->Name << "\"." << std::endl
  566. << "Please fix the cycle and run ctest again." << std::endl);
  567. return false;
  568. }
  569. }
  570. visited.push_back(*d);
  571. }
  572. visited.pop_back();
  573. }
  574. }
  575. return true;
  576. }