536 lines
17 KiB
C++
536 lines
17 KiB
C++
/*-------------------------------------------------------------------------
|
|
* 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
|
|
{
|
|
|
|
/* 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;
|
|
case ri::Shader::SHADERTYPE_RAYGEN: tagName = "RaygenShader"; break;
|
|
case ri::Shader::SHADERTYPE_ANY_HIT: tagName = "AnyHitShader"; break;
|
|
case ri::Shader::SHADERTYPE_CLOSEST_HIT: tagName = "ClosestHitShader"; break;
|
|
case ri::Shader::SHADERTYPE_MISS: tagName = "MissShader"; break;
|
|
case ri::Shader::SHADERTYPE_INTERSECTION: tagName = "IntersectionShader"; break;
|
|
case ri::Shader::SHADERTYPE_CALLABLE: tagName = "CallableShader"; break;
|
|
case ri::Shader::SHADERTYPE_TASK: tagName = "TaskShader"; break;
|
|
case ri::Shader::SHADERTYPE_MESH: tagName = "MeshShader"; 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", result.caseVersion)
|
|
<< 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
|