/*------------------------------------------------------------------------- * 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 Batch result to XML export. *//*--------------------------------------------------------------------*/ #include "xeTestLogParser.hpp" #include "xeTestResultParser.hpp" #include "xeXMLWriter.hpp" #include "xeTestLogWriter.hpp" #include "deFilePath.hpp" #include "deString.h" #include "deStringUtil.hpp" #include "deCommandLine.hpp" #include #include #include #include #include #include using std::map; using std::string; using std::vector; static const char *CASELIST_STYLESHEET = "caselist.xsl"; static const char *TESTCASE_STYLESHEET = "testlog.xsl"; enum OutputMode { OUTPUTMODE_SEPARATE = 0, //!< Separate OUTPUTMODE_SINGLE, OUTPUTMODE_LAST }; namespace opt { DE_DECLARE_COMMAND_LINE_OPT(OutMode, OutputMode); void registerOptions(de::cmdline::Parser &parser) { using de::cmdline::NamedValue; using de::cmdline::Option; static const NamedValue s_modes[] = {{"single", OUTPUTMODE_SINGLE}, {"separate", OUTPUTMODE_SEPARATE}}; parser << Option("m", "mode", "Output mode", s_modes, "single"); } } // namespace opt struct CommandLine { CommandLine(void) : outputMode(OUTPUTMODE_SINGLE) { } std::string batchResultFile; std::string outputPath; OutputMode outputMode; }; static bool parseCommandLine(CommandLine &cmdLine, int argc, const char *const *argv) { de::cmdline::Parser parser; de::cmdline::CommandLine opts; opt::registerOptions(parser); if (!parser.parse(argc - 1, argv + 1, &opts, std::cerr) || opts.getArgs().size() != 2) { printf("%s: [options] [testlog] [destination path]\n", argv[0]); parser.help(std::cout); return false; } cmdLine.outputMode = opts.getOption(); cmdLine.batchResultFile = opts.getArgs()[0]; cmdLine.outputPath = opts.getArgs()[1]; return true; } static void parseBatchResult(xe::TestLogParser &parser, const char *filename) { std::ifstream in(filename, std::ios_base::binary); uint8_t buf[2048]; for (;;) { in.read((char *)&buf[0], sizeof(buf)); int numRead = (int)in.gcount(); if (numRead > 0) parser.parse(&buf[0], numRead); if (numRead < (int)sizeof(buf)) break; } } // Export to single file struct BatchResultTotals { BatchResultTotals(void) { for (int i = 0; i < xe::TESTSTATUSCODE_LAST; i++) countByCode[i] = 0; } int countByCode[xe::TESTSTATUSCODE_LAST]; }; class ResultToSingleXmlLogHandler : public xe::TestLogHandler { public: ResultToSingleXmlLogHandler(xe::xml::Writer &writer, BatchResultTotals &totals) : m_writer(writer), m_totals(totals) { } void setSessionInfo(const xe::SessionInfo &) { } xe::TestCaseResultPtr startTestCaseResult(const char *casePath) { return xe::TestCaseResultPtr(new xe::TestCaseResultData(casePath)); } void testCaseResultUpdated(const xe::TestCaseResultPtr &) { } void testCaseResultComplete(const xe::TestCaseResultPtr &resultData) { xe::TestCaseResult result; xe::parseTestCaseResultFromData(&m_resultParser, &result, *resultData.get()); // Write result. xe::writeTestResult(result, m_writer); // Record total XE_CHECK(de::inBounds(result.statusCode, 0, xe::TESTSTATUSCODE_LAST)); m_totals.countByCode[result.statusCode] += 1; } private: xe::xml::Writer &m_writer; BatchResultTotals &m_totals; xe::TestResultParser m_resultParser; }; static void writeTotals(xe::xml::Writer &writer, const BatchResultTotals &totals) { using xe::xml::Writer; int totalCases = 0; writer << Writer::BeginElement("ResultTotals"); for (int code = 0; code < xe::TESTSTATUSCODE_LAST; code++) { writer << Writer::Attribute(xe::getTestStatusCodeName((xe::TestStatusCode)code), de::toString(totals.countByCode[code]).c_str()); totalCases += totals.countByCode[code]; } writer << Writer::Attribute("All", de::toString(totalCases).c_str()) << Writer::EndElement; } static void batchResultToSingleXmlFile(const char *batchResultFilename, const char *dstFileName) { std::ofstream out(dstFileName, std::ios_base::binary); xe::xml::Writer writer(out); BatchResultTotals totals; ResultToSingleXmlLogHandler handler(writer, totals); xe::TestLogParser parser(&handler); XE_CHECK(out.good()); out << "\n" << "\n"; writer << xe::xml::Writer::BeginElement("BatchResult") << xe::xml::Writer::Attribute("FileName", de::FilePath(batchResultFilename).getBaseName()); // Parse and write individual cases parseBatchResult(parser, batchResultFilename); // Write ResultTotals writeTotals(writer, totals); writer << xe::xml::Writer::EndElement; out << "\n"; } // Export to separate files class ResultToXmlFilesLogHandler : public xe::TestLogHandler { public: ResultToXmlFilesLogHandler(vector &resultHeaders, const char *dstPath) : m_resultHeaders(resultHeaders) , m_dstPath(dstPath) { } void setSessionInfo(const xe::SessionInfo &) { } xe::TestCaseResultPtr startTestCaseResult(const char *casePath) { return xe::TestCaseResultPtr(new xe::TestCaseResultData(casePath)); } void testCaseResultUpdated(const xe::TestCaseResultPtr &) { } void testCaseResultComplete(const xe::TestCaseResultPtr &resultData) { xe::TestCaseResult result; xe::parseTestCaseResultFromData(&m_resultParser, &result, *resultData.get()); // Write result. { de::FilePath casePath = de::FilePath::join(m_dstPath, (result.casePath + ".xml").c_str()); std::ofstream out(casePath.getPath(), std::ofstream::binary | std::ofstream::trunc); xe::xml::Writer xmlWriter(out); if (!out.good()) throw xe::Error(string("Failed to open ") + casePath.getPath()); out << "\n" << "\n"; xe::writeTestResult(result, xmlWriter); out << "\n"; } m_resultHeaders.push_back(xe::TestCaseResultHeader(result)); } private: vector &m_resultHeaders; std::string m_dstPath; xe::TestResultParser m_resultParser; }; typedef std::map ShortTestResultMap; static void writeTestCaseListNode(const xe::TestNode *testNode, const ShortTestResultMap &resultMap, xe::xml::Writer &dst) { using xe::xml::Writer; bool isGroup = testNode->getNodeType() == xe::TESTNODETYPE_GROUP; string fullPath; testNode->getFullPath(fullPath); if (isGroup) { const xe::TestGroup *group = static_cast(testNode); dst << Writer::BeginElement("TestGroup") << Writer::Attribute("Name", testNode->getName()); for (int childNdx = 0; childNdx < group->getNumChildren(); childNdx++) writeTestCaseListNode(group->getChild(childNdx), resultMap, dst); dst << Writer::EndElement; } else { DE_ASSERT(testNode->getNodeType() == xe::TESTNODETYPE_TEST_CASE); const xe::TestCase *testCase = static_cast(testNode); ShortTestResultMap::const_iterator resultPos = resultMap.find(testCase); const xe::TestCaseResultHeader *result = resultPos != resultMap.end() ? resultPos->second : DE_NULL; DE_ASSERT(result); dst << Writer::BeginElement("TestCase") << Writer::Attribute("Name", testNode->getName()) << Writer::Attribute("Type", xe::getTestCaseTypeName(result->caseType)) << Writer::Attribute("StatusCode", xe::getTestStatusCodeName(result->statusCode)) << Writer::Attribute("StatusDetails", result->statusDetails.c_str()) << Writer::EndElement; } } static void writeTestCaseList(const xe::TestRoot &root, const ShortTestResultMap &resultMap, xe::xml::Writer &dst) { using xe::xml::Writer; dst << Writer::BeginElement("TestRoot"); for (int childNdx = 0; childNdx < root.getNumChildren(); childNdx++) writeTestCaseListNode(root.getChild(childNdx), resultMap, dst); dst << Writer::EndElement; } static void batchResultToSeparateXmlFiles(const char *batchResultFilename, const char *dstPath) { xe::TestRoot testRoot; vector shortResults; ShortTestResultMap resultMap; // Initialize destination directory. if (!de::FilePath(dstPath).exists()) de::createDirectoryAndParents(dstPath); else XE_CHECK_MSG(de::FilePath(dstPath).getType() == de::FilePath::TYPE_DIRECTORY, "Destination is not directory"); // Parse batch result and write out test cases. { ResultToXmlFilesLogHandler handler(shortResults, dstPath); xe::TestLogParser parser(&handler); parseBatchResult(parser, batchResultFilename); } // Build case hierarchy & short result map. { xe::TestHierarchyBuilder hierarchyBuilder(&testRoot); for (vector::const_iterator result = shortResults.begin(); result != shortResults.end(); result++) { xe::TestCase *testCase = hierarchyBuilder.createCase(result->casePath.c_str(), result->caseType); resultMap.insert(std::make_pair(testCase, &(*result))); } } // Create caselist. { de::FilePath indexPath = de::FilePath::join(dstPath, "caselist.xml"); std::ofstream out(indexPath.getPath(), std::ofstream::binary | std::ofstream::trunc); xe::xml::Writer xmlWriter(out); XE_CHECK_MSG(out.good(), "Failed to open caselist.xml"); out << "\n" << "\n"; writeTestCaseList(testRoot, resultMap, xmlWriter); out << "\n"; } } int main(int argc, const char *const *argv) { try { CommandLine cmdLine; if (!parseCommandLine(cmdLine, argc, argv)) return -1; if (cmdLine.outputMode == OUTPUTMODE_SINGLE) batchResultToSingleXmlFile(cmdLine.batchResultFile.c_str(), cmdLine.outputPath.c_str()); else batchResultToSeparateXmlFiles(cmdLine.batchResultFile.c_str(), cmdLine.outputPath.c_str()); } catch (const std::exception &e) { printf("%s\n", e.what()); return -1; } return 0; }