まえがき
やんごとなき事情により、Google Testの結果をレポート出力しようと思った。
標準機能の --gtest_output
だと、人が見るためではないjsonかxmlのみだった(標準出力を見ろってはなしだけど)ので、Markdownで出力するコードを書いてみた。
技術的にはここらへんやjson出力のソースコードを参考にして、テストイベントハンドラでMarkdownのフォーマットに沿ってひたすらいい感じに出力するだけです。
Google Testのコードに則ってC++03で書いていきます。(最近はC++11前提みたいですが。)
出力結果
件数とエラーしか出力していないのでとても簡素です。
ソースコード
短いのでほぼ全部載せます。
エスケープ処理は適当です。
# include <stdio.h>
# include <fstream>
# include <iostream>
# include "gtest/gtest.h"
namespace {
using namespace ::testing;
static std::string FormatTimeInMillisAsDuration(TimeInMillis ms) {
::std::stringstream ss;
ss << (static_cast<double>(ms) * 1e-3) << "s";
return ss.str();
}
static std::string FormatTimeInMillsAsTime(TimeInMillis ms) {
time_t t = ms / 1000;
struct tm* ltm = localtime(&t);
if (ltm) {
char buf[64] = {};
// YYYY-MM-DDThh:mm:ss
std::strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S", ltm);
return buf;
}
else
return "";
}
static const char* TestPartResultTypeToString(TestPartResult::Type type) {
switch (type) {
case TestPartResult::kSkip:
return "Skipped";
case TestPartResult::kSuccess:
return "Success";
case TestPartResult::kNonFatalFailure:
return "NonFatalFailure";
case TestPartResult::kFatalFailure:
return "FatalFailure";
default:
return "Unknown result type";
}
}
class MarkdownUnitTestResultPrinter : public EmptyTestEventListener {
public:
explicit MarkdownUnitTestResultPrinter(const char* output_file)
: output_file_(output_file) {
if (output_file_.empty()) {
GTEST_LOG_(FATAL) << "Markdown output file may not be null";
}
}
virtual void OnTestIterationEnd(const UnitTest& unit_test, int iteration) {
std::ofstream out(output_file_.c_str());
std::stringstream stream;
PrintMdUnitTest(&stream, unit_test);
out << stream.str();
}
private:
static std::string EscapeMd(const std::string& str) {
Message m;
for (size_t i = 0; i < str.size(); ++i) {
const char ch = str[i];
switch (ch) {
case '\\':
case '`':
case '*':
case '_':
case '{':
case '}':
case '[':
case ']':
case '(':
case ')':
case '#':
case '+':
case '-':
case '!':
case '.':
case '<':
case '>':
m << '\\' << ch;
break;
case '\n':
m << "<br>";
break;
default:
m << ch;
break;
}
}
return m.GetString();
}
static void OutputMdTableHeader(std::ostream* stream,
const std::string& name = "Measure of",
const std::string& value = "Value") {
*stream << "| " << name << " | " << value << " |\n";
*stream << "| --- | --- |\n";
}
static void OutputMdTableRow(std::ostream* stream, const std::string& name,
const std::string& value) {
*stream << "| " << name << " | " << EscapeMd(value) << " |\n";
}
static void OutputMdTableRow(std::ostream* stream, const std::string& name,
int value) {
OutputMdTableRow(stream, name, internal::StreamableToString(value));
}
static void OutputMdTestInfo(::std::ostream* stream,
const TestInfo& test_info) {
const TestResult& result = *test_info.result();
*stream << "### Test: " << EscapeMd(test_info.name()) << "\n";
OutputMdTableHeader(stream);
if (test_info.value_param() != nullptr) {
OutputMdTableRow(stream, "value_param", test_info.value_param());
}
if (test_info.type_param() != nullptr) {
OutputMdTableRow(stream, "type_param", test_info.type_param());
}
if (GTEST_FLAG(list_tests)) {
OutputMdTableRow(stream, "file", test_info.file());
OutputMdTableRow(stream, "line", test_info.line());
return;
}
OutputMdTableRow(stream, "status",
test_info.should_run() ? "RUN" : "NOTRUN");
OutputMdTableRow(stream, "time",
FormatTimeInMillisAsDuration(result.elapsed_time()));
*stream << "\n";
*stream << TestPropertiesAsMd(result);
if (result.total_part_count() > 0) {
*stream << "| \\# | type | location | message |\n"
<< "| ----: | :---- | :---- | :---- |\n";
for (int i = 0; i < result.total_part_count(); ++i) {
const TestPartResult& part = result.GetTestPartResult(i);
const std::string location =
internal::FormatCompilerIndependentFileLocation(part.file_name(),
part.line_number());
*stream << "| " << internal::StreamableToString(i + 1)
<< " | " << TestPartResultTypeToString(part.type())
<< " | `" << location << "` "
<< " | " << EscapeMd(part.message()) << " |\n";
}
*stream << "\n";
}
}
static void PrintMdTestCase(::std::ostream* stream,
const TestCase& test_case) {
*stream << "## Test Case: " << EscapeMd(test_case.name()) << "\n";
OutputMdTableHeader(stream);
OutputMdTableRow(stream, "tests", test_case.reportable_test_count());
if (!GTEST_FLAG(list_tests)) {
OutputMdTableRow(stream, "failures", test_case.failed_test_count());
OutputMdTableRow(stream, "disabled",
test_case.reportable_disabled_test_count());
OutputMdTableRow(stream, "errors", 0);
OutputMdTableRow(stream, "time",
FormatTimeInMillisAsDuration(test_case.elapsed_time()));
*stream << "\n";
if (test_case.ad_hoc_test_result().test_property_count() > 0) {
*stream << "--- \n";
OutputMdTableHeader(stream);
*stream << TestPropertiesAsMd(test_case.ad_hoc_test_result()) << "\n";
}
}
for (int i = 0; i < test_case.total_test_count(); ++i) {
if (test_case.GetTestInfo(i)->is_reportable()) {
OutputMdTestInfo(stream, *test_case.GetTestInfo(i));
}
}
*stream << "\n";
}
static void PrintMdUnitTest(::std::ostream* stream,
const UnitTest& unit_test) {
*stream << "# Summary\n";
OutputMdTableHeader(stream);
OutputMdTableRow(stream, "tests", unit_test.reportable_test_count());
OutputMdTableRow(stream, "failures", unit_test.failed_test_count());
OutputMdTableRow(stream, "disabled",
unit_test.reportable_disabled_test_count());
if (GTEST_FLAG(shuffle)) {
OutputMdTableRow(stream, "random_seed", unit_test.random_seed());
}
OutputMdTableRow(stream, "timestamp",
FormatTimeInMillsAsTime(unit_test.start_timestamp()));
OutputMdTableRow(stream, "time",
FormatTimeInMillisAsDuration(unit_test.elapsed_time()));
*stream << "\n";
if (unit_test.ad_hoc_test_result().test_property_count() > 0) {
OutputMdTableHeader(stream, "Propertiy Key", "Value");
*stream << TestPropertiesAsMd(unit_test.ad_hoc_test_result()) << "\n";
}
for (int i = 0; i < unit_test.total_test_case_count(); ++i) {
if (unit_test.GetTestCase(i)->reportable_test_count() > 0) {
PrintMdTestCase(stream, *unit_test.GetTestCase(i));
}
}
}
static std::string TestPropertiesAsMd(const testing::TestResult& result) {
Message attributes;
for (int i = 0; i < result.test_property_count(); ++i) {
const TestProperty& property = result.GetTestProperty(i);
attributes << "| " << property.key() << " | "
<< EscapeMd(property.value()) << " |\n";
}
return attributes.GetString();
}
const std::string output_file_;
};
}
int main(int argc, char** argv) {
printf("Running main() from %s\n", __FILE__);
InitGoogleTest(&argc, argv);
UnitTest& unit_test = *UnitTest::GetInstance();
bool markdown_output = true;
if (markdown_output) {
TestEventListeners& listeners = unit_test.listeners();
listeners.Append(new MarkdownUnitTestResultPrinter("test_report.md"));
}
int ret_val = RUN_ALL_TESTS();
return ret_val;
}