From 2f84bd17581920eb5cbfc3b41e8cf477ba6a5853 Mon Sep 17 00:00:00 2001 From: Ken Martin Date: Thu, 25 Jan 2007 11:16:16 -0500 Subject: [PATCH] ENH: added per test timeout support --- Source/CTest/cmCTestBuildAndTestHandler.cxx | 13 +- Source/CTest/cmCTestBuildAndTestHandler.h | 1 + Source/CTest/cmCTestTestHandler.cxx | 579 ++++++++++---------- Source/CTest/cmCTestTestHandler.h | 10 +- Source/cmCTest.cxx | 16 +- Source/cmCTest.h | 2 +- Source/cmTest.cxx | 9 + Source/ctest.cxx | 4 +- 8 files changed, 345 insertions(+), 289 deletions(-) diff --git a/Source/CTest/cmCTestBuildAndTestHandler.cxx b/Source/CTest/cmCTestBuildAndTestHandler.cxx index d82015bdc..f7b74810c 100644 --- a/Source/CTest/cmCTestBuildAndTestHandler.cxx +++ b/Source/CTest/cmCTestBuildAndTestHandler.cxx @@ -29,6 +29,7 @@ cmCTestBuildAndTestHandler::cmCTestBuildAndTestHandler() this->BuildTwoConfig = false; this->BuildNoClean = false; this->BuildNoCMake = false; + this->Timeout = 0; } //---------------------------------------------------------------------- @@ -146,7 +147,6 @@ int cmCTestBuildAndTestHandler::RunCMakeAndTest(std::string* outstring) cmSystemTools::SetErrorCallback(CMakeMessageCallback, &cmakeOutString); cmSystemTools::SetStdoutCallback(CMakeStdoutCallback, &cmakeOutString); cmOStringStream out; - // What is this? double timeout = this->CTest->GetTimeOut(); // if the generator and make program are not specified then it is an error if (!this->BuildGenerator.size() || !this->BuildMakeProgram.size()) @@ -217,7 +217,7 @@ int cmCTestBuildAndTestHandler::RunCMakeAndTest(std::string* outstring) *outstring = out.str(); } - // if not test was specified then we are done + // if no test was specified then we are done if (!this->TestCommand.size()) { return 0; @@ -361,8 +361,8 @@ int cmCTestBuildAndTestHandler::RunCMakeAndTest(std::string* outstring) out << this->TestCommandArgs[k] << " "; } out << "\n"; - // What is this? this->TimeOut = timeout; - int runTestRes = this->CTest->RunTest(testCommand, &outs, &retval, 0); + int runTestRes = this->CTest->RunTest(testCommand, &outs, &retval, 0, + this->Timeout); if(runTestRes != cmsysProcess_State_Exited || retval != 0) { out << "Failed to run test command: " << testCommand[0] << "\n"; @@ -432,6 +432,11 @@ int cmCTestBuildAndTestHandler::ProcessCommandLineArguments( idx++; this->ExecutableDirectory = allArgs[idx]; } + if(currentArg.find("--test-timeout",0) == 0 && idx < allArgs.size() - 1) + { + idx++; + this->Timeout = atof(allArgs[idx].c_str()); + } if(currentArg.find("--build-generator",0) == 0 && idx < allArgs.size() - 1) { idx++; diff --git a/Source/CTest/cmCTestBuildAndTestHandler.h b/Source/CTest/cmCTestBuildAndTestHandler.h index 073760db6..b5fd639db 100644 --- a/Source/CTest/cmCTestBuildAndTestHandler.h +++ b/Source/CTest/cmCTestBuildAndTestHandler.h @@ -75,6 +75,7 @@ protected: std::vector TestCommandArgs; std::vector BuildTargets; bool BuildNoCMake; + double Timeout; }; #endif diff --git a/Source/CTest/cmCTestTestHandler.cxx b/Source/CTest/cmCTestTestHandler.cxx index fef7e4218..527de975c 100644 --- a/Source/CTest/cmCTestTestHandler.cxx +++ b/Source/CTest/cmCTestTestHandler.cxx @@ -558,6 +558,292 @@ int cmCTestTestHandler::ProcessHandler() return 0; } +//---------------------------------------------------------------------- +void cmCTestTestHandler::ProcessOneTest(cmCTestTestProperties *it, + std::vector &passed, + std::vector &failed, + int cnt, int tmsize) +{ + const std::string& testname = it->Name; + std::vector& args = it->Args; + cmCTestTestResult cres; + cres.Properties = &*it; + cres.ExecutionTime = 0; + cres.ReturnValue = -1; + cres.Status = cmCTestTestHandler::NOT_RUN; + cres.TestCount = cnt; + cres.Name = testname; + cres.Path = it->Directory.c_str(); + + cmCTestLog(this->CTest, HANDLER_OUTPUT, std::setw(3) << cnt << "/"); + cmCTestLog(this->CTest, HANDLER_OUTPUT, std::setw(3) << tmsize << " "); + if ( this->MemCheck ) + { + cmCTestLog(this->CTest, HANDLER_OUTPUT, "Memory Check"); + } + else + { + cmCTestLog(this->CTest, HANDLER_OUTPUT, "Testing"); + } + cmCTestLog(this->CTest, HANDLER_OUTPUT, " "); + std::string outname = testname; + outname.resize(30, ' '); + *this->LogFile << cnt << "/" << tmsize << " Testing: " << testname + << std::endl; + + if ( this->CTest->GetShowOnly() ) + { + cmCTestLog(this->CTest, HANDLER_OUTPUT, outname.c_str() << std::endl); + } + else + { + cmCTestLog(this->CTest, HANDLER_OUTPUT, outname.c_str()); + } + + cmCTestLog(this->CTest, DEBUG, "Testing " << args[0].c_str() << " ... "); + // find the test executable + std::string actualCommand = this->FindTheExecutable(args[1].c_str()); + std::string testCommand + = cmSystemTools::ConvertToOutputPath(actualCommand.c_str()); + + // continue if we did not find the executable + if (testCommand == "") + { + *this->LogFile << "Unable to find executable: " << args[1].c_str() + << std::endl; + cmCTestLog(this->CTest, ERROR_MESSAGE, "Unable to find executable: " + << args[1].c_str() << std::endl); + cres.Output = "Unable to find executable: " + args[1]; + if ( !this->CTest->GetShowOnly() ) + { + cres.FullCommandLine = actualCommand; + this->TestResults.push_back( cres ); + failed.push_back(testname); + return; + } + } + + // add the arguments + std::vector::const_iterator j = args.begin(); + ++j; + ++j; + std::vector arguments; + this->GenerateTestCommand(arguments); + arguments.push_back(actualCommand.c_str()); + for(;j != args.end(); ++j) + { + testCommand += " "; + testCommand += cmSystemTools::EscapeSpaces(j->c_str()); + arguments.push_back(j->c_str()); + } + arguments.push_back(0); + + /** + * Run an executable command and put the stdout in output. + */ + std::string output; + int retVal = 0; + + + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, std::endl + << (this->MemCheck?"MemCheck":"Test") + << " command: " << testCommand + << std::endl); + *this->LogFile << cnt << "/" << tmsize + << " Test: " << testname.c_str() << std::endl; + *this->LogFile << "Command: "; + std::vector::size_type ll; + for ( ll = 0; ll < arguments.size()-1; ll ++ ) + { + *this->LogFile << "\"" << arguments[ll] << "\" "; + } + *this->LogFile + << std::endl + << "Directory: " << it->Directory << std::endl + << "\"" << testname.c_str() << "\" start time: " + << this->CTest->CurrentTime() << std::endl + << "Output:" << std::endl + << "----------------------------------------------------------" + << std::endl; + int res = 0; + double clock_start, clock_finish; + clock_start = cmSystemTools::GetTime(); + + if ( !this->CTest->GetShowOnly() ) + { + res = this->CTest->RunTest(arguments, &output, &retVal, this->LogFile, + it->Timeout); + } + + clock_finish = cmSystemTools::GetTime(); + + if ( this->LogFile ) + { + double ttime = clock_finish - clock_start; + int hours = static_cast(ttime / (60 * 60)); + int minutes = static_cast(ttime / 60) % 60; + int seconds = static_cast(ttime) % 60; + char buffer[100]; + sprintf(buffer, "%02d:%02d:%02d", hours, minutes, seconds); + *this->LogFile + << "----------------------------------------------------------" + << std::endl + << "\"" << testname.c_str() << "\" end time: " + << this->CTest->CurrentTime() << std::endl + << "\"" << testname.c_str() << "\" time elapsed: " + << buffer << std::endl + << "----------------------------------------------------------" + << std::endl << std::endl; + } + + cres.ExecutionTime = (double)(clock_finish - clock_start); + cres.FullCommandLine = testCommand; + + if ( !this->CTest->GetShowOnly() ) + { + bool testFailed = false; + std::vector::iterator passIt; + bool forceFail = false; + if ( it->RequiredRegularExpressions.size() > 0 ) + { + bool found = false; + for ( passIt = it->RequiredRegularExpressions.begin(); + passIt != it->RequiredRegularExpressions.end(); + ++ passIt ) + { + if ( passIt->find(output.c_str()) ) + { + found = true; + } + } + if ( !found ) + { + forceFail = true; + } + } + if ( it->ErrorRegularExpressions.size() > 0 ) + { + for ( passIt = it->ErrorRegularExpressions.begin(); + passIt != it->ErrorRegularExpressions.end(); + ++ passIt ) + { + if ( passIt->find(output.c_str()) ) + { + forceFail = true; + } + } + } + + if (res == cmsysProcess_State_Exited && + (retVal == 0 || it->RequiredRegularExpressions.size()) && + !forceFail) + { + cmCTestLog(this->CTest, HANDLER_OUTPUT, " Passed"); + if ( it->WillFail ) + { + cmCTestLog(this->CTest, HANDLER_OUTPUT, " - But it should fail!"); + cres.Status = cmCTestTestHandler::FAILED; + testFailed = true; + } + else + { + cres.Status = cmCTestTestHandler::COMPLETED; + } + cmCTestLog(this->CTest, HANDLER_OUTPUT, std::endl); + } + else + { + testFailed = true; + + cres.Status = cmCTestTestHandler::FAILED; + if ( res == cmsysProcess_State_Expired ) + { + cmCTestLog(this->CTest, HANDLER_OUTPUT, "***Timeout" << std::endl); + cres.Status = cmCTestTestHandler::TIMEOUT; + } + else if ( res == cmsysProcess_State_Exception ) + { + cmCTestLog(this->CTest, HANDLER_OUTPUT, "***Exception: "); + switch ( retVal ) + { + case cmsysProcess_Exception_Fault: + cmCTestLog(this->CTest, HANDLER_OUTPUT, "SegFault"); + cres.Status = cmCTestTestHandler::SEGFAULT; + break; + case cmsysProcess_Exception_Illegal: + cmCTestLog(this->CTest, HANDLER_OUTPUT, "Illegal"); + cres.Status = cmCTestTestHandler::ILLEGAL; + break; + case cmsysProcess_Exception_Interrupt: + cmCTestLog(this->CTest, HANDLER_OUTPUT, "Interrupt"); + cres.Status = cmCTestTestHandler::INTERRUPT; + break; + case cmsysProcess_Exception_Numerical: + cmCTestLog(this->CTest, HANDLER_OUTPUT, "Numerical"); + cres.Status = cmCTestTestHandler::NUMERICAL; + break; + default: + cmCTestLog(this->CTest, HANDLER_OUTPUT, "Other"); + cres.Status = cmCTestTestHandler::OTHER_FAULT; + } + cmCTestLog(this->CTest, HANDLER_OUTPUT, std::endl); + } + else if ( res == cmsysProcess_State_Error ) + { + cmCTestLog(this->CTest, HANDLER_OUTPUT, "***Bad command " << res + << std::endl); + cres.Status = cmCTestTestHandler::BAD_COMMAND; + } + else + { + // Force fail will also be here? + cmCTestLog(this->CTest, HANDLER_OUTPUT, "***Failed"); + if ( it->WillFail ) + { + cres.Status = cmCTestTestHandler::COMPLETED; + cmCTestLog(this->CTest, HANDLER_OUTPUT, " - supposed to fail"); + testFailed = false; + } + cmCTestLog(this->CTest, HANDLER_OUTPUT, std::endl); + } + } + if ( testFailed ) + { + failed.push_back(testname); + } + else + { + passed.push_back(testname); + } + if (!output.empty() && output.find("DartStuff.find(output.c_str())) + { + std::string dartString = this->DartStuff.match(1); + cmSystemTools::ReplaceString(output, dartString.c_str(),""); + cres.RegressionImages + = this->GenerateRegressionImages(dartString); + } + } + } + + if ( cres.Status == cmCTestTestHandler::COMPLETED ) + { + this->CleanTestOutput(output, static_cast + (this->CustomMaximumPassedTestOutputSize)); + } + else + { + this->CleanTestOutput(output, static_cast + (this->CustomMaximumFailedTestOutputSize)); + } + + cres.Output = output; + cres.ReturnValue = retVal; + cres.CompletionStatus = "Completed"; + this->TestResults.push_back( cres ); +} + //---------------------------------------------------------------------- void cmCTestTestHandler::ProcessDirectory(std::vector &passed, std::vector &failed) @@ -612,27 +898,16 @@ void cmCTestTestHandler::ProcessDirectory(std::vector &passed, { continue; } - - const std::string& testname = it->Name; - std::vector& args = it->Args; - cmCTestTestResult cres; - cres.Properties = &*it; - cres.ExecutionTime = 0; - cres.ReturnValue = -1; - cres.Status = cmCTestTestHandler::NOT_RUN; - cres.TestCount = cnt; - + if (!(last_directory == it->Directory)) { cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, - "Changing directory into " << it->Directory.c_str() << "\n"); + "Changing directory into " << it->Directory.c_str() << "\n"); *this->LogFile << "Changing directory into: " << it->Directory.c_str() - << std::endl; + << std::endl; last_directory = it->Directory; cmSystemTools::ChangeDirectory(it->Directory.c_str()); } - cres.Name = testname; - cres.Path = it->Directory.c_str(); if (this->UseUnion) { @@ -649,278 +924,15 @@ void cmCTestTestHandler::ProcessDirectory(std::vector &passed, // is this test in the list of tests to run? If not then skip it if ((this->TestsToRun.size() && std::find(this->TestsToRun.begin(), - this->TestsToRun.end(), inREcnt) + this->TestsToRun.end(), inREcnt) == this->TestsToRun.end()) || !it->IsInBasedOnREOptions) { continue; } } - - cmCTestLog(this->CTest, HANDLER_OUTPUT, std::setw(3) << cnt << "/"); - cmCTestLog(this->CTest, HANDLER_OUTPUT, std::setw(3) << tmsize << " "); - if ( this->MemCheck ) - { - cmCTestLog(this->CTest, HANDLER_OUTPUT, "Memory Check"); - } - else - { - cmCTestLog(this->CTest, HANDLER_OUTPUT, "Testing"); - } - cmCTestLog(this->CTest, HANDLER_OUTPUT, " "); - std::string outname = testname; - outname.resize(30, ' '); - *this->LogFile << cnt << "/" << tmsize << " Testing: " << testname - << std::endl; - - if ( this->CTest->GetShowOnly() ) - { - cmCTestLog(this->CTest, HANDLER_OUTPUT, outname.c_str() << std::endl); - } - else - { - cmCTestLog(this->CTest, HANDLER_OUTPUT, outname.c_str()); - } - - cmCTestLog(this->CTest, DEBUG, "Testing " << args[0].c_str() << " ... "); - // find the test executable - std::string actualCommand = this->FindTheExecutable(args[1].c_str()); - std::string testCommand - = cmSystemTools::ConvertToOutputPath(actualCommand.c_str()); - - // continue if we did not find the executable - if (testCommand == "") - { - *this->LogFile << "Unable to find executable: " << args[1].c_str() - << std::endl; - cmCTestLog(this->CTest, ERROR_MESSAGE, "Unable to find executable: " - << args[1].c_str() << std::endl); - cres.Output = "Unable to find executable: " + args[1]; - if ( !this->CTest->GetShowOnly() ) - { - cres.FullCommandLine = actualCommand; - this->TestResults.push_back( cres ); - failed.push_back(testname); - continue; - } - } - - // add the arguments - std::vector::const_iterator j = args.begin(); - ++j; - ++j; - std::vector arguments; - this->GenerateTestCommand(arguments); - arguments.push_back(actualCommand.c_str()); - for(;j != args.end(); ++j) - { - testCommand += " "; - testCommand += cmSystemTools::EscapeSpaces(j->c_str()); - arguments.push_back(j->c_str()); - } - arguments.push_back(0); - - /** - * Run an executable command and put the stdout in output. - */ - std::string output; - int retVal = 0; - - - cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, std::endl - << (this->MemCheck?"MemCheck":"Test") << " command: " << testCommand - << std::endl); - *this->LogFile << cnt << "/" << tmsize - << " Test: " << testname.c_str() << std::endl; - *this->LogFile << "Command: "; - std::vector::size_type ll; - for ( ll = 0; ll < arguments.size()-1; ll ++ ) - { - *this->LogFile << "\"" << arguments[ll] << "\" "; - } - *this->LogFile - << std::endl - << "Directory: " << it->Directory << std::endl - << "\"" << testname.c_str() << "\" start time: " - << this->CTest->CurrentTime() << std::endl - << "Output:" << std::endl - << "----------------------------------------------------------" - << std::endl; - int res = 0; - double clock_start, clock_finish; - clock_start = cmSystemTools::GetTime(); - - if ( !this->CTest->GetShowOnly() ) - { - res = this->CTest->RunTest(arguments, &output, &retVal, this->LogFile); - } - - clock_finish = cmSystemTools::GetTime(); - - if ( this->LogFile ) - { - double ttime = clock_finish - clock_start; - int hours = static_cast(ttime / (60 * 60)); - int minutes = static_cast(ttime / 60) % 60; - int seconds = static_cast(ttime) % 60; - char buffer[100]; - sprintf(buffer, "%02d:%02d:%02d", hours, minutes, seconds); - *this->LogFile - << "----------------------------------------------------------" - << std::endl - << "\"" << testname.c_str() << "\" end time: " - << this->CTest->CurrentTime() << std::endl - << "\"" << testname.c_str() << "\" time elapsed: " - << buffer << std::endl - << "----------------------------------------------------------" - << std::endl << std::endl; - } - - cres.ExecutionTime = (double)(clock_finish - clock_start); - cres.FullCommandLine = testCommand; - - if ( !this->CTest->GetShowOnly() ) - { - bool testFailed = false; - std::vector::iterator passIt; - bool forceFail = false; - if ( it->RequiredRegularExpressions.size() > 0 ) - { - bool found = false; - for ( passIt = it->RequiredRegularExpressions.begin(); - passIt != it->RequiredRegularExpressions.end(); - ++ passIt ) - { - if ( passIt->find(output.c_str()) ) - { - found = true; - } - } - if ( !found ) - { - forceFail = true; - } - } - if ( it->ErrorRegularExpressions.size() > 0 ) - { - for ( passIt = it->ErrorRegularExpressions.begin(); - passIt != it->ErrorRegularExpressions.end(); - ++ passIt ) - { - if ( passIt->find(output.c_str()) ) - { - forceFail = true; - } - } - } - - if (res == cmsysProcess_State_Exited && - (retVal == 0 || it->RequiredRegularExpressions.size()) && - !forceFail) - { - cmCTestLog(this->CTest, HANDLER_OUTPUT, " Passed"); - if ( it->WillFail ) - { - cmCTestLog(this->CTest, HANDLER_OUTPUT, " - But it should fail!"); - cres.Status = cmCTestTestHandler::FAILED; - testFailed = true; - } - else - { - cres.Status = cmCTestTestHandler::COMPLETED; - } - cmCTestLog(this->CTest, HANDLER_OUTPUT, std::endl); - } - else - { - testFailed = true; - - cres.Status = cmCTestTestHandler::FAILED; - if ( res == cmsysProcess_State_Expired ) - { - cmCTestLog(this->CTest, HANDLER_OUTPUT, "***Timeout" << std::endl); - cres.Status = cmCTestTestHandler::TIMEOUT; - } - else if ( res == cmsysProcess_State_Exception ) - { - cmCTestLog(this->CTest, HANDLER_OUTPUT, "***Exception: "); - switch ( retVal ) - { - case cmsysProcess_Exception_Fault: - cmCTestLog(this->CTest, HANDLER_OUTPUT, "SegFault"); - cres.Status = cmCTestTestHandler::SEGFAULT; - break; - case cmsysProcess_Exception_Illegal: - cmCTestLog(this->CTest, HANDLER_OUTPUT, "Illegal"); - cres.Status = cmCTestTestHandler::ILLEGAL; - break; - case cmsysProcess_Exception_Interrupt: - cmCTestLog(this->CTest, HANDLER_OUTPUT, "Interrupt"); - cres.Status = cmCTestTestHandler::INTERRUPT; - break; - case cmsysProcess_Exception_Numerical: - cmCTestLog(this->CTest, HANDLER_OUTPUT, "Numerical"); - cres.Status = cmCTestTestHandler::NUMERICAL; - break; - default: - cmCTestLog(this->CTest, HANDLER_OUTPUT, "Other"); - cres.Status = cmCTestTestHandler::OTHER_FAULT; - } - cmCTestLog(this->CTest, HANDLER_OUTPUT, std::endl); - } - else if ( res == cmsysProcess_State_Error ) - { - cmCTestLog(this->CTest, HANDLER_OUTPUT, "***Bad command " << res - << std::endl); - cres.Status = cmCTestTestHandler::BAD_COMMAND; - } - else - { - // Force fail will also be here? - cmCTestLog(this->CTest, HANDLER_OUTPUT, "***Failed"); - if ( it->WillFail ) - { - cres.Status = cmCTestTestHandler::COMPLETED; - cmCTestLog(this->CTest, HANDLER_OUTPUT, " - supposed to fail"); - testFailed = false; - } - cmCTestLog(this->CTest, HANDLER_OUTPUT, std::endl); - } - } - if ( testFailed ) - { - failed.push_back(testname); - } - else - { - passed.push_back(testname); - } - if (!output.empty() && output.find("DartStuff.find(output.c_str())) - { - std::string dartString = this->DartStuff.match(1); - cmSystemTools::ReplaceString(output, dartString.c_str(),""); - cres.RegressionImages - = this->GenerateRegressionImages(dartString); - } - } - } - - if ( cres.Status == cmCTestTestHandler::COMPLETED ) - { - this->CleanTestOutput(output, static_cast( - this->CustomMaximumPassedTestOutputSize)); - } - else - { - this->CleanTestOutput(output, static_cast( - this->CustomMaximumFailedTestOutputSize)); - } - - cres.Output = output; - cres.ReturnValue = retVal; - cres.CompletionStatus = "Completed"; - this->TestResults.push_back( cres ); + + // process this one test + this->ProcessOneTest(&(*it), passed, failed, cnt, tmsize); } this->EndTest = this->CTest->CurrentTime(); @@ -1647,6 +1659,10 @@ bool cmCTestTestHandler::SetTestsProperties( { rtit->WillFail = cmSystemTools::IsOn(val.c_str()); } + if ( key == "TIMEOUT" ) + { + rtit->Timeout = atof(val.c_str()); + } if ( key == "FAIL_REGULAR_EXPRESSION" ) { std::vector lval; @@ -1751,6 +1767,7 @@ bool cmCTestTestHandler::AddTest(const std::vector& args) test.IsInBasedOnREOptions = true; test.WillFail = false; + test.Timeout = 0; if (this->UseIncludeRegExpFlag && !this->IncludeTestsRegularExpression.find(testname.c_str())) { diff --git a/Source/CTest/cmCTestTestHandler.h b/Source/CTest/cmCTestTestHandler.h index 2fe199811..c23089f23 100644 --- a/Source/CTest/cmCTestTestHandler.h +++ b/Source/CTest/cmCTestTestHandler.h @@ -85,6 +85,7 @@ public: std::map Measurements; bool IsInBasedOnREOptions; bool WillFail; + double Timeout; }; struct cmCTestTestResult @@ -145,11 +146,18 @@ private: virtual void GenerateDartOutput(std::ostream& os); /** - * Run the test for a directory and any subdirectories + * Run the tests for a directory and any subdirectories */ void ProcessDirectory(std::vector &passed, std::vector &failed); + /** + * Run one test + */ + void ProcessOneTest(cmCTestTestProperties *props, + std::vector &passed, + std::vector &failed, + int count, int tmsize); typedef std::vector ListOfTests; /** diff --git a/Source/cmCTest.cxx b/Source/cmCTest.cxx index 8ca976db9..e06eaecd1 100644 --- a/Source/cmCTest.cxx +++ b/Source/cmCTest.cxx @@ -1074,7 +1074,7 @@ int cmCTest::RunMakeCommand(const char* command, std::string* output, //---------------------------------------------------------------------- int cmCTest::RunTest(std::vector argv, std::string* output, int *retVal, - std::ostream* log) + std::ostream* log, double testTimeOut) { if(cmSystemTools::SameFile(argv[0], this->CTestSelf.c_str()) && !this->ForceNewCTestProcess) @@ -1087,6 +1087,14 @@ int cmCTest::RunTest(std::vector argv, { if(argv[i]) { + // if this test has a test command make sure we pass the timeout in + if (strcmp(argv[i],"--test-command") == 0 && testTimeOut) + { + args.push_back("--test-timeout"); + cmOStringStream msg; + msg << testTimeOut; + args.push_back(msg.str()); + } args.push_back(argv[i]); } } @@ -1128,6 +1136,12 @@ int cmCTest::RunTest(std::vector argv, { timeout = this->TimeOut; } + cmCTestLog(this, HANDLER_VERBOSE_OUTPUT, "-- timeout set to " + << testTimeOut << std::endl); + if (testTimeOut && testTimeOut < timeout) + { + timeout = testTimeOut; + } // always have at least 1 second if we got to here if (timeout <= 0) { diff --git a/Source/cmCTest.h b/Source/cmCTest.h index ca2e1a90b..298f5a03a 100644 --- a/Source/cmCTest.h +++ b/Source/cmCTest.h @@ -232,7 +232,7 @@ public: //! Run command specialized for tests. Returns process status and retVal is // return value or exception. int RunTest(std::vector args, std::string* output, int *retVal, - std::ostream* logfile); + std::ostream* logfile, double testTimeOut); /** * Execute handler and return its result. If the handler fails, it returns diff --git a/Source/cmTest.cxx b/Source/cmTest.cxx index 47f8792cc..2e0b92b79 100644 --- a/Source/cmTest.cxx +++ b/Source/cmTest.cxx @@ -117,6 +117,15 @@ void cmTest::DefineProperties(cmake *cm) "against the specified regular expressions and at least one of the" " regular expressions has to match, otherwise the test will fail."); + cm->DefineProperty + ("TIMEOUT", cmProperty::TEST, + "How many seconds to allow for this test.", + "This property if set will limit a test to nto take more than " + "the specified number of seconds to run. If it exceeds that the " + "test process will be killed and ctest will move to the next test. " + "This setting takes precedence over DART_TESTING_TIMEOUT and " + "CTEST_TESTING_TIMOUT."); + cm->DefineProperty ("WILL_FAIL", cmProperty::TEST, "If set to true, this will invert the pass/fail flag of the test.", diff --git a/Source/ctest.cxx b/Source/ctest.cxx index d55597fea..77d9fee82 100644 --- a/Source/ctest.cxx +++ b/Source/ctest.cxx @@ -148,7 +148,7 @@ static const cmDocumentationEntry cmDocumentationOptions[] = "complete. Other options that affect this mode are --build-target " "--build-nocmake, --build-run-dir, " "--build-two-config, --build-exe-dir, --build-project," - "--build-noclean, --build-options"}, + "--build-noclean, --build-options, --test-timeout"}, {"--build-target", "Specify a specific target to build.", "This option goes with the --build-and-test option, if left out the all " "target is built." }, @@ -168,6 +168,8 @@ static const cmDocumentationEntry cmDocumentationOptions[] = {"--test-command", "The test to run with the --build-and-test option.", "" }, + {"--test-timeout", "The time limit in seconds for --test-command.", "" + }, {"--tomorrow-tag", "Nightly or experimental starts with next day tag.", "This is useful if the build will not finish in one day." }, {"--ctest-config", "The configuration file used to initialize CTest state "