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