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 uint8_t * bytes,size_t numBytes)65 void ContainerFormatParser::feed(const uint8_t *bytes, size_t numBytes)
66 {
67 // Grow buffer if necessary.
68 if (m_buf.getNumFree() < (int)numBytes)
69 m_buf.resize(getNextBufferSize(m_buf.getSize(), m_buf.getNumElements() + (int)numBytes));
70
71 // Append to front.
72 m_buf.pushFront(bytes, (int)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(uint8_t * dst,int numBytes,int offset)109 void ContainerFormatParser::getData(uint8_t *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
getTestRunsParams(void) const127 const char *ContainerFormatParser::getTestRunsParams(void) const
128 {
129 DE_ASSERT(m_element == CONTAINERELEMENT_TEST_RUN_PARAM_BEGIN);
130 return m_value.c_str();
131 }
132
advance(void)133 void ContainerFormatParser::advance(void)
134 {
135 if (m_element != CONTAINERELEMENT_INCOMPLETE)
136 {
137 m_buf.popBack(m_elementLen);
138
139 m_element = CONTAINERELEMENT_INCOMPLETE;
140 m_elementLen = 0;
141 m_attribute.clear();
142 m_value.clear();
143 }
144
145 for (;;)
146 {
147 int curChar = getChar(m_elementLen);
148
149 if (curChar != (int)END_OF_BUFFER)
150 m_elementLen += 1;
151
152 if (curChar == END_OF_STRING)
153 {
154 if (m_elementLen == 1)
155 m_element = CONTAINERELEMENT_END_OF_STRING;
156 else if (m_state == STATE_CONTAINER_LINE)
157 parseContainerLine();
158 else
159 m_element = CONTAINERELEMENT_TEST_LOG_DATA;
160
161 break;
162 }
163 else if (curChar == (int)END_OF_BUFFER)
164 {
165 if (m_elementLen > 0 && m_state == STATE_DATA)
166 m_element = CONTAINERELEMENT_TEST_LOG_DATA;
167
168 break;
169 }
170 else if (curChar == '\r' || curChar == '\n')
171 {
172 // Check for \r\n
173 int nextChar = getChar(m_elementLen);
174 if (curChar == '\n' || (nextChar != (int)END_OF_BUFFER && nextChar != '\n'))
175 {
176 if (m_state == STATE_CONTAINER_LINE)
177 parseContainerLine();
178 else
179 m_element = CONTAINERELEMENT_TEST_LOG_DATA;
180
181 m_state = STATE_AT_LINE_START;
182 break;
183 }
184 // else handle following end or \n in next iteration.
185 }
186 else if (m_state == STATE_AT_LINE_START)
187 {
188 DE_ASSERT(m_elementLen == 1);
189 m_state = (curChar == '#') ? STATE_CONTAINER_LINE : STATE_DATA;
190 }
191 }
192 }
193
parseContainerLine(void)194 void ContainerFormatParser::parseContainerLine(void)
195 {
196 static const struct
197 {
198 const char *name;
199 ContainerElement element;
200 } s_elements[] = {
201 {"beginTestCaseResult", CONTAINERELEMENT_BEGIN_TEST_CASE_RESULT},
202 {"endTestCaseResult", CONTAINERELEMENT_END_TEST_CASE_RESULT},
203 {"terminateTestCaseResult", CONTAINERELEMENT_TERMINATE_TEST_CASE_RESULT},
204 {"sessionInfo", CONTAINERELEMENT_SESSION_INFO},
205 {"beginSession", CONTAINERELEMENT_BEGIN_SESSION},
206 {"endSession", CONTAINERELEMENT_END_SESSION},
207 {"beginTestRunParamsCollection", CONTAINERELEMENT_TEST_RUN_PARAM_SESSION_BEGIN},
208 {"endTestRunParamsCollection", CONTAINERELEMENT_TEST_RUN_PARAM_SESSION_END},
209 {"beginTestRunParams", CONTAINERELEMENT_TEST_RUN_PARAM_BEGIN},
210 {"endTestRunParams", CONTAINERELEMENT_TEST_RUN_PARAM_END},
211 };
212
213 DE_ASSERT(m_elementLen >= 1);
214 DE_ASSERT(getChar(0) == '#');
215
216 int offset = 1;
217
218 for (int elemNdx = 0; elemNdx < DE_LENGTH_OF_ARRAY(s_elements); elemNdx++)
219 {
220 bool isMatch = false;
221 int ndx = 0;
222
223 for (;;)
224 {
225 int bufChar = (offset + ndx < m_elementLen) ? getChar(offset + ndx) : 0;
226 bool bufEnd =
227 bufChar == 0 || bufChar == ' ' || bufChar == '\r' || bufChar == '\n' || bufChar == (int)END_OF_BUFFER;
228 int elemChar = s_elements[elemNdx].name[ndx];
229 bool elemEnd = elemChar == 0;
230
231 if (bufEnd || elemEnd)
232 {
233 isMatch = bufEnd == elemEnd;
234 break;
235 }
236 else if (bufChar != elemChar)
237 break;
238
239 ndx += 1;
240 }
241
242 if (isMatch)
243 {
244 m_element = s_elements[elemNdx].element;
245 offset += ndx;
246 break;
247 }
248 }
249
250 switch (m_element)
251 {
252 case CONTAINERELEMENT_BEGIN_SESSION:
253 case CONTAINERELEMENT_END_SESSION:
254 case CONTAINERELEMENT_END_TEST_CASE_RESULT:
255 case CONTAINERELEMENT_TEST_RUN_PARAM_SESSION_BEGIN:
256 case CONTAINERELEMENT_TEST_RUN_PARAM_SESSION_END:
257 case CONTAINERELEMENT_TEST_RUN_PARAM_END:
258 break; // No attribute or value.
259
260 case CONTAINERELEMENT_BEGIN_TEST_CASE_RESULT:
261 case CONTAINERELEMENT_TERMINATE_TEST_CASE_RESULT:
262 case CONTAINERELEMENT_TEST_RUN_PARAM_BEGIN:
263 if (getChar(offset) != ' ')
264 error("Expected value after instruction");
265 offset += 1;
266 parseContainerValue(m_value, offset);
267 break;
268
269 case CONTAINERELEMENT_SESSION_INFO:
270 if (getChar(offset) != ' ')
271 error("Expected attribute name after #sessionInfo");
272 offset += 1;
273 parseContainerValue(m_attribute, offset);
274 if (getChar(offset) != ' ')
275 error("No value for #sessionInfo attribute");
276 offset += 1;
277
278 if (m_attribute == "timestamp")
279 {
280 m_value.clear();
281
282 // \note Candy produces unescaped timestamps.
283 for (;;)
284 {
285 const int curChar = offset < m_elementLen ? getChar(offset) : 0;
286 const bool isEnd = curChar == 0 || curChar == (int)END_OF_BUFFER || curChar == '\n' || curChar == '\t';
287
288 if (isEnd)
289 break;
290 else
291 m_value.push_back((char)curChar);
292
293 offset += 1;
294 }
295 }
296 else
297 parseContainerValue(m_value, offset);
298 break;
299
300 default:
301 // \todo [2012-06-09 pyry] Implement better way to handle # at the beginning of log lines.
302 m_element = CONTAINERELEMENT_TEST_LOG_DATA;
303 break;
304 }
305 }
306
parseContainerValue(std::string & dst,int & offset) const307 void ContainerFormatParser::parseContainerValue(std::string &dst, int &offset) const
308 {
309 DE_ASSERT(offset < m_elementLen);
310
311 bool isString = getChar(offset) == '"' || getChar(offset) == '\'';
312 int quotChar = isString ? getChar(offset) : 0;
313
314 if (isString)
315 offset += 1;
316
317 dst.clear();
318
319 for (;;)
320 {
321 int curChar = offset < m_elementLen ? getChar(offset) : 0;
322 bool isEnd = curChar == 0 || curChar == (int)END_OF_BUFFER ||
323 (isString ? (curChar == quotChar) : (curChar == ' ' || curChar == '\n' || curChar == '\r'));
324
325 if (isEnd)
326 break;
327 else
328 {
329 // \todo [2012-06-09 pyry] Escapes.
330 dst.push_back((char)curChar);
331 }
332
333 offset += 1;
334 }
335
336 if (isString && getChar(offset) == quotChar)
337 offset += 1;
338 }
339
340 } // namespace xe
341