/*-------------------------------------------------------------------------
* 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;
}