1/*-------------------------------------------------------------------------
2 * drawElements Quality Program Test Executor
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 Test batch executor.
22 *//*--------------------------------------------------------------------*/
23
24#include "xeBatchExecutor.hpp"
25#include "xeTestResultParser.hpp"
26
27#include <sstream>
28#include <cstdio>
29
30namespace xe
31{
32
33using std::string;
34using std::vector;
35
36enum
37{
38	TEST_LOG_TMP_BUFFER_SIZE	= 1024,
39	INFO_LOG_TMP_BUFFER_SIZE	= 256
40};
41
42// \todo [2012-11-01 pyry] Update execute set in handler.
43
44static inline bool isExecutedInBatch (const BatchResult* batchResult, const TestCase* testCase)
45{
46	std::string fullPath;
47	testCase->getFullPath(fullPath);
48
49	if (batchResult->hasTestCaseResult(fullPath.c_str()))
50	{
51		ConstTestCaseResultPtr data = batchResult->getTestCaseResult(fullPath.c_str());
52		return data->getStatusCode() != TESTSTATUSCODE_PENDING && data->getStatusCode() != TESTSTATUSCODE_RUNNING;
53	}
54	else
55		return false;
56}
57
58// \todo [2012-06-19 pyry] These can be optimized using TestSetIterator (once implemented)
59
60static void computeExecuteSet (TestSet& executeSet, const TestNode* root, const TestSet& testSet, const BatchResult* batchResult)
61{
62	ConstTestNodeIterator	iter	= ConstTestNodeIterator::begin(root);
63	ConstTestNodeIterator	end		= ConstTestNodeIterator::end(root);
64
65	for (; iter != end; ++iter)
66	{
67		const TestNode* node = *iter;
68
69		if (node->getNodeType() == TESTNODETYPE_TEST_CASE && testSet.hasNode(node))
70		{
71			const TestCase* testCase = static_cast<const TestCase*>(node);
72
73			if (!isExecutedInBatch(batchResult, testCase))
74				executeSet.addCase(testCase);
75		}
76	}
77}
78
79static void computeBatchRequest (TestSet& requestSet, const TestSet& executeSet, const TestNode* root, int maxCasesInSet)
80{
81	ConstTestNodeIterator	iter		= ConstTestNodeIterator::begin(root);
82	ConstTestNodeIterator	end			= ConstTestNodeIterator::end(root);
83	int						numCases	= 0;
84
85	for (; (iter != end) && (numCases < maxCasesInSet); ++iter)
86	{
87		const TestNode* node = *iter;
88
89		if (node->getNodeType() == TESTNODETYPE_TEST_CASE && executeSet.hasNode(node))
90		{
91			const TestCase* testCase = static_cast<const TestCase*>(node);
92			requestSet.addCase(testCase);
93			numCases += 1;
94		}
95	}
96}
97
98static int removeExecuted (TestSet& set, const TestNode* root, const BatchResult* batchResult)
99{
100	TestSet					oldSet		(set);
101	ConstTestNodeIterator	iter		= ConstTestNodeIterator::begin(root);
102	ConstTestNodeIterator	end			= ConstTestNodeIterator::end(root);
103	int						numRemoved	= 0;
104
105	for (; iter != end; ++iter)
106	{
107		const TestNode* node = *iter;
108
109		if (node->getNodeType() == TESTNODETYPE_TEST_CASE && oldSet.hasNode(node))
110		{
111			const TestCase* testCase = static_cast<const TestCase*>(node);
112
113			if (isExecutedInBatch(batchResult, testCase))
114			{
115				set.removeCase(testCase);
116				numRemoved += 1;
117			}
118		}
119	}
120
121	return numRemoved;
122}
123
124BatchExecutorLogHandler::BatchExecutorLogHandler (BatchResult* batchResult)
125	: m_batchResult(batchResult)
126{
127}
128
129BatchExecutorLogHandler::~BatchExecutorLogHandler (void)
130{
131}
132
133void BatchExecutorLogHandler::setSessionInfo (const SessionInfo& sessionInfo)
134{
135	m_batchResult->getSessionInfo() = sessionInfo;
136}
137
138TestCaseResultPtr BatchExecutorLogHandler::startTestCaseResult (const char* casePath)
139{
140	// \todo [2012-11-01 pyry] What to do with duplicate results?
141	if (m_batchResult->hasTestCaseResult(casePath))
142		return m_batchResult->getTestCaseResult(casePath);
143	else
144		return m_batchResult->createTestCaseResult(casePath);
145}
146
147void BatchExecutorLogHandler::testCaseResultUpdated (const TestCaseResultPtr&)
148{
149}
150
151void BatchExecutorLogHandler::testCaseResultComplete (const TestCaseResultPtr& result)
152{
153	// \todo [2012-11-01 pyry] Remove from execute set here instead of updating it between sessions.
154	printf("%s\n", result->getTestCasePath());
155}
156
157BatchExecutor::BatchExecutor (const TargetConfiguration& config, CommLink* commLink, const TestNode* root, const TestSet& testSet, BatchResult* batchResult, InfoLog* infoLog)
158	: m_config			(config)
159	, m_commLink		(commLink)
160	, m_root			(root)
161	, m_testSet			(testSet)
162	, m_logHandler		(batchResult)
163	, m_batchResult		(batchResult)
164	, m_infoLog			(infoLog)
165	, m_state			(STATE_NOT_STARTED)
166	, m_testLogParser	(&m_logHandler)
167{
168}
169
170BatchExecutor::~BatchExecutor (void)
171{
172}
173
174void BatchExecutor::run (void)
175{
176	XE_CHECK(m_state == STATE_NOT_STARTED);
177
178	// Check commlink state.
179	{
180		CommLinkState	commState	= COMMLINKSTATE_LAST;
181		std::string		stateStr	= "";
182
183		commState = m_commLink->getState(stateStr);
184
185		if (commState == COMMLINKSTATE_ERROR)
186		{
187			// Report error.
188			XE_FAIL((string("CommLink error: '") + stateStr + "'").c_str());
189		}
190		else if (commState != COMMLINKSTATE_READY)
191			XE_FAIL("CommLink is not ready");
192	}
193
194	// Compute initial execute set.
195	computeExecuteSet(m_casesToExecute, m_root, m_testSet, m_batchResult);
196
197	// Register callbacks.
198	m_commLink->setCallbacks(enqueueStateChanged, enqueueTestLogData, enqueueInfoLogData, this);
199
200	try
201	{
202		if (!m_casesToExecute.empty())
203		{
204			TestSet batchRequest;
205			computeBatchRequest(batchRequest, m_casesToExecute, m_root, m_config.maxCasesPerSession);
206			launchTestSet(batchRequest);
207
208			m_state = STATE_STARTED;
209		}
210		else
211			m_state = STATE_FINISHED;
212
213		// Run handler loop until we are finished.
214		while (m_state != STATE_FINISHED)
215			m_dispatcher.callNext();
216	}
217	catch (...)
218	{
219		m_commLink->setCallbacks(DE_NULL, DE_NULL, DE_NULL, DE_NULL);
220		throw;
221	}
222
223	// De-register callbacks.
224	m_commLink->setCallbacks(DE_NULL, DE_NULL, DE_NULL, DE_NULL);
225}
226
227void BatchExecutor::cancel (void)
228{
229	m_state = STATE_FINISHED;
230	m_dispatcher.cancel();
231}
232
233void BatchExecutor::onStateChanged (CommLinkState state, const char* message)
234{
235	switch (state)
236	{
237		case COMMLINKSTATE_READY:
238		case COMMLINKSTATE_TEST_PROCESS_LAUNCHING:
239		case COMMLINKSTATE_TEST_PROCESS_RUNNING:
240			break; // Ignore.
241
242		case COMMLINKSTATE_TEST_PROCESS_FINISHED:
243		{
244			// Feed end of string to parser. This terminates open test case if such exists.
245			{
246				deUint8 eos = 0;
247				onTestLogData(&eos, 1);
248			}
249
250			int numExecuted = removeExecuted(m_casesToExecute, m_root, m_batchResult);
251
252			// \note No new batch is launched if no cases were executed in last one. Otherwise excutor
253			//       could end up in infinite loop.
254			if (!m_casesToExecute.empty() && numExecuted > 0)
255			{
256				// Reset state and start batch.
257				m_testLogParser.reset();
258
259				m_commLink->reset();
260				XE_CHECK(m_commLink->getState() == COMMLINKSTATE_READY);
261
262				TestSet batchRequest;
263				computeBatchRequest(batchRequest, m_casesToExecute, m_root, m_config.maxCasesPerSession);
264				launchTestSet(batchRequest);
265			}
266			else
267				m_state = STATE_FINISHED;
268
269			break;
270		}
271
272		case COMMLINKSTATE_TEST_PROCESS_LAUNCH_FAILED:
273			printf("Failed to start test process: '%s'\n", message);
274			m_state = STATE_FINISHED;
275			break;
276
277		case COMMLINKSTATE_ERROR:
278			printf("CommLink error: '%s'\n", message);
279			m_state = STATE_FINISHED;
280			break;
281
282		default:
283			XE_FAIL("Unknown state");
284	}
285}
286
287void BatchExecutor::onTestLogData (const deUint8* bytes, size_t numBytes)
288{
289	try
290	{
291		m_testLogParser.parse(bytes, numBytes);
292	}
293	catch (const ParseError& e)
294	{
295		// \todo [2012-07-06 pyry] Log error.
296		DE_UNREF(e);
297	}
298}
299
300void BatchExecutor::onInfoLogData (const deUint8* bytes, size_t numBytes)
301{
302	if (numBytes > 0 && m_infoLog)
303		m_infoLog->append(bytes, numBytes);
304}
305
306static void writeCaseListNode (std::ostream& str, const TestNode* node, const TestSet& testSet)
307{
308	DE_ASSERT(testSet.hasNode(node));
309
310	TestNodeType nodeType = node->getNodeType();
311
312	if (nodeType != TESTNODETYPE_ROOT)
313		str << node->getName();
314
315	if (nodeType == TESTNODETYPE_ROOT || nodeType == TESTNODETYPE_GROUP)
316	{
317		const TestGroup*	group	= static_cast<const TestGroup*>(node);
318		bool				isFirst	= true;
319
320		str << "{";
321
322		for (int ndx = 0; ndx < group->getNumChildren(); ndx++)
323		{
324			const TestNode* child = group->getChild(ndx);
325
326			if (testSet.hasNode(child))
327			{
328				if (!isFirst)
329					str << ",";
330
331				writeCaseListNode(str, child, testSet);
332				isFirst = false;
333			}
334		}
335
336		str << "}";
337	}
338}
339
340void BatchExecutor::launchTestSet (const TestSet& testSet)
341{
342	std::ostringstream caseList;
343	XE_CHECK(testSet.hasNode(m_root));
344	XE_CHECK(m_root->getNodeType() == TESTNODETYPE_ROOT);
345	writeCaseListNode(caseList, m_root, testSet);
346
347	m_commLink->startTestProcess(m_config.binaryName.c_str(), m_config.cmdLineArgs.c_str(), m_config.workingDir.c_str(), caseList.str().c_str());
348}
349
350void BatchExecutor::enqueueStateChanged (void* userPtr, CommLinkState state, const char* message)
351{
352	BatchExecutor*	executor	= static_cast<BatchExecutor*>(userPtr);
353	CallWriter		writer		(&executor->m_dispatcher, BatchExecutor::dispatchStateChanged);
354
355	writer << executor
356		   << state
357		   << message;
358
359	writer.enqueue();
360}
361
362void BatchExecutor::enqueueTestLogData (void* userPtr, const deUint8* bytes, size_t numBytes)
363{
364	BatchExecutor*	executor	= static_cast<BatchExecutor*>(userPtr);
365	CallWriter		writer		(&executor->m_dispatcher, BatchExecutor::dispatchTestLogData);
366
367	writer << executor
368		   << numBytes;
369
370	writer.write(bytes, numBytes);
371	writer.enqueue();
372}
373
374void BatchExecutor::enqueueInfoLogData (void* userPtr, const deUint8* bytes, size_t numBytes)
375{
376	BatchExecutor*	executor	= static_cast<BatchExecutor*>(userPtr);
377	CallWriter		writer		(&executor->m_dispatcher, BatchExecutor::dispatchInfoLogData);
378
379	writer << executor
380		   << numBytes;
381
382	writer.write(bytes, numBytes);
383	writer.enqueue();
384}
385
386void BatchExecutor::dispatchStateChanged (CallReader& data)
387{
388	BatchExecutor*	executor	= DE_NULL;
389	CommLinkState	state		= COMMLINKSTATE_LAST;
390	std::string		message;
391
392	data >> executor
393		 >> state
394		 >> message;
395
396	executor->onStateChanged(state, message.c_str());
397}
398
399void BatchExecutor::dispatchTestLogData (CallReader& data)
400{
401	BatchExecutor*	executor	= DE_NULL;
402	size_t			numBytes;
403
404	data >> executor
405		 >> numBytes;
406
407	executor->onTestLogData(data.getDataBlock(numBytes), numBytes);
408}
409
410void BatchExecutor::dispatchInfoLogData (CallReader& data)
411{
412	BatchExecutor*	executor	= DE_NULL;
413	size_t			numBytes;
414
415	data >> executor
416		 >> numBytes;
417
418	executor->onInfoLogData(data.getDataBlock(numBytes), numBytes);
419}
420
421} // xe
422