Преглед изворни кода

ENH: add initial ctest -j feature

Bill Hoffman пре 17 година
родитељ
комит
bb7b27e417

+ 3 - 0
Source/CMakeLists.txt

@@ -314,6 +314,7 @@ INCLUDE_DIRECTORIES(
 # Sources for CTestLib
 #
 SET(CTEST_SRCS cmCTest.cxx
+  CTest/cmProcess.cxx
   CTest/cmCTestBuildAndTestHandler.cxx
   CTest/cmCTestBuildCommand.cxx
   CTest/cmCTestBuildHandler.cxx
@@ -326,6 +327,7 @@ SET(CTEST_SRCS cmCTest.cxx
   CTest/cmCTestHandlerCommand.cxx
   CTest/cmCTestMemCheckCommand.cxx
   CTest/cmCTestMemCheckHandler.cxx
+  CTest/cmCTestMultiProcessHandler.cxx
   CTest/cmCTestReadCustomFilesCommand.cxx
   CTest/cmCTestRunScriptCommand.cxx
   CTest/cmCTestScriptHandler.cxx
@@ -458,3 +460,4 @@ IF(APPLE)
 ENDIF(APPLE)
 
 INSTALL_FILES(${CMAKE_DATA_DIR}/include cmCPluginAPI.h)
+

+ 6 - 0
Source/CTest/cmCTestGenericHandler.cxx

@@ -161,6 +161,12 @@ bool cmCTestGenericHandler::StartLogFile(const char* name,
     ostr << "_" << this->CTest->GetCurrentTag();
     }
   ostr << ".log";
+  // if this is a parallel subprocess then add the id to the
+  // file so they don't clobber each other
+  if(this->CTest->GetParallelSubprocess())
+    {
+    ostr << "." << this->CTest->GetParallelSubprocessId();
+    }
   if( !this->CTest->OpenOutputFile("Temporary", ostr.str().c_str(), xofs) )
     {
     cmCTestLog(this->CTest, ERROR_MESSAGE, "Cannot create log file: "

+ 323 - 0
Source/CTest/cmCTestMultiProcessHandler.cxx

@@ -0,0 +1,323 @@
+/*=========================================================================
+
+  Program:   CMake - Cross-Platform Makefile Generator
+  Module:    $RCSfile$
+  Language:  C++
+  Date:      $Date$
+  Version:   $Revision$
+
+  Copyright (c) 2002 Kitware, Inc., Insight Consortium.  All rights reserved.
+  See Copyright.txt or http://www.cmake.org/HTML/Copyright.html for details.
+
+     This software is distributed WITHOUT ANY WARRANTY; without even
+     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+     PURPOSE.  See the above copyright notices for more information.
+
+=========================================================================*/
+#include "cmCTestMultiProcessHandler.h"
+#include "cmProcess.h"
+#include "cmStandardIncludes.h"
+#include "cmCTest.h"
+
+
+cmCTestMultiProcessHandler::cmCTestMultiProcessHandler()
+{
+  this->ParallelLevel = 1;
+  this->ProcessId = 0;
+}
+  // Set the tests
+void 
+cmCTestMultiProcessHandler::SetTests(std::map<int, std::set<int> >& tests,
+                                          std::map<int,cmStdString>& testNames)
+{
+  // set test run map to false for all
+  for(std::map<int, std::set<int> >::iterator i = this->Tests.begin();
+      i != this->Tests.end(); ++i)
+    {
+    this->TestRunningMap[i->first] = false;
+    this->TestFinishMap[i->first] = false;
+    }
+  this->Tests = tests;
+  this->TestNames = testNames;
+}
+  // Set the max number of tests that can be run at the same time.
+void cmCTestMultiProcessHandler::SetParallelLevel(size_t l)
+{
+  this->ParallelLevel = l;
+}
+
+
+void cmCTestMultiProcessHandler::RunTests()
+{
+  this->StartNextTests();
+  while(this->Tests.size() != 0)
+    {
+    this->CheckOutput();
+    this->StartNextTests();
+    }
+  // let all running tests finish
+  while(this->CheckOutput())
+    {
+    }
+  
+  for(std::map<int, cmStdString>::iterator i =
+        this->TestOutput.begin();
+      i != this->TestOutput.end(); ++i)
+    {
+    cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, 
+               i->second << std::endl);
+    }
+      
+}
+
+void cmCTestMultiProcessHandler::StartTestProcess(int test)
+{
+  cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, 
+            " test " << test << "\n");
+  this->TestRunningMap[test] = true; // mark the test as running
+  // now remove the test itself
+  this->Tests.erase(test);
+  // now run the test
+  cmProcess* newp = new cmProcess;
+  newp->SetId(this->ProcessId);
+  newp->SetId(test);
+  newp->SetCommand(this->CTestCommand.c_str());
+  std::vector<std::string> args;
+  args.push_back("-I");
+  cmOStringStream strm;
+  strm << test << "," << test;
+  args.push_back(strm.str());
+  args.push_back("--parallel-cache");
+  args.push_back(this->CTestCacheFile.c_str());
+  args.push_back("--internal-ctest-parallel"); 
+  cmOStringStream strm2;
+  strm2 << test;
+  args.push_back(strm2.str());
+  if(this->CTest->GetExtraVerbose())
+    {
+    args.push_back("-VV");
+    }
+  newp->SetCommandArguments(args);
+  if(!newp->StartProcess())
+    {
+     cmCTestLog(this->CTest, ERROR_MESSAGE, 
+                "Error starting " << newp->GetCommand() << "\n");
+    this->EndTest(newp);
+    }
+  else
+    {
+    this->RunningTests.insert(newp);
+    }
+ cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, 
+            "ctest -I " << test << "\n");
+ this->ProcessId++;
+}
+
+bool cmCTestMultiProcessHandler::StartTest(int test)
+{
+  // copy the depend tests locally because when 
+  // a test is finished it will be removed from the depend list
+  // and we don't want to be iterating a list while removing from it
+  std::set<int> depends = this->Tests[test];
+  size_t totalDepends = depends.size();
+  if(totalDepends)
+    {
+    for(std::set<int>::const_iterator i = depends.begin();
+        i != depends.end(); ++i)
+      {
+      // if the test is not already running then start it
+      if(!this->TestRunningMap[*i])
+        {
+        // this test might be finished, but since
+        // this is a copy of the depend map we might
+        // still have it
+        if(!this->TestFinishMap[*i])
+          {
+          // only start one test in this function
+          return this->StartTest(*i);
+          }
+        else
+          {
+          // the depend has been and finished
+          totalDepends--;
+          }
+        }
+      }
+    }
+  // if there are no depends left then run this test
+  if(totalDepends == 0)
+    {
+    // Start this test it has no depends 
+    this->StartTestProcess(test);
+    return true;
+    }
+  // This test was not able to start because it is waiting 
+  // on depends to run
+  return false;  
+}
+
+void cmCTestMultiProcessHandler::StartNextTests()
+{
+  cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, std::endl
+             << "Number of running tests : " << this->RunningTests.size()
+             << "\n");
+  size_t numToStart = this->ParallelLevel - this->RunningTests.size();
+  if(numToStart == 0)
+    {
+    return;
+    }
+  std::map<int, std::set<int> > tests = this->Tests;
+  for(std::map<int, std::set<int> >::iterator i = tests.begin();
+      i !=  tests.end(); ++i)
+    {
+    // start test should start only one test
+    if(this->StartTest(i->first))
+      {
+      numToStart--;
+      }
+    else
+      {
+      cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, std::endl
+             << "Test did not start waiting on depends to finish: " 
+                 << i->first << "\n");
+      }
+    if(numToStart == 0 )
+      {
+      return;
+      }
+    }
+}
+
+
+bool cmCTestMultiProcessHandler::CheckOutput()
+{
+  // no more output we are done
+  if(this->RunningTests.size() == 0)
+    {
+    return false;
+    }
+  std::vector<cmProcess*> finished;
+  std::string out, err;
+  for(std::set<cmProcess*>::const_iterator i = this->RunningTests.begin();
+      i != this->RunningTests.end(); ++i)
+    {
+    cmProcess* p = *i;
+    int pipe = p->CheckOutput(.1, out, err);
+    if(pipe == cmsysProcess_Pipe_STDOUT)
+      {
+      cmCTestLog(this->CTest, HANDLER_OUTPUT, 
+                 p->GetId() << ": " << out << std::endl);
+      this->TestOutput[ p->GetId() ] += out;
+      this->TestOutput[ p->GetId() ] += "\n";
+      }
+    else if(pipe == cmsysProcess_Pipe_STDERR)
+      {
+      cmCTestLog(this->CTest, HANDLER_OUTPUT, 
+                 p->GetId() << ": " << err << std::endl);
+      this->TestOutput[ p->GetId() ] += err;
+      this->TestOutput[ p->GetId() ] += "\n";
+      }
+    if(!p->IsRunning())
+      {
+      finished.push_back(p);
+      }
+    }
+  for( std::vector<cmProcess*>::iterator i = finished.begin();
+       i != finished.end(); ++i)
+    {
+    cmProcess* p = *i;
+    this->EndTest(p);
+    }
+  return true;
+}
+
+void cmCTestMultiProcessHandler::EndTest(cmProcess* p)
+{
+  int test = p->GetId();
+  int exitVal = p->GetExitValue();
+  cmCTestTestHandler::cmCTestTestResult cres;
+  cres.Properties = 0;
+  cres.ExecutionTime = 0;// ???
+  cres.ReturnValue = exitVal;
+  cres.Status = cmCTestTestHandler::COMPLETED;
+  cres.TestCount = test;  
+  cres.Name = this->TestNames[test];
+  cres.Path.clear();
+  if(exitVal)
+    {
+    cres.Status = cmCTestTestHandler::FAILED;
+    this->Failed->push_back(this->TestNames[test]);
+    }
+  else
+    {
+    this->Passed->push_back(this->TestNames[test]);
+    }
+  this->TestResults->push_back(cres);
+  // remove test from depend of all other tests
+  for( std::map<int, std::set<int> >::iterator i = this->Tests.begin();
+       i!=  this->Tests.end(); ++i)
+    {
+    i->second.erase(test);
+    }
+  this->TestFinishMap[test] = true;
+  this->TestRunningMap[test] = false;
+  this->RunningTests.erase(p);
+  delete p;
+  cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, 
+             "finish test " << test << "\n");
+}
+
+
+void cmCTestMultiProcessHandler::PrintTests()
+{
+#undef cout
+  for( std::map<int, std::set<int> >::iterator i = this->Tests.begin();
+       i!=  this->Tests.end(); ++i)
+    {
+    std::cout << "Test " << i->first << "  (";
+    for(std::set<int>::iterator j = i->second.begin(); 
+        j != i->second.end(); ++j)
+      {
+      std::cout << *j << " ";
+      }
+    std::cout << ")\n";
+    }
+}
+
+#if 0
+int main()
+{
+  cmCTestMultiProcessHandler h;
+  h.SetParallelLevel(4);
+  std::map<int, std::set<int> > tests;
+  std::set<int> depends;
+  for(int i =1; i < 92; i++)
+    {
+    tests[i] = depends;
+    }
+  depends.clear();
+  depends.insert(45);  subprject
+  tests[46] = depends;  subproject-stage2
+  depends.clear();
+  depends.insert(55);  simpleinstall simpleinstall-s2
+  tests[56] = depends;
+  depends.clear();
+  depends.insert(70);  wrapping
+  tests[71] = depends; qtwrapping
+  depends.clear();
+  depends.insert(71);  qtwrapping
+  tests[72] = depends;  testdriver1
+  depends.clear();
+  depends.insert(72)    testdriver1
+  tests[73] = depends;  testdriver2
+  depends.clear();
+  depends.insert(73);   testdriver2
+  tests[74] = depends;  testdriver3
+  depends.clear();
+  depends.insert(79);   linkorder1
+  tests[80] = depends;  linkorder2
+  h.SetTests(tests);
+  h.PrintTests();
+  h.RunTests();
+}
+#endif

+ 84 - 0
Source/CTest/cmCTestMultiProcessHandler.h

@@ -0,0 +1,84 @@
+/*=========================================================================
+
+  Program:   CMake - Cross-Platform Makefile Generator
+  Module:    $RCSfile$
+  Language:  C++
+  Date:      $Date$
+  Version:   $Revision$
+
+  Copyright (c) 2002 Kitware, Inc., Insight Consortium.  All rights reserved.
+  See Copyright.txt or http://www.cmake.org/HTML/Copyright.html for details.
+
+     This software is distributed WITHOUT ANY WARRANTY; without even
+     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+     PURPOSE.  See the above copyright notices for more information.
+
+=========================================================================*/
+#ifndef cmCTestMultiProcessHandler_h
+#define cmCTestMultiProcessHandler_h
+
+#include <set>
+#include <map>
+#include <string>
+#include <vector>
+class cmProcess;
+#include <cmStandardIncludes.h>
+#include <cmCTestTestHandler.h>
+
+/** \class cmCTestMultiProcessHandler
+ * \brief run parallel ctest
+ *
+ * cmCTestMultiProcessHandler 
+ */
+class cmCTestMultiProcessHandler 
+{
+public:
+  cmCTestMultiProcessHandler();
+  // Set the tests
+  void SetTests(std::map<int, std::set<int> >& tests,
+                std::map<int, cmStdString>& testNames);
+  // Set the max number of tests that can be run at the same time.
+  void SetParallelLevel(size_t);
+  void RunTests();
+  void PrintTests();
+  void SetCTestCommand(const char* c) { this->CTestCommand = c;}
+  void SetTestCacheFile(const char* c) { this->CTestCacheFile = c;}
+  void SetPassFailVectors(std::vector<cmStdString>* passed,
+                          std::vector<cmStdString>* failed)
+    {
+      this->Passed = passed;
+      this->Failed = failed;
+    }
+  void SetTestResults(std::vector<cmCTestTestHandler::cmCTestTestResult>* r)
+    {
+      this->TestResults = r;
+    }
+  void SetCTest(cmCTest* ctest) { this->CTest = ctest;}
+protected:  
+  cmCTest* CTest;
+  // Start the next test or tests as many as are allowed by
+  // ParallelLevel
+  void StartNextTests();
+  void StartTestProcess(int test);
+  bool StartTest(int test);
+  void EndTest(cmProcess*);
+  // Return true if there are still tests running
+  // check all running processes for output and exit case
+  bool CheckOutput();
+  // map from test number to set of depend tests
+  std::map<int, std::set<int> > Tests;
+  std::map<int, cmStdString> TestNames;
+  std::map<int, bool> TestRunningMap;
+  std::map<int, bool> TestFinishMap;
+  std::map<int, cmStdString> TestOutput;
+  std::string CTestCommand;
+  std::string CTestCacheFile;
+  std::vector<cmStdString>* Passed;
+  std::vector<cmStdString>* Failed;
+  std::vector<cmCTestTestHandler::cmCTestTestResult>* TestResults;
+  int ProcessId;
+  size_t ParallelLevel; // max number of process that can be run at once
+  std::set<cmProcess*> RunningTests;  // current running tests
+};
+
+#endif

+ 374 - 64
Source/CTest/cmCTestTestHandler.cxx

@@ -16,7 +16,7 @@
 =========================================================================*/
 
 #include "cmCTestTestHandler.h"
-
+#include "cmCTestMultiProcessHandler.h"
 #include "cmCTest.h"
 #include "cmake.h"
 #include "cmGeneratedFileStream.h"
@@ -500,11 +500,14 @@ int cmCTestTestHandler::ProcessHandler()
     }
   
   this->TestResults.clear();
-
-  cmCTestLog(this->CTest, HANDLER_OUTPUT,
-    (this->MemCheck ? "Memory check" : "Test")
-             << " project " << cmSystemTools::GetCurrentWorkingDirectory()
-             << std::endl);
+ // do not output startup if this is a sub-process for parallel tests
+  if(!this->CTest->GetParallelSubprocess())
+    {
+    cmCTestLog(this->CTest, HANDLER_OUTPUT,
+               (this->MemCheck ? "Memory check" : "Test")
+               << " project " << cmSystemTools::GetCurrentWorkingDirectory()
+               << std::endl);
+    }
   if ( ! this->PreProcessHandler() )
     {
     return -1;
@@ -517,7 +520,6 @@ int cmCTestTestHandler::ProcessHandler()
   std::vector<cmStdString> passed;
   std::vector<cmStdString> failed;
   int total;
-
   this->ProcessDirectory(passed, failed);
 
   total = int(passed.size()) + int(failed.size());
@@ -550,33 +552,37 @@ int cmCTestTestHandler::ProcessHandler()
       {
       percent = 99;
       }
-    cmCTestLog(this->CTest, HANDLER_OUTPUT, std::endl
-      << static_cast<int>(percent + .5) << "% tests passed, "
-      << failed.size() << " tests failed out of " << total << std::endl);
-    //fprintf(stderr,"\n%.0f%% tests passed, %i tests failed out of %i\n",
-    //  percent, int(failed.size()), total);
+    if(!this->CTest->GetParallelSubprocess())
+      {
+      cmCTestLog(this->CTest, HANDLER_OUTPUT, std::endl
+                 << static_cast<int>(percent + .5) << "% tests passed, "
+                 << failed.size() << " tests failed out of " 
+                 << total << std::endl);
+      }
 
     if (failed.size())
       {
       cmGeneratedFileStream ofs;
-
-      cmCTestLog(this->CTest, ERROR_MESSAGE, std::endl
-        << "The following tests FAILED:" << std::endl);
-      this->StartLogFile("TestsFailed", ofs);
-
-      std::vector<cmCTestTestHandler::cmCTestTestResult>::iterator ftit;
-      for(ftit = this->TestResults.begin();
-        ftit != this->TestResults.end(); ++ftit)
+      if(!this->CTest->GetParallelSubprocess())
         {
-        if ( ftit->Status != cmCTestTestHandler::COMPLETED )
+        cmCTestLog(this->CTest, ERROR_MESSAGE, std::endl
+                   << "The following tests FAILED:" << std::endl);
+        this->StartLogFile("TestsFailed", ofs);
+        
+        std::vector<cmCTestTestHandler::cmCTestTestResult>::iterator ftit;
+        for(ftit = this->TestResults.begin();
+            ftit != this->TestResults.end(); ++ftit)
           {
-          ofs << ftit->TestCount << ":" << ftit->Name << std::endl;
-          cmCTestLog(this->CTest, HANDLER_OUTPUT, "\t" << std::setw(3)
-            << ftit->TestCount << " - " << ftit->Name.c_str() << " ("
-            << this->GetTestStatus(ftit->Status) << ")" << std::endl);
+          if ( ftit->Status != cmCTestTestHandler::COMPLETED )
+            {
+            ofs << ftit->TestCount << ":" << ftit->Name << std::endl;
+            cmCTestLog(this->CTest, HANDLER_OUTPUT, "\t" << std::setw(3)
+                       << ftit->TestCount << " - " << ftit->Name.c_str() << " ("
+                       << this->GetTestStatus(ftit->Status) << ")" << std::endl);
+            }
           }
+        
         }
-
       }
     }
 
@@ -677,8 +683,8 @@ void cmCTestTestHandler::ProcessOneTest(cmCTestTestProperties *it,
   
   // add the arguments
   std::vector<std::string>::const_iterator j = args.begin();
-  ++j;
-  ++j;
+  ++j; // skip test name
+  ++j; // skip command as it is in actualCommand
   std::vector<const char*> arguments;
   this->GenerateTestCommand(arguments);
   arguments.push_back(actualCommand.c_str());
@@ -917,23 +923,20 @@ void cmCTestTestHandler::ProcessOneTest(cmCTestTestProperties *it,
 }
 
 //----------------------------------------------------------------------
-void cmCTestTestHandler::ProcessDirectory(std::vector<cmStdString> &passed,
-                                          std::vector<cmStdString> &failed)
+void cmCTestTestHandler::ComputeTestList()
 {
-  std::string current_dir = cmSystemTools::GetCurrentWorkingDirectory();
-  this->TestList.clear();
-
-  this->GetListOfTests();
+  this->TestList.clear(); // clear list of test
+  if(this->CTest->GetParallelSubprocess())
+    {
+    this->LoadTestList();
+    return;
+    }
+  else
+    {
+    this->GetListOfTests();
+    }
   cmCTestTestHandler::ListOfTests::size_type tmsize = this->TestList.size();
 
-  this->StartTest = this->CTest->CurrentTime();
-  this->StartTestTime = static_cast<unsigned int>(cmSystemTools::GetTime());
-  double elapsed_time_start = cmSystemTools::GetTime();
-
-  *this->LogFile << "Start testing: " << this->StartTest << std::endl
-    << "----------------------------------------------------------"
-    << std::endl;
-
   // how many tests are in based on RegExp?
   int inREcnt = 0;
   cmCTestTestHandler::ListOfTests::iterator it;
@@ -953,10 +956,11 @@ void cmCTestTestHandler::ProcessDirectory(std::vector<cmStdString> &passed,
     {
     this->ExpandTestsToRunInformation(inREcnt);
     }
-
+  // Now create a final list of tests to run
   int cnt = 0;
   inREcnt = 0;
-  std::string last_directory = "";
+  std::string last_directory = ""; 
+  ListOfTests finalList;
   for ( it = this->TestList.begin(); it != this->TestList.end(); it ++ )
     {
     cnt ++;
@@ -965,23 +969,6 @@ void cmCTestTestHandler::ProcessDirectory(std::vector<cmStdString> &passed,
       inREcnt++;
       }
 
-    // if we are out of time then skip this test, we leave two minutes 
-    // to submit results
-    if (this->CTest->GetRemainingTimeAllowed() - 120 <= 0)
-      {
-      continue;
-      }
-    
-    if (!(last_directory == it->Directory))
-      {
-      cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
-                 "Changing directory into " << it->Directory.c_str() << "\n");
-      *this->LogFile << "Changing directory into: " << it->Directory.c_str()
-                     << std::endl;
-      last_directory = it->Directory;
-      cmSystemTools::ChangeDirectory(it->Directory.c_str());
-      }
-
     if (this->UseUnion)
       {
       // if it is not in the list and not in the regexp then skip
@@ -1003,10 +990,322 @@ void cmCTestTestHandler::ProcessDirectory(std::vector<cmStdString> &passed,
         continue;
         }
       }
-    
+    it->Index = cnt;  // save the index into the test list for this test
+    finalList.push_back(*it);
+    }
+  // Save the total number of tests before exclusions
+  this->TotalNumberOfTests = this->TestList.size();
+  // Set the TestList to the final list of all test
+  this->TestList = finalList;
+}
+ 
+bool cmCTestTestHandler::GetValue(const char* tag,
+                                  int& value,
+                                  std::ifstream& fin)
+{
+  std::string line;
+  cmSystemTools::GetLineFromStream(fin, line);
+  if(line == tag)
+    {
+    fin >> value;
+    cmSystemTools::GetLineFromStream(fin, line); // read blank line
+    }
+  else
+    {
+    cmCTestLog(this->CTest, ERROR_MESSAGE,
+               "parse error: missing tag: "
+               << tag << " found [" << line << "]" << std::endl);
+    return false;
+    }
+  return true;
+}
+
+bool cmCTestTestHandler::GetValue(const char* tag,
+                                  double& value,
+                                  std::ifstream& fin)
+{
+  std::string line;
+  cmSystemTools::GetLineFromStream(fin, line);
+  if(line == tag)
+    {
+    fin >> value;
+    cmSystemTools::GetLineFromStream(fin, line); // read blank line
+    }
+  else
+    {
+    cmCTestLog(this->CTest, ERROR_MESSAGE,
+               "parse error: missing tag: "
+               << tag << " found [" << line << "]" << std::endl);
+    return false;
+    }
+  return true;
+}
+
+bool cmCTestTestHandler::GetValue(const char* tag,
+                                  bool& value,
+                                  std::ifstream& fin)
+{
+  std::string line;
+  cmSystemTools::GetLineFromStream(fin, line);
+  if(line == tag)
+    {
+    fin >> value;
+    cmSystemTools::GetLineFromStream(fin, line); // read blank line
+    }
+  else
+    {
+    cmCTestLog(this->CTest, ERROR_MESSAGE,
+               "parse error: missing tag: "
+               << tag << " found [" << line << "]" << std::endl);
+    return false;
+    }
+  return true;
+}
+
+bool cmCTestTestHandler::GetValue(const char* tag,
+                                  size_t& value,
+                                  std::ifstream& fin)
+{
+  std::string line;
+  cmSystemTools::GetLineFromStream(fin, line);
+  if(line == tag)
+    {
+    fin >> value;
+    cmSystemTools::GetLineFromStream(fin, line); // read blank line
+    }
+  else
+    { 
+    cmCTestLog(this->CTest, ERROR_MESSAGE,
+               "parse error: missing tag: "
+               << tag << " found [" << line.c_str() << "]" << std::endl);
+    return false;
+    }
+  return true;
+}
+
+bool cmCTestTestHandler::GetValue(const char* tag,
+                                  std::string& value,
+                                  std::ifstream& fin)
+{
+  std::string line;
+  cmSystemTools::GetLineFromStream(fin, line);
+  if(line == tag)
+    {
+    return cmSystemTools::GetLineFromStream(fin, value);
+    }
+  else
+    {
+    cmCTestLog(this->CTest, ERROR_MESSAGE,
+               "parse error: missing tag: "
+               << tag << " found [" << line << "]" << std::endl);
+    return false;
+    }
+  return true;
+}
+
+
+// This should load only one test and is used in -j N mode.
+// it is used by the sub-process ctest runs which should have
+// only one -I N test to run.
+void cmCTestTestHandler::LoadTestList()
+{
+  this->TestList.clear();
+  std::string fname = this->CTest->GetBinaryDir()
+    + "/Testing/Temporary/PCache.txt";
+  std::ifstream fin(fname.c_str());
+  std::string line;
+  if(!fin)
+    {
+    cmCTestLog(this->CTest, ERROR_MESSAGE,
+               "Could not load PCache.txt file: "
+               << fname.c_str() << std::endl);
+    return;
+    }
+  bool ok = true;
+  int numTestsToRun;
+  ok = ok && this->GetValue("TotalNumberOfTests:", 
+                            this->TotalNumberOfTests, fin);
+  ok = ok && this->GetValue("NumberOfTestsToRun:", numTestsToRun, fin);
+  this->ExpandTestsToRunInformation(this->TotalNumberOfTests);
+  if(this->TestsToRun.size() != 1)
+    { 
+    cmCTestLog(this->CTest, ERROR_MESSAGE,
+               "Error when in parallel mode only one test should be run: "
+               << this->TestsToRun.size() << std::endl);
+    }
+  int testIndexToRun = this->TestsToRun[0];
+  this->CTest->SetParallelSubprocessId(testIndexToRun);
+  if(!ok)
+    {
+    return;
+    }
+  for(int i =0; i < numTestsToRun; i++)
+    {
+    cmCTestTestProperties p;
+    int numArgs;
+    bool ok = this->GetValue("Name:", p.Name, fin);
+    ok = ok && this->GetValue("Directory:", p.Directory, fin);
+    ok = ok && this->GetValue("Args:", numArgs, fin);
+    for(int j =0; j < numArgs; ++j)
+      {
+      cmSystemTools::GetLineFromStream(fin, line);
+      p.Args.push_back(line);
+      }
+    int numDep = 0;
+    ok = ok && this->GetValue("Depends:", numDep, fin);
+    for(int j =0; j < numDep; ++j)
+      {
+      cmSystemTools::GetLineFromStream(fin, line);
+      p.Depends.push_back(line);
+      }
+    int isinre;
+    ok = ok && this->GetValue("IsInBasedOnREOptions:", isinre, fin);
+    ok = ok && this->GetValue("WillFail:", p.WillFail, fin);
+    ok = ok && this->GetValue("TimeOut:", p.Timeout, fin);
+    ok = ok && this->GetValue("Index:", p.Index, fin);
+    if(!ok)
+      {
+      return;
+      }
+    if(p.Index == testIndexToRun)
+      {
+      // add the one test and stop reading
+      this->TestList.push_back(p);
+      return;
+      }
+    }
+}
+std::string cmCTestTestHandler::SaveTestList()
+{
+  std::string fname = this->CTest->GetBinaryDir()
+    + "/Testing/Temporary/PCache.txt";
+  cmGeneratedFileStream fout(fname.c_str());
+  if(!fout)
+    {
+    cmCTestLog(this->CTest, ERROR_MESSAGE, std::endl
+               << "Could not open PCache.txt for write:" 
+               << fname.c_str()
+               << std::endl);
+    }
+  fout << "TotalNumberOfTests:\n";
+  fout << this->TotalNumberOfTests << "\n";
+  fout << "NumberOfTestsToRun:\n";
+  fout << this->TestList.size() << "\n";
+  for (ListOfTests::iterator it = this->TestList.begin();
+       it != this->TestList.end(); it ++ )
+    {
+    cmCTestTestProperties& p = *it;
+    fout << "Name:\n"
+         << p.Name.c_str()
+         << "\nDirectory:\n"
+         << p.Directory.c_str()
+         << "\nArgs:\n"
+         << p.Args.size() << "\n";
+    for(std::vector<std::string>::iterator i = p.Args.begin();
+        i != p.Args.end(); ++i)
+      {
+      fout << i->c_str() << "\n";
+      }
+    fout << "Depends:\n" << p.Depends.size() << "\n";
+    for(std::vector<std::string>::iterator i = p.Depends.begin();
+        i != p.Depends.end(); ++i)
+      {
+      fout << i->c_str() << "\n";
+      }
+    fout << "IsInBasedOnREOptions:\n"
+         << p.IsInBasedOnREOptions
+         << "\nWillFail:\n"
+         << p.WillFail
+         << "\nTimeOut:\n"
+         << p.Timeout
+         << "\nIndex:\n"
+         << p.Index << "\n";
+    }
+  fout.close();
+  return fname;
+}
+
+void cmCTestTestHandler::ProcessParallel(std::vector<cmStdString> &passed,
+                                         std::vector<cmStdString> &failed)
+{
+  this->ComputeTestList();
+  cmCTestMultiProcessHandler parallel;
+  parallel.SetCTest(this->CTest);
+  parallel.SetParallelLevel(this->CTest->GetParallelLevel()); 
+  std::set<int> depends;
+  std::map<int, std::set<int> > tests; 
+  std::map<int, cmStdString> testnames;
+  for (ListOfTests::iterator it = this->TestList.begin();
+       it != this->TestList.end(); it ++ )
+    { 
+    cmCTestTestProperties& p = *it;
+    testnames[p.Index] = p.Name;
+    if(p.Depends.size())
+      {
+      for(std::vector<std::string>::iterator i = p.Depends.begin();
+        i != p.Depends.end(); ++i)
+        {
+        for(ListOfTests::iterator it2 = this->TestList.begin();
+            it2 != this->TestList.end(); it2 ++ )
+          {
+          if(it2->Name == *i)
+            {
+            depends.insert(it2->Index);
+            break; // break out of test loop as name can only match 1
+            }
+          }
+        }
+      }
+    tests[it->Index] = depends;
+    }
+  parallel.SetCTestCommand(this->CTest->GetCTestExecutable());
+  parallel.SetTests(tests, testnames);
+  std::string fname = this->SaveTestList();
+  parallel.SetTestCacheFile(fname.c_str());
+  parallel.SetPassFailVectors(&passed, &failed);
+  this->TestResults.clear();
+  parallel.SetTestResults(&this->TestResults);
+  parallel.RunTests();
+  cmSystemTools::RemoveFile(fname.c_str());
+}
+
+
+//----------------------------------------------------------------------
+void cmCTestTestHandler::ProcessDirectory(std::vector<cmStdString> &passed,
+                                          std::vector<cmStdString> &failed)
+{
+  if(this->CTest->GetParallelLevel() > 0)
+    {
+    this->ProcessParallel(passed, failed);
+    return;
+    }
+  // save the current working directory
+  std::string current_dir = cmSystemTools::GetCurrentWorkingDirectory();
+  // compute the list of tests to run
+  this->ComputeTestList();
+  this->StartTest = this->CTest->CurrentTime();
+  this->StartTestTime = static_cast<unsigned int>(cmSystemTools::GetTime());
+  double elapsed_time_start = cmSystemTools::GetTime();
+  *this->LogFile << "Start testing: " << this->StartTest << std::endl
+    << "----------------------------------------------------------"
+    << std::endl;
+  std::string last_directory = "";
+  // run each test
+  for (ListOfTests::iterator it = this->TestList.begin();
+       it != this->TestList.end(); it ++ )
+    {
+    if (!(last_directory == it->Directory))
+      {
+      cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
+                 "Changing directory into " << it->Directory.c_str() << "\n");
+      *this->LogFile << "Changing directory into: " << it->Directory.c_str()
+                     << std::endl;
+      last_directory = it->Directory;
+      cmSystemTools::ChangeDirectory(it->Directory.c_str());
+      }
     // process this one test
-    this->ProcessOneTest(&(*it), passed, failed, cnt, 
-                         static_cast<int>(tmsize));
+    this->ProcessOneTest(&(*it), passed, failed, it->Index, 
+                         static_cast<int>(this->TotalNumberOfTests));
     }
 
   this->EndTest = this->CTest->CurrentTime();
@@ -1890,6 +2189,17 @@ bool cmCTestTestHandler::SetTestsProperties(
                   std::string(crit->c_str())));
               }
             }
+          if ( key == "DEPENDS" )
+            { 
+            std::vector<std::string> lval;
+            cmSystemTools::ExpandListArgument(val.c_str(), lval);
+            std::vector<std::string>::iterator crit;
+            for ( crit = lval.begin(); crit != lval.end(); ++ crit )
+              {
+              cmCTestTestProperties* tp = &(*rtit);
+              rtit->Depends.push_back(*crit);
+              }
+            }
           if ( key == "MEASUREMENT" )
             {
             size_t pos = val.find_first_of("=");

+ 32 - 3
Source/CTest/cmCTestTestHandler.h

@@ -80,6 +80,7 @@ public:
     cmStdString Name;
     cmStdString Directory;
     std::vector<std::string> Args;
+    std::vector<std::string> Depends;
     std::vector<std::pair<cmsys::RegularExpression,
                           std::string> > ErrorRegularExpressions;
     std::vector<std::pair<cmsys::RegularExpression,
@@ -88,6 +89,7 @@ public:
     bool IsInBasedOnREOptions;
     bool WillFail;
     double Timeout;
+    int Index;
   };
 
   struct cmCTestTestResult
@@ -120,6 +122,7 @@ public:
                                     std::vector<std::string> &failed);
 
 protected:
+  // comput a final test list
   virtual int PreProcessHandler();
   virtual int PostProcessHandler();
   virtual void GenerateTestCommand(std::vector<const char*>& args);
@@ -152,7 +155,7 @@ protected:
 
 
 
-private:
+public:
   enum { // Program statuses
     NOT_RUN = 0,
     TIMEOUT,
@@ -166,7 +169,7 @@ private:
     COMPLETED
   };
 
-
+private:
   /**
    * Generate the Dart compatible output
    */
@@ -183,7 +186,32 @@ private:
    * Get the list of tests in directory and subdirectories.
    */
   void GetListOfTests();
-
+  // compute the lists of tests that will actually run
+  // based on union regex and -I stuff
+  void ComputeTestList();
+  
+  // Save the state of the test list and return the file
+  // name it was saved to
+  std::string SaveTestList();
+  void LoadTestList();
+  bool GetValue(const char* tag,
+                std::string& value,
+                std::ifstream& fin);
+  bool GetValue(const char* tag,
+                int& value,
+                std::ifstream& fin);
+  bool GetValue(const char* tag,
+                size_t& value,
+                std::ifstream& fin);
+  bool GetValue(const char* tag,
+                bool& value,
+                std::ifstream& fin);
+  bool GetValue(const char* tag,
+                double& value,
+                std::ifstream& fin);
+  // run in -j N mode
+  void ProcessParallel(std::vector<cmStdString> &passed,
+                       std::vector<cmStdString> &failed);
   /**
    * Find the executable for a test
    */
@@ -210,6 +238,7 @@ private:
   std::string TestsToRunString;
   bool UseUnion;
   ListOfTests TestList;
+  size_t TotalNumberOfTests;
   cmsys::RegularExpression DartStuff;
 
   std::ostream* LogFile;

+ 38 - 5
Source/cmCTest.cxx

@@ -229,10 +229,13 @@ std::string cmCTest::MakeURLSafe(const std::string& str)
 //----------------------------------------------------------------------
 cmCTest::cmCTest()
 {
+  this->ParallelSubprocess     = false;
+  this->ParallelLevel          = 0;
   this->SubmitIndex            = 0;
   this->ForceNewCTestProcess   = false;
   this->TomorrowTag            = false;
   this->Verbose                = false;
+  
   this->Debug                  = false;
   this->ShowLineNumbers        = false;
   this->Quiet                  = false;
@@ -802,7 +805,11 @@ int cmCTest::ProcessTests()
   int cc;
   int update_count = 0;
 
-  cmCTestLog(this, OUTPUT, "Start processing tests" << std::endl);
+  // do not output startup if this is a sub-process for parallel tests
+  if(!this->GetParallelSubprocess())
+    {
+    cmCTestLog(this, OUTPUT, "Start processing tests" << std::endl);
+    }
 
   for ( cc = 0; cc < LAST_TEST; cc ++ )
     {
@@ -915,8 +922,11 @@ int cmCTest::ProcessTests()
     }
   if ( res != 0 )
     {
-    cmCTestLog(this, ERROR_MESSAGE, "Errors while running CTest"
-      << std::endl);
+    if(!this->GetParallelSubprocess())
+      {
+      cmCTestLog(this, ERROR_MESSAGE, "Errors while running CTest"
+                 << std::endl);
+      }
     }
   return res;
 }
@@ -1626,10 +1636,31 @@ void cmCTest::HandleCommandLineArguments(size_t &i,
                                          std::vector<std::string> &args)
 {
   std::string arg = args[i];
-  if(this->CheckArgument(arg, "--ctest-config") && i < args.size() - 1)
+
+  if(this->CheckArgument(arg, "-j", "--parallel") && i < args.size() - 1)
+    {
+    i++;
+    int plevel = atoi(args[i].c_str());
+    this->SetParallelLevel(plevel);
+    }
+  else if(arg.find("-j") == 0)
+    {
+    int plevel = atoi(arg.substr(2).c_str());
+    this->SetParallelLevel(plevel);
+    }
+  if(this->CheckArgument(arg, "--internal-ctest-parallel") && i < args.size() - 1)
+    {
+    i++;
+    int pid = atoi(args[i].c_str());
+    this->SetParallelSubprocessId(pid);
+    this->SetParallelSubprocess();
+    }
+  
+  if(this->CheckArgument(arg, "--parallel-cache") && i < args.size() - 1)
     {
     i++;
-    this->CTestConfigFile= args[i];
+    int plevel = atoi(args[i].c_str());
+    this->SetParallelCacheFile(args[i].c_str());
     }
   
   if(this->CheckArgument(arg, "-C", "--build-config") &&
@@ -1966,6 +1997,8 @@ int cmCTest::Run(std::vector<std::string> &args, std::string* output)
       }
     else
       {
+      // What is this?  -V seems to be the same as -VV, 
+      // and Verbose is always on in this case
       this->ExtraVerbose = this->Verbose;
       this->Verbose = true;
       cmCTest::t_TestingHandlers::iterator it;

+ 18 - 0
Source/cmCTest.h

@@ -95,6 +95,18 @@ public:
   std::string const& GetConfigType();
   double GetTimeOut() { return this->TimeOut; }
   void SetTimeOut(double t) { this->TimeOut = t; }
+  // how many test to run at the same time
+  int GetParallelLevel() { return this->ParallelLevel; }
+  void SetParallelLevel(int t) { this->ParallelLevel = t; }
+
+  bool GetParallelSubprocess() { return this->ParallelSubprocess; }
+  void SetParallelSubprocess() { this->ParallelSubprocess = true; }
+
+  void SetParallelSubprocessId(int id) { this->ParallelSubprocessId = id;}
+  int GetParallelSubprocessId() { return this->ParallelSubprocessId;}
+  const char* GetParallelCacheFile()
+    { return this->ParallelCacheFile.c_str();}
+  void SetParallelCacheFile(const char* c) { this->ParallelCacheFile = c; }
 
   /**
    * Check if CTest file exists
@@ -310,6 +322,8 @@ public:
   void SetSpecificTrack(const char* track);
   const char* GetSpecificTrack();
 
+  bool GetVerbose() { return this->Verbose;}
+  bool GetExtraVerbose() { return this->ExtraVerbose;}
 private:
   std::string ConfigType;
   bool Verbose;
@@ -359,6 +373,10 @@ private:
 
   double                  TimeOut;
 
+  std::string             ParallelCacheFile;
+  int                     ParallelLevel;
+  int                     ParallelSubprocessId;
+  bool                    ParallelSubprocess;
   int                     CompatibilityMode;
 
   // information for the --build-and-test options

+ 1 - 1
Source/cmGlobalGenerator.cxx

@@ -196,7 +196,7 @@ void cmGlobalGenerator::FindMakeProgram(cmMakefile* mf)
 
 void
 cmGlobalGenerator::EnableLanguage(std::vector<std::string>const& languages,
-                                  cmMakefile *mf, bool)
+                                  cmMakefile *mf, bool optional)
 {
   if(languages.size() == 0)
     {

+ 5 - 2
Source/cmGlobalUnixMakefileGenerator3.cxx

@@ -59,8 +59,11 @@ void cmGlobalUnixMakefileGenerator3
     
     if(!mf->GetDefinition(langComp.c_str()))
       {
-      cmSystemTools::Error(langComp.c_str(), 
-                           " not set, after EnableLanguage");
+      if(!optional)
+        {
+        cmSystemTools::Error(langComp.c_str(), 
+                             " not set, after EnableLanguage");
+        }
       continue;
       }
     const char* name = mf->GetRequiredDefinition(langComp.c_str()); 

+ 9 - 0
Tests/CMakeLists.txt

@@ -580,6 +580,15 @@ ${CMake_BINARY_DIR}/bin/cmake -DVERSION=CVS -P ${CMake_SOURCE_DIR}/Utilities/Rel
     --build-project LinkLineOrder
     --test-command Exec2
     )
+  IF(COMMAND SET_TESTS_PROPERTIES AND COMMAND GET_TEST_PROPERTY)
+    SET_TESTS_PROPERTIES ( qtwrapping PROPERTIES DEPENDS wrapping)
+    SET_TESTS_PROPERTIES ( testdriver1 PROPERTIES DEPENDS qtwrapping)
+    SET_TESTS_PROPERTIES ( testdriver2 PROPERTIES DEPENDS testdriver1)
+    SET_TESTS_PROPERTIES ( testdriver3 PROPERTIES DEPENDS testdriver2)
+    SET_TESTS_PROPERTIES ( linkorder2 PROPERTIES DEPENDS linkorder1)
+    SET_TESTS_PROPERTIES ( SubProject-Stage2 PROPERTIES DEPENDS SubProject)
+    SET_TESTS_PROPERTIES ( SimpleInstall-Stage2 PROPERTIES DEPENDS SimpleInstall)
+  ENDIF(COMMAND SET_TESTS_PROPERTIES AND COMMAND GET_TEST_PROPERTY)
 
   IF(NOT CMAKE_TEST_DIFFERENT_GENERATOR)
     ADD_TEST(kwsys ${CMAKE_CTEST_COMMAND}