/*------------------------------------------------------------------------- * 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 case result parser. *//*--------------------------------------------------------------------*/ #include "xeTestResultParser.hpp" #include "xeTestCaseResult.hpp" #include "xeBatchResult.hpp" #include "deString.h" #include "deInt32.h" #include #include using std::string; using std::vector; namespace xe { static inline int toInt(const char *str) { return atoi(str); } static inline double toDouble(const char *str) { return atof(str); } static inline int64_t toInt64(const char *str) { std::istringstream s(str); int64_t val; s >> val; return val; } static inline bool toBool(const char *str) { return deStringEqual(str, "OK") || deStringEqual(str, "True"); } static const char *stripLeadingWhitespace(const char *str) { int whitespaceCount = 0; while (str[whitespaceCount] != 0 && (str[whitespaceCount] == ' ' || str[whitespaceCount] == '\t' || str[whitespaceCount] == '\r' || str[whitespaceCount] == '\n')) whitespaceCount += 1; return str + whitespaceCount; } struct EnumMapEntry { uint32_t hash; const char *name; int value; }; static const EnumMapEntry s_statusCodeMap[] = { {0x7c8a99bc, "Pass", TESTSTATUSCODE_PASS}, {0x7c851ca1, "Fail", TESTSTATUSCODE_FAIL}, {0x10ecd324, "QualityWarning", TESTSTATUSCODE_QUALITY_WARNING}, {0x341ae835, "CompatibilityWarning", TESTSTATUSCODE_COMPATIBILITY_WARNING}, {0x058acbca, "Pending", TESTSTATUSCODE_PENDING}, {0xc4d74b26, "Running", TESTSTATUSCODE_RUNNING}, {0x6409f93c, "NotSupported", TESTSTATUSCODE_NOT_SUPPORTED}, {0xfa5a9ab7, "ResourceError", TESTSTATUSCODE_RESOURCE_ERROR}, {0xad6793ec, "InternalError", TESTSTATUSCODE_INTERNAL_ERROR}, {0x838f3034, "Canceled", TESTSTATUSCODE_CANCELED}, {0x42b6efac, "Timeout", TESTSTATUSCODE_TIMEOUT}, {0x0cfb98f6, "Crash", TESTSTATUSCODE_CRASH}, {0xe326e01d, "Disabled", TESTSTATUSCODE_DISABLED}, {0x77061af2, "Terminated", TESTSTATUSCODE_TERMINATED}, {0xd9e6b393, "Waiver", TESTSTATUSCODE_WAIVER}}; static const EnumMapEntry s_resultItemMap[] = {{0xce8ac2e4, "Result", ri::TYPE_RESULT}, {0x7c8cdcea, "Text", ri::TYPE_TEXT}, {0xc6540c6e, "Number", ri::TYPE_NUMBER}, {0x0d656c88, "Image", ri::TYPE_IMAGE}, {0x8ac9ee14, "ImageSet", ri::TYPE_IMAGESET}, {0x1181fa5a, "VertexShader", ri::TYPE_SHADER}, {0xa93daef0, "FragmentShader", ri::TYPE_SHADER}, {0x8f066128, "GeometryShader", ri::TYPE_SHADER}, {0x235a931c, "TessControlShader", ri::TYPE_SHADER}, {0xa1bf7153, "TessEvaluationShader", ri::TYPE_SHADER}, {0x6c1415d9, "ComputeShader", ri::TYPE_SHADER}, {0x68738b22, "RaygenShader", ri::TYPE_SHADER}, {0x51d29ce9, "AnyHitShader", ri::TYPE_SHADER}, {0x8c64a6be, "ClosestHitShader", ri::TYPE_SHADER}, {0xb30ed398, "MissShader", ri::TYPE_SHADER}, {0x26150e53, "IntersectionShader", ri::TYPE_SHADER}, {0x7e50944c, "CallableShader", ri::TYPE_SHADER}, {0x925c7349, "MeshShader", ri::TYPE_SHADER}, {0xc3a35d6f, "TaskShader", ri::TYPE_SHADER}, {0x72863a54, "ShaderProgram", ri::TYPE_SHADERPROGRAM}, {0xb4efc08d, "ShaderSource", ri::TYPE_SHADERSOURCE}, {0xaee4380a, "SpirVAssemblySource", ri::TYPE_SPIRVSOURCE}, {0xff265913, "InfoLog", ri::TYPE_INFOLOG}, {0x84159b73, "EglConfig", ri::TYPE_EGLCONFIG}, {0xdd34391f, "EglConfigSet", ri::TYPE_EGLCONFIGSET}, {0xebbb3aba, "Section", ri::TYPE_SECTION}, {0xa0f15677, "KernelSource", ri::TYPE_KERNELSOURCE}, {0x1ee9083a, "CompileInfo", ri::TYPE_COMPILEINFO}, {0xf1004023, "SampleList", ri::TYPE_SAMPLELIST}, {0xf0feae93, "SampleInfo", ri::TYPE_SAMPLEINFO}, {0x2aa6f14e, "ValueInfo", ri::TYPE_VALUEINFO}, {0xd09429e7, "Sample", ri::TYPE_SAMPLE}, {0x0e4a4722, "Value", ri::TYPE_SAMPLEVALUE}}; static const EnumMapEntry s_imageFormatMap[] = {{0xcc4ffac8, "RGB888", ri::Image::FORMAT_RGB888}, {0x20dcb0c1, "RGBA8888", ri::Image::FORMAT_RGBA8888}}; static const EnumMapEntry s_compressionMap[] = {{0x7c89bbd5, "None", ri::Image::COMPRESSION_NONE}, {0x0b88118a, "PNG", ri::Image::COMPRESSION_PNG}}; static const EnumMapEntry s_shaderTypeFromTagMap[] = { {0x1181fa5a, "VertexShader", ri::Shader::SHADERTYPE_VERTEX}, {0xa93daef0, "FragmentShader", ri::Shader::SHADERTYPE_FRAGMENT}, {0x8f066128, "GeometryShader", ri::Shader::SHADERTYPE_GEOMETRY}, {0x235a931c, "TessControlShader", ri::Shader::SHADERTYPE_TESS_CONTROL}, {0xa1bf7153, "TessEvaluationShader", ri::Shader::SHADERTYPE_TESS_EVALUATION}, {0x6c1415d9, "ComputeShader", ri::Shader::SHADERTYPE_COMPUTE}, {0x68738b22, "RaygenShader", ri::Shader::SHADERTYPE_RAYGEN}, {0x51d29ce9, "AnyHitShader", ri::Shader::SHADERTYPE_ANY_HIT}, {0x8c64a6be, "ClosestHitShader", ri::Shader::SHADERTYPE_CLOSEST_HIT}, {0xb30ed398, "MissShader", ri::Shader::SHADERTYPE_MISS}, {0x26150e53, "IntersectionShader", ri::Shader::SHADERTYPE_INTERSECTION}, {0x7e50944c, "CallableShader", ri::Shader::SHADERTYPE_CALLABLE}, {0xc3a35d6f, "TaskShader", ri::Shader::SHADERTYPE_TASK}, {0x925c7349, "MeshShader", ri::Shader::SHADERTYPE_MESH}, }; static const EnumMapEntry s_testTypeMap[] = {{0x7fa80959, "SelfValidate", TESTCASETYPE_SELF_VALIDATE}, {0xdb797567, "Capability", TESTCASETYPE_CAPABILITY}, {0x2ca3ec10, "Accuracy", TESTCASETYPE_ACCURACY}, {0xa48ac277, "Performance", TESTCASETYPE_PERFORMANCE}}; static const EnumMapEntry s_logVersionMap[] = { {0x0b7dac93, "0.2.0", TESTLOGVERSION_0_2_0}, {0x0b7db0d4, "0.3.0", TESTLOGVERSION_0_3_0}, {0x0b7db0d5, "0.3.1", TESTLOGVERSION_0_3_1}, {0x0b7db0d6, "0.3.2", TESTLOGVERSION_0_3_2}, {0x0b7db0d7, "0.3.3", TESTLOGVERSION_0_3_3}, {0x0b7db0d8, "0.3.4", TESTLOGVERSION_0_3_4}}; static const EnumMapEntry s_sampleValueTagMap[] = { {0xddf2d0d1, "Predictor", ri::ValueInfo::VALUETAG_PREDICTOR}, {0x9bee2c34, "Response", ri::ValueInfo::VALUETAG_RESPONSE}, }; #if defined(DE_DEBUG) static void printHashes(const char *name, const EnumMapEntry *entries, int numEntries) { printf("%s:\n", name); for (int ndx = 0; ndx < numEntries; ndx++) printf("0x%08x\t%s\n", deStringHash(entries[ndx].name), entries[ndx].name); printf("\n"); } #define PRINT_HASHES(MAP) printHashes(#MAP, MAP, DE_LENGTH_OF_ARRAY(MAP)) void TestResultParser_printHashes(void) { PRINT_HASHES(s_statusCodeMap); PRINT_HASHES(s_resultItemMap); PRINT_HASHES(s_imageFormatMap); PRINT_HASHES(s_compressionMap); PRINT_HASHES(s_shaderTypeFromTagMap); PRINT_HASHES(s_testTypeMap); PRINT_HASHES(s_logVersionMap); PRINT_HASHES(s_sampleValueTagMap); } #endif static inline int getEnumValue(const char *enumName, const EnumMapEntry *entries, int numEntries, const char *name) { uint32_t hash = deStringHash(name); for (int ndx = 0; ndx < numEntries; ndx++) { if (entries[ndx].hash == hash && deStringEqual(entries[ndx].name, name)) return entries[ndx].value; } throw TestResultParseError(string("Could not map '") + name + "' to " + enumName); } TestStatusCode getTestStatusCode(const char *statusCode) { return (TestStatusCode)getEnumValue("status code", s_statusCodeMap, DE_LENGTH_OF_ARRAY(s_statusCodeMap), statusCode); } static ri::Type getResultItemType(const char *elemName) { return (ri::Type)getEnumValue("result item type", s_resultItemMap, DE_LENGTH_OF_ARRAY(s_resultItemMap), elemName); } static ri::Image::Format getImageFormat(const char *imageFormat) { return (ri::Image::Format)getEnumValue("image format", s_imageFormatMap, DE_LENGTH_OF_ARRAY(s_imageFormatMap), imageFormat); } static ri::Image::Compression getImageCompression(const char *compression) { return (ri::Image::Compression)getEnumValue("image compression", s_compressionMap, DE_LENGTH_OF_ARRAY(s_compressionMap), compression); } static ri::Shader::ShaderType getShaderTypeFromTagName(const char *shaderType) { return (ri::Shader::ShaderType)getEnumValue("shader type", s_shaderTypeFromTagMap, DE_LENGTH_OF_ARRAY(s_shaderTypeFromTagMap), shaderType); } static TestCaseType getTestCaseType(const char *caseType) { return (TestCaseType)getEnumValue("test case type", s_testTypeMap, DE_LENGTH_OF_ARRAY(s_testTypeMap), caseType); } static TestLogVersion getTestLogVersion(const char *logVersion) { return (TestLogVersion)getEnumValue("test log version", s_logVersionMap, DE_LENGTH_OF_ARRAY(s_logVersionMap), logVersion); } static ri::ValueInfo::ValueTag getSampleValueTag(const char *tag) { return (ri::ValueInfo::ValueTag)getEnumValue("sample value tag", s_sampleValueTagMap, DE_LENGTH_OF_ARRAY(s_sampleValueTagMap), tag); } static TestCaseType getTestCaseTypeFromPath(const char *casePath) { if (deStringBeginsWith(casePath, "dEQP-GLES2.")) { const char *group = casePath + 11; if (deStringBeginsWith(group, "capability.")) return TESTCASETYPE_CAPABILITY; else if (deStringBeginsWith(group, "accuracy.")) return TESTCASETYPE_ACCURACY; else if (deStringBeginsWith(group, "performance.")) return TESTCASETYPE_PERFORMANCE; } return TESTCASETYPE_SELF_VALIDATE; } static ri::NumericValue getNumericValue(const std::string &value) { const bool isFloat = value.find('.') != std::string::npos || value.find('e') != std::string::npos; if (isFloat) { const double num = toDouble(stripLeadingWhitespace(value.c_str())); return ri::NumericValue(num); } else { const int64_t num = toInt64(stripLeadingWhitespace(value.c_str())); return ri::NumericValue(num); } } TestResultParser::TestResultParser(void) : m_result(DE_NULL) , m_state(STATE_NOT_INITIALIZED) , m_logVersion(TESTLOGVERSION_LAST) , m_curItemList(DE_NULL) , m_base64DecodeOffset(0) { } TestResultParser::~TestResultParser(void) { } void TestResultParser::clear(void) { m_xmlParser.clear(); m_itemStack.clear(); m_result = DE_NULL; m_state = STATE_NOT_INITIALIZED; m_logVersion = TESTLOGVERSION_LAST; m_curItemList = DE_NULL; m_base64DecodeOffset = 0; m_curNumValue.clear(); } void TestResultParser::init(TestCaseResult *dstResult) { clear(); m_result = dstResult; m_state = STATE_INITIALIZED; m_curItemList = &dstResult->resultItems; } TestResultParser::ParseResult TestResultParser::parse(const uint8_t *bytes, int numBytes) { DE_ASSERT(m_result && m_state != STATE_NOT_INITIALIZED); try { bool resultChanged = false; m_xmlParser.feed(bytes, numBytes); for (;;) { xml::Element curElement = m_xmlParser.getElement(); if (curElement == xml::ELEMENT_INCOMPLETE || curElement == xml::ELEMENT_END_OF_STRING) break; switch (curElement) { case xml::ELEMENT_START: handleElementStart(); break; case xml::ELEMENT_END: handleElementEnd(); break; case xml::ELEMENT_DATA: handleData(); break; default: DE_ASSERT(false); } resultChanged = true; m_xmlParser.advance(); } if (m_xmlParser.getElement() == xml::ELEMENT_END_OF_STRING) { if (m_state != STATE_TEST_CASE_RESULT_ENDED) throw TestResultParseError("Unexpected end of log data"); return PARSERESULT_COMPLETE; } else return resultChanged ? PARSERESULT_CHANGED : PARSERESULT_NOT_CHANGED; } catch (const TestResultParseError &e) { // Set error code to result. m_result->statusCode = TESTSTATUSCODE_INTERNAL_ERROR; m_result->statusDetails = e.what(); return PARSERESULT_ERROR; } catch (const xml::ParseError &e) { // Set error code to result. m_result->statusCode = TESTSTATUSCODE_INTERNAL_ERROR; m_result->statusDetails = e.what(); return PARSERESULT_ERROR; } } const char *TestResultParser::getAttribute(const char *name) { if (!m_xmlParser.hasAttribute(name)) throw TestResultParseError(string("Missing attribute '") + name + "' in <" + m_xmlParser.getElementName() + ">"); return m_xmlParser.getAttribute(name); } ri::Item *TestResultParser::getCurrentItem(void) { return !m_itemStack.empty() ? m_itemStack.back() : DE_NULL; } ri::List *TestResultParser::getCurrentItemList(void) { DE_ASSERT(m_curItemList); return m_curItemList; } void TestResultParser::updateCurrentItemList(void) { m_curItemList = DE_NULL; for (vector::reverse_iterator i = m_itemStack.rbegin(); i != m_itemStack.rend(); i++) { ri::Item *item = *i; ri::Type type = item->getType(); if (type == ri::TYPE_IMAGESET) m_curItemList = &static_cast(item)->images; else if (type == ri::TYPE_SECTION) m_curItemList = &static_cast(item)->items; else if (type == ri::TYPE_EGLCONFIGSET) m_curItemList = &static_cast(item)->configs; else if (type == ri::TYPE_SHADERPROGRAM) m_curItemList = &static_cast(item)->shaders; if (m_curItemList) break; } if (!m_curItemList) m_curItemList = &m_result->resultItems; } void TestResultParser::pushItem(ri::Item *item) { m_itemStack.push_back(item); updateCurrentItemList(); } void TestResultParser::popItem(void) { m_itemStack.pop_back(); updateCurrentItemList(); } void TestResultParser::handleElementStart(void) { const char *elemName = m_xmlParser.getElementName(); if (m_state == STATE_INITIALIZED) { // Expect TestCaseResult. if (!deStringEqual(elemName, "TestCaseResult")) throw TestResultParseError(string("Expected , got <") + elemName + ">"); const char *version = getAttribute("Version"); m_logVersion = getTestLogVersion(version); // \note Currently assumed that all known log versions are supported. m_result->caseVersion = version; m_result->casePath = getAttribute("CasePath"); m_result->caseType = TESTCASETYPE_SELF_VALIDATE; if (m_xmlParser.hasAttribute("CaseType")) m_result->caseType = getTestCaseType(m_xmlParser.getAttribute("CaseType")); else { // Do guess based on path for legacy log files. if (m_logVersion >= TESTLOGVERSION_0_3_2) throw TestResultParseError("Missing CaseType attribute in "); m_result->caseType = getTestCaseTypeFromPath(m_result->casePath.c_str()); } m_state = STATE_IN_TEST_CASE_RESULT; } else { ri::List *curList = getCurrentItemList(); ri::Type itemType = getResultItemType(elemName); ri::Item *item = DE_NULL; ri::Item *parentItem = getCurrentItem(); ri::Type parentType = parentItem ? parentItem->getType() : ri::TYPE_LAST; switch (itemType) { case ri::TYPE_RESULT: { ri::Result *result = curList->allocItem(); result->statusCode = getTestStatusCode(getAttribute("StatusCode")); item = result; break; } case ri::TYPE_TEXT: item = curList->allocItem(); break; case ri::TYPE_SECTION: { ri::Section *section = curList->allocItem(); section->name = getAttribute("Name"); section->description = getAttribute("Description"); item = section; break; } case ri::TYPE_NUMBER: { ri::Number *number = curList->allocItem(); number->name = getAttribute("Name"); number->description = getAttribute("Description"); number->unit = getAttribute("Unit"); if (m_xmlParser.hasAttribute("Tag")) number->tag = m_xmlParser.getAttribute("Tag"); item = number; m_curNumValue.clear(); break; } case ri::TYPE_IMAGESET: { ri::ImageSet *imageSet = curList->allocItem(); imageSet->name = getAttribute("Name"); imageSet->description = getAttribute("Description"); item = imageSet; break; } case ri::TYPE_IMAGE: { ri::Image *image = curList->allocItem(); image->name = getAttribute("Name"); image->description = getAttribute("Description"); image->width = toInt(getAttribute("Width")); image->height = toInt(getAttribute("Height")); image->format = getImageFormat(getAttribute("Format")); image->compression = getImageCompression(getAttribute("CompressionMode")); item = image; break; } case ri::TYPE_SHADERPROGRAM: { ri::ShaderProgram *shaderProgram = curList->allocItem(); shaderProgram->linkStatus = toBool(getAttribute("LinkStatus")); item = shaderProgram; break; } case ri::TYPE_SHADER: { if (parentType != ri::TYPE_SHADERPROGRAM) throw TestResultParseError(string("<") + elemName + "> outside of "); ri::Shader *shader = curList->allocItem(); shader->shaderType = getShaderTypeFromTagName(elemName); shader->compileStatus = toBool(getAttribute("CompileStatus")); item = shader; break; } case ri::TYPE_SPIRVSOURCE: { if (parentType != ri::TYPE_SHADERPROGRAM) throw TestResultParseError(string("<") + elemName + "> outside of "); item = curList->allocItem(); break; } case ri::TYPE_SHADERSOURCE: if (parentType == ri::TYPE_SHADER) item = &static_cast(parentItem)->source; else throw TestResultParseError("Unexpected "); break; case ri::TYPE_INFOLOG: if (parentType == ri::TYPE_SHADERPROGRAM) item = &static_cast(parentItem)->linkInfoLog; else if (parentType == ri::TYPE_SHADER) item = &static_cast(parentItem)->infoLog; else if (parentType == ri::TYPE_COMPILEINFO) item = &static_cast(parentItem)->infoLog; else throw TestResultParseError("Unexpected "); break; case ri::TYPE_KERNELSOURCE: item = curList->allocItem(); break; case ri::TYPE_COMPILEINFO: { ri::CompileInfo *info = curList->allocItem(); info->name = getAttribute("Name"); info->description = getAttribute("Description"); info->compileStatus = toBool(getAttribute("CompileStatus")); item = info; break; } case ri::TYPE_EGLCONFIGSET: { ri::EglConfigSet *set = curList->allocItem(); set->name = getAttribute("Name"); set->description = m_xmlParser.hasAttribute("Description") ? m_xmlParser.getAttribute("Description") : ""; item = set; break; } case ri::TYPE_EGLCONFIG: { ri::EglConfig *config = curList->allocItem(); config->bufferSize = toInt(getAttribute("BufferSize")); config->redSize = toInt(getAttribute("RedSize")); config->greenSize = toInt(getAttribute("GreenSize")); config->blueSize = toInt(getAttribute("BlueSize")); config->luminanceSize = toInt(getAttribute("LuminanceSize")); config->alphaSize = toInt(getAttribute("AlphaSize")); config->alphaMaskSize = toInt(getAttribute("AlphaMaskSize")); config->bindToTextureRGB = toBool(getAttribute("BindToTextureRGB")); config->bindToTextureRGBA = toBool(getAttribute("BindToTextureRGBA")); config->colorBufferType = getAttribute("ColorBufferType"); config->configCaveat = getAttribute("ConfigCaveat"); config->configID = toInt(getAttribute("ConfigID")); config->conformant = getAttribute("Conformant"); config->depthSize = toInt(getAttribute("DepthSize")); config->level = toInt(getAttribute("Level")); config->maxPBufferWidth = toInt(getAttribute("MaxPBufferWidth")); config->maxPBufferHeight = toInt(getAttribute("MaxPBufferHeight")); config->maxPBufferPixels = toInt(getAttribute("MaxPBufferPixels")); config->maxSwapInterval = toInt(getAttribute("MaxSwapInterval")); config->minSwapInterval = toInt(getAttribute("MinSwapInterval")); config->nativeRenderable = toBool(getAttribute("NativeRenderable")); config->renderableType = getAttribute("RenderableType"); config->sampleBuffers = toInt(getAttribute("SampleBuffers")); config->samples = toInt(getAttribute("Samples")); config->stencilSize = toInt(getAttribute("StencilSize")); config->surfaceTypes = getAttribute("SurfaceTypes"); config->transparentType = getAttribute("TransparentType"); config->transparentRedValue = toInt(getAttribute("TransparentRedValue")); config->transparentGreenValue = toInt(getAttribute("TransparentGreenValue")); config->transparentBlueValue = toInt(getAttribute("TransparentBlueValue")); item = config; break; } case ri::TYPE_SAMPLELIST: { ri::SampleList *list = curList->allocItem(); list->name = getAttribute("Name"); list->description = getAttribute("Description"); item = list; break; } case ri::TYPE_SAMPLEINFO: { if (parentType != ri::TYPE_SAMPLELIST) throw TestResultParseError(" outside of "); ri::SampleList *list = static_cast(parentItem); ri::SampleInfo *info = &list->sampleInfo; item = info; break; } case ri::TYPE_VALUEINFO: { if (parentType != ri::TYPE_SAMPLEINFO) throw TestResultParseError(" outside of "); ri::SampleInfo *sampleInfo = static_cast(parentItem); ri::ValueInfo *valueInfo = sampleInfo->valueInfos.allocItem(); valueInfo->name = getAttribute("Name"); valueInfo->description = getAttribute("Description"); valueInfo->tag = getSampleValueTag(getAttribute("Tag")); if (m_xmlParser.hasAttribute("Unit")) valueInfo->unit = getAttribute("Unit"); item = valueInfo; break; } case ri::TYPE_SAMPLE: { if (parentType != ri::TYPE_SAMPLELIST) throw TestResultParseError(" outside of "); ri::SampleList *list = static_cast(parentItem); ri::Sample *sample = list->samples.allocItem(); item = sample; break; } case ri::TYPE_SAMPLEVALUE: { if (parentType != ri::TYPE_SAMPLE) throw TestResultParseError(" outside of "); ri::Sample *sample = static_cast(parentItem); ri::SampleValue *value = sample->values.allocItem(); item = value; break; } default: throw TestResultParseError(string("Unsupported element '") + elemName + ("'")); } DE_ASSERT(item); pushItem(item); // Reset base64 decoding offset. m_base64DecodeOffset = 0; } } void TestResultParser::handleElementEnd(void) { const char *elemName = m_xmlParser.getElementName(); if (m_state != STATE_IN_TEST_CASE_RESULT) throw TestResultParseError(string("Unexpected outside of "); if (deStringEqual(elemName, "TestCaseResult")) { // Logs from buggy test cases may contain invalid XML. // DE_ASSERT(getCurrentItem() == DE_NULL); // \todo [2012-11-22 pyry] Log warning. m_state = STATE_TEST_CASE_RESULT_ENDED; } else { ri::Type itemType = getResultItemType(elemName); ri::Item *curItem = getCurrentItem(); if (!curItem || itemType != curItem->getType()) throw TestResultParseError(string("Unexpected "); if (itemType == ri::TYPE_RESULT) { ri::Result *result = static_cast(curItem); m_result->statusCode = result->statusCode; m_result->statusDetails = result->details; } else if (itemType == ri::TYPE_NUMBER) { // Parse value for number. ri::Number *number = static_cast(curItem); number->value = getNumericValue(m_curNumValue); m_curNumValue.clear(); } else if (itemType == ri::TYPE_SAMPLEVALUE) { ri::SampleValue *value = static_cast(curItem); value->value = getNumericValue(m_curNumValue); m_curNumValue.clear(); } popItem(); } } void TestResultParser::handleData(void) { ri::Item *curItem = getCurrentItem(); ri::Type type = curItem ? curItem->getType() : ri::TYPE_LAST; switch (type) { case ri::TYPE_RESULT: m_xmlParser.appendDataStr(static_cast(curItem)->details); break; case ri::TYPE_TEXT: m_xmlParser.appendDataStr(static_cast(curItem)->text); break; case ri::TYPE_SHADERSOURCE: m_xmlParser.appendDataStr(static_cast(curItem)->source); break; case ri::TYPE_SPIRVSOURCE: m_xmlParser.appendDataStr(static_cast(curItem)->source); break; case ri::TYPE_INFOLOG: m_xmlParser.appendDataStr(static_cast(curItem)->log); break; case ri::TYPE_KERNELSOURCE: m_xmlParser.appendDataStr(static_cast(curItem)->source); break; case ri::TYPE_NUMBER: case ri::TYPE_SAMPLEVALUE: m_xmlParser.appendDataStr(m_curNumValue); break; case ri::TYPE_IMAGE: { ri::Image *image = static_cast(curItem); // Base64 decode. int numBytesIn = m_xmlParser.getDataSize(); for (int inNdx = 0; inNdx < numBytesIn; inNdx++) { uint8_t byte = m_xmlParser.getDataByte(inNdx); uint8_t decodedBits = 0; if (de::inRange(byte, 'A', 'Z')) decodedBits = (uint8_t)(byte - 'A'); else if (de::inRange(byte, 'a', 'z')) decodedBits = (uint8_t)(('Z' - 'A' + 1) + (byte - 'a')); else if (de::inRange(byte, '0', '9')) decodedBits = (uint8_t)(('Z' - 'A' + 1) + ('z' - 'a' + 1) + (byte - '0')); else if (byte == '+') decodedBits = ('Z' - 'A' + 1) + ('z' - 'a' + 1) + ('9' - '0' + 1); else if (byte == '/') decodedBits = ('Z' - 'A' + 1) + ('z' - 'a' + 1) + ('9' - '0' + 2); else if (byte == '=') { // Padding at end - remove last byte. if (image->data.empty()) throw TestResultParseError("Malformed base64 data"); image->data.pop_back(); continue; } else continue; // Not an B64 input character. int phase = m_base64DecodeOffset % 4; if (phase == 0) image->data.resize(image->data.size() + 3, 0); if ((int)image->data.size() < (m_base64DecodeOffset >> 2) * 3 + 3) throw TestResultParseError("Malformed base64 data"); uint8_t *outPtr = &image->data[(m_base64DecodeOffset >> 2) * 3]; switch (phase) { case 0: outPtr[0] |= (uint8_t)(decodedBits << 2); break; case 1: outPtr[0] = (uint8_t)(outPtr[0] | (uint8_t)(decodedBits >> 4)); outPtr[1] = (uint8_t)(outPtr[1] | (uint8_t)((decodedBits & 0xF) << 4)); break; case 2: outPtr[1] = (uint8_t)(outPtr[1] | (uint8_t)(decodedBits >> 2)); outPtr[2] = (uint8_t)(outPtr[2] | (uint8_t)((decodedBits & 0x3) << 6)); break; case 3: outPtr[2] |= decodedBits; break; default: DE_ASSERT(false); } m_base64DecodeOffset += 1; } break; } default: // Just ignore data. break; } } //! Helper for parsing TestCaseResult from TestCaseResultData. void parseTestCaseResultFromData(TestResultParser *parser, TestCaseResult *result, const TestCaseResultData &data) { DE_ASSERT(result->resultItems.getNumItems() == 0); // Initialize status codes etc. from data. result->casePath = data.getTestCasePath(); result->caseType = TESTCASETYPE_SELF_VALIDATE; result->statusCode = data.getStatusCode(); result->statusDetails = data.getStatusDetails(); if (data.getDataSize() > 0) { parser->init(result); const TestResultParser::ParseResult parseResult = parser->parse(data.getData(), data.getDataSize()); if (result->statusCode == TESTSTATUSCODE_LAST) { result->statusCode = TESTSTATUSCODE_INTERNAL_ERROR; if (parseResult == TestResultParser::PARSERESULT_ERROR) result->statusDetails = "Test case result parsing failed"; else if (parseResult != TestResultParser::PARSERESULT_COMPLETE) result->statusDetails = "Incomplete test case result"; else result->statusDetails = "Test case result is missing item"; } } else if (result->statusCode == TESTSTATUSCODE_LAST) { result->statusCode = TESTSTATUSCODE_TERMINATED; result->statusDetails = "Empty test case result"; } if (result->casePath.empty()) throw Error("Empty test case path in result"); if (result->caseType == TESTCASETYPE_LAST) throw Error("Invalid test case type in result"); DE_ASSERT(result->statusCode != TESTSTATUSCODE_LAST); } } // namespace xe