/*------------------------------------------------------------------------- * 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 Test executor. *//*--------------------------------------------------------------------*/ #include "tcuTestSessionExecutor.hpp" #include "qpTestLog.h" #include "tcuCommandLine.hpp" #include "tcuTestLog.hpp" #include "deClock.h" namespace tcu { using std::vector; 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(false); return QP_TEST_CASE_TYPE_LAST; } } TestSessionExecutor::TestSessionExecutor(TestPackageRoot &root, TestContext &testCtx) : m_testCtx(testCtx) , m_inflater(testCtx) , m_caseListFilter(testCtx.getCommandLine().createCaseListFilter(testCtx.getArchive())) , m_iterator(root, m_inflater, *m_caseListFilter) , m_state(STATE_TRAVERSE_HIERARCHY) , m_abortSession(false) , m_isInTestCase(false) , m_testStartTime(0) , m_packageStartTime(0) { } TestSessionExecutor::~TestSessionExecutor(void) { } bool TestSessionExecutor::iterate(void) { while (!m_abortSession) { switch (m_state) { case STATE_TRAVERSE_HIERARCHY: { const TestHierarchyIterator::State hierIterState = m_iterator.getState(); if (hierIterState == TestHierarchyIterator::STATE_ENTER_NODE || hierIterState == TestHierarchyIterator::STATE_LEAVE_NODE) { TestNode *const curNode = m_iterator.getNode(); const TestNodeType nodeType = curNode->getNodeType(); const bool isEnter = hierIterState == TestHierarchyIterator::STATE_ENTER_NODE; switch (nodeType) { case NODETYPE_PACKAGE: { TestPackage *const testPackage = static_cast(curNode); isEnter ? enterTestPackage(testPackage) : leaveTestPackage(testPackage); break; } case NODETYPE_GROUP: { isEnter ? enterTestGroup(m_iterator.getNodePath()) : leaveTestGroup(m_iterator.getNodePath()); break; // nada } case NODETYPE_SELF_VALIDATE: case NODETYPE_PERFORMANCE: case NODETYPE_CAPABILITY: case NODETYPE_ACCURACY: { TestCase *const testCase = static_cast(curNode); if (isEnter) { if (enterTestCase(testCase, m_iterator.getNodePath())) m_state = STATE_EXECUTE_TEST_CASE; // else remain in TRAVERSING_HIERARCHY => node will be exited from in the next iteration } else leaveTestCase(testCase); break; } default: DE_ASSERT(false); break; } m_iterator.next(); break; } else { DE_ASSERT(hierIterState == TestHierarchyIterator::STATE_FINISHED); m_status.isComplete = true; return false; } } case STATE_EXECUTE_TEST_CASE: { DE_ASSERT(m_iterator.getState() == TestHierarchyIterator::STATE_LEAVE_NODE && isTestNodeTypeExecutable(m_iterator.getNode()->getNodeType())); TestCase *const testCase = static_cast(m_iterator.getNode()); const TestCase::IterateResult iterResult = iterateTestCase(testCase); if (iterResult == TestCase::STOP) m_state = STATE_TRAVERSE_HIERARCHY; return true; } default: DE_ASSERT(false); break; } } return false; } void TestSessionExecutor::enterTestPackage(TestPackage *testPackage) { // Create test case wrapper DE_ASSERT(!m_caseExecutor); m_caseExecutor = de::MovePtr(testPackage->createExecutor()); testPackage->setCaseListFilter(m_caseListFilter.get()); m_packageStartTime = deGetMicroseconds(); } void TestSessionExecutor::leaveTestPackage(TestPackage *testPackage) { DE_UNREF(testPackage); m_caseExecutor->deinitTestPackage(m_testCtx); // If m_caseExecutor uses local status then it may perform some tests in deinitTestPackage(). We have to update TestSessionExecutor::m_status if (m_caseExecutor->usesLocalStatus()) m_caseExecutor->updateGlobalStatus(m_status); const int64_t duration = deGetMicroseconds() - m_packageStartTime; m_packageStartTime = 0; if (!std::string(m_testCtx.getCommandLine().getServerAddress()).empty()) m_caseExecutor->reportDurations(m_testCtx, std::string(testPackage->getName()), duration, m_groupsDurationTime); m_caseExecutor.clear(); if (!std::string(m_testCtx.getCommandLine().getServerAddress()).empty()) { m_testCtx.getLog().startTestsCasesTime(); m_testCtx.getLog() << TestLog::Integer(testPackage->getName(), "Total tests case duration in microseconds", "us", QP_KEY_TAG_TIME, duration); for (std::map::iterator it = m_groupsDurationTime.begin(); it != m_groupsDurationTime.end(); ++it) m_testCtx.getLog() << TestLog::Integer(it->first, "The test group case duration in microseconds", "us", QP_KEY_TAG_TIME, it->second); m_testCtx.getLog().endTestsCasesTime(); } } void TestSessionExecutor::enterTestGroup(const std::string &casePath) { m_groupsDurationTime[casePath] = deGetMicroseconds(); } void TestSessionExecutor::leaveTestGroup(const std::string &casePath) { m_groupsDurationTime[casePath] = deGetMicroseconds() - m_groupsDurationTime[casePath]; } bool TestSessionExecutor::enterTestCase(TestCase *testCase, const std::string &casePath) { TestLog &log = m_testCtx.getLog(); const qpTestCaseType caseType = nodeTypeToTestCaseType(testCase->getNodeType()); bool initOk = false; print("\nTest case '%s'..\n", casePath.c_str()); #if (DE_OS == DE_OS_WIN32) fflush(stdout); #endif m_testCtx.setTestResult(QP_TEST_RESULT_LAST, ""); m_testCtx.setTerminateAfter(false); log.startCase(casePath.c_str(), caseType); m_isInTestCase = true; m_testStartTime = deGetMicroseconds(); try { m_caseExecutor->init(testCase, casePath); initOk = true; } catch (const std::bad_alloc &) { DE_ASSERT(!initOk); m_testCtx.setTestResult(QP_TEST_RESULT_RESOURCE_ERROR, "Failed to allocate memory in test case init"); m_testCtx.setTerminateAfter(true); } catch (const tcu::TestException &e) { DE_ASSERT(!initOk); DE_ASSERT(e.getTestResult() != QP_TEST_RESULT_LAST); m_testCtx.setTestResult(e.getTestResult(), e.getMessage()); m_testCtx.setTerminateAfter(e.isFatal()); log << e; } catch (const tcu::Exception &e) { DE_ASSERT(!initOk); m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, e.getMessage()); log << e; } DE_ASSERT(initOk || m_testCtx.getTestResult() != QP_TEST_RESULT_LAST); return initOk; } void TestSessionExecutor::leaveTestCase(TestCase *testCase) { TestLog &log = m_testCtx.getLog(); // De-init case. try { m_caseExecutor->deinit(testCase); } catch (const tcu::Exception &e) { const bool suppressLogging = m_testCtx.getLog().isSupressLogging(); if (suppressLogging) m_testCtx.getLog().supressLogging(false); log << e << TestLog::Message << "Error in test case deinit, test program will terminate." << TestLog::EndMessage; m_testCtx.setTerminateAfter(true); m_testCtx.getLog().supressLogging(suppressLogging); } { const int64_t duration = deGetMicroseconds() - m_testStartTime; m_testStartTime = 0; m_testCtx.getLog() << TestLog::Integer("TestDuration", "Test case duration in microseconds", "us", QP_KEY_TAG_TIME, duration); } { 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); #if (DE_OS == DE_OS_WIN32) fflush(stdout); #endif if (!m_caseExecutor->usesLocalStatus()) { m_status.numExecuted += 1; switch (testResult) { case QP_TEST_RESULT_PASS: m_status.numPassed += 1; break; case QP_TEST_RESULT_NOT_SUPPORTED: m_status.numNotSupported += 1; break; case QP_TEST_RESULT_QUALITY_WARNING: m_status.numWarnings += 1; break; case QP_TEST_RESULT_COMPATIBILITY_WARNING: m_status.numWarnings += 1; break; case QP_TEST_RESULT_WAIVER: m_status.numWaived += 1; break; case QP_TEST_RESULT_DEVICE_LOST: m_status.numDeviceLost += 1; m_status.numFailed += 1; break; default: m_status.numFailed += 1; break; } } else { m_caseExecutor->updateGlobalStatus(m_status); } // terminateAfter, Resource error or any error in deinit means that execution should end if (terminateAfter || testResult == QP_TEST_RESULT_RESOURCE_ERROR || (m_status.numFailed > 0 && m_testCtx.getCommandLine().isTerminateOnFailEnabled()) || (m_status.numDeviceLost > 0 && m_testCtx.getCommandLine().isTerminateOnDeviceLostEnabled())) m_abortSession = true; } if (m_testCtx.getWatchDog()) qpWatchDog_reset(m_testCtx.getWatchDog()); } TestCase::IterateResult TestSessionExecutor::iterateTestCase(TestCase *testCase) { TestLog &log = m_testCtx.getLog(); TestCase::IterateResult iterateResult = TestCase::STOP; m_testCtx.touchWatchdog(); try { iterateResult = m_caseExecutor->iterate(testCase); } catch (const std::bad_alloc &) { m_testCtx.setTestResult(QP_TEST_RESULT_RESOURCE_ERROR, "Failed to allocate memory during test execution"); m_testCtx.setTerminateAfter(true); } catch (const tcu::TestException &e) { log << e; m_testCtx.setTestResult(e.getTestResult(), e.getMessage()); m_testCtx.setTerminateAfter(e.isFatal()); } catch (const tcu::Exception &e) { log << e; m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, e.getMessage()); } return iterateResult; } } // namespace tcu