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 
27 namespace xe
28 {
29 
30 enum
31 {
32 	CONTAINERFORMATPARSER_INITIAL_BUFFER_SIZE = 1024
33 };
34 
getNextBufferSize(int curSize,int minNewSize)35 static int getNextBufferSize (int curSize, int minNewSize)
36 {
37 	return de::max(curSize*2, 1<<deLog2Ceil32(minNewSize));
38 }
39 
ContainerFormatParser(void)40 ContainerFormatParser::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 
~ContainerFormatParser(void)48 ContainerFormatParser::~ContainerFormatParser (void)
49 {
50 }
51 
clear(void)52 void 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 
error(const std::string & what)60 void ContainerFormatParser::error (const std::string& what)
61 {
62 	throw ContainerParseError(what);
63 }
64 
feed(const deUint8 * bytes,int numBytes)65 void 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 
getSessionInfoAttribute(void) const79 const char* ContainerFormatParser::getSessionInfoAttribute (void) const
80 {
81 	DE_ASSERT(m_element == CONTAINERELEMENT_SESSION_INFO);
82 	return m_attribute.c_str();
83 }
84 
getSessionInfoValue(void) const85 const char* ContainerFormatParser::getSessionInfoValue (void) const
86 {
87 	DE_ASSERT(m_element == CONTAINERELEMENT_SESSION_INFO);
88 	return m_value.c_str();
89 }
90 
getTestCasePath(void) const91 const char* ContainerFormatParser::getTestCasePath (void) const
92 {
93 	DE_ASSERT(m_element == CONTAINERELEMENT_BEGIN_TEST_CASE_RESULT);
94 	return m_value.c_str();
95 }
96 
getTerminateReason(void) const97 const char* ContainerFormatParser::getTerminateReason (void) const
98 {
99 	DE_ASSERT(m_element == CONTAINERELEMENT_TERMINATE_TEST_CASE_RESULT);
100 	return m_value.c_str();
101 }
102 
getDataSize(void) const103 int ContainerFormatParser::getDataSize (void) const
104 {
105 	DE_ASSERT(m_element == CONTAINERELEMENT_TEST_LOG_DATA);
106 	return m_elementLen;
107 }
108 
getData(deUint8 * dst,int numBytes,int offset)109 void 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 
getChar(int offset) const117 int 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 
advance(void)127 void 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 
parseContainerLine(void)188 void 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 
parseContainerValue(std::string & dst,int & offset) const293 void 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