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