| /*------------------------------------------------------------------------- |
| * 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::vector; |
| using std::string; |
| using std::map; |
| |
| 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::Option; |
| using de::cmdline::NamedValue; |
| |
| static const NamedValue<OutputMode> s_modes[] = |
| { |
| { "single", OUTPUTMODE_SINGLE }, |
| { "separate", OUTPUTMODE_SEPARATE } |
| }; |
| |
| parser << Option<OutMode>("m", "mode", "Output mode", s_modes, "single"); |
| } |
| |
| } // 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); |
| deUint8 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; |
| } |