/*------------------------------------------------------------------------- * 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 Android ExecServer. *//*--------------------------------------------------------------------*/ #include "tcuAndroidExecService.hpp" #include "deFile.h" #include "deClock.h" #if 0 # define DBG_PRINT(ARGS) print ARGS #else # define DBG_PRINT(ARGS) #endif namespace tcu { namespace Android { static const char* LOG_FILE_NAME = "/sdcard/dEQP-log.qpa"; enum { PROCESS_START_TIMEOUT = 5000*1000, //!< Timeout in usec. PROCESS_QUERY_INTERVAL = 1000*1000 //!< Running query interval limit in usec. }; static void checkJniException (JNIEnv* env, const char* file, int line) { if (env->ExceptionOccurred()) { env->ExceptionDescribe(); env->ExceptionClear(); throw InternalError("JNI Exception", DE_NULL, file, line); } } #define JNI_CHECK(EXPR) do { checkJniException(env, __FILE__, __LINE__); TCU_CHECK_INTERNAL(EXPR); } while (deGetFalse()) // TestProcess TestProcess::TestProcess (JavaVM* vm, jobject context) : m_vm (vm) , m_remoteCls (0) , m_remote (0) , m_start (0) , m_kill (0) , m_isRunning (0) , m_launchTime (0) , m_lastQueryTime (0) , m_lastRunningStatus (false) , m_logReader (xs::LOG_BUFFER_BLOCK_SIZE, xs::LOG_BUFFER_NUM_BLOCKS) { DBG_PRINT(("TestProcess::TestProcess(%p, %p)", vm, context)); JNIEnv* env = getCurrentThreadEnv(); jobject remote = 0; jstring logFileName = 0; try { jclass remoteCls = 0; jmethodID ctorId = 0; remoteCls = env->FindClass("com/drawelements/deqp/testercore/RemoteAPI"); JNI_CHECK(remoteCls); // Acquire global reference to RemoteAPI class. m_remoteCls = reinterpret_cast(env->NewGlobalRef(remoteCls)); JNI_CHECK(m_remoteCls); env->DeleteLocalRef(remoteCls); remoteCls = 0; ctorId = env->GetMethodID(m_remoteCls, "", "(Landroid/content/Context;Ljava/lang/String;)V"); JNI_CHECK(ctorId); logFileName = env->NewStringUTF(LOG_FILE_NAME); JNI_CHECK(logFileName); // Create RemoteAPI instance. remote = env->NewObject(m_remoteCls, ctorId, context, logFileName); JNI_CHECK(remote); env->DeleteLocalRef(logFileName); logFileName = 0; // Acquire global reference to remote. m_remote = env->NewGlobalRef(remote); JNI_CHECK(m_remote); env->DeleteLocalRef(remote); remote = 0; m_start = env->GetMethodID(m_remoteCls, "start", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z"); JNI_CHECK(m_start); m_kill = env->GetMethodID(m_remoteCls, "kill", "()Z"); JNI_CHECK(m_kill); m_isRunning = env->GetMethodID(m_remoteCls, "isRunning", "()Z"); JNI_CHECK(m_isRunning); } catch (...) { if (logFileName) env->DeleteLocalRef(logFileName); if (remote) env->DeleteLocalRef(remote); if (m_remoteCls) env->DeleteGlobalRef(reinterpret_cast(m_remoteCls)); if (m_remote) env->DeleteGlobalRef(m_remote); throw; } } TestProcess::~TestProcess (void) { DBG_PRINT(("TestProcess::~TestProcess()")); try { JNIEnv* env = getCurrentThreadEnv(); env->DeleteGlobalRef(m_remote); env->DeleteGlobalRef(m_remoteCls); } catch (...) { } } void TestProcess::start (const char* name, const char* params, const char* workingDir, const char* caseList) { DBG_PRINT(("TestProcess::start(%s, %s, %s, ...)", name, params, workingDir)); JNIEnv* env = getCurrentThreadEnv(); jstring nameStr = 0; jstring paramsStr = 0; jstring caseListStr = 0; DE_UNREF(workingDir); // Remove old log file if such exists. if (deFileExists(LOG_FILE_NAME)) { if (!deDeleteFile(LOG_FILE_NAME) || deFileExists(LOG_FILE_NAME)) throw xs::TestProcessException(std::string("Failed to remove '") + LOG_FILE_NAME + "'"); } try { nameStr = env->NewStringUTF(name); JNI_CHECK(nameStr); paramsStr = env->NewStringUTF(params); JNI_CHECK(paramsStr); caseListStr = env->NewStringUTF(caseList); JNI_CHECK(caseListStr); jboolean res = env->CallBooleanMethod(m_remote, m_start, nameStr, paramsStr, caseListStr); checkJniException(env, __FILE__, __LINE__); if (res == JNI_FALSE) throw xs::TestProcessException("Failed to launch activity"); m_launchTime = deGetMicroseconds(); m_lastQueryTime = m_launchTime; m_lastRunningStatus = true; } catch (...) { if (nameStr) env->DeleteLocalRef(nameStr); if (paramsStr) env->DeleteLocalRef(paramsStr); if (caseListStr) env->DeleteLocalRef(caseListStr); throw; } env->DeleteLocalRef(nameStr); env->DeleteLocalRef(paramsStr); env->DeleteLocalRef(caseListStr); } void TestProcess::terminate (void) { DBG_PRINT(("TestProcess::terminate()")); JNIEnv* env = getCurrentThreadEnv(); jboolean res = env->CallBooleanMethod(m_remote, m_kill); checkJniException(env, __FILE__, __LINE__); DE_UNREF(res); // Failure to kill process is ignored. } void TestProcess::cleanup (void) { DBG_PRINT(("TestProcess::cleanup()")); terminate(); m_logReader.stop(); } bool TestProcess::isRunning (void) { deUint64 curTime = deGetMicroseconds(); // On Android process launch is asynchronous so we don't want to poll for process until after some time. if (curTime-m_launchTime < PROCESS_START_TIMEOUT || curTime-m_lastQueryTime < PROCESS_QUERY_INTERVAL) return m_lastRunningStatus; JNIEnv* env = getCurrentThreadEnv(); jboolean res = env->CallBooleanMethod(m_remote, m_isRunning); checkJniException(env, __FILE__, __LINE__); DBG_PRINT(("TestProcess::isRunning(): %s", res == JNI_TRUE ? "true" : "false")); m_lastQueryTime = curTime; m_lastRunningStatus = res == JNI_TRUE; return m_lastRunningStatus; } JNIEnv* TestProcess::getCurrentThreadEnv (void) { JNIEnv* env = DE_NULL; jint ret = m_vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); if (ret == JNI_OK) return env; else throw InternalError("GetEnv() failed"); } int TestProcess::readTestLog (deUint8* dst, int numBytes) { if (!m_logReader.isRunning()) { if (deGetMicroseconds() - m_launchTime > xs::LOG_FILE_TIMEOUT*1000) { // Timeout, kill process. terminate(); DBG_PRINT(("TestProcess:readTestLog(): Log file timeout occurred!")); return 0; // \todo [2013-08-13 pyry] Throw exception? } if (!deFileExists(LOG_FILE_NAME)) return 0; // Start reader. m_logReader.start(LOG_FILE_NAME); } DE_ASSERT(m_logReader.isRunning()); return m_logReader.read(dst, numBytes); } int TestProcess::getExitCode (void) const { return 0; } int TestProcess::readInfoLog (deUint8* dst, int numBytes) { // \todo [2012-11-12 pyry] Read device log. DE_UNREF(dst && numBytes); return 0; } // ExecutionServer ExecutionServer::ExecutionServer (JavaVM* vm, xs::TestProcess* testProcess, deSocketFamily family, int port, RunMode runMode) : xs::ExecutionServer (testProcess, family, port, runMode) , m_vm (vm) { } xs::ConnectionHandler* ExecutionServer::createHandler (de::Socket* socket, const de::SocketAddress& clientAddress) { DE_UNREF(clientAddress); return new ConnectionHandler(m_vm, this, socket); } // ConnectionHandler ConnectionHandler::ConnectionHandler (JavaVM* vm, xs::ExecutionServer* server, de::Socket* socket) : xs::ExecutionRequestHandler (server, socket) , m_vm (vm) { } void ConnectionHandler::run (void) { JNIEnv* env = DE_NULL; if (m_vm->AttachCurrentThread(&env, DE_NULL) != 0) { print("AttachCurrentThread() failed"); return; } xs::ExecutionRequestHandler::run(); if (m_vm->DetachCurrentThread() != 0) print("DetachCurrentThread() failed"); } // ServerThread ServerThread::ServerThread (JavaVM* vm, xs::TestProcess* process, deSocketFamily family, int port) : m_server(vm, process, family, port, xs::ExecutionServer::RUNMODE_FOREVER) { } void ServerThread::run (void) { try { m_server.runServer(); } catch (const std::exception& e) { die("ServerThread::run(): %s", e.what()); } } void ServerThread::stop (void) { m_server.stopServer(); join(); } // ExecService ExecService::ExecService (JavaVM* vm, jobject context, int port, deSocketFamily family) : m_process (vm, context) , m_thread (vm, &m_process, family, port) { } ExecService::~ExecService (void) { } void ExecService::start (void) { m_thread.start(); } void ExecService::stop (void) { m_thread.stop(); } } // Android } // tcu