• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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