| /*------------------------------------------------------------------------- |
| * 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 <vector> |
| #include <string> |
| #include <map> |
| #include <cstdio> |
| #include <fstream> |
| #include <iostream> |
| |
| 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<OutputMode> s_modes[] = {{"single", OUTPUTMODE_SINGLE}, {"separate", OUTPUTMODE_SEPARATE}}; |
| |
| parser << Option<OutMode>("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<opt::OutMode>(); |
| 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<int>(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 << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" |
| << "<?xml-stylesheet href=\"" << TESTCASE_STYLESHEET << "\" type=\"text/xsl\"?>\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<xe::TestCaseResultHeader> &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 << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" |
| << "<?xml-stylesheet href=\"" << TESTCASE_STYLESHEET << "\" type=\"text/xsl\"?>\n"; |
| xe::writeTestResult(result, xmlWriter); |
| out << "\n"; |
| } |
| |
| m_resultHeaders.push_back(xe::TestCaseResultHeader(result)); |
| } |
| |
| private: |
| vector<xe::TestCaseResultHeader> &m_resultHeaders; |
| std::string m_dstPath; |
| xe::TestResultParser m_resultParser; |
| }; |
| |
| typedef std::map<const xe::TestCase *, const xe::TestCaseResultHeader *> 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<const xe::TestGroup *>(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<const xe::TestCase *>(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<xe::TestCaseResultHeader> 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<xe::TestCaseResultHeader>::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 << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" |
| << "<?xml-stylesheet href=\"" << CASELIST_STYLESHEET << "\" type=\"text/xsl\"?>\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; |
| } |