blob: 3378aeec04e8230dd344bf9b3a3971de0a0946c9 [file] [log] [blame] [edit]
// Copyright 2025 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <cinttypes>
#include <sstream>
#include <fbl/string_buffer.h>
#include <fbl/string_printf.h>
#include <rapidjson/document.h>
#include <rapidjson/prettywriter.h>
#include <zxtest/base/assertion.h>
#include <zxtest/base/json-reporter.h>
#include <zxtest/base/log-sink.h>
#include <zxtest/base/test-case.h>
namespace zxtest::internal {
namespace {
fbl::String FormatTimeMs(uint64_t time_ms) {
return fbl::StringPrintf("%" PRIu64 ".%03" PRIu64 "s", time_ms / 1000, time_ms % 1000);
}
class CustomFileSinkWriter {
public:
using Ch = char;
explicit CustomFileSinkWriter(FileLogSink* sink) : sink_(sink) {}
Ch* PutBegin() { return 0; }
void Put(Ch c) { sink_->Write("%c", c); }
void Flush() { sink_->Flush(); }
size_t PutEnd(Ch*) { return 0; }
private:
FileLogSink* sink_;
};
void AddAssertion(rapidjson::PrettyWriter<CustomFileSinkWriter>& json, const Assertion& assertion) {
json.StartObject();
json.Key("failure");
std::ostringstream message;
message << assertion.location().filename << ":" << assertion.location().line_number
<< ": Failure: " << assertion.description();
json.String(message.str().c_str());
json.Key("type");
json.String("");
json.EndObject();
}
} // namespace
class JsonWriter {
public:
explicit JsonWriter(std::unique_ptr<FileLogSink> sink)
: sink_(std::move(sink)), file_writer_(sink_.get()), writer_(file_writer_) {}
rapidjson::PrettyWriter<CustomFileSinkWriter>& out() { return writer_; }
private:
std::unique_ptr<FileLogSink> sink_;
CustomFileSinkWriter file_writer_;
rapidjson::PrettyWriter<CustomFileSinkWriter> writer_;
};
JsonReporter::JsonReporter(std::unique_ptr<FileLogSink> sink)
: json_writer_(new JsonWriter(std::move(sink))) {}
JsonReporter::~JsonReporter() { delete json_writer_; }
void JsonReporter::OnProgramStart(const Runner& runner) {
// Start a new output file.
// All iterations would be placed under a single set of test
// suites, but when run using the zxtest_runner we will always execute
// a single test one time. Passing --gtest_repeat is also restricted
// so users will not be able to trigger multiple iterations when
// using fx test. Manual execution is not restricted.
timers_.program.Reset();
auto& json = json_writer_->out();
json.StartObject();
json.Key("name");
json.String("AllTests");
json.Key("testsuites");
json.StartArray();
}
void JsonReporter::OnTestCaseStart(const TestCase& test_case) {
ResetSuite();
suite_entered = true;
auto& json = json_writer_->out();
json.StartObject();
json.Key("name");
json.String(test_case.name().c_str());
json.Key("testsuite");
json.StartArray();
}
void JsonReporter::OnTestStart(const TestCase& test_case, const TestInfo& test) {
ResetCase();
auto& json = json_writer_->out();
test_entered = true;
run_test_count++;
suite_test_count++;
json.StartObject();
json.Key("name");
json.String(test.name().c_str());
json.Key("file");
json.String(test.location().filename);
json.Key("line");
json.Uint64(test.location().line_number);
}
void JsonReporter::OnAssertion(const Assertion& assertion) {
auto& json = json_writer_->out();
if (!test_entered) {
if (!suite_entered) {
// Create a synthetic suite
json.StartObject();
json.Key("name");
json.String("");
json.Key("tests");
json.Uint64(0);
json.Key("failures");
json.Uint64(0);
json.Key("disabled");
json.Uint64(0);
json.Key("time");
json.String("0.000s");
json.Key("testsuite");
json.StartArray();
}
// Create a synthetic case with the failure.
json.StartObject();
json.Key("name");
json.String("");
json.Key("file");
json.String(assertion.location().filename);
json.Key("line");
json.Uint64(assertion.location().line_number);
json.Key("status");
json.String("RUN");
json.Key("result");
json.String("COMPLETED");
json.Key("time");
json.String("0.000s");
json.Key("failures");
json.StartArray();
AddAssertion(json, assertion);
json.EndArray();
json.EndObject();
if (!suite_entered) {
json.EndArray();
json.EndObject();
}
return;
}
if (!test_has_assertion) {
test_has_assertion = true;
json.Key("failures");
json.StartArray();
}
AddAssertion(json, assertion);
}
void JsonReporter::OnMessage(const Message& message) {
std::ostringstream formatted_message;
formatted_message << message.location().filename << ":" << message.location().line_number << ": "
<< message.text();
test_messages_.push_back(formatted_message.str());
}
void JsonReporter::OnTestSkip(const TestCase& test_case, const TestInfo& test) {
auto& json = json_writer_->out();
// No need to check for list mode, since we will never call that method when listing.
if (test_has_assertion) {
json.EndArray();
}
json.Key("status");
json.String("NOTRUN");
json.Key("result");
json.String("SKIPPED");
auto format_time = FormatTimeMs(timers_.test.GetElapsedTime());
json.Key("time");
json.String(format_time.c_str());
json.Key("skipped");
json.StartArray();
// test_messages_ are cleared in ResetCase()
for (const auto& message : test_messages_) {
json.StartObject();
json.Key("message");
json.String(message.c_str());
json.EndObject();
}
json.EndArray();
json.EndObject();
run_skipped_count++;
suite_skipped_count++;
test_entered = false;
}
void JsonReporter::OnTestFailure(const TestCase& test_case, const TestInfo& test) {
auto& json = json_writer_->out();
if (!list_mode_) {
if (test_has_assertion) {
json.EndArray();
}
json.Key("status");
json.String("RUN");
json.Key("result");
json.String("COMPLETED");
auto format_time = FormatTimeMs(timers_.test.GetElapsedTime());
json.Key("time");
json.String(format_time.c_str());
}
json.EndObject();
run_failure_count++;
suite_failure_count++;
test_entered = false;
}
void JsonReporter::OnTestSuccess(const TestCase& test_case, const TestInfo& test) {
auto& json = json_writer_->out();
if (!list_mode_) {
if (test_has_assertion) {
json.EndArray();
}
json.Key("status");
json.String("RUN");
json.Key("result");
json.String("COMPLETED");
auto format_time = FormatTimeMs(timers_.test.GetElapsedTime());
json.Key("time");
json.String(format_time.c_str());
}
json.EndObject();
test_entered = false;
}
void JsonReporter::OnTestCaseEnd(const TestCase& test_case) {
auto& json = json_writer_->out();
json.EndArray();
json.Key("tests");
json.Uint64(suite_test_count);
if (!list_mode_) {
json.Key("failures");
json.Uint64(suite_failure_count);
json.Key("disabled");
json.Uint64(suite_skipped_count);
json.Key("time");
auto format_time = FormatTimeMs(timers_.test_suite.GetElapsedTime());
json.String(format_time.c_str());
}
json.EndObject();
suite_entered = false;
}
void JsonReporter::OnProgramEnd(const Runner& runner) {
auto& json = json_writer_->out();
json.EndArray();
json.Key("tests");
json.Uint64(run_test_count);
if (!list_mode_) {
json.Key("failures");
json.Uint64(run_failure_count);
json.Key("disabled");
json.Uint64(run_skipped_count);
json.Key("time");
auto format_time = FormatTimeMs(timers_.program.GetElapsedTime());
json.String(format_time.c_str());
}
json.EndObject();
json.Flush();
}
} // namespace zxtest::internal