/*------------------------------------------------------------------------- * 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 (false) // 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) { uint64_t 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(uint8_t *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(uint8_t *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(); } } // namespace Android } // namespace tcu