1 // Copyright 2019 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "testing/perf/luci_test_result.h"
6
7 #include <utility>
8
9 #include "base/check.h"
10 #include "base/files/file_util.h"
11 #include "base/i18n/time_formatting.h"
12 #include "base/json/json_writer.h"
13 #include "base/strings/string_util.h"
14 #include "base/strings/stringprintf.h"
15 #include "base/values.h"
16 #include "testing/gtest/include/gtest/gtest.h"
17
18 namespace perf_test {
19
20 namespace {
21
22 constexpr char kKeyFilePath[] = "filePath";
23 constexpr char kKeyContents[] = "contents";
24 constexpr char kKeyContentType[] = "contentType";
25 constexpr char kKeyTestResult[] = "testResult";
26 constexpr char kKeyTestPath[] = "testPath";
27 constexpr char kKeyVariant[] = "variant";
28 constexpr char kKeyStatus[] = "status";
29 constexpr char kKeyExpected[] = "expected";
30 constexpr char kKeyStartTime[] = "startTime";
31 constexpr char kKeyRunDuration[] = "runDuration";
32 constexpr char kKeyOutputArtifacts[] = "outputArtifacts";
33 constexpr char kKeyTags[] = "tags";
34 constexpr char kKeyKey[] = "key";
35 constexpr char kKeyValue[] = "value";
36
ToString(LuciTestResult::Status status)37 std::string ToString(LuciTestResult::Status status) {
38 using Status = LuciTestResult::Status;
39 switch (status) {
40 case Status::kUnspecified:
41 return "UNSPECIFIED";
42 case Status::kPass:
43 return "PASS";
44 case Status::kFail:
45 return "FAIL";
46 case Status::kCrash:
47 return "CRASH";
48 case Status::kAbort:
49 return "ABORT";
50 case Status::kSkip:
51 return "SKIP";
52 }
53 }
54
ToValue(const LuciTestResult::Artifact & artifact)55 base::Value ToValue(const LuciTestResult::Artifact& artifact) {
56 // One and only one of the two optional fields must have value.
57 DCHECK(artifact.file_path.has_value() != artifact.contents.has_value());
58
59 base::Value::Dict dict;
60
61 if (artifact.file_path.has_value()) {
62 dict.Set(kKeyFilePath, artifact.file_path->AsUTF8Unsafe());
63 } else {
64 DCHECK(artifact.contents.has_value());
65 dict.Set(kKeyContents, artifact.contents.value());
66 }
67
68 dict.Set(kKeyContentType, artifact.content_type);
69 return base::Value(std::move(dict));
70 }
71
ToValue(const LuciTestResult & result)72 base::Value ToValue(const LuciTestResult& result) {
73 base::Value::Dict test_report;
74
75 base::Value::Dict* test_result = test_report.EnsureDict(kKeyTestResult);
76 test_result->Set(kKeyTestPath, result.test_path());
77
78 if (!result.extra_variant_pairs().empty()) {
79 base::Value::Dict* variant_dict = test_result->EnsureDict(kKeyVariant);
80 for (const auto& pair : result.extra_variant_pairs())
81 variant_dict->Set(pair.first, pair.second);
82 }
83
84 test_result->Set(kKeyStatus, ToString(result.status()));
85 test_result->Set(kKeyExpected, result.is_expected());
86
87 if (!result.start_time().is_null()) {
88 test_result->Set(kKeyStartTime,
89 base::TimeFormatAsIso8601(result.start_time()));
90 }
91 if (!result.duration().is_zero()) {
92 test_result->Set(
93 kKeyRunDuration,
94 base::StringPrintf("%.2fs", result.duration().InSecondsF()));
95 }
96
97 if (!result.output_artifacts().empty()) {
98 base::Value::Dict* artifacts_dict =
99 test_result->EnsureDict(kKeyOutputArtifacts);
100 for (const auto& pair : result.output_artifacts())
101 artifacts_dict->Set(pair.first, ToValue(pair.second));
102 }
103
104 if (!result.tags().empty()) {
105 base::Value::List* tags_list = test_result->EnsureList(kKeyTags);
106 for (const auto& tag : result.tags()) {
107 base::Value::Dict tag_dict;
108 tag_dict.Set(kKeyKey, tag.key);
109 tag_dict.Set(kKeyValue, tag.value);
110 tags_list->Append(std::move(tag_dict));
111 }
112 }
113
114 return base::Value(std::move(test_report));
115 }
116
ToJson(const LuciTestResult & result)117 std::string ToJson(const LuciTestResult& result) {
118 std::string json;
119 CHECK(base::JSONWriter::Write(ToValue(result), &json));
120 return json;
121 }
122
123 } // namespace
124
125 ///////////////////////////////////////////////////////////////////////////////
126 // LuciTestResult::Artifact
127
128 LuciTestResult::Artifact::Artifact() = default;
129 LuciTestResult::Artifact::Artifact(const Artifact& other) = default;
Artifact(const base::FilePath file_path,const std::string & content_type)130 LuciTestResult::Artifact::Artifact(const base::FilePath file_path,
131 const std::string& content_type)
132 : file_path(file_path), content_type(content_type) {}
Artifact(const std::string & contents,const std::string & content_type)133 LuciTestResult::Artifact::Artifact(const std::string& contents,
134 const std::string& content_type)
135 : contents(contents), content_type(content_type) {}
136 LuciTestResult::Artifact::~Artifact() = default;
137
138 ///////////////////////////////////////////////////////////////////////////////
139 // LuciTestResult
140
141 LuciTestResult::LuciTestResult() = default;
142 LuciTestResult::LuciTestResult(const LuciTestResult& other) = default;
143 LuciTestResult::LuciTestResult(LuciTestResult&& other) = default;
144 LuciTestResult::~LuciTestResult() = default;
145
146 // static
CreateForGTest()147 LuciTestResult LuciTestResult::CreateForGTest() {
148 LuciTestResult result;
149
150 const testing::TestInfo* const test_info =
151 testing::UnitTest::GetInstance()->current_test_info();
152
153 std::string test_case_name = test_info->name();
154 std::string param_index;
155
156 // If there is a "/", extract |param_index| after it and strip it from
157 // |test_case_name|.
158 auto pos = test_case_name.rfind('/');
159 if (pos != std::string::npos) {
160 param_index = test_case_name.substr(pos + 1);
161 test_case_name.resize(pos);
162 }
163
164 result.set_test_path(base::StringPrintf("%s.%s", test_info->test_suite_name(),
165 test_case_name.c_str()));
166
167 if (test_info->type_param())
168 result.AddVariant("param/instantiation", test_info->type_param());
169
170 if (!param_index.empty())
171 result.AddVariant("param/index", param_index);
172
173 result.set_status(test_info->result()->Passed()
174 ? LuciTestResult::Status::kPass
175 : LuciTestResult::Status::kFail);
176 // Assumes that the expectation is test passing.
177 result.set_is_expected(result.status() == LuciTestResult::Status::kPass);
178
179 // Start timestamp and duration is not set before the test run finishes,
180 // e.g. when called from PerformanceTest::TearDownOnMainThread.
181 if (test_info->result()->start_timestamp()) {
182 result.set_start_time(base::Time::FromTimeT(
183 static_cast<time_t>(test_info->result()->start_timestamp() / 1000)));
184 result.set_duration(
185 base::Milliseconds(test_info->result()->elapsed_time()));
186 }
187
188 return result;
189 }
190
AddVariant(const std::string & key,const std::string & value)191 void LuciTestResult::AddVariant(const std::string& key,
192 const std::string& value) {
193 auto result = extra_variant_pairs_.insert({key, value});
194 DCHECK(result.second);
195 }
196
AddOutputArtifactFile(const std::string & artifact_name,const base::FilePath & file_path,const std::string & content_type)197 void LuciTestResult::AddOutputArtifactFile(const std::string& artifact_name,
198 const base::FilePath& file_path,
199 const std::string& content_type) {
200 Artifact artifact(file_path, content_type);
201 auto insert_result = output_artifacts_.insert(
202 std::make_pair(artifact_name, std::move(artifact)));
203 DCHECK(insert_result.second);
204 }
205
AddOutputArtifactContents(const std::string & artifact_name,const std::string & contents,const std::string & content_type)206 void LuciTestResult::AddOutputArtifactContents(
207 const std::string& artifact_name,
208 const std::string& contents,
209 const std::string& content_type) {
210 Artifact artifact(contents, content_type);
211 auto insert_result = output_artifacts_.insert(
212 std::make_pair(artifact_name, std::move(artifact)));
213 DCHECK(insert_result.second);
214 }
215
AddTag(const std::string & key,const std::string & value)216 void LuciTestResult::AddTag(const std::string& key, const std::string& value) {
217 tags_.emplace_back(Tag{key, value});
218 }
219
WriteToFile(const base::FilePath & result_file) const220 void LuciTestResult::WriteToFile(const base::FilePath& result_file) const {
221 const std::string json = ToJson(*this);
222 const int json_size = json.size();
223 CHECK(WriteFile(result_file, json.data(), json_size) == json_size);
224 }
225
226 } // namespace perf_test
227