/*------------------------------------------------------------------------- * 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 #include #include #include #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& 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 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& 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 m_testList; std::vector m_buildTree; // reference to object containing information about used waivers SessionInfo& m_sessionInfo; // reference to vector containing final tree std::vector& m_finalTree; }; WaiverTreeBuilder::WaiverTreeBuilder(const std::string& waiverFile, const std::string& packageName, const char* vendorTag, const char* deviceTag, SessionInfo& sessionInfo, std::vector& 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(wholeContent.c_str()), static_cast(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 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 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& 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 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(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& 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& 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& 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& 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(std::stoul(vendor, 0, 0))); } bool VKWaiverTreeBuilder::matchDevice(const std::string& device) const { return (m_currentDeviceId == static_cast(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