You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
421 lines
11 KiB
421 lines
11 KiB
/*------------------------------------------------------------------------- |
|
* drawElements Quality Program Test Executor |
|
* ------------------------------------------ |
|
* |
|
* Copyright 2014 The Android Open Source Project |
|
* |
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
* you may not use this file except in compliance with the License. |
|
* You may obtain a copy of the License at |
|
* |
|
* http://www.apache.org/licenses/LICENSE-2.0 |
|
* |
|
* Unless required by applicable law or agreed to in writing, software |
|
* distributed under the License is distributed on an "AS IS" BASIS, |
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
* See the License for the specific language governing permissions and |
|
* limitations under the License. |
|
* |
|
*//*! |
|
* \file |
|
* \brief Test batch executor. |
|
*//*--------------------------------------------------------------------*/ |
|
|
|
#include "xeBatchExecutor.hpp" |
|
#include "xeTestResultParser.hpp" |
|
|
|
#include <sstream> |
|
#include <cstdio> |
|
|
|
namespace xe |
|
{ |
|
|
|
using std::string; |
|
using std::vector; |
|
|
|
enum |
|
{ |
|
TEST_LOG_TMP_BUFFER_SIZE = 1024, |
|
INFO_LOG_TMP_BUFFER_SIZE = 256 |
|
}; |
|
|
|
// \todo [2012-11-01 pyry] Update execute set in handler. |
|
|
|
static inline bool isExecutedInBatch (const BatchResult* batchResult, const TestCase* testCase) |
|
{ |
|
std::string fullPath; |
|
testCase->getFullPath(fullPath); |
|
|
|
if (batchResult->hasTestCaseResult(fullPath.c_str())) |
|
{ |
|
ConstTestCaseResultPtr data = batchResult->getTestCaseResult(fullPath.c_str()); |
|
return data->getStatusCode() != TESTSTATUSCODE_PENDING && data->getStatusCode() != TESTSTATUSCODE_RUNNING; |
|
} |
|
else |
|
return false; |
|
} |
|
|
|
// \todo [2012-06-19 pyry] These can be optimized using TestSetIterator (once implemented) |
|
|
|
static void computeExecuteSet (TestSet& executeSet, const TestNode* root, const TestSet& testSet, const BatchResult* batchResult) |
|
{ |
|
ConstTestNodeIterator iter = ConstTestNodeIterator::begin(root); |
|
ConstTestNodeIterator end = ConstTestNodeIterator::end(root); |
|
|
|
for (; iter != end; ++iter) |
|
{ |
|
const TestNode* node = *iter; |
|
|
|
if (node->getNodeType() == TESTNODETYPE_TEST_CASE && testSet.hasNode(node)) |
|
{ |
|
const TestCase* testCase = static_cast<const TestCase*>(node); |
|
|
|
if (!isExecutedInBatch(batchResult, testCase)) |
|
executeSet.addCase(testCase); |
|
} |
|
} |
|
} |
|
|
|
static void computeBatchRequest (TestSet& requestSet, const TestSet& executeSet, const TestNode* root, int maxCasesInSet) |
|
{ |
|
ConstTestNodeIterator iter = ConstTestNodeIterator::begin(root); |
|
ConstTestNodeIterator end = ConstTestNodeIterator::end(root); |
|
int numCases = 0; |
|
|
|
for (; (iter != end) && (numCases < maxCasesInSet); ++iter) |
|
{ |
|
const TestNode* node = *iter; |
|
|
|
if (node->getNodeType() == TESTNODETYPE_TEST_CASE && executeSet.hasNode(node)) |
|
{ |
|
const TestCase* testCase = static_cast<const TestCase*>(node); |
|
requestSet.addCase(testCase); |
|
numCases += 1; |
|
} |
|
} |
|
} |
|
|
|
static int removeExecuted (TestSet& set, const TestNode* root, const BatchResult* batchResult) |
|
{ |
|
TestSet oldSet (set); |
|
ConstTestNodeIterator iter = ConstTestNodeIterator::begin(root); |
|
ConstTestNodeIterator end = ConstTestNodeIterator::end(root); |
|
int numRemoved = 0; |
|
|
|
for (; iter != end; ++iter) |
|
{ |
|
const TestNode* node = *iter; |
|
|
|
if (node->getNodeType() == TESTNODETYPE_TEST_CASE && oldSet.hasNode(node)) |
|
{ |
|
const TestCase* testCase = static_cast<const TestCase*>(node); |
|
|
|
if (isExecutedInBatch(batchResult, testCase)) |
|
{ |
|
set.removeCase(testCase); |
|
numRemoved += 1; |
|
} |
|
} |
|
} |
|
|
|
return numRemoved; |
|
} |
|
|
|
BatchExecutorLogHandler::BatchExecutorLogHandler (BatchResult* batchResult) |
|
: m_batchResult(batchResult) |
|
{ |
|
} |
|
|
|
BatchExecutorLogHandler::~BatchExecutorLogHandler (void) |
|
{ |
|
} |
|
|
|
void BatchExecutorLogHandler::setSessionInfo (const SessionInfo& sessionInfo) |
|
{ |
|
m_batchResult->getSessionInfo() = sessionInfo; |
|
} |
|
|
|
TestCaseResultPtr BatchExecutorLogHandler::startTestCaseResult (const char* casePath) |
|
{ |
|
// \todo [2012-11-01 pyry] What to do with duplicate results? |
|
if (m_batchResult->hasTestCaseResult(casePath)) |
|
return m_batchResult->getTestCaseResult(casePath); |
|
else |
|
return m_batchResult->createTestCaseResult(casePath); |
|
} |
|
|
|
void BatchExecutorLogHandler::testCaseResultUpdated (const TestCaseResultPtr&) |
|
{ |
|
} |
|
|
|
void BatchExecutorLogHandler::testCaseResultComplete (const TestCaseResultPtr& result) |
|
{ |
|
// \todo [2012-11-01 pyry] Remove from execute set here instead of updating it between sessions. |
|
printf("%s\n", result->getTestCasePath()); |
|
} |
|
|
|
BatchExecutor::BatchExecutor (const TargetConfiguration& config, CommLink* commLink, const TestNode* root, const TestSet& testSet, BatchResult* batchResult, InfoLog* infoLog) |
|
: m_config (config) |
|
, m_commLink (commLink) |
|
, m_root (root) |
|
, m_testSet (testSet) |
|
, m_logHandler (batchResult) |
|
, m_batchResult (batchResult) |
|
, m_infoLog (infoLog) |
|
, m_state (STATE_NOT_STARTED) |
|
, m_testLogParser (&m_logHandler) |
|
{ |
|
} |
|
|
|
BatchExecutor::~BatchExecutor (void) |
|
{ |
|
} |
|
|
|
void BatchExecutor::run (void) |
|
{ |
|
XE_CHECK(m_state == STATE_NOT_STARTED); |
|
|
|
// Check commlink state. |
|
{ |
|
CommLinkState commState = COMMLINKSTATE_LAST; |
|
std::string stateStr = ""; |
|
|
|
commState = m_commLink->getState(stateStr); |
|
|
|
if (commState == COMMLINKSTATE_ERROR) |
|
{ |
|
// Report error. |
|
XE_FAIL((string("CommLink error: '") + stateStr + "'").c_str()); |
|
} |
|
else if (commState != COMMLINKSTATE_READY) |
|
XE_FAIL("CommLink is not ready"); |
|
} |
|
|
|
// Compute initial execute set. |
|
computeExecuteSet(m_casesToExecute, m_root, m_testSet, m_batchResult); |
|
|
|
// Register callbacks. |
|
m_commLink->setCallbacks(enqueueStateChanged, enqueueTestLogData, enqueueInfoLogData, this); |
|
|
|
try |
|
{ |
|
if (!m_casesToExecute.empty()) |
|
{ |
|
TestSet batchRequest; |
|
computeBatchRequest(batchRequest, m_casesToExecute, m_root, m_config.maxCasesPerSession); |
|
launchTestSet(batchRequest); |
|
|
|
m_state = STATE_STARTED; |
|
} |
|
else |
|
m_state = STATE_FINISHED; |
|
|
|
// Run handler loop until we are finished. |
|
while (m_state != STATE_FINISHED) |
|
m_dispatcher.callNext(); |
|
} |
|
catch (...) |
|
{ |
|
m_commLink->setCallbacks(DE_NULL, DE_NULL, DE_NULL, DE_NULL); |
|
throw; |
|
} |
|
|
|
// De-register callbacks. |
|
m_commLink->setCallbacks(DE_NULL, DE_NULL, DE_NULL, DE_NULL); |
|
} |
|
|
|
void BatchExecutor::cancel (void) |
|
{ |
|
m_state = STATE_FINISHED; |
|
m_dispatcher.cancel(); |
|
} |
|
|
|
void BatchExecutor::onStateChanged (CommLinkState state, const char* message) |
|
{ |
|
switch (state) |
|
{ |
|
case COMMLINKSTATE_READY: |
|
case COMMLINKSTATE_TEST_PROCESS_LAUNCHING: |
|
case COMMLINKSTATE_TEST_PROCESS_RUNNING: |
|
break; // Ignore. |
|
|
|
case COMMLINKSTATE_TEST_PROCESS_FINISHED: |
|
{ |
|
// Feed end of string to parser. This terminates open test case if such exists. |
|
{ |
|
deUint8 eos = 0; |
|
onTestLogData(&eos, 1); |
|
} |
|
|
|
int numExecuted = removeExecuted(m_casesToExecute, m_root, m_batchResult); |
|
|
|
// \note No new batch is launched if no cases were executed in last one. Otherwise excutor |
|
// could end up in infinite loop. |
|
if (!m_casesToExecute.empty() && numExecuted > 0) |
|
{ |
|
// Reset state and start batch. |
|
m_testLogParser.reset(); |
|
|
|
m_commLink->reset(); |
|
XE_CHECK(m_commLink->getState() == COMMLINKSTATE_READY); |
|
|
|
TestSet batchRequest; |
|
computeBatchRequest(batchRequest, m_casesToExecute, m_root, m_config.maxCasesPerSession); |
|
launchTestSet(batchRequest); |
|
} |
|
else |
|
m_state = STATE_FINISHED; |
|
|
|
break; |
|
} |
|
|
|
case COMMLINKSTATE_TEST_PROCESS_LAUNCH_FAILED: |
|
printf("Failed to start test process: '%s'\n", message); |
|
m_state = STATE_FINISHED; |
|
break; |
|
|
|
case COMMLINKSTATE_ERROR: |
|
printf("CommLink error: '%s'\n", message); |
|
m_state = STATE_FINISHED; |
|
break; |
|
|
|
default: |
|
XE_FAIL("Unknown state"); |
|
} |
|
} |
|
|
|
void BatchExecutor::onTestLogData (const deUint8* bytes, size_t numBytes) |
|
{ |
|
try |
|
{ |
|
m_testLogParser.parse(bytes, numBytes); |
|
} |
|
catch (const ParseError& e) |
|
{ |
|
// \todo [2012-07-06 pyry] Log error. |
|
DE_UNREF(e); |
|
} |
|
} |
|
|
|
void BatchExecutor::onInfoLogData (const deUint8* bytes, size_t numBytes) |
|
{ |
|
if (numBytes > 0 && m_infoLog) |
|
m_infoLog->append(bytes, numBytes); |
|
} |
|
|
|
static void writeCaseListNode (std::ostream& str, const TestNode* node, const TestSet& testSet) |
|
{ |
|
DE_ASSERT(testSet.hasNode(node)); |
|
|
|
TestNodeType nodeType = node->getNodeType(); |
|
|
|
if (nodeType != TESTNODETYPE_ROOT) |
|
str << node->getName(); |
|
|
|
if (nodeType == TESTNODETYPE_ROOT || nodeType == TESTNODETYPE_GROUP) |
|
{ |
|
const TestGroup* group = static_cast<const TestGroup*>(node); |
|
bool isFirst = true; |
|
|
|
str << "{"; |
|
|
|
for (int ndx = 0; ndx < group->getNumChildren(); ndx++) |
|
{ |
|
const TestNode* child = group->getChild(ndx); |
|
|
|
if (testSet.hasNode(child)) |
|
{ |
|
if (!isFirst) |
|
str << ","; |
|
|
|
writeCaseListNode(str, child, testSet); |
|
isFirst = false; |
|
} |
|
} |
|
|
|
str << "}"; |
|
} |
|
} |
|
|
|
void BatchExecutor::launchTestSet (const TestSet& testSet) |
|
{ |
|
std::ostringstream caseList; |
|
XE_CHECK(testSet.hasNode(m_root)); |
|
XE_CHECK(m_root->getNodeType() == TESTNODETYPE_ROOT); |
|
writeCaseListNode(caseList, m_root, testSet); |
|
|
|
m_commLink->startTestProcess(m_config.binaryName.c_str(), m_config.cmdLineArgs.c_str(), m_config.workingDir.c_str(), caseList.str().c_str()); |
|
} |
|
|
|
void BatchExecutor::enqueueStateChanged (void* userPtr, CommLinkState state, const char* message) |
|
{ |
|
BatchExecutor* executor = static_cast<BatchExecutor*>(userPtr); |
|
CallWriter writer (&executor->m_dispatcher, BatchExecutor::dispatchStateChanged); |
|
|
|
writer << executor |
|
<< state |
|
<< message; |
|
|
|
writer.enqueue(); |
|
} |
|
|
|
void BatchExecutor::enqueueTestLogData (void* userPtr, const deUint8* bytes, size_t numBytes) |
|
{ |
|
BatchExecutor* executor = static_cast<BatchExecutor*>(userPtr); |
|
CallWriter writer (&executor->m_dispatcher, BatchExecutor::dispatchTestLogData); |
|
|
|
writer << executor |
|
<< numBytes; |
|
|
|
writer.write(bytes, numBytes); |
|
writer.enqueue(); |
|
} |
|
|
|
void BatchExecutor::enqueueInfoLogData (void* userPtr, const deUint8* bytes, size_t numBytes) |
|
{ |
|
BatchExecutor* executor = static_cast<BatchExecutor*>(userPtr); |
|
CallWriter writer (&executor->m_dispatcher, BatchExecutor::dispatchInfoLogData); |
|
|
|
writer << executor |
|
<< numBytes; |
|
|
|
writer.write(bytes, numBytes); |
|
writer.enqueue(); |
|
} |
|
|
|
void BatchExecutor::dispatchStateChanged (CallReader& data) |
|
{ |
|
BatchExecutor* executor = DE_NULL; |
|
CommLinkState state = COMMLINKSTATE_LAST; |
|
std::string message; |
|
|
|
data >> executor |
|
>> state |
|
>> message; |
|
|
|
executor->onStateChanged(state, message.c_str()); |
|
} |
|
|
|
void BatchExecutor::dispatchTestLogData (CallReader& data) |
|
{ |
|
BatchExecutor* executor = DE_NULL; |
|
size_t numBytes; |
|
|
|
data >> executor |
|
>> numBytes; |
|
|
|
executor->onTestLogData(data.getDataBlock(numBytes), numBytes); |
|
} |
|
|
|
void BatchExecutor::dispatchInfoLogData (CallReader& data) |
|
{ |
|
BatchExecutor* executor = DE_NULL; |
|
size_t numBytes; |
|
|
|
data >> executor |
|
>> numBytes; |
|
|
|
executor->onInfoLogData(data.getDataBlock(numBytes), numBytes); |
|
} |
|
|
|
} // xe
|
|
|