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 log container format parser.
22 *//*--------------------------------------------------------------------*/
23
24#include "xeContainerFormatParser.hpp"
25#include "deInt32.h"
26
27namespace xe
28{
29
30enum
31{
32	CONTAINERFORMATPARSER_INITIAL_BUFFER_SIZE = 1024
33};
34
35static int getNextBufferSize (int curSize, int minNewSize)
36{
37	return de::max(curSize*2, 1<<deLog2Ceil32(minNewSize));
38}
39
40ContainerFormatParser::ContainerFormatParser (void)
41	: m_element		(CONTAINERELEMENT_INCOMPLETE)
42	, m_elementLen	(0)
43	, m_state		(STATE_AT_LINE_START)
44	, m_buf			(CONTAINERFORMATPARSER_INITIAL_BUFFER_SIZE)
45{
46}
47
48ContainerFormatParser::~ContainerFormatParser (void)
49{
50}
51
52void ContainerFormatParser::clear (void)
53{
54	m_element		= CONTAINERELEMENT_INCOMPLETE;
55	m_elementLen	= 0;
56	m_state			= STATE_AT_LINE_START;
57	m_buf.clear();
58}
59
60void ContainerFormatParser::error (const std::string& what)
61{
62	throw ContainerParseError(what);
63}
64
65void ContainerFormatParser::feed (const deUint8* bytes, int numBytes)
66{
67	// Grow buffer if necessary.
68	if (m_buf.getNumFree() < numBytes)
69		m_buf.resize(getNextBufferSize(m_buf.getSize(), m_buf.getNumElements()+numBytes));
70
71	// Append to front.
72	m_buf.pushFront(bytes, numBytes);
73
74	// If we haven't parsed complete element, re-try after data feed.
75	if (m_element == CONTAINERELEMENT_INCOMPLETE)
76		advance();
77}
78
79const char* ContainerFormatParser::getSessionInfoAttribute (void) const
80{
81	DE_ASSERT(m_element == CONTAINERELEMENT_SESSION_INFO);
82	return m_attribute.c_str();
83}
84
85const char* ContainerFormatParser::getSessionInfoValue (void) const
86{
87	DE_ASSERT(m_element == CONTAINERELEMENT_SESSION_INFO);
88	return m_value.c_str();
89}
90
91const char* ContainerFormatParser::getTestCasePath (void) const
92{
93	DE_ASSERT(m_element == CONTAINERELEMENT_BEGIN_TEST_CASE_RESULT);
94	return m_value.c_str();
95}
96
97const char* ContainerFormatParser::getTerminateReason (void) const
98{
99	DE_ASSERT(m_element == CONTAINERELEMENT_TERMINATE_TEST_CASE_RESULT);
100	return m_value.c_str();
101}
102
103int ContainerFormatParser::getDataSize (void) const
104{
105	DE_ASSERT(m_element == CONTAINERELEMENT_TEST_LOG_DATA);
106	return m_elementLen;
107}
108
109void ContainerFormatParser::getData (deUint8* dst, int numBytes, int offset)
110{
111	DE_ASSERT(de::inBounds(offset, 0, m_elementLen) && numBytes > 0 && de::inRange(numBytes+offset, 0, m_elementLen));
112
113	for (int ndx = 0; ndx < numBytes; ndx++)
114		dst[ndx] = m_buf.peekBack(offset+ndx);
115}
116
117int ContainerFormatParser::getChar (int offset) const
118{
119	DE_ASSERT(de::inRange(offset, 0, m_buf.getNumElements()));
120
121	if (offset < m_buf.getNumElements())
122		return m_buf.peekBack(offset);
123	else
124		return END_OF_BUFFER;
125}
126
127void ContainerFormatParser::advance (void)
128{
129	if (m_element != CONTAINERELEMENT_INCOMPLETE)
130	{
131		m_buf.popBack(m_elementLen);
132
133		m_element		= CONTAINERELEMENT_INCOMPLETE;
134		m_elementLen	= 0;
135		m_attribute.clear();
136		m_value.clear();
137	}
138
139	for (;;)
140	{
141		int curChar = getChar(m_elementLen);
142
143		if (curChar != (int)END_OF_BUFFER)
144			m_elementLen += 1;
145
146		if (curChar == END_OF_STRING)
147		{
148			if (m_elementLen == 1)
149				m_element = CONTAINERELEMENT_END_OF_STRING;
150			else if (m_state == STATE_CONTAINER_LINE)
151				parseContainerLine();
152			else
153				m_element = CONTAINERELEMENT_TEST_LOG_DATA;
154
155			break;
156		}
157		else if (curChar == (int)END_OF_BUFFER)
158		{
159			if (m_elementLen > 0 && m_state == STATE_DATA)
160				m_element = CONTAINERELEMENT_TEST_LOG_DATA;
161
162			break;
163		}
164		else if (curChar == '\r' || curChar == '\n')
165		{
166			// Check for \r\n
167			int nextChar = getChar(m_elementLen);
168			if (curChar == '\n' || (nextChar != (int)END_OF_BUFFER && nextChar != '\n'))
169			{
170				if (m_state == STATE_CONTAINER_LINE)
171					parseContainerLine();
172				else
173					m_element = CONTAINERELEMENT_TEST_LOG_DATA;
174
175				m_state = STATE_AT_LINE_START;
176				break;
177			}
178			// else handle following end or \n in next iteration.
179		}
180		else if (m_state == STATE_AT_LINE_START)
181		{
182			DE_ASSERT(m_elementLen == 1);
183			m_state = (curChar == '#') ? STATE_CONTAINER_LINE : STATE_DATA;
184		}
185	}
186}
187
188void ContainerFormatParser::parseContainerLine (void)
189{
190	static const struct
191	{
192		const char*			name;
193		ContainerElement	element;
194	} s_elements[] =
195	{
196		{ "beginTestCaseResult",		CONTAINERELEMENT_BEGIN_TEST_CASE_RESULT		},
197		{ "endTestCaseResult",			CONTAINERELEMENT_END_TEST_CASE_RESULT		},
198		{ "terminateTestCaseResult",	CONTAINERELEMENT_TERMINATE_TEST_CASE_RESULT	},
199		{ "sessionInfo",				CONTAINERELEMENT_SESSION_INFO				},
200		{ "beginSession",				CONTAINERELEMENT_BEGIN_SESSION				},
201		{ "endSession",					CONTAINERELEMENT_END_SESSION				}
202	};
203
204	DE_ASSERT(m_elementLen >= 1);
205	DE_ASSERT(getChar(0) == '#');
206
207	int offset = 1;
208
209	for (int elemNdx = 0; elemNdx < DE_LENGTH_OF_ARRAY(s_elements); elemNdx++)
210	{
211		bool	isMatch	= false;
212		int		ndx		= 0;
213
214		for (;;)
215		{
216			int		bufChar		= (offset+ndx < m_elementLen) ? getChar(offset+ndx) : 0;
217			bool	bufEnd		= bufChar == 0 || bufChar == ' ' || bufChar == '\r' || bufChar == '\n' || bufChar == (int)END_OF_BUFFER;
218			int		elemChar	= s_elements[elemNdx].name[ndx];
219			bool	elemEnd		= elemChar == 0;
220
221			if (bufEnd || elemEnd)
222			{
223				isMatch = bufEnd == elemEnd;
224				break;
225			}
226			else if (bufChar != elemChar)
227				break;
228
229			ndx += 1;
230		}
231
232		if (isMatch)
233		{
234			m_element	 = s_elements[elemNdx].element;
235			offset		+= ndx;
236			break;
237		}
238	}
239
240	switch (m_element)
241	{
242		case CONTAINERELEMENT_BEGIN_SESSION:
243		case CONTAINERELEMENT_END_SESSION:
244		case CONTAINERELEMENT_END_TEST_CASE_RESULT:
245			break; // No attribute or value.
246
247		case CONTAINERELEMENT_BEGIN_TEST_CASE_RESULT:
248		case CONTAINERELEMENT_TERMINATE_TEST_CASE_RESULT:
249			if (getChar(offset) != ' ')
250				error("Expected value after instruction");
251			offset += 1;
252			parseContainerValue(m_value, offset);
253			break;
254
255		case CONTAINERELEMENT_SESSION_INFO:
256			if (getChar(offset) != ' ')
257				error("Expected attribute name after #sessionInfo");
258			offset += 1;
259			parseContainerValue(m_attribute, offset);
260			if (getChar(offset) != ' ')
261				error("No value for #sessionInfo attribute");
262			offset += 1;
263
264			if (m_attribute == "timestamp")
265			{
266				m_value.clear();
267
268				// \note Candy produces timestamps in very stupid fashion.
269				for (;;)
270				{
271					const int	curChar	= offset < m_elementLen ? getChar(offset) : 0;
272					const bool	isEnd	= curChar == 0 || curChar == (int)END_OF_BUFFER || curChar == '\n' || curChar == '\t';
273
274					if (isEnd)
275						break;
276					else
277						m_value.push_back((char)curChar);
278
279					offset += 1;
280				}
281			}
282			else
283				parseContainerValue(m_value, offset);
284			break;
285
286		default:
287			// \todo [2012-06-09 pyry] Implement better way to handle # at the beginning of log lines.
288			m_element = CONTAINERELEMENT_TEST_LOG_DATA;
289			break;
290	}
291}
292
293void ContainerFormatParser::parseContainerValue (std::string& dst, int& offset) const
294{
295	DE_ASSERT(offset < m_elementLen);
296
297	bool	isString	= getChar(offset) == '"' || getChar(offset) == '\'';
298	int		quotChar	= isString ? getChar(offset) : 0;
299
300	if (isString)
301		offset += 1;
302
303	dst.clear();
304
305	for (;;)
306	{
307		int		curChar		= offset < m_elementLen ? getChar(offset) : 0;
308		bool	isEnd		= curChar == 0 || curChar == (int)END_OF_BUFFER ||
309							  (isString ? (curChar == quotChar) : (curChar == ' ' || curChar == '\n' || curChar == '\r'));
310
311		if (isEnd)
312			break;
313		else
314		{
315			// \todo [2012-06-09 pyry] Escapes.
316			dst.push_back((char)curChar);
317		}
318
319		offset += 1;
320	}
321
322	if (isString && getChar(offset) == quotChar)
323		offset += 1;
324}
325
326} // xe
327