blob: 09a1f199b0a32cfc73b085416fa62f9435fda901 [file] [log] [blame]
/*-------------------------------------------------------------------------
* Vulkan CTS Framework
* --------------------
*
* Copyright (c) 2020 The Khronos Group Inc.
*
* 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 Waiver mechanism implementation.
*//*--------------------------------------------------------------------*/
#include "tcuWaiverUtil.hpp"
#include <fstream>
#include <iostream>
#include <sstream>
#include <iomanip>
#include "deString.h"
#include "deStringUtil.hpp"
#include "xeXMLParser.hpp"
#include "tcuCommandLine.hpp"
namespace tcu
{
SessionInfo::SessionInfo(deUint32 vendorId,
deUint32 deviceId,
const std::string& cmdLine)
: m_cmdLine (cmdLine)
{
m_info << std::hex
<< "#sessionInfo vendorID 0x" << vendorId << "\n"
<< "#sessionInfo deviceID 0x" << deviceId << "\n";
}
SessionInfo::SessionInfo(std::string vendor,
std::string renderer,
const std::string& cmdLine)
: m_cmdLine (cmdLine)
{
m_info << "#sessionInfo vendor \"" << vendor << "\"\n"
<< "#sessionInfo renderer \"" << renderer << "\"\n";
}
std::string SessionInfo::get()
{
if (!m_waiverUrls.empty())
{
m_info << "#sessionInfo waiverUrls \"" << m_waiverUrls << "\"\n";
m_waiverUrls.clear();
}
if (!m_cmdLine.empty())
{
m_info << "#sessionInfo commandLineParameters \"" << m_cmdLine << "\"\n";
m_cmdLine.clear();
}
return m_info.str();
}
// Base class for GL and VK waiver tree builders
class WaiverTreeBuilder
{
public:
typedef WaiverUtil::WaiverComponent WaiverComponent;
public:
WaiverTreeBuilder (const std::string& waiverFile,
const std::string& packageName,
const char* vendorTag,
const char* deviceTag,
SessionInfo& sessionInfo,
std::vector<WaiverComponent>& waiverTree);
virtual ~WaiverTreeBuilder();
void build (void);
protected:
// structure representing component during tree construction
struct BuilComponent
{
std::string name;
deUint32 parentIndex; // index in allComponents vector
std::vector<deUint32> childrenIndex; // index in allComponents vector
BuilComponent(std::string n, deUint32 p)
: name(std::move(n))
, parentIndex(p)
{}
};
// parse waiver.xml and read list of waived tests defined
// specificly for current device id and current vendor id
void readWaivedTestsFromXML (void);
// use list of paths to build a temporary tree which
// consists of BuilComponents that help with tree construction
void buildTreeFromPathList (void);
// use temporary tree to create final tree containing
// only things that are needed during searches
void constructFinalTree (void);
// helper methods used to identify if proper waiver for vendor was found
virtual bool matchVendor (const std::string& vendor) const = 0;
// helper methods used after waiver for current vendor was found to check
// if it is defined also for currend deviceId/renderer
virtual bool matchDevice (const std::string& device) const = 0;
// helper method used in buildTreeFromPathList; returns index
// of component having same ancestors as the component specified
// in the argument or 0 when build tree does not include this component
deUint32 findComponentInBuildTree(const std::vector<std::string>& pathComponents, deUint32 index) const;
private:
const std::string& m_waiverFile;
const std::string& m_packageName;
const char* m_vendorTag;
const char* m_deviceTag;
// helper attributes used during construction
std::vector<std::string> m_testList;
std::vector<BuilComponent> m_buildTree;
// reference to object containing information about used waivers
SessionInfo& m_sessionInfo;
// reference to vector containing final tree
std::vector<WaiverComponent>& m_finalTree;
};
WaiverTreeBuilder::WaiverTreeBuilder(const std::string& waiverFile,
const std::string& packageName,
const char* vendorTag,
const char* deviceTag,
SessionInfo& sessionInfo,
std::vector<WaiverComponent>& waiverTree)
: m_waiverFile (waiverFile)
, m_packageName (packageName)
, m_vendorTag (vendorTag)
, m_deviceTag (deviceTag)
, m_sessionInfo (sessionInfo)
, m_finalTree (waiverTree)
{
}
WaiverTreeBuilder::~WaiverTreeBuilder()
{
}
void WaiverTreeBuilder::build(void)
{
readWaivedTestsFromXML();
buildTreeFromPathList();
constructFinalTree();
}
void WaiverTreeBuilder::readWaivedTestsFromXML()
{
std::ifstream iStream(m_waiverFile);
if (!iStream.is_open())
return;
// get whole waiver file content
std::stringstream buffer;
buffer << iStream.rdbuf();
std::string wholeContent = buffer.str();
// feed parser with xml content
xe::xml::Parser xmlParser;
xmlParser.feed(reinterpret_cast<const deUint8*>(wholeContent.c_str()), static_cast<int>(wholeContent.size()));
xmlParser.advance();
// first we find matching vendor, then search for matching device/renderer and then memorize cases
bool vendorFound = false;
bool deviceFound = false;
bool scanDevice = false;
bool memorizeCase = false;
std::string waiverUrl;
std::vector<std::string> waiverTestList;
while (true)
{
// we are grabing elements one by one - depth-first traversal in pre-order
xe::xml::Element currElement = xmlParser.getElement();
// stop if there is parsing error or we didnt found
// waiver for current vendor id and device id/renderer
if (currElement == xe::xml::ELEMENT_INCOMPLETE ||
currElement == xe::xml::ELEMENT_END_OF_STRING)
break;
const char* elemName = xmlParser.getElementName();
switch (currElement)
{
case xe::xml::ELEMENT_START:
if (vendorFound)
{
if (!deviceFound)
{
// if we found proper vendor and are reading deviceIds/rendererers list then allow it
scanDevice = deStringEqual(elemName, m_deviceTag); // e.g. "d"
if (scanDevice)
break;
}
// if we found waiver for current vendor and are reading test case names then allow it
memorizeCase = deStringEqual(elemName, "t");
break;
}
// we are searching for waiver definition for current vendor, till we find
// it we skip everythingh; we also skip tags that we don't need eg. description
if (!deStringEqual(elemName, "waiver"))
break;
// we found waiver tag, check if it is deffined for current vendor
waiverTestList.clear();
if (xmlParser.hasAttribute(m_vendorTag))
{
vendorFound = matchVendor(xmlParser.getAttribute(m_vendorTag));
// if waiver vendor matches current one then memorize waiver url
// it will be needed when deviceId/renderer will match current one
if (vendorFound)
waiverUrl = xmlParser.getAttribute("url");
}
break;
case xe::xml::ELEMENT_DATA:
if (scanDevice)
{
// check if device read from xml matches current device/renderer
std::string waivedDevice;
xmlParser.getDataStr(waivedDevice);
deviceFound = matchDevice(waivedDevice);
}
else if (memorizeCase)
{
// memorize whats betwean <t></t> tags when case name starts with current package name
// note: waiver tree is constructed per package
std::string waivedCaseName;
xmlParser.getDataStr(waivedCaseName);
if (waivedCaseName.find(m_packageName) == 0)
waiverTestList.push_back(waivedCaseName);
}
break;
case xe::xml::ELEMENT_END:
memorizeCase = false;
scanDevice = false;
if (deStringEqual(elemName, "waiver"))
{
// when we found proper waiver we can copy memorized cases and update waiver info
if (vendorFound && deviceFound)
{
DE_ASSERT(m_testList.empty() || waiverUrl.empty());
std::string& urls = m_sessionInfo.m_waiverUrls;
m_testList.insert(m_testList.end(), waiverTestList.begin(), waiverTestList.end());
// if m_waiverUrls is not empty then we found another waiver
// definition that should be applyed for this device; we need to
// add space to urls attribute to separate new url from previous one
if (!urls.empty())
urls.append(" ");
urls.append(waiverUrl);
}
vendorFound = false;
deviceFound = false;
}
break;
default:
DE_ASSERT(false);
}
xmlParser.advance();
}
}
deUint32 WaiverTreeBuilder::findComponentInBuildTree(const std::vector<std::string>& pathComponents, deUint32 index) const
{
const std::string& checkedName = pathComponents[index];
// check if same component is already in the build tree; we start from 1 - skiping root
for (deUint32 componentIndex = 1 ; componentIndex < m_buildTree.size() ; ++componentIndex)
{
const BuilComponent& componentInTree = m_buildTree[componentIndex];
if (componentInTree.name != checkedName)
continue;
// names match so we need to make sure that all their ancestors match too;
deUint32 reverseLevel = index;
deUint32 ancestorInTreeIndex = componentInTree.parentIndex;
// if this component is the next after root then there is no ancestors to check
if (reverseLevel == 1)
return componentIndex;
while (--reverseLevel > 0)
{
// names dont match - we can move to searching other build tree items
if (pathComponents[reverseLevel] != m_buildTree[ancestorInTreeIndex].name)
break;
// when previous path component matches ancestor name then we need do check earlier path component
ancestorInTreeIndex = m_buildTree[ancestorInTreeIndex].parentIndex;
// we reached root
if (ancestorInTreeIndex == 0)
{
// if next level would be root then ancestors match
if (reverseLevel == 1)
return componentIndex;
// if next level is not root then ancestors dont match
break;
}
}
}
// searched path component is not in the tree
return 0;
}
void WaiverTreeBuilder::buildTreeFromPathList(void)
{
if (m_testList.empty())
return;
deUint32 parentIndex = 0;
// construct root node
m_buildTree.emplace_back("root", DE_NULL);
for (const auto& path : m_testList)
{
const std::vector<std::string> pathComponents = de::splitString(path, '.');
// first component is parented to root
parentIndex = 0;
// iterate over all components of current path, but skip first one (e.g. "dEQP-VK", "KHR-GLES31")
for (deUint32 level = 1 ; level < pathComponents.size() ; ++level)
{
// check if same component is already in the tree and we dont need to add it
deUint32 componentIndex = findComponentInBuildTree(pathComponents, level);
if (componentIndex)
{
parentIndex = componentIndex;
continue;
}
// component is not in the tree, add it
const std::string componentName = pathComponents[level];
m_buildTree.emplace_back(componentName, parentIndex);
// add current component as a child to its parent and assume
// that this component will be parent of next component
componentIndex = static_cast<deUint32>(m_buildTree.size() - 1);
m_buildTree[parentIndex].childrenIndex.push_back(componentIndex);
parentIndex = componentIndex;
}
}
}
void WaiverTreeBuilder::constructFinalTree(void)
{
if (m_buildTree.empty())
return;
// translate vector of BuilComponents to vector of WaiverComponents
m_finalTree.resize(m_buildTree.size());
for (deUint32 i = 0; i < m_finalTree.size(); ++i)
{
BuilComponent& buildCmponent = m_buildTree[i];
WaiverComponent& waiverComponent = m_finalTree[i];
waiverComponent.name = std::move(buildCmponent.name);
waiverComponent.children.resize(buildCmponent.childrenIndex.size());
// set pointers for children
for (deUint32 j = 0; j < buildCmponent.childrenIndex.size(); ++j)
{
deUint32 childIndexInTree = buildCmponent.childrenIndex[j];
waiverComponent.children[j] = &m_finalTree[childIndexInTree];
}
}
}
// Class that builds a tree out of waiver definitions for OpenGL tests.
// Most of functionalities are shared betwean VK and GL builders and they
// were extracted to WaiverTreeBuilder base class.
class GLWaiverTreeBuilder : public WaiverTreeBuilder
{
public:
GLWaiverTreeBuilder (const std::string& waiverFile,
const std::string& packageName,
const std::string& currentVendor,
const std::string& currentRenderer,
SessionInfo& sessionInfo,
std::vector<WaiverComponent>& waiverTree);
bool matchVendor (const std::string& vendor) const override;
bool matchDevice (const std::string& device) const override;
private:
const std::string m_currentVendor;
const std::string m_currentRenderer;
};
GLWaiverTreeBuilder::GLWaiverTreeBuilder(const std::string& waiverFile,
const std::string& packageName,
const std::string& currentVendor,
const std::string& currentRenderer,
SessionInfo& sessionInfo,
std::vector<WaiverComponent>& waiverTree)
: WaiverTreeBuilder (waiverFile, packageName, "vendor", "r", sessionInfo, waiverTree)
, m_currentVendor (currentVendor)
, m_currentRenderer (currentRenderer)
{
}
bool GLWaiverTreeBuilder::matchVendor(const std::string& vendor) const
{
return tcu::matchWildcards(vendor.cbegin(),
vendor.cend(),
m_currentVendor.cbegin(),
m_currentVendor.cend(),
false);
}
bool GLWaiverTreeBuilder::matchDevice(const std::string& device) const
{
// make sure that renderer name in .xml is not within "", those extra characters should be removed
DE_ASSERT(device[0] != '\"');
return tcu::matchWildcards(device.cbegin(),
device.cend(),
m_currentRenderer.cbegin(),
m_currentRenderer.cend(),
false);
}
// Class that builds a tree out of waiver definitions for Vulkan tests.
// Most of functionalities are shared betwean VK and GL builders and they
// were extracted to WaiverTreeBuilder base class.
class VKWaiverTreeBuilder : public WaiverTreeBuilder
{
public:
VKWaiverTreeBuilder (const std::string& waiverFile,
const std::string& packageName,
const deUint32 currentVendor,
const deUint32 currentRenderer,
SessionInfo& sessionInfo,
std::vector<WaiverComponent>& waiverTree);
bool matchVendor (const std::string& vendor) const override;
bool matchDevice (const std::string& device) const override;
private:
const deUint32 m_currentVendorId;
const deUint32 m_currentDeviceId;
};
VKWaiverTreeBuilder::VKWaiverTreeBuilder(const std::string& waiverFile,
const std::string& packageName,
const deUint32 currentVendor,
const deUint32 currentRenderer,
SessionInfo& sessionInfo,
std::vector<WaiverComponent>& waiverTree)
: WaiverTreeBuilder(waiverFile, packageName, "vendorId", "d", sessionInfo, waiverTree)
, m_currentVendorId(currentVendor)
, m_currentDeviceId(currentRenderer)
{
}
bool VKWaiverTreeBuilder::matchVendor(const std::string& vendor) const
{
return (m_currentVendorId == static_cast<deUint32>(std::stoul(vendor, 0, 0)));
}
bool VKWaiverTreeBuilder::matchDevice(const std::string& device) const
{
return (m_currentDeviceId == static_cast<deUint32>(std::stoul(device, 0, 0)));
}
void WaiverUtil::setup(const std::string waiverFile, std::string packageName, deUint32 vendorId, deUint32 deviceId, SessionInfo& sessionInfo)
{
VKWaiverTreeBuilder(waiverFile, packageName, vendorId, deviceId, sessionInfo, m_waiverTree).build();
}
void WaiverUtil::setup(const std::string waiverFile, std::string packageName, std::string vendor, std::string renderer, SessionInfo& sessionInfo)
{
GLWaiverTreeBuilder(waiverFile, packageName, vendor, renderer, sessionInfo, m_waiverTree).build();
}
bool WaiverUtil::isOnWaiverList(const std::string& casePath) const
{
if (m_waiverTree.empty())
return false;
// skip root e.g. "dEQP-VK"
size_t firstDotPos = casePath.find('.');
std::string::const_iterator componentStart = casePath.cbegin() + firstDotPos + 1;
std::string::const_iterator componentEnd = componentStart;
std::string::const_iterator pathEnd = casePath.cend();
const WaiverComponent* waiverComponent = m_waiverTree.data();
// check path component by component
while (true)
{
// find the last character of next component
++componentEnd;
for (; componentEnd < pathEnd ; ++componentEnd)
{
if (*componentEnd == '.')
break;
}
// check if one of children has the same component name
for (const auto& c : waiverComponent->children)
{
bool matchFound = tcu::matchWildcards(c->name.cbegin(),
c->name.cend(),
componentStart,
componentEnd,
false);
// current waiver component matches curent path component - go to next component
if (matchFound)
{
waiverComponent = c;
break;
}
}
// we checked all components - if our pattern was a leaf then this test should be waived
if (componentEnd == pathEnd)
return waiverComponent->children.empty();
// go to next test path component
componentStart = componentEnd + 1;
}
return false;
}
} // vk