1/*-------------------------------------------------------------------------
2 * drawElements Quality Program Execution Server
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 TestProcess implementation for Unix-like systems.
22 *//*--------------------------------------------------------------------*/
23
24#include "xsPosixTestProcess.hpp"
25#include "deFilePath.hpp"
26#include "deClock.h"
27
28#include <string.h>
29#include <stdio.h>
30
31using std::string;
32using std::vector;
33
34namespace xs
35{
36
37namespace posix
38{
39
40CaseListWriter::CaseListWriter (void)
41	: m_file	(DE_NULL)
42	, m_run		(false)
43{
44}
45
46CaseListWriter::~CaseListWriter (void)
47{
48}
49
50void CaseListWriter::start (const char* caseList, deFile* dst)
51{
52	DE_ASSERT(!isStarted());
53	m_file	= dst;
54	m_run	= true;
55
56	int caseListSize = (int)strlen(caseList)+1;
57	m_caseList.resize(caseListSize);
58	std::copy(caseList, caseList+caseListSize, m_caseList.begin());
59
60	// Set to non-blocking mode.
61	if (!deFile_setFlags(m_file, DE_FILE_NONBLOCKING))
62		XS_FAIL("Failed to set non-blocking mode");
63
64	de::Thread::start();
65}
66
67void CaseListWriter::run (void)
68{
69	deInt64 pos = 0;
70
71	while (m_run && pos < (deInt64)m_caseList.size())
72	{
73		deInt64			numWritten	= 0;
74		deFileResult	result		= deFile_write(m_file, &m_caseList[0] + pos, m_caseList.size()-pos, &numWritten);
75
76		if (result == DE_FILERESULT_SUCCESS)
77			pos += numWritten;
78		else if (result == DE_FILERESULT_WOULD_BLOCK)
79			deSleep(1); // Yield.
80		else
81			break; // Error.
82	}
83}
84
85void CaseListWriter::stop (void)
86{
87	if (!isStarted())
88		return; // Nothing to do.
89
90	m_run = false;
91
92	// Join thread.
93	join();
94
95	m_file = DE_NULL;
96}
97
98PipeReader::PipeReader (ThreadedByteBuffer* dst)
99	: m_file	(DE_NULL)
100	, m_buf		(dst)
101{
102}
103
104PipeReader::~PipeReader (void)
105{
106}
107
108void PipeReader::start (deFile* file)
109{
110	DE_ASSERT(!isStarted());
111
112	// Set to non-blocking mode.
113	if (!deFile_setFlags(file, DE_FILE_NONBLOCKING))
114		XS_FAIL("Failed to set non-blocking mode");
115
116	m_file = file;
117
118	de::Thread::start();
119}
120
121void PipeReader::run (void)
122{
123	std::vector<deUint8>	tmpBuf		(FILEREADER_TMP_BUFFER_SIZE);
124	deInt64					numRead		= 0;
125
126	while (!m_buf->isCanceled())
127	{
128		deFileResult result = deFile_read(m_file, &tmpBuf[0], (deInt64)tmpBuf.size(), &numRead);
129
130		if (result == DE_FILERESULT_SUCCESS)
131		{
132			// Write to buffer.
133			try
134			{
135				m_buf->write((int)numRead, &tmpBuf[0]);
136				m_buf->flush();
137			}
138			catch (const ThreadedByteBuffer::CanceledException&)
139			{
140				// Canceled.
141				break;
142			}
143		}
144		else if (result == DE_FILERESULT_END_OF_FILE ||
145				 result == DE_FILERESULT_WOULD_BLOCK)
146		{
147			// Wait for more data.
148			deSleep(FILEREADER_IDLE_SLEEP);
149		}
150		else
151			break; // Error.
152	}
153}
154
155void PipeReader::stop (void)
156{
157	if (!isStarted())
158		return; // Nothing to do.
159
160	// Buffer must be in canceled state or otherwise stopping reader might block.
161	DE_ASSERT(m_buf->isCanceled());
162
163	// Join thread.
164	join();
165
166	m_file = DE_NULL;
167}
168
169} // unix
170
171PosixTestProcess::PosixTestProcess (void)
172	: m_process				(DE_NULL)
173	, m_processStartTime	(0)
174	, m_infoBuffer			(INFO_BUFFER_BLOCK_SIZE, INFO_BUFFER_NUM_BLOCKS)
175	, m_stdOutReader		(&m_infoBuffer)
176	, m_stdErrReader		(&m_infoBuffer)
177	, m_logReader			(LOG_BUFFER_BLOCK_SIZE, LOG_BUFFER_NUM_BLOCKS)
178{
179}
180
181PosixTestProcess::~PosixTestProcess (void)
182{
183	delete m_process;
184}
185
186void PosixTestProcess::start (const char* name, const char* params, const char* workingDir, const char* caseList)
187{
188	bool hasCaseList = strlen(caseList) > 0;
189
190	XS_CHECK(!m_process);
191
192	de::FilePath logFilePath = de::FilePath::join(workingDir, "TestResults.qpa");
193	m_logFileName = logFilePath.getPath();
194
195	// Remove old file if such exists.
196	if (deFileExists(m_logFileName.c_str()))
197	{
198		if (!deDeleteFile(m_logFileName.c_str()) || deFileExists(m_logFileName.c_str()))
199			throw TestProcessException(string("Failed to remove '") + m_logFileName + "'");
200	}
201
202	// Construct command line.
203	string cmdLine = de::FilePath(name).isAbsolutePath() ? name : de::FilePath::join(workingDir, name).normalize().getPath();
204	cmdLine += string(" --deqp-log-filename=") + logFilePath.getBaseName();
205
206	if (hasCaseList)
207		cmdLine += " --deqp-stdin-caselist";
208
209	if (strlen(params) > 0)
210		cmdLine += string(" ") + params;
211
212	DE_ASSERT(!m_process);
213	m_process = new de::Process();
214
215	try
216	{
217		m_process->start(cmdLine.c_str(), strlen(workingDir) > 0 ? workingDir : DE_NULL);
218	}
219	catch (const de::ProcessError& e)
220	{
221		delete m_process;
222		m_process = DE_NULL;
223		throw TestProcessException(e.what());
224	}
225
226	m_processStartTime = deGetMicroseconds();
227
228	// Create stdout & stderr readers.
229	if (m_process->getStdOut())
230		m_stdOutReader.start(m_process->getStdOut());
231
232	if (m_process->getStdErr())
233		m_stdErrReader.start(m_process->getStdErr());
234
235	// Start case list writer.
236	if (hasCaseList)
237	{
238		deFile* dst = m_process->getStdIn();
239		if (dst)
240			m_caseListWriter.start(caseList, dst);
241		else
242		{
243			cleanup();
244			throw TestProcessException("Failed to write case list");
245		}
246	}
247}
248
249void PosixTestProcess::terminate (void)
250{
251	if (m_process)
252	{
253		try
254		{
255			m_process->kill();
256		}
257		catch (const std::exception& e)
258		{
259			printf("PosixTestProcess::terminate(): Failed to kill process: %s\n", e.what());
260		}
261	}
262}
263
264void PosixTestProcess::cleanup (void)
265{
266	m_caseListWriter.stop();
267	m_logReader.stop();
268
269	// \note Info buffer must be canceled before stopping pipe readers.
270	m_infoBuffer.cancel();
271
272	m_stdErrReader.stop();
273	m_stdOutReader.stop();
274
275	// Reset info buffer.
276	m_infoBuffer.clear();
277
278	if (m_process)
279	{
280		try
281		{
282			if (m_process->isRunning())
283			{
284				m_process->kill();
285				m_process->waitForFinish();
286			}
287		}
288		catch (const de::ProcessError& e)
289		{
290			printf("PosixTestProcess::stop(): Failed to kill process: %s\n", e.what());
291		}
292
293		delete m_process;
294		m_process = DE_NULL;
295	}
296}
297
298bool PosixTestProcess::isRunning (void)
299{
300	if (m_process)
301		return m_process->isRunning();
302	else
303		return false;
304}
305
306int PosixTestProcess::getExitCode (void) const
307{
308	if (m_process)
309		return m_process->getExitCode();
310	else
311		return -1;
312}
313
314int PosixTestProcess::readTestLog (deUint8* dst, int numBytes)
315{
316	if (!m_logReader.isRunning())
317	{
318		if (deGetMicroseconds() - m_processStartTime > LOG_FILE_TIMEOUT*1000)
319		{
320			// Timeout, kill process.
321			terminate();
322			return 0; // \todo [2013-08-13 pyry] Throw exception?
323		}
324
325		if (!deFileExists(m_logFileName.c_str()))
326			return 0;
327
328		// Start reader.
329		m_logReader.start(m_logFileName.c_str());
330	}
331
332	DE_ASSERT(m_logReader.isRunning());
333	return m_logReader.read(dst, numBytes);
334}
335
336} // xs
337