/* * Copyright (C) 2017 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. */ #define LOG_TAG "ValidateAudioConfig" #include #include #define LIBXML_SCHEMAS_ENABLED #include #define LIBXML_XINCLUDE_ENABLED #include #define LIBXML_XPATH_ENABLED #include #include #include #include "ValidateXml.h" namespace android { namespace hardware { namespace audio { namespace common { namespace test { namespace utility { /** Map libxml2 structures to their corresponding deleters. */ template constexpr void (*xmlDeleter)(T* t); template <> constexpr auto xmlDeleter = xmlSchemaFree; template <> constexpr auto xmlDeleter = xmlFreeDoc; template <> constexpr auto xmlDeleter = xmlSchemaFreeParserCtxt; template <> constexpr auto xmlDeleter = xmlSchemaFreeValidCtxt; template <> constexpr auto xmlDeleter = xmlXPathFreeContext; template <> constexpr auto xmlDeleter = xmlXPathFreeObject; /** @return a unique_ptr with the correct deleter for the libxml2 object. */ template constexpr auto make_xmlUnique(T* t) { // Wrap deleter in lambda to enable empty base optimization auto deleter = [](T* t) { xmlDeleter(t); }; return std::unique_ptr{t, deleter}; } /** Class that handles libxml2 initialization and cleanup. NOT THREAD SAFE*/ struct Libxml2Global { Libxml2Global() { xmlLineNumbersDefault(1); // Better error message xmlSetGenericErrorFunc(this, errorCb); } ~Libxml2Global() { // TODO: check if all those cleanup are needed xmlSetGenericErrorFunc(nullptr, nullptr); xmlSchemaCleanupTypes(); xmlCleanupParser(); xmlCleanupThreads(); } const std::string& getErrors() { return errors; } private: static void errorCb(void* ctxt, const char* msg, ...) { auto* self = static_cast(ctxt); va_list args; va_start(args, msg); char* formatedMsg; if (vasprintf(&formatedMsg, msg, args) >= 0) { LOG_PRI(ANDROID_LOG_ERROR, LOG_TAG, "%s", formatedMsg); self->errors += "Error: "; self->errors += formatedMsg; } free(formatedMsg); va_end(args); } std::string errors; }; ::testing::AssertionResult validateXml(const char* xmlFilePathExpr, const char* xsdFilePathExpr, const char* xmlFilePath, const char* xsdFilePath) { Libxml2Global libxml2; auto context = [&]() { return std::string() + " While validating: " + xmlFilePathExpr + "\n Which is: " + xmlFilePath + "\nAgainst the schema: " + xsdFilePathExpr + "\n Which is: " + xsdFilePath + "\nLibxml2 errors:\n" + libxml2.getErrors(); }; auto schemaParserCtxt = make_xmlUnique(xmlSchemaNewParserCtxt(xsdFilePath)); auto schema = make_xmlUnique(xmlSchemaParse(schemaParserCtxt.get())); if (schema == nullptr) { return ::testing::AssertionFailure() << "Failed to parse schema (xsd)\n" << context(); } auto doc = make_xmlUnique(xmlReadFile(xmlFilePath, nullptr, 0)); if (doc == nullptr) { return ::testing::AssertionFailure() << "Failed to parse xml\n" << context(); } if (xmlXIncludeProcess(doc.get()) == -1) { return ::testing::AssertionFailure() << "Failed to resolve xincludes in xml\n" << context(); } auto schemaCtxt = make_xmlUnique(xmlSchemaNewValidCtxt(schema.get())); int ret = xmlSchemaValidateDoc(schemaCtxt.get(), doc.get()); if (ret > 0) { return ::testing::AssertionFailure() << "XML is not valid according to the xsd\n" << context(); } if (ret < 0) { return ::testing::AssertionFailure() << "Internal or API error\n" << context(); } return ::testing::AssertionSuccess(); } std::vector findValidXmlFiles( const char* xsdFilePathExpr, const char* xmlFileName, std::vector xmlFileLocations, const char* xsdFilePath, std::vector* errors) { using namespace std::string_literals; std::vector foundFiles; for (const char* location : xmlFileLocations) { std::string xmlFilePath = location + "/"s + xmlFileName; if (access(xmlFilePath.c_str(), F_OK) != 0) { // If the file does not exist ignore this location and fallback on the next one continue; } auto result = validateXml("xmlFilePath", xsdFilePathExpr, xmlFilePath.c_str(), xsdFilePath); if (!result) { if (errors != nullptr) errors->push_back(result.message()); } else { foundFiles.push_back(xmlFilePath); } } return foundFiles; } template ::testing::AssertionResult validateXmlMultipleLocations( const char* xmlFileNameExpr, const char* xmlFileLocationsExpr, const char* xsdFilePathExpr, const char* xmlFileName, std::vector xmlFileLocations, const char* xsdFilePath) { using namespace std::string_literals; std::vector errors; std::vector foundFiles = findValidXmlFiles( xsdFilePathExpr, xmlFileName, xmlFileLocations, xsdFilePath, &errors); if (atLeastOneRequired && foundFiles.empty()) { errors.push_back("No xml file found in provided locations.\n"); } return ::testing::AssertionResult(errors.empty()) << errors.size() << " error" << (errors.size() == 1 ? " " : "s ") << std::accumulate(begin(errors), end(errors), "occurred during xml validation:\n"s) << " While validating all: " << xmlFileNameExpr << "\n Which is: " << xmlFileName << "\n In the following folders: " << xmlFileLocationsExpr << "\n Which is: " << ::testing::PrintToString(xmlFileLocations) << (atLeastOneRequired ? "Where at least one file must be found." : "Where no file might exist."); } template ::testing::AssertionResult validateXmlMultipleLocations(const char*, const char*, const char*, const char*, std::vector, const char*); template ::testing::AssertionResult validateXmlMultipleLocations(const char*, const char*, const char*, const char*, std::vector, const char*); ::testing::AssertionResult isNonEmptyXpath( const char* xmlFilePath, const char* xpathQuery, bool* result) { Libxml2Global libxml2; auto context = [&]() { return std::string() + " In: " + xmlFilePath + "\nLibxml2 errors:\n" + libxml2.getErrors(); }; auto doc = make_xmlUnique(xmlReadFile(xmlFilePath, nullptr, 0)); if (doc == nullptr) { return ::testing::AssertionFailure() << "Failed to parse xml\n" << context(); } if (xmlXIncludeProcess(doc.get()) == -1) { return ::testing::AssertionFailure() << "Failed to resolve xincludes in xml\n" << context(); } auto xpathCtxt = make_xmlUnique(xmlXPathNewContext(doc.get())); if (xpathCtxt == nullptr) { return ::testing::AssertionFailure() << "Failed to create xpath context\n" << context(); } auto xpathObj = make_xmlUnique(xmlXPathEvalExpression(BAD_CAST xpathQuery, xpathCtxt.get())); if (xpathObj == nullptr) { return ::testing::AssertionFailure() << "Failed to evaluate xpath: \'" << xpathQuery << "\'\n" << context(); } auto nodeSet = xpathObj.get()->nodesetval; *result = nodeSet ? nodeSet->nodeNr != 0 : false; return ::testing::AssertionSuccess(); } } // namespace utility } // namespace test } // namespace common } // namespace audio } // namespace hardware } // namespace android