blob: 17e894bd627db0aa19282b6ce89b8dfd5e70a577 [file] [log] [blame]
// Copyright 2019 The Chromium 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 "testing/perf/luci_test_result.h"
#include <utility>
#include "base/check.h"
#include "base/files/file_util.h"
#include "base/json/json_writer.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/values.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace perf_test {
namespace {
constexpr char kKeyFilePath[] = "filePath";
constexpr char kKeyContents[] = "contents";
constexpr char kKeyContentType[] = "contentType";
constexpr char kKeyTestResult[] = "testResult";
constexpr char kKeyTestPath[] = "testPath";
constexpr char kKeyVariant[] = "variant";
constexpr char kKeyStatus[] = "status";
constexpr char kKeyExpected[] = "expected";
constexpr char kKeyStartTime[] = "startTime";
constexpr char kKeyRunDuration[] = "runDuration";
constexpr char kKeyOutputArtifacts[] = "outputArtifacts";
constexpr char kKeyTags[] = "tags";
constexpr char kKeyKey[] = "key";
constexpr char kKeyValue[] = "value";
// Returns iso timeformat string of |time| in UTC.
std::string ToUtcIsoTime(base::Time time) {
base::Time::Exploded utc_exploded;
time.UTCExplode(&utc_exploded);
return base::StringPrintf(
"%d-%02d-%02dT%02d:%02d:%02d.%03dZ", utc_exploded.year,
utc_exploded.month, utc_exploded.day_of_month, utc_exploded.hour,
utc_exploded.minute, utc_exploded.second, utc_exploded.millisecond);
}
std::string ToString(LuciTestResult::Status status) {
using Status = LuciTestResult::Status;
switch (status) {
case Status::kUnspecified:
return "UNSPECIFIED";
case Status::kPass:
return "PASS";
case Status::kFail:
return "FAIL";
case Status::kCrash:
return "CRASH";
case Status::kAbort:
return "ABORT";
case Status::kSkip:
return "SKIP";
}
}
base::Value ToValue(const LuciTestResult::Artifact& artifact) {
// One and only one of the two optional fields must have value.
DCHECK(artifact.file_path.has_value() != artifact.contents.has_value());
base::Value dict(base::Value::Type::DICTIONARY);
if (artifact.file_path.has_value()) {
dict.SetStringKey(kKeyFilePath, artifact.file_path->AsUTF8Unsafe());
} else {
DCHECK(artifact.contents.has_value());
dict.SetStringKey(kKeyContents, artifact.contents.value());
}
dict.SetStringKey(kKeyContentType, artifact.content_type);
return dict;
}
base::Value ToValue(const LuciTestResult& result) {
base::Value test_report(base::Value::Type::DICTIONARY);
base::Value* test_result = test_report.SetKey(
kKeyTestResult, base::Value(base::Value::Type::DICTIONARY));
test_result->SetStringKey(kKeyTestPath, result.test_path());
if (!result.extra_variant_pairs().empty()) {
base::Value* variant_dict = test_result->SetKey(
kKeyVariant, base::Value(base::Value::Type::DICTIONARY));
for (const auto& pair : result.extra_variant_pairs())
variant_dict->SetStringKey(pair.first, pair.second);
}
test_result->SetStringKey(kKeyStatus, ToString(result.status()));
test_result->SetBoolKey(kKeyExpected, result.is_expected());
if (!result.start_time().is_null()) {
test_result->SetStringKey(kKeyStartTime, ToUtcIsoTime(result.start_time()));
}
if (!result.duration().is_zero()) {
test_result->SetStringKey(
kKeyRunDuration,
base::StringPrintf("%.2fs", result.duration().InSecondsF()));
}
if (!result.output_artifacts().empty()) {
base::Value* artifacts_dict = test_result->SetKey(
kKeyOutputArtifacts, base::Value(base::Value::Type::DICTIONARY));
for (const auto& pair : result.output_artifacts())
artifacts_dict->SetKey(pair.first, ToValue(pair.second));
}
if (!result.tags().empty()) {
base::Value* tags_list =
test_result->SetKey(kKeyTags, base::Value(base::Value::Type::LIST));
for (const auto& tag : result.tags()) {
base::Value tag_dict(base::Value::Type::DICTIONARY);
tag_dict.SetStringKey(kKeyKey, tag.key);
tag_dict.SetStringKey(kKeyValue, tag.value);
tags_list->Append(std::move(tag_dict));
}
}
return test_report;
}
std::string ToJson(const LuciTestResult& result) {
std::string json;
CHECK(base::JSONWriter::Write(ToValue(result), &json));
return json;
}
} // namespace
///////////////////////////////////////////////////////////////////////////////
// LuciTestResult::Artifact
LuciTestResult::Artifact::Artifact() = default;
LuciTestResult::Artifact::Artifact(const Artifact& other) = default;
LuciTestResult::Artifact::Artifact(const base::FilePath file_path,
const std::string& content_type)
: file_path(file_path), content_type(content_type) {}
LuciTestResult::Artifact::Artifact(const std::string& contents,
const std::string& content_type)
: contents(contents), content_type(content_type) {}
LuciTestResult::Artifact::~Artifact() = default;
///////////////////////////////////////////////////////////////////////////////
// LuciTestResult
LuciTestResult::LuciTestResult() = default;
LuciTestResult::LuciTestResult(const LuciTestResult& other) = default;
LuciTestResult::LuciTestResult(LuciTestResult&& other) = default;
LuciTestResult::~LuciTestResult() = default;
// static
LuciTestResult LuciTestResult::CreateForGTest() {
LuciTestResult result;
const testing::TestInfo* const test_info =
testing::UnitTest::GetInstance()->current_test_info();
std::string test_case_name = test_info->name();
std::string param_index;
// If there is a "/", extract |param_index| after it and strip it from
// |test_case_name|.
auto pos = test_case_name.rfind('/');
if (pos != std::string::npos) {
param_index = test_case_name.substr(pos + 1);
test_case_name.resize(pos);
}
result.set_test_path(base::StringPrintf("%s.%s", test_info->test_suite_name(),
test_case_name.c_str()));
if (test_info->type_param())
result.AddVariant("param/instantiation", test_info->type_param());
if (!param_index.empty())
result.AddVariant("param/index", param_index);
result.set_status(test_info->result()->Passed()
? LuciTestResult::Status::kPass
: LuciTestResult::Status::kFail);
// Assumes that the expectation is test passing.
result.set_is_expected(result.status() == LuciTestResult::Status::kPass);
// Start timestamp and duration is not set before the test run finishes,
// e.g. when called from PerformanceTest::TearDownOnMainThread.
if (test_info->result()->start_timestamp()) {
result.set_start_time(base::Time::FromTimeT(
static_cast<time_t>(test_info->result()->start_timestamp() / 1000)));
result.set_duration(
base::TimeDelta::FromMilliseconds(test_info->result()->elapsed_time()));
}
return result;
}
void LuciTestResult::AddVariant(const std::string& key,
const std::string& value) {
auto result = extra_variant_pairs_.insert({key, value});
DCHECK(result.second);
}
void LuciTestResult::AddOutputArtifactFile(const std::string& artifact_name,
const base::FilePath& file_path,
const std::string& content_type) {
Artifact artifact(file_path, content_type);
auto insert_result = output_artifacts_.insert(
std::make_pair(artifact_name, std::move(artifact)));
DCHECK(insert_result.second);
}
void LuciTestResult::AddOutputArtifactContents(
const std::string& artifact_name,
const std::string& contents,
const std::string& content_type) {
Artifact artifact(contents, content_type);
auto insert_result = output_artifacts_.insert(
std::make_pair(artifact_name, std::move(artifact)));
DCHECK(insert_result.second);
}
void LuciTestResult::AddTag(const std::string& key, const std::string& value) {
tags_.emplace_back(Tag{key, value});
}
void LuciTestResult::WriteToFile(const base::FilePath& result_file) const {
const std::string json = ToJson(*this);
const int json_size = json.size();
CHECK(WriteFile(result_file, json.data(), json_size) == json_size);
}
} // namespace perf_test