blob: 48a6b85b3ff959f1d87ed87782448ec154e8a13f [file] [log] [blame]
/*-------------------------------------------------------------------------
* 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 uint8_t *bytes, size_t numBytes)
{
// Grow buffer if necessary.
if (m_buf.getNumFree() < (int)numBytes)
m_buf.resize(getNextBufferSize(m_buf.getSize(), m_buf.getNumElements() + (int)numBytes));
// Append to front.
m_buf.pushFront(bytes, (int)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(uint8_t *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 unescaped timestamps.
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;
}
} // namespace xe