| /*------------------------------------------------------------------------- |
| * 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<jclass>(env->NewGlobalRef(remoteCls)); |
| JNI_CHECK(m_remoteCls); |
| env->DeleteLocalRef(remoteCls); |
| remoteCls = 0; |
| |
| ctorId = env->GetMethodID(m_remoteCls, "<init>", "(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<jobject>(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<void**>(&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 |