/*------------------------------------------------------------------------- * 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(uint32_t vendorId, uint32_t deviceId, const std::string &deviceName, const std::string &cmdLine) : m_cmdLine(cmdLine) { m_info << std::hex << "#sessionInfo vendorID 0x" << vendorId << "\n" << "#sessionInfo deviceID 0x" << deviceId << "\n" << "#sessionInfo deviceName " << deviceName << "\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; uint32_t parentIndex; // index in allComponents vector std::vector childrenIndex; // index in allComponents vector BuilComponent(std::string n, uint32_t 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 uint32_t findComponentInBuildTree(const std::vector &pathComponents, uint32_t 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(); } } uint32_t WaiverTreeBuilder::findComponentInBuildTree(const std::vector &pathComponents, uint32_t 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 (uint32_t 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; uint32_t reverseLevel = index; uint32_t 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; uint32_t parentIndex = 0; // construct root node m_buildTree.emplace_back("root", 0); 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 (uint32_t level = 1; level < pathComponents.size(); ++level) { // check if same component is already in the tree and we dont need to add it uint32_t 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 (uint32_t 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 (uint32_t j = 0; j < buildCmponent.childrenIndex.size(); ++j) { uint32_t 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 ¤tVendor, const std::string ¤tRenderer, 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 ¤tVendor, const std::string ¤tRenderer, 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 uint32_t currentVendor, const uint32_t currentRenderer, SessionInfo &sessionInfo, std::vector &waiverTree); bool matchVendor(const std::string &vendor) const override; bool matchDevice(const std::string &device) const override; private: const uint32_t m_currentVendorId; const uint32_t m_currentDeviceId; }; VKWaiverTreeBuilder::VKWaiverTreeBuilder(const std::string &waiverFile, const std::string &packageName, const uint32_t currentVendor, const uint32_t 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, uint32_t vendorId, uint32_t 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; } } // namespace tcu