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 35 static int getNextBufferSize (int curSize, int minNewSize) 36 { 37 return de::max(curSize*2, 1<<deLog2Ceil32(minNewSize)); 38 } 39 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 48 ContainerFormatParser::~ContainerFormatParser (void) 49 { 50 } 51 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 60 void ContainerFormatParser::error (const std::string& what) 61 { 62 throw ContainerParseError(what); 63 } 64 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 79 const char* ContainerFormatParser::getSessionInfoAttribute (void) const 80 { 81 DE_ASSERT(m_element == CONTAINERELEMENT_SESSION_INFO); 82 return m_attribute.c_str(); 83 } 84 85 const char* ContainerFormatParser::getSessionInfoValue (void) const 86 { 87 DE_ASSERT(m_element == CONTAINERELEMENT_SESSION_INFO); 88 return m_value.c_str(); 89 } 90 91 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 97 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 103 int ContainerFormatParser::getDataSize (void) const 104 { 105 DE_ASSERT(m_element == CONTAINERELEMENT_TEST_LOG_DATA); 106 return m_elementLen; 107 } 108 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 117 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 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 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 293 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