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.
422 lines
11 KiB
422 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
|