/*------------------------------------------------------------------------- * 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 iOS App Wrapper. *//*--------------------------------------------------------------------*/ #include "tcuIOSApp.h" #include "tcuIOSPlatform.hh" #include "tcuApp.hpp" #include "tcuCommandLine.hpp" #include "tcuRenderTarget.hpp" #include "tcuTestLog.hpp" #include "tcuResource.hpp" #include "deThread.hpp" #include "deMutex.hpp" #include "xsExecutionServer.hpp" #include "xsTestProcess.hpp" #include "xsPosixFileReader.hpp" #include "deFilePath.hpp" #include "deClock.h" #include "deMemory.h" #include <string> #import <Foundation/NSObject.h> #import <Foundation/NSString.h> #import <Foundation/NSBundle.h> #import <Foundation/NSPathUtilities.h> using std::string; namespace { class TestThreadState { public: enum State { STATE_NOT_RUNNING = 0, STATE_RUNNING, STATE_STOP_REQUESTED, STATE_LAST }; TestThreadState (void); ~TestThreadState (void); void requestStart (const char* cmdLine); void requestStop (void); State getState (void); void testExecFinished (void); const char* getCommandLine (void) const { return m_cmdLine.c_str(); } private: de::Mutex m_lock; State m_state; std::string m_cmdLine; }; TestThreadState::TestThreadState (void) : m_state(STATE_NOT_RUNNING) { } TestThreadState::~TestThreadState (void) { } void TestThreadState::requestStart (const char* cmdLine) { de::ScopedLock stateLock(m_lock); TCU_CHECK(m_state == STATE_NOT_RUNNING); m_cmdLine = cmdLine; m_state = STATE_RUNNING; } void TestThreadState::requestStop (void) { de::ScopedLock stateLock(m_lock); if (m_state != STATE_NOT_RUNNING) m_state = STATE_STOP_REQUESTED; } void TestThreadState::testExecFinished (void) { de::ScopedLock stateLock(m_lock); m_state = STATE_NOT_RUNNING; } TestThreadState::State TestThreadState::getState (void) { de::ScopedLock stateLock(m_lock); return m_state; } class LocalTestProcess : public xs::TestProcess { public: LocalTestProcess (TestThreadState& state, const char* logFileName); ~LocalTestProcess (void); void start (const char* name, const char* params, const char* workingDir, const char* caseList); void terminate (void); void cleanup (void); bool isRunning (void); int getExitCode (void) const { return 0; /* not available */ } int readInfoLog (deUint8* dst, int numBytes) { DE_UNREF(dst && numBytes); return 0; /* not supported */ } int readTestLog (deUint8* dst, int numBytes); const char* getLogFileName (void) const { return m_logFileName.c_str(); } private: TestThreadState& m_state; string m_logFileName; xs::posix::FileReader m_logReader; deUint64 m_processStartTime; }; LocalTestProcess::LocalTestProcess (TestThreadState& state, const char* logFileName) : m_state (state) , m_logFileName (logFileName) , m_logReader (xs::LOG_BUFFER_BLOCK_SIZE, xs::LOG_BUFFER_NUM_BLOCKS) , m_processStartTime (0) { } LocalTestProcess::~LocalTestProcess (void) { } void LocalTestProcess::start (const char* name, const char* params, const char* workingDir, const char* caseList) { DE_UNREF(name && workingDir); // Delete old log file. if (deFileExists(m_logFileName.c_str())) TCU_CHECK(deDeleteFile(m_logFileName.c_str())); string cmdLine = string("deqp"); if (caseList && strlen(caseList) > 0) cmdLine += string(" --deqp-caselist=") + caseList; if (params && strlen(params) > 0) cmdLine += string(" ") + params; m_state.requestStart(cmdLine.c_str()); m_processStartTime = deGetMicroseconds(); } void LocalTestProcess::terminate (void) { m_state.requestStop(); } void LocalTestProcess::cleanup (void) { if (isRunning()) { m_state.requestStop(); // Wait until stopped. while (isRunning()) deSleep(50); } m_logReader.stop(); } bool LocalTestProcess::isRunning (void) { return m_state.getState() != TestThreadState::STATE_NOT_RUNNING; } int LocalTestProcess::readTestLog (deUint8* dst, int numBytes) { if (!m_logReader.isRunning()) { if (deGetMicroseconds() - m_processStartTime > xs::LOG_FILE_TIMEOUT*1000) { // Timeout, kill execution. terminate(); return 0; // \todo [2013-08-13 pyry] Throw exception? } if (!deFileExists(m_logFileName.c_str())) return 0; // Start reader. m_logReader.start(m_logFileName.c_str()); } DE_ASSERT(m_logReader.isRunning()); return m_logReader.read(dst, numBytes); } class ServerThread : public de::Thread { public: ServerThread (xs::TestProcess* testProcess, int port); ~ServerThread (void); void run (void); void stop (void); private: xs::ExecutionServer m_server; bool m_isRunning; }; ServerThread::ServerThread (xs::TestProcess* testProcess, int port) : m_server (testProcess, DE_SOCKETFAMILY_INET4, port, xs::ExecutionServer::RUNMODE_FOREVER) , m_isRunning (false) { } ServerThread::~ServerThread (void) { stop(); } void ServerThread::run (void) { m_isRunning = true; m_server.runServer(); } void ServerThread::stop (void) { if (m_isRunning) { m_server.stopServer(); join(); m_isRunning = false; } } string getAppBundleDir (void) { NSString* dataPath = [[NSBundle mainBundle] bundlePath]; const char* utf8Str = [dataPath UTF8String]; return string(utf8Str); } string getAppDocumentsDir (void) { NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString* docPath = [paths objectAtIndex:0]; const char* utf8Str = [docPath UTF8String]; return string(utf8Str); } } // anonymous struct tcuIOSApp_s { public: tcuIOSApp_s (void* view); ~tcuIOSApp_s (void); void iterate (void); protected: void createTestApp (void); void destroyTestApp (void); TestThreadState m_state; LocalTestProcess m_testProcess; ServerThread m_server; tcu::DirArchive m_archive; tcu::ios::ScreenManager m_screenManager; tcu::ios::Platform m_platform; tcu::TestLog* m_log; tcu::CommandLine* m_cmdLine; tcu::App* m_app; }; tcuIOSApp_s::tcuIOSApp_s (void* view) : m_testProcess (m_state, de::FilePath::join(getAppDocumentsDir(), "TestResults.qpa").getPath()) , m_server (&m_testProcess, 50016) , m_archive (getAppBundleDir().c_str()) , m_screenManager ((tcuEAGLView*)view) , m_platform (&m_screenManager) , m_log (DE_NULL) , m_cmdLine (DE_NULL) , m_app (DE_NULL) { // Start server. m_server.start(); } tcuIOSApp_s::~tcuIOSApp_s (void) { m_server.stop(); destroyTestApp(); } void tcuIOSApp::createTestApp (void) { DE_ASSERT(!m_app && !m_log && !m_cmdLine && !m_platform); try { m_log = new tcu::TestLog(m_testProcess.getLogFileName()); m_cmdLine = new tcu::CommandLine(m_state.getCommandLine()); m_app = new tcu::App(m_platform, m_archive, *m_log, *m_cmdLine); } catch (const std::exception& e) { destroyTestApp(); tcu::die("%s", e.what()); } } void tcuIOSApp::destroyTestApp (void) { delete m_app; delete m_cmdLine; delete m_log; m_app = DE_NULL; m_cmdLine = DE_NULL; m_log = DE_NULL; } void tcuIOSApp::iterate (void) { TestThreadState::State curState = m_state.getState(); if (curState == TestThreadState::STATE_RUNNING) { if (!m_app) createTestApp(); TCU_CHECK(m_app); if (!m_app->iterate()) { destroyTestApp(); m_state.testExecFinished(); } } else if (curState == TestThreadState::STATE_STOP_REQUESTED) { destroyTestApp(); m_state.testExecFinished(); } // else wait until state has changed? } tcuIOSApp* tcuIOSApp_create (void* view) { try { return new tcuIOSApp(view); } catch (const std::exception& e) { tcu::die("FATAL ERROR: %s", e.what()); return DE_NULL; } } void tcuIOSApp_destroy (tcuIOSApp* app) { delete app; } deBool tcuIOSApp_iterate (tcuIOSApp* app) { try { app->iterate(); return DE_TRUE; } catch (const std::exception& e) { tcu::print("FATAL ERROR: %s\n", e.what()); return DE_FALSE; } }