/*------------------------------------------------------------------------- * drawElements Quality Program Test Executor * ------------------------------------------ * * Copyright 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * *//*! * \file * \brief Test log container format parser. *//*--------------------------------------------------------------------*/ #include "xeContainerFormatParser.hpp" #include "deInt32.h" namespace xe { enum { CONTAINERFORMATPARSER_INITIAL_BUFFER_SIZE = 1024 }; static int getNextBufferSize (int curSize, int minNewSize) { return de::max(curSize*2, 1<<deLog2Ceil32(minNewSize)); } ContainerFormatParser::ContainerFormatParser (void) : m_element (CONTAINERELEMENT_INCOMPLETE) , m_elementLen (0) , m_state (STATE_AT_LINE_START) , m_buf (CONTAINERFORMATPARSER_INITIAL_BUFFER_SIZE) { } ContainerFormatParser::~ContainerFormatParser (void) { } void ContainerFormatParser::clear (void) { m_element = CONTAINERELEMENT_INCOMPLETE; m_elementLen = 0; m_state = STATE_AT_LINE_START; m_buf.clear(); } void ContainerFormatParser::error (const std::string& what) { throw ContainerParseError(what); } void ContainerFormatParser::feed (const deUint8* bytes, int numBytes) { // Grow buffer if necessary. if (m_buf.getNumFree() < numBytes) m_buf.resize(getNextBufferSize(m_buf.getSize(), m_buf.getNumElements()+numBytes)); // Append to front. m_buf.pushFront(bytes, numBytes); // If we haven't parsed complete element, re-try after data feed. if (m_element == CONTAINERELEMENT_INCOMPLETE) advance(); } const char* ContainerFormatParser::getSessionInfoAttribute (void) const { DE_ASSERT(m_element == CONTAINERELEMENT_SESSION_INFO); return m_attribute.c_str(); } const char* ContainerFormatParser::getSessionInfoValue (void) const { DE_ASSERT(m_element == CONTAINERELEMENT_SESSION_INFO); return m_value.c_str(); } const char* ContainerFormatParser::getTestCasePath (void) const { DE_ASSERT(m_element == CONTAINERELEMENT_BEGIN_TEST_CASE_RESULT); return m_value.c_str(); } const char* ContainerFormatParser::getTerminateReason (void) const { DE_ASSERT(m_element == CONTAINERELEMENT_TERMINATE_TEST_CASE_RESULT); return m_value.c_str(); } int ContainerFormatParser::getDataSize (void) const { DE_ASSERT(m_element == CONTAINERELEMENT_TEST_LOG_DATA); return m_elementLen; } void ContainerFormatParser::getData (deUint8* dst, int numBytes, int offset) { DE_ASSERT(de::inBounds(offset, 0, m_elementLen) && numBytes > 0 && de::inRange(numBytes+offset, 0, m_elementLen)); for (int ndx = 0; ndx < numBytes; ndx++) dst[ndx] = m_buf.peekBack(offset+ndx); } int ContainerFormatParser::getChar (int offset) const { DE_ASSERT(de::inRange(offset, 0, m_buf.getNumElements())); if (offset < m_buf.getNumElements()) return m_buf.peekBack(offset); else return END_OF_BUFFER; } void ContainerFormatParser::advance (void) { if (m_element != CONTAINERELEMENT_INCOMPLETE) { m_buf.popBack(m_elementLen); m_element = CONTAINERELEMENT_INCOMPLETE; m_elementLen = 0; m_attribute.clear(); m_value.clear(); } for (;;) { int curChar = getChar(m_elementLen); if (curChar != (int)END_OF_BUFFER) m_elementLen += 1; if (curChar == END_OF_STRING) { if (m_elementLen == 1) m_element = CONTAINERELEMENT_END_OF_STRING; else if (m_state == STATE_CONTAINER_LINE) parseContainerLine(); else m_element = CONTAINERELEMENT_TEST_LOG_DATA; break; } else if (curChar == (int)END_OF_BUFFER) { if (m_elementLen > 0 && m_state == STATE_DATA) m_element = CONTAINERELEMENT_TEST_LOG_DATA; break; } else if (curChar == '\r' || curChar == '\n') { // Check for \r\n int nextChar = getChar(m_elementLen); if (curChar == '\n' || (nextChar != (int)END_OF_BUFFER && nextChar != '\n')) { if (m_state == STATE_CONTAINER_LINE) parseContainerLine(); else m_element = CONTAINERELEMENT_TEST_LOG_DATA; m_state = STATE_AT_LINE_START; break; } // else handle following end or \n in next iteration. } else if (m_state == STATE_AT_LINE_START) { DE_ASSERT(m_elementLen == 1); m_state = (curChar == '#') ? STATE_CONTAINER_LINE : STATE_DATA; } } } void ContainerFormatParser::parseContainerLine (void) { static const struct { const char* name; ContainerElement element; } s_elements[] = { { "beginTestCaseResult", CONTAINERELEMENT_BEGIN_TEST_CASE_RESULT }, { "endTestCaseResult", CONTAINERELEMENT_END_TEST_CASE_RESULT }, { "terminateTestCaseResult", CONTAINERELEMENT_TERMINATE_TEST_CASE_RESULT }, { "sessionInfo", CONTAINERELEMENT_SESSION_INFO }, { "beginSession", CONTAINERELEMENT_BEGIN_SESSION }, { "endSession", CONTAINERELEMENT_END_SESSION } }; DE_ASSERT(m_elementLen >= 1); DE_ASSERT(getChar(0) == '#'); int offset = 1; for (int elemNdx = 0; elemNdx < DE_LENGTH_OF_ARRAY(s_elements); elemNdx++) { bool isMatch = false; int ndx = 0; for (;;) { int bufChar = (offset+ndx < m_elementLen) ? getChar(offset+ndx) : 0; bool bufEnd = bufChar == 0 || bufChar == ' ' || bufChar == '\r' || bufChar == '\n' || bufChar == (int)END_OF_BUFFER; int elemChar = s_elements[elemNdx].name[ndx]; bool elemEnd = elemChar == 0; if (bufEnd || elemEnd) { isMatch = bufEnd == elemEnd; break; } else if (bufChar != elemChar) break; ndx += 1; } if (isMatch) { m_element = s_elements[elemNdx].element; offset += ndx; break; } } switch (m_element) { case CONTAINERELEMENT_BEGIN_SESSION: case CONTAINERELEMENT_END_SESSION: case CONTAINERELEMENT_END_TEST_CASE_RESULT: break; // No attribute or value. case CONTAINERELEMENT_BEGIN_TEST_CASE_RESULT: case CONTAINERELEMENT_TERMINATE_TEST_CASE_RESULT: if (getChar(offset) != ' ') error("Expected value after instruction"); offset += 1; parseContainerValue(m_value, offset); break; case CONTAINERELEMENT_SESSION_INFO: if (getChar(offset) != ' ') error("Expected attribute name after #sessionInfo"); offset += 1; parseContainerValue(m_attribute, offset); if (getChar(offset) != ' ') error("No value for #sessionInfo attribute"); offset += 1; if (m_attribute == "timestamp") { m_value.clear(); // \note Candy produces timestamps in very stupid fashion. for (;;) { const int curChar = offset < m_elementLen ? getChar(offset) : 0; const bool isEnd = curChar == 0 || curChar == (int)END_OF_BUFFER || curChar == '\n' || curChar == '\t'; if (isEnd) break; else m_value.push_back((char)curChar); offset += 1; } } else parseContainerValue(m_value, offset); break; default: // \todo [2012-06-09 pyry] Implement better way to handle # at the beginning of log lines. m_element = CONTAINERELEMENT_TEST_LOG_DATA; break; } } void ContainerFormatParser::parseContainerValue (std::string& dst, int& offset) const { DE_ASSERT(offset < m_elementLen); bool isString = getChar(offset) == '"' || getChar(offset) == '\''; int quotChar = isString ? getChar(offset) : 0; if (isString) offset += 1; dst.clear(); for (;;) { int curChar = offset < m_elementLen ? getChar(offset) : 0; bool isEnd = curChar == 0 || curChar == (int)END_OF_BUFFER || (isString ? (curChar == quotChar) : (curChar == ' ' || curChar == '\n' || curChar == '\r')); if (isEnd) break; else { // \todo [2012-06-09 pyry] Escapes. dst.push_back((char)curChar); } offset += 1; } if (isString && getChar(offset) == quotChar) offset += 1; } } // xe