blob: 69500a235d2e986b38dbc50ab7430293dc4fb068 [file] [log] [blame]
/*-------------------------------------------------------------------------
* drawElements Quality Program Tester Core
* ----------------------------------------
*
* 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 Class for executing tests.
*//*--------------------------------------------------------------------*/
#include "tcuTestExecutor.hpp"
#include "tcuCommandLine.hpp"
#include "tcuPlatform.hpp"
#include "tcuTestLog.hpp"
#include "deInt32.h"
#include <typeinfo>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
using std::string;
using std::vector;
namespace tcu
{
TestExecutor::TestExecutor (TestContext& testCtx, const CommandLine& cmdLine)
: m_testCtx (testCtx)
, m_cmdLine (cmdLine)
, m_rootNode (DE_NULL)
, m_testCaseWrapper (DE_NULL)
, m_testCaseListFile (DE_NULL)
, m_testCaseListWriter (DE_NULL)
{
m_abortSession = false;
m_isInTestCase = false;
// Create the root node.
TestPackageRegistry* packageRegistry = TestPackageRegistry::getSingleton();
vector<TestPackageRegistry::PackageInfo*> packageInfos = packageRegistry->getPackageInfos();
vector<TestNode*> testPackages;
for (int i = 0; i < (int)packageInfos.size(); i++)
testPackages.push_back(packageInfos[i]->createFunc(testCtx));
m_rootNode = new TestPackageRoot(testCtx, testPackages);
// Init traverse stack.
NodeIter iter(m_rootNode);
m_sessionStack.push_back(iter);
}
TestExecutor::~TestExecutor (void)
{
if (m_testCaseListWriter)
qpXmlWriter_destroy(m_testCaseListWriter);
if (m_testCaseListFile)
fclose(m_testCaseListFile);
delete m_rootNode;
}
// Test sub-case iteration.
void TestExecutor::enterTestPackage (TestPackage* testPackage, const char* packageName)
{
DE_ASSERT(testPackage && packageName);
// Open file/writer for case dumping.
const RunMode runMode = m_cmdLine.getRunMode();
if (runMode == RUNMODE_DUMP_XML_CASELIST || runMode == RUNMODE_DUMP_TEXT_CASELIST)
{
const char* const ext = (runMode == RUNMODE_DUMP_XML_CASELIST) ? "xml" : "txt";
const string fileName = string(packageName) + "-cases." + ext;
print("Dumping all test case names in '%s' to file '%s'..\n", packageName, fileName.c_str());
TCU_CHECK(m_testCaseListFile = fopen(fileName.c_str(), "wb"));
if (runMode == RUNMODE_DUMP_XML_CASELIST)
{
TCU_CHECK(m_testCaseListWriter = qpXmlWriter_createFileWriter(m_testCaseListFile, DE_FALSE));
qpXmlWriter_startDocument(m_testCaseListWriter);
qpXmlWriter_startElement(m_testCaseListWriter, "TestCaseList", 0, DE_NULL);
}
}
// Initialize package.
testPackage->init();
// Store test case wrapper
m_testCaseWrapper = &testPackage->getTestCaseWrapper();
DE_ASSERT(m_testCaseWrapper);
// Set archive.
m_testCtx.setCurrentArchive(testPackage->getArchive());
}
void TestExecutor::leaveTestPackage (TestPackage* testPackage)
{
DE_ASSERT(testPackage);
const RunMode runMode = m_cmdLine.getRunMode();
if (runMode == RUNMODE_DUMP_XML_CASELIST)
{
qpXmlWriter_endElement(m_testCaseListWriter, "TestCaseList");
qpXmlWriter_endDocument(m_testCaseListWriter);
qpXmlWriter_destroy(m_testCaseListWriter);
m_testCaseListWriter = DE_NULL;
}
if (runMode == RUNMODE_DUMP_TEXT_CASELIST || runMode == RUNMODE_DUMP_XML_CASELIST)
{
fclose(m_testCaseListFile);
m_testCaseListFile = DE_NULL;
}
DE_ASSERT(!m_testCaseListWriter && !m_testCaseListFile);
m_testCaseWrapper = DE_NULL;
m_testCtx.setCurrentArchive(m_testCtx.getRootArchive());
// Deinitialize package.
testPackage->deinit();
}
void TestExecutor::enterGroupNode (TestCaseGroup* testGroup, const char* casePath)
{
DE_UNREF(casePath);
testGroup->init();
}
void TestExecutor::leaveGroupNode (TestCaseGroup* testGroup)
{
testGroup->deinit();
}
static qpTestCaseType nodeTypeToTestCaseType (TestNodeType nodeType)
{
switch (nodeType)
{
case NODETYPE_SELF_VALIDATE: return QP_TEST_CASE_TYPE_SELF_VALIDATE;
case NODETYPE_PERFORMANCE: return QP_TEST_CASE_TYPE_PERFORMANCE;
case NODETYPE_CAPABILITY: return QP_TEST_CASE_TYPE_CAPABILITY;
case NODETYPE_ACCURACY: return QP_TEST_CASE_TYPE_ACCURACY;
default:
DE_ASSERT(DE_FALSE);
return QP_TEST_CASE_TYPE_LAST;
}
}
bool TestExecutor::enterTestCase (TestCase* testCase, const char* casePath)
{
const RunMode runMode = m_cmdLine.getRunMode();
const qpTestCaseType caseType = nodeTypeToTestCaseType(testCase->getNodeType());
if (runMode == RUNMODE_EXECUTE)
{
print("\nTest case '%s'..\n", casePath);
m_testCtx.getLog().startCase(casePath, caseType);
m_isInTestCase = true;
m_testCtx.setTestResult(QP_TEST_RESULT_LAST, "");
if (!m_testCaseWrapper->initTestCase(testCase))
{
if (m_testCtx.getTestResult() == QP_TEST_RESULT_LAST)
m_testCtx.setTestResult(QP_TEST_RESULT_INTERNAL_ERROR, "Unexpected error in subcase init");
return false;
}
}
return true;
}
void TestExecutor::leaveTestCase (TestCase* testCase)
{
const RunMode runMode = m_cmdLine.getRunMode();
if (runMode == RUNMODE_EXECUTE)
{
// De-init case.
const bool deinitOk = m_testCaseWrapper->deinitTestCase(testCase);
const qpTestResult testResult = m_testCtx.getTestResult();
const char* const testResultDesc = m_testCtx.getTestResultDesc();
const bool terminateAfter = m_testCtx.getTerminateAfter();
DE_ASSERT(testResult != QP_TEST_RESULT_LAST);
m_isInTestCase = false;
m_testCtx.getLog().endCase(testResult, testResultDesc);
// Update statistics.
print(" %s (%s)\n", qpGetTestResultName(testResult), testResultDesc);
m_result.numExecuted += 1;
switch (testResult)
{
case QP_TEST_RESULT_PASS: m_result.numPassed += 1; break;
case QP_TEST_RESULT_NOT_SUPPORTED: m_result.numNotSupported += 1; break;
case QP_TEST_RESULT_QUALITY_WARNING: m_result.numWarnings += 1; break;
case QP_TEST_RESULT_COMPATIBILITY_WARNING: m_result.numWarnings += 1; break;
default: m_result.numFailed += 1; break;
}
// terminateAfter, Resource error or any error in deinit means that execution should end
if (terminateAfter || !deinitOk || testResult == QP_TEST_RESULT_RESOURCE_ERROR)
m_abortSession = true;
// \todo [2011-02-09 pyry] Disable watchdog temporarily?
if (m_testCtx.getWatchDog())
qpWatchDog_reset(m_testCtx.getWatchDog());
}
}
// Return true while session should still continue, false otherwise.
bool TestExecutor::iterate (void)
{
try
{
while (!m_sessionStack.empty())
{
// Get full path to node.
string nodePath = "";
for (int ndx = 0; ndx < (int)m_sessionStack.size(); ndx++)
{
NodeIter& iter = m_sessionStack[ndx];
if (ndx > 1) // ignore root package
nodePath += ".";
nodePath += iter.node->getName();
}
// Handle the node.
NodeIter& iter = m_sessionStack[m_sessionStack.size()-1];
DE_ASSERT(iter.node != DE_NULL);
TestNode* node = iter.node;
bool isLeaf = isTestNodeTypeExecutable(node->getNodeType());
switch (iter.getState())
{
case NodeIter::STATE_BEGIN:
{
// Return to parent if name doesn't match filter.
if (!(isLeaf ? m_cmdLine.checkTestCaseName(nodePath.c_str()) : m_cmdLine.checkTestGroupName(nodePath.c_str())))
{
m_sessionStack.pop_back();
break;
}
// Enter node.
bool enterOk = true;
switch (node->getNodeType())
{
case NODETYPE_ROOT: /* nada */ break;
case NODETYPE_PACKAGE: enterTestPackage(static_cast<TestPackage*>(node), nodePath.c_str()); break;
case NODETYPE_GROUP: enterGroupNode(static_cast<TestCaseGroup*>(node), nodePath.c_str()); break;
case NODETYPE_PERFORMANCE:
case NODETYPE_CAPABILITY:
case NODETYPE_ACCURACY: /* fall-trough */
case NODETYPE_SELF_VALIDATE: enterOk = enterTestCase(static_cast<TestCase*>(node), nodePath.c_str()); break;
default: DE_ASSERT(false);
}
if (m_cmdLine.getRunMode() == RUNMODE_EXECUTE)
{
if (isLeaf)
{
if (enterOk)
iter.setState(NodeIter::STATE_EXECUTE_TEST);
else
iter.setState(NodeIter::STATE_FINISH);
}
else
{
iter.setState(NodeIter::STATE_TRAVERSE_CHILDREN);
}
}
else if (m_cmdLine.getRunMode() == RUNMODE_DUMP_XML_CASELIST)
{
if (node->getNodeType() != NODETYPE_ROOT && node->getNodeType() != NODETYPE_PACKAGE)
{
string caseName = iter.node->getName();
string description = iter.node->getDescription();
qpXmlAttribute attribs[8];
int numAttribs = 0;
const char* caseType = DE_NULL;
switch (node->getNodeType())
{
case NODETYPE_SELF_VALIDATE: caseType = "SelfValidate"; break;
case NODETYPE_CAPABILITY: caseType = "Capability"; break;
case NODETYPE_ACCURACY: caseType = "Accuracy"; break;
case NODETYPE_PERFORMANCE: caseType = "Performance"; break;
default: caseType = "TestGroup"; break;
}
attribs[numAttribs++] = qpSetStringAttrib("Name", caseName.c_str());
attribs[numAttribs++] = qpSetStringAttrib("CaseType", caseType);
attribs[numAttribs++] = qpSetStringAttrib("Description", description.c_str());
qpXmlWriter_startElement(m_testCaseListWriter, "TestCase", numAttribs, attribs);
}
iter.setState(isLeaf ? NodeIter::STATE_FINISH : NodeIter::STATE_TRAVERSE_CHILDREN);
}
else if (m_cmdLine.getRunMode() == RUNMODE_DUMP_TEXT_CASELIST)
{
// \note Case list file is not open until we are in test package.
if (isLeaf)
fprintf(m_testCaseListFile, "TEST: %s\n", nodePath.c_str());
else if (node->getNodeType() != NODETYPE_ROOT)
fprintf(m_testCaseListFile, "GROUP: %s\n", nodePath.c_str());
iter.setState(isLeaf ? NodeIter::STATE_FINISH : NodeIter::STATE_TRAVERSE_CHILDREN);
}
break;
}
case NodeIter::STATE_EXECUTE_TEST:
{
// Touch the watchdog.
m_testCtx.touchWatchdog();
// Iterate the sub-case.
TestCase::IterateResult iterateResult = m_testCaseWrapper->iterateTestCase(static_cast<TestCase*>(node));
if (iterateResult == TestCase::STOP)
iter.setState(NodeIter::STATE_FINISH);
return true; // return after each iteration (when another iteration follows).
}
case NodeIter::STATE_TRAVERSE_CHILDREN:
{
int numChildren = (int)iter.children.size();
if (++iter.curChildNdx < numChildren)
{
// Push child to stack.
TestNode* childNode = iter.children[iter.curChildNdx];
m_sessionStack.push_back(NodeIter(childNode));
}
else
iter.setState(NodeIter::STATE_FINISH);
break;
}
case NodeIter::STATE_FINISH:
{
if (m_cmdLine.getRunMode() == RUNMODE_DUMP_XML_CASELIST)
{
if (node->getNodeType() != NODETYPE_ROOT && node->getNodeType() != NODETYPE_PACKAGE)
qpXmlWriter_endElement(m_testCaseListWriter, "TestCase");
}
// Leave node.
switch (node->getNodeType())
{
case NODETYPE_ROOT: /* nada */ break;
case NODETYPE_PACKAGE: leaveTestPackage(static_cast<TestPackage*>(node)); break;
case NODETYPE_GROUP: leaveGroupNode(static_cast<TestCaseGroup*>(node)); break;
case NODETYPE_ACCURACY:
case NODETYPE_CAPABILITY:
case NODETYPE_PERFORMANCE: /* fall-thru */
case NODETYPE_SELF_VALIDATE: leaveTestCase(static_cast<TestCase*>(node)); break;
default: DE_ASSERT(false);
}
m_sessionStack.pop_back();
// Return if execution should abort.
if (m_abortSession)
return false;
// Otherwise continue iterating.
break;
}
default:
DE_ASSERT(DE_FALSE);
break;
}
}
}
catch (const std::exception& e)
{
print("TestExecutor::iterateSession(): Caught unhandled %s: %s\n", typeid(e).name(), e.what());
throw;
}
m_result.isComplete = true;
return false;
}
} // tcu