/*------------------------------------------------------------------------- * 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 writer. *//*--------------------------------------------------------------------*/ #include "xeTestLogWriter.hpp" #include "xeXMLWriter.hpp" #include "deStringUtil.hpp" #include <fstream> namespace xe { static const char* TEST_LOG_VERSION = "0.3.3"; /* Batch result writer. */ struct ContainerValue { ContainerValue (const std::string& value_) : value(value_) {} ContainerValue (const char* value_) : value(value_) {} std::string value; }; std::ostream& operator<< (std::ostream& stream, const ContainerValue& value) { if (value.value.find(' ') != std::string::npos) { // Escape. stream << '"'; for (std::string::const_iterator i = value.value.begin(); i != value.value.end(); i++) { if (*i == '"' || *i == '\\') stream << '\\'; stream << *i; } stream << '"'; } else stream << value.value; return stream; } static void writeSessionInfo (const SessionInfo& info, std::ostream& stream) { if (!info.releaseName.empty()) stream << "#sessionInfo releaseName " << ContainerValue(info.releaseName) << "\n"; if (!info.releaseId.empty()) stream << "#sessionInfo releaseId " << ContainerValue(info.releaseId) << "\n"; if (!info.targetName.empty()) stream << "#sessionInfo targetName " << ContainerValue(info.targetName) << "\n"; if (!info.candyTargetName.empty()) stream << "#sessionInfo candyTargetName " << ContainerValue(info.candyTargetName) << "\n"; if (!info.configName.empty()) stream << "#sessionInfo configName " << ContainerValue(info.configName) << "\n"; if (!info.resultName.empty()) stream << "#sessionInfo resultName " << ContainerValue(info.resultName) << "\n"; // \note Current format uses unescaped timestamps for some strange reason. if (!info.timestamp.empty()) stream << "#sessionInfo timestamp " << info.timestamp << "\n"; } static void writeTestCase (const TestCaseResultData& caseData, std::ostream& stream) { stream << "\n#beginTestCaseResult " << caseData.getTestCasePath() << "\n"; if (caseData.getDataSize() > 0) { stream.write((const char*)caseData.getData(), caseData.getDataSize()); deUint8 lastCh = caseData.getData()[caseData.getDataSize()-1]; if (lastCh != '\n' && lastCh != '\r') stream << "\n"; } TestStatusCode dataCode = caseData.getStatusCode(); if (dataCode == TESTSTATUSCODE_CRASH || dataCode == TESTSTATUSCODE_TIMEOUT || dataCode == TESTSTATUSCODE_TERMINATED) stream << "#terminateTestCaseResult " << getTestStatusCodeName(dataCode) << "\n"; else stream << "#endTestCaseResult\n"; } void writeTestLog (const BatchResult& result, std::ostream& stream) { writeSessionInfo(result.getSessionInfo(), stream); stream << "#beginSession\n"; for (int ndx = 0; ndx < result.getNumTestCaseResults(); ndx++) { ConstTestCaseResultPtr caseData = result.getTestCaseResult(ndx); writeTestCase(*caseData, stream); } stream << "\n#endSession\n"; } void writeBatchResultToFile (const BatchResult& result, const char* filename) { std::ofstream str(filename, std::ofstream::binary|std::ofstream::trunc); writeTestLog(result, str); str.close(); } /* Test result log writer. */ static const char* getImageFormatName (ri::Image::Format format) { switch (format) { case ri::Image::FORMAT_RGB888: return "RGB888"; case ri::Image::FORMAT_RGBA8888: return "RGBA8888"; default: DE_ASSERT(false); return DE_NULL; } } static const char* getImageCompressionName (ri::Image::Compression compression) { switch (compression) { case ri::Image::COMPRESSION_NONE: return "None"; case ri::Image::COMPRESSION_PNG: return "PNG"; default: DE_ASSERT(false); return DE_NULL; } } static const char* getSampleValueTagName (ri::ValueInfo::ValueTag tag) { switch (tag) { case ri::ValueInfo::VALUETAG_PREDICTOR: return "Predictor"; case ri::ValueInfo::VALUETAG_RESPONSE: return "Response"; default: DE_ASSERT(false); return DE_NULL; } } inline const char* getBoolName (bool val) { return val ? "True" : "False"; } // \todo [2012-09-07 pyry] Move to tcutil? class Base64Formatter { public: const deUint8* data; int numBytes; Base64Formatter (const deUint8* data_, int numBytes_) : data(data_), numBytes(numBytes_) {} }; std::ostream& operator<< (std::ostream& str, const Base64Formatter& fmt) { static const char s_base64Table[64] = { 'A','B','C','D','E','F','G','H','I','J','K','L','M', 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z', 'a','b','c','d','e','f','g','h','i','j','k','l','m', 'n','o','p','q','r','s','t','u','v','w','x','y','z', '0','1','2','3','4','5','6','7','8','9','+','/' }; const deUint8* data = fmt.data; int numBytes = fmt.numBytes; int srcNdx = 0; DE_ASSERT(data && (numBytes > 0)); /* Loop all input chars. */ while (srcNdx < numBytes) { int numRead = de::min(3, numBytes - srcNdx); deUint8 s0 = data[srcNdx]; deUint8 s1 = (numRead >= 2) ? data[srcNdx+1] : 0; deUint8 s2 = (numRead >= 3) ? data[srcNdx+2] : 0; char d[4]; srcNdx += numRead; d[0] = s_base64Table[s0 >> 2]; d[1] = s_base64Table[((s0&0x3)<<4) | (s1>>4)]; d[2] = s_base64Table[((s1&0xF)<<2) | (s2>>6)]; d[3] = s_base64Table[s2&0x3F]; if (numRead < 3) d[3] = '='; if (numRead < 2) d[2] = '='; /* Write data. */ str.write(&d[0], sizeof(d)); } return str; } inline Base64Formatter toBase64 (const deUint8* bytes, int numBytes) { return Base64Formatter(bytes, numBytes); } static const char* getStatusName (bool value) { return value ? "OK" : "Fail"; } static void writeResultItem (const ri::Item& item, xml::Writer& dst) { using xml::Writer; switch (item.getType()) { case ri::TYPE_RESULT: // Ignored here, written at end. break; case ri::TYPE_TEXT: dst << Writer::BeginElement("Text") << static_cast<const ri::Text&>(item).text << Writer::EndElement; break; case ri::TYPE_NUMBER: { const ri::Number& number = static_cast<const ri::Number&>(item); dst << Writer::BeginElement("Number") << Writer::Attribute("Name", number.name) << Writer::Attribute("Description", number.description) << Writer::Attribute("Unit", number.unit) << Writer::Attribute("Tag", number.tag) << number.value << Writer::EndElement; break; } case ri::TYPE_IMAGE: { const ri::Image& image = static_cast<const ri::Image&>(item); dst << Writer::BeginElement("Image") << Writer::Attribute("Name", image.name) << Writer::Attribute("Description", image.description) << Writer::Attribute("Width", de::toString(image.width)) << Writer::Attribute("Height", de::toString(image.height)) << Writer::Attribute("Format", getImageFormatName(image.format)) << Writer::Attribute("CompressionMode", getImageCompressionName(image.compression)) << toBase64(&image.data[0], (int)image.data.size()) << Writer::EndElement; break; } case ri::TYPE_IMAGESET: { const ri::ImageSet& imageSet = static_cast<const ri::ImageSet&>(item); dst << Writer::BeginElement("ImageSet") << Writer::Attribute("Name", imageSet.name) << Writer::Attribute("Description", imageSet.description); for (int ndx = 0; ndx < imageSet.images.getNumItems(); ndx++) writeResultItem(imageSet.images.getItem(ndx), dst); dst << Writer::EndElement; break; } case ri::TYPE_SHADER: { const ri::Shader& shader = static_cast<const ri::Shader&>(item); const char* tagName = DE_NULL; switch (shader.shaderType) { case ri::Shader::SHADERTYPE_VERTEX: tagName = "VertexShader"; break; case ri::Shader::SHADERTYPE_FRAGMENT: tagName = "FragmentShader"; break; case ri::Shader::SHADERTYPE_GEOMETRY: tagName = "GeometryShader"; break; case ri::Shader::SHADERTYPE_TESS_CONTROL: tagName = "TessControlShader"; break; case ri::Shader::SHADERTYPE_TESS_EVALUATION: tagName = "TessEvaluationShader"; break; case ri::Shader::SHADERTYPE_COMPUTE: tagName = "ComputeShader"; break; default: throw Error("Unknown shader type"); } dst << Writer::BeginElement(tagName) << Writer::Attribute("CompileStatus", getStatusName(shader.compileStatus)); writeResultItem(shader.source, dst); writeResultItem(shader.infoLog, dst); dst << Writer::EndElement; break; } case ri::TYPE_SHADERPROGRAM: { const ri::ShaderProgram& program = static_cast<const ri::ShaderProgram&>(item); dst << Writer::BeginElement("ShaderProgram") << Writer::Attribute("LinkStatus", getStatusName(program.linkStatus)); writeResultItem(program.linkInfoLog, dst); for (int ndx = 0; ndx < program.shaders.getNumItems(); ndx++) writeResultItem(program.shaders.getItem(ndx), dst); dst << Writer::EndElement; break; } case ri::TYPE_SHADERSOURCE: dst << Writer::BeginElement("ShaderSource") << static_cast<const ri::ShaderSource&>(item).source << Writer::EndElement; break; case ri::TYPE_SPIRVSOURCE: dst << Writer::BeginElement("SpirVAssemblySource") << static_cast<const ri::SpirVSource&>(item).source << Writer::EndElement; break; case ri::TYPE_INFOLOG: dst << Writer::BeginElement("InfoLog") << static_cast<const ri::InfoLog&>(item).log << Writer::EndElement; break; case ri::TYPE_SECTION: { const ri::Section& section = static_cast<const ri::Section&>(item); dst << Writer::BeginElement("Section") << Writer::Attribute("Name", section.name) << Writer::Attribute("Description", section.description); for (int ndx = 0; ndx < section.items.getNumItems(); ndx++) writeResultItem(section.items.getItem(ndx), dst); dst << Writer::EndElement; break; } case ri::TYPE_KERNELSOURCE: dst << Writer::BeginElement("KernelSource") << static_cast<const ri::KernelSource&>(item).source << Writer::EndElement; break; case ri::TYPE_COMPILEINFO: { const ri::CompileInfo& compileInfo = static_cast<const ri::CompileInfo&>(item); dst << Writer::BeginElement("CompileInfo") << Writer::Attribute("Name", compileInfo.name) << Writer::Attribute("Description", compileInfo.description) << Writer::Attribute("CompileStatus", getStatusName(compileInfo.compileStatus)); writeResultItem(compileInfo.infoLog, dst); dst << Writer::EndElement; break; } case ri::TYPE_EGLCONFIG: { const ri::EglConfig& config = static_cast<const ri::EglConfig&>(item); dst << Writer::BeginElement("EglConfig") << Writer::Attribute("BufferSize", de::toString(config.bufferSize)) << Writer::Attribute("RedSize", de::toString(config.redSize)) << Writer::Attribute("GreenSize", de::toString(config.greenSize)) << Writer::Attribute("BlueSize", de::toString(config.blueSize)) << Writer::Attribute("LuminanceSize", de::toString(config.luminanceSize)) << Writer::Attribute("AlphaSize", de::toString(config.alphaSize)) << Writer::Attribute("AlphaMaskSize", de::toString(config.alphaMaskSize)) << Writer::Attribute("BindToTextureRGB", getBoolName(config.bindToTextureRGB)) << Writer::Attribute("BindToTextureRGBA", getBoolName(config.bindToTextureRGBA)) << Writer::Attribute("ColorBufferType", config.colorBufferType) << Writer::Attribute("ConfigCaveat", config.configCaveat) << Writer::Attribute("ConfigID", de::toString(config.configID)) << Writer::Attribute("Conformant", config.conformant) << Writer::Attribute("DepthSize", de::toString(config.depthSize)) << Writer::Attribute("Level", de::toString(config.level)) << Writer::Attribute("MaxPBufferWidth", de::toString(config.maxPBufferWidth)) << Writer::Attribute("MaxPBufferHeight", de::toString(config.maxPBufferHeight)) << Writer::Attribute("MaxPBufferPixels", de::toString(config.maxPBufferPixels)) << Writer::Attribute("MaxSwapInterval", de::toString(config.maxSwapInterval)) << Writer::Attribute("MinSwapInterval", de::toString(config.minSwapInterval)) << Writer::Attribute("NativeRenderable", getBoolName(config.nativeRenderable)) << Writer::Attribute("RenderableType", config.renderableType) << Writer::Attribute("SampleBuffers", de::toString(config.sampleBuffers)) << Writer::Attribute("Samples", de::toString(config.samples)) << Writer::Attribute("StencilSize", de::toString(config.stencilSize)) << Writer::Attribute("SurfaceTypes", config.surfaceTypes) << Writer::Attribute("TransparentType", config.transparentType) << Writer::Attribute("TransparentRedValue", de::toString(config.transparentRedValue)) << Writer::Attribute("TransparentGreenValue", de::toString(config.transparentGreenValue)) << Writer::Attribute("TransparentBlueValue", de::toString(config.transparentBlueValue)) << Writer::EndElement; break; } case ri::TYPE_EGLCONFIGSET: { const ri::EglConfigSet& configSet = static_cast<const ri::EglConfigSet&>(item); dst << Writer::BeginElement("EglConfigSet") << Writer::Attribute("Name", configSet.name) << Writer::Attribute("Description", configSet.description); for (int ndx = 0; ndx < configSet.configs.getNumItems(); ndx++) writeResultItem(configSet.configs.getItem(ndx), dst); dst << Writer::EndElement; break; } case ri::TYPE_SAMPLELIST: { const ri::SampleList& list = static_cast<const ri::SampleList&>(item); dst << Writer::BeginElement("SampleList") << Writer::Attribute("Name", list.name) << Writer::Attribute("Description", list.description); writeResultItem(list.sampleInfo, dst); for (int ndx = 0; ndx < list.samples.getNumItems(); ndx++) writeResultItem(list.samples.getItem(ndx), dst); dst << Writer::EndElement; break; } case ri::TYPE_SAMPLEINFO: { const ri::SampleInfo& info = static_cast<const ri::SampleInfo&>(item); dst << Writer::BeginElement("SampleInfo"); for (int ndx = 0; ndx < info.valueInfos.getNumItems(); ndx++) writeResultItem(info.valueInfos.getItem(ndx), dst); dst << Writer::EndElement; break; } case ri::TYPE_VALUEINFO: { const ri::ValueInfo& info = static_cast<const ri::ValueInfo&>(item); dst << Writer::BeginElement("ValueInfo") << Writer::Attribute("Name", info.name) << Writer::Attribute("Description", info.description) << Writer::Attribute("Tag", getSampleValueTagName(info.tag)); if (!info.unit.empty()) dst << Writer::Attribute("Unit", info.unit); dst << Writer::EndElement; break; } case ri::TYPE_SAMPLE: { const ri::Sample& sample = static_cast<const ri::Sample&>(item); dst << Writer::BeginElement("Sample"); for (int ndx = 0; ndx < sample.values.getNumItems(); ndx++) writeResultItem(sample.values.getItem(ndx), dst); dst << Writer::EndElement; break; } case ri::TYPE_SAMPLEVALUE: { const ri::SampleValue& value = static_cast<const ri::SampleValue&>(item); dst << Writer::BeginElement("Value") << value.value << Writer::EndElement; break; } default: XE_FAIL("Unsupported result item"); } } void writeTestResult (const TestCaseResult& result, xe::xml::Writer& xmlWriter) { using xml::Writer; xmlWriter << Writer::BeginElement("TestCaseResult") << Writer::Attribute("Version", TEST_LOG_VERSION) << Writer::Attribute("CasePath", result.casePath) << Writer::Attribute("CaseType", getTestCaseTypeName(result.caseType)); for (int ndx = 0; ndx < result.resultItems.getNumItems(); ndx++) writeResultItem(result.resultItems.getItem(ndx), xmlWriter); // Result item is not logged until end. xmlWriter << Writer::BeginElement("Result") << Writer::Attribute("StatusCode", getTestStatusCodeName(result.statusCode)) << result.statusDetails << Writer::EndElement; xmlWriter << Writer::EndElement; } void writeTestResult (const TestCaseResult& result, std::ostream& stream) { xml::Writer xmlWriter(stream); stream << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; writeTestResult(result, xmlWriter); } void writeTestResultToFile (const TestCaseResult& result, const char* filename) { std::ofstream str(filename, std::ofstream::binary|std::ofstream::trunc); writeTestResult(result, str); str.close(); } } // xe