1/*-------------------------------------------------------------------------
2 * drawElements Quality Program Tester Core
3 * ----------------------------------------
4 *
5 * Copyright 2014 The Android Open Source Project
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 *      http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 *
19 *//*!
20 * \file
21 * \brief iOS App Wrapper.
22 *//*--------------------------------------------------------------------*/
23
24#include "tcuIOSApp.h"
25#include "tcuIOSPlatform.hh"
26#include "tcuApp.hpp"
27#include "tcuCommandLine.hpp"
28#include "tcuRenderTarget.hpp"
29#include "tcuTestLog.hpp"
30#include "tcuResource.hpp"
31#include "deThread.hpp"
32#include "deMutex.hpp"
33#include "xsExecutionServer.hpp"
34#include "xsTestProcess.hpp"
35#include "xsPosixFileReader.hpp"
36#include "deFilePath.hpp"
37#include "deClock.h"
38#include "deMemory.h"
39
40#include <string>
41
42#import <Foundation/NSObject.h>
43#import <Foundation/NSString.h>
44#import <Foundation/NSBundle.h>
45#import <Foundation/NSPathUtilities.h>
46
47using std::string;
48
49namespace
50{
51
52class TestThreadState
53{
54public:
55	enum State
56	{
57		STATE_NOT_RUNNING	= 0,
58		STATE_RUNNING,
59		STATE_STOP_REQUESTED,
60
61		STATE_LAST
62	};
63
64							TestThreadState			(void);
65							~TestThreadState		(void);
66
67	void					requestStart			(const char* cmdLine);
68	void					requestStop				(void);
69	State					getState				(void);
70
71	void					testExecFinished		(void);
72
73	const char*				getCommandLine			(void) const { return m_cmdLine.c_str(); }
74
75private:
76	de::Mutex				m_lock;
77
78	State					m_state;
79	std::string				m_cmdLine;
80};
81
82TestThreadState::TestThreadState (void)
83	: m_state(STATE_NOT_RUNNING)
84{
85}
86
87TestThreadState::~TestThreadState (void)
88{
89}
90
91void TestThreadState::requestStart (const char* cmdLine)
92{
93	de::ScopedLock stateLock(m_lock);
94
95	TCU_CHECK(m_state == STATE_NOT_RUNNING);
96
97	m_cmdLine	= cmdLine;
98	m_state		= STATE_RUNNING;
99}
100
101void TestThreadState::requestStop (void)
102{
103	de::ScopedLock stateLock(m_lock);
104
105	if (m_state != STATE_NOT_RUNNING)
106		m_state = STATE_STOP_REQUESTED;
107}
108
109void TestThreadState::testExecFinished (void)
110{
111	de::ScopedLock stateLock(m_lock);
112	m_state = STATE_NOT_RUNNING;
113}
114
115TestThreadState::State TestThreadState::getState (void)
116{
117	de::ScopedLock stateLock(m_lock);
118	return m_state;
119}
120
121class LocalTestProcess : public xs::TestProcess
122{
123public:
124							LocalTestProcess		(TestThreadState& state, const char* logFileName);
125							~LocalTestProcess		(void);
126
127	void					start					(const char* name, const char* params, const char* workingDir, const char* caseList);
128	void					terminate				(void);
129	void					cleanup					(void);
130
131	bool					isRunning				(void);
132	int						getExitCode				(void) const { return 0; /* not available */ }
133
134	int						readInfoLog				(deUint8* dst, int numBytes) { DE_UNREF(dst && numBytes); return 0; /* not supported */ }
135	int						readTestLog				(deUint8* dst, int numBytes);
136
137	const char*				getLogFileName			(void) const { return m_logFileName.c_str(); }
138
139private:
140	TestThreadState&		m_state;
141	string					m_logFileName;
142	xs::posix::FileReader	m_logReader;
143	deUint64				m_processStartTime;
144};
145
146LocalTestProcess::LocalTestProcess (TestThreadState& state, const char* logFileName)
147	: m_state				(state)
148	, m_logFileName			(logFileName)
149	, m_logReader			(xs::LOG_BUFFER_BLOCK_SIZE, xs::LOG_BUFFER_NUM_BLOCKS)
150	, m_processStartTime	(0)
151{
152}
153
154LocalTestProcess::~LocalTestProcess (void)
155{
156}
157
158void LocalTestProcess::start (const char* name, const char* params, const char* workingDir, const char* caseList)
159{
160	DE_UNREF(name && workingDir);
161
162	// Delete old log file.
163	if (deFileExists(m_logFileName.c_str()))
164		TCU_CHECK(deDeleteFile(m_logFileName.c_str()));
165
166	string cmdLine = string("deqp");
167	if (caseList && strlen(caseList) > 0)
168		cmdLine += string(" --deqp-caselist=") + caseList;
169
170	if (params && strlen(params) > 0)
171		cmdLine += string(" ") + params;
172
173	m_state.requestStart(cmdLine.c_str());
174	m_processStartTime = deGetMicroseconds();
175}
176
177void LocalTestProcess::terminate (void)
178{
179	m_state.requestStop();
180}
181
182void LocalTestProcess::cleanup (void)
183{
184	if (isRunning())
185	{
186		m_state.requestStop();
187
188		// Wait until stopped.
189		while (isRunning())
190			deSleep(50);
191	}
192
193	m_logReader.stop();
194}
195
196bool LocalTestProcess::isRunning (void)
197{
198	return m_state.getState() != TestThreadState::STATE_NOT_RUNNING;
199}
200
201int LocalTestProcess::readTestLog (deUint8* dst, int numBytes)
202{
203	if (!m_logReader.isRunning())
204	{
205		if (deGetMicroseconds() - m_processStartTime > xs::LOG_FILE_TIMEOUT*1000)
206		{
207			// Timeout, kill execution.
208			terminate();
209			return 0; // \todo [2013-08-13 pyry] Throw exception?
210		}
211
212		if (!deFileExists(m_logFileName.c_str()))
213			return 0;
214
215		// Start reader.
216		m_logReader.start(m_logFileName.c_str());
217	}
218
219	DE_ASSERT(m_logReader.isRunning());
220	return m_logReader.read(dst, numBytes);
221}
222
223class ServerThread : public de::Thread
224{
225public:
226						ServerThread		(xs::TestProcess* testProcess, int port);
227						~ServerThread		(void);
228
229	void				run					(void);
230	void				stop				(void);
231
232private:
233	xs::ExecutionServer	m_server;
234	bool				m_isRunning;
235};
236
237ServerThread::ServerThread (xs::TestProcess* testProcess, int port)
238	: m_server		(testProcess, DE_SOCKETFAMILY_INET4, port, xs::ExecutionServer::RUNMODE_FOREVER)
239	, m_isRunning	(false)
240{
241}
242
243ServerThread::~ServerThread (void)
244{
245	stop();
246}
247
248void ServerThread::run (void)
249{
250	m_isRunning = true;
251	m_server.runServer();
252}
253
254void ServerThread::stop (void)
255{
256	if (m_isRunning)
257	{
258		m_server.stopServer();
259		join();
260		m_isRunning = false;
261	}
262}
263
264string getAppBundleDir (void)
265{
266	NSString*	dataPath	= [[NSBundle mainBundle] bundlePath];
267	const char*	utf8Str		= [dataPath UTF8String];
268
269	return string(utf8Str);
270}
271
272string getAppDocumentsDir (void)
273{
274	NSArray*	paths		= NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
275	NSString*	docPath		= [paths objectAtIndex:0];
276	const char*	utf8Str		= [docPath UTF8String];
277
278	return string(utf8Str);
279}
280
281} // anonymous
282
283struct tcuIOSApp_s
284{
285public:
286							tcuIOSApp_s		(void* view);
287							~tcuIOSApp_s	(void);
288
289	void					iterate			(void);
290
291protected:
292	void					createTestApp	(void);
293	void					destroyTestApp	(void);
294
295	TestThreadState			m_state;
296	LocalTestProcess		m_testProcess;
297	ServerThread			m_server;
298
299	tcu::DirArchive			m_archive;
300	tcu::ios::ScreenManager	m_screenManager;
301	tcu::ios::Platform		m_platform;
302
303	tcu::TestLog*			m_log;
304	tcu::CommandLine*		m_cmdLine;
305	tcu::App*				m_app;
306};
307
308tcuIOSApp_s::tcuIOSApp_s (void* view)
309	: m_testProcess		(m_state, de::FilePath::join(getAppDocumentsDir(), "TestResults.qpa").getPath())
310	, m_server			(&m_testProcess, 50016)
311	, m_archive			(getAppBundleDir().c_str())
312	, m_screenManager	((tcuEAGLView*)view)
313	, m_platform		(&m_screenManager)
314	, m_log				(DE_NULL)
315	, m_cmdLine			(DE_NULL)
316	, m_app				(DE_NULL)
317{
318	// Start server.
319	m_server.start();
320}
321
322tcuIOSApp_s::~tcuIOSApp_s (void)
323{
324	m_server.stop();
325	destroyTestApp();
326}
327
328void tcuIOSApp::createTestApp (void)
329{
330	DE_ASSERT(!m_app && !m_log && !m_cmdLine && !m_platform);
331
332	try
333	{
334		m_log		= new tcu::TestLog(m_testProcess.getLogFileName());
335		m_cmdLine	= new tcu::CommandLine(m_state.getCommandLine());
336		m_app		= new tcu::App(m_platform, m_archive, *m_log, *m_cmdLine);
337	}
338	catch (const std::exception& e)
339	{
340		destroyTestApp();
341		tcu::die("%s", e.what());
342	}
343}
344
345void tcuIOSApp::destroyTestApp (void)
346{
347	delete m_app;
348	delete m_cmdLine;
349	delete m_log;
350	m_app		= DE_NULL;
351	m_cmdLine	= DE_NULL;
352	m_log		= DE_NULL;
353}
354
355void tcuIOSApp::iterate (void)
356{
357	TestThreadState::State curState = m_state.getState();
358
359	if (curState == TestThreadState::STATE_RUNNING)
360	{
361		if (!m_app)
362			createTestApp();
363
364		TCU_CHECK(m_app);
365
366		if (!m_app->iterate())
367		{
368			destroyTestApp();
369			m_state.testExecFinished();
370		}
371	}
372	else if (curState == TestThreadState::STATE_STOP_REQUESTED)
373	{
374		destroyTestApp();
375		m_state.testExecFinished();
376	}
377	// else wait until state has changed?
378}
379
380tcuIOSApp* tcuIOSApp_create (void* view)
381{
382	try
383	{
384		return new tcuIOSApp(view);
385	}
386	catch (const std::exception& e)
387	{
388		tcu::die("FATAL ERROR: %s", e.what());
389		return DE_NULL;
390	}
391}
392
393void tcuIOSApp_destroy (tcuIOSApp* app)
394{
395	delete app;
396}
397
398deBool tcuIOSApp_iterate (tcuIOSApp* app)
399{
400	try
401	{
402		app->iterate();
403		return DE_TRUE;
404	}
405	catch (const std::exception& e)
406	{
407		tcu::print("FATAL ERROR: %s\n", e.what());
408		return DE_FALSE;
409	}
410}
411