blob: 2c00055d46e81686b46b9a5e2b0a5e8594d6834e [file] [log] [blame]
Satoru Takabayashie7f6d2a2018-08-08 17:06:29 +09001// Copyright 2018 The Chromium OS Authors. All rights reserved.
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 "crash-reporter/util.h"
6
Satoru Takabayashi9a587522018-10-29 09:36:27 +09007#include <stdlib.h>
8
9#include <memory>
10
Satoru Takabayashie7f6d2a2018-08-08 17:06:29 +090011#include <base/files/file_util.h>
12#include <base/files/scoped_temp_dir.h>
Jeffrey Kardatzke437fa922019-05-09 11:34:32 -070013#include <base/rand_util.h>
14#include <brillo/process.h>
15#include <brillo/streams/memory_stream.h>
Satoru Takabayashie7f6d2a2018-08-08 17:06:29 +090016#include <gtest/gtest.h>
17
18#include "crash-reporter/crash_sender_paths.h"
19#include "crash-reporter/paths.h"
20#include "crash-reporter/test_util.h"
21
22namespace util {
Satoru Takabayashib2ca40d2018-08-09 14:02:04 +090023namespace {
24
Jeffrey Kardatzke437fa922019-05-09 11:34:32 -070025constexpr char kLsbReleaseContents[] =
Satoru Takabayashib2ca40d2018-08-09 14:02:04 +090026 "CHROMEOS_RELEASE_BOARD=bob\n"
27 "CHROMEOS_RELEASE_NAME=Chromium OS\n"
28 "CHROMEOS_RELEASE_VERSION=10964.0.2018_08_13_1405\n";
29
Jeffrey Kardatzkeea333932019-04-12 10:17:51 -070030constexpr char kHwClassContents[] = "fake_hwclass";
31
Jeffrey Kardatzke437fa922019-05-09 11:34:32 -070032constexpr char kGzipPath[] = "/bin/gzip";
33
34constexpr char kSemiRandomData[] =
35 "ABJCI239AJSDLKJ;kalkjkjsd98723;KJHASD87;kqw3p088ad;lKJASDP823;KJ";
36constexpr int kRandomDataMinLength = 32768; // 32kB
37constexpr int kRandomDataMaxLength = 262144; // 256kB
38
39// Verifies that |raw_file| corresponds to the gzip'd version of
40// |compressed_file| by decompressing it and comparing the contents. Returns
41// true if they match, false otherwise. This will overwrite the contents of
42// |compressed_file| in the process of doing this.
43bool VerifyCompression(const base::FilePath& raw_file,
44 const base::FilePath& compressed_file) {
45 if (!base::PathExists(raw_file)) {
46 LOG(ERROR) << "raw_file doesn't exist for verifying compression: "
47 << raw_file.value();
48 return false;
49 }
50 if (!base::PathExists(compressed_file)) {
51 LOG(ERROR) << "compressed_file doesn't exist for verifying compression: "
52 << compressed_file.value();
53 return false;
54 }
55 brillo::ProcessImpl proc;
56 proc.AddArg(kGzipPath);
57 proc.AddArg("-d"); // decompress
58 proc.AddArg(compressed_file.value());
59 std::string error;
60 const int res = util::RunAndCaptureOutput(&proc, STDERR_FILENO, &error);
61 if (res < 0) {
62 PLOG(ERROR) << "Failed to execute gzip";
63 return false;
64 }
65 if (res != 0) {
66 LOG(ERROR) << "Failed to un-gzip " << compressed_file.value();
67 util::LogMultilineError(error);
68 return false;
69 }
70 base::FilePath uncompressed_file = compressed_file.RemoveFinalExtension();
71 std::string raw_contents;
72 std::string uncompressed_contents;
73 if (!base::ReadFileToString(raw_file, &raw_contents)) {
74 LOG(ERROR) << "Failed reading in raw_file " << raw_file.value();
75 return false;
76 }
77 if (!base::ReadFileToString(uncompressed_file, &uncompressed_contents)) {
78 LOG(ERROR) << "Failed reading in uncompressed_file "
79 << uncompressed_file.value();
80 return false;
81 }
82 return raw_contents == uncompressed_contents;
83}
84
85// We use a somewhat random string of ASCII data to better reflect the data we
86// would be compressing for real. We also shouldn't use something like
87// base::RandBytesAsString() because that will generate uniformly random data
88// which does not compress.
89std::string CreateSemiRandomString(size_t size) {
90 std::string result;
91 result.reserve(size);
92 while (result.length() < size) {
93 int rem = size - result.length();
94 if (rem > sizeof(kSemiRandomData) - 1)
95 rem = sizeof(kSemiRandomData) - 1;
96 int rand_start = base::RandInt(0, rem - 1);
97 int rand_end = base::RandInt(rand_start + 1, rem);
98 result.append(&kSemiRandomData[rand_start], rand_end - rand_start);
99 }
100 return result;
101}
102
Satoru Takabayashib2ca40d2018-08-09 14:02:04 +0900103} // namespace
Satoru Takabayashie7f6d2a2018-08-08 17:06:29 +0900104
105class CrashCommonUtilTest : public testing::Test {
Jeffrey Kardatzke437fa922019-05-09 11:34:32 -0700106 protected:
Satoru Takabayashie7f6d2a2018-08-08 17:06:29 +0900107 void SetUp() override {
108 ASSERT_TRUE(scoped_temp_dir_.CreateUniqueTempDir());
109 test_dir_ = scoped_temp_dir_.GetPath();
110 paths::SetPrefixForTesting(test_dir_);
111 }
112
113 void TearDown() override { paths::SetPrefixForTesting(base::FilePath()); }
114
115 base::FilePath test_dir_;
116 base::ScopedTempDir scoped_temp_dir_;
117};
118
119TEST_F(CrashCommonUtilTest, IsCrashTestInProgress) {
120 EXPECT_FALSE(IsCrashTestInProgress());
121 ASSERT_TRUE(
122 test_util::CreateFile(paths::GetAt(paths::kSystemRunStateDirectory,
123 paths::kCrashTestInProgress),
124 ""));
125 EXPECT_TRUE(IsCrashTestInProgress());
126}
127
Satoru Takabayashi2d728042018-12-10 09:19:00 +0900128TEST_F(CrashCommonUtilTest, IsDeviceCoredumpUploadAllowed) {
129 EXPECT_FALSE(IsDeviceCoredumpUploadAllowed());
130 ASSERT_TRUE(
131 test_util::CreateFile(paths::GetAt(paths::kCrashReporterStateDirectory,
132 paths::kDeviceCoredumpUploadAllowed),
133 ""));
134 EXPECT_TRUE(IsDeviceCoredumpUploadAllowed());
135}
136
Satoru Takabayashie7f6d2a2018-08-08 17:06:29 +0900137TEST_F(CrashCommonUtilTest, IsDeveloperImage) {
138 EXPECT_FALSE(IsDeveloperImage());
139
140 ASSERT_TRUE(test_util::CreateFile(paths::Get(paths::kLeaveCoreFile), ""));
141 EXPECT_TRUE(IsDeveloperImage());
142
143 ASSERT_TRUE(
144 test_util::CreateFile(paths::GetAt(paths::kSystemRunStateDirectory,
145 paths::kCrashTestInProgress),
146 ""));
147 EXPECT_FALSE(IsDeveloperImage());
148}
149
Satoru Takabayashif6a36802018-08-14 16:23:05 +0900150TEST_F(CrashCommonUtilTest, IsTestImage) {
151 EXPECT_FALSE(IsTestImage());
152
153 // Should return false because the channel is stable.
154 ASSERT_TRUE(test_util::CreateFile(
155 paths::GetAt(paths::kEtcDirectory, paths::kLsbRelease),
156 "CHROMEOS_RELEASE_TRACK=stable-channel"));
157 EXPECT_FALSE(IsTestImage());
158
159 // Should return true because the channel is testimage.
160 ASSERT_TRUE(test_util::CreateFile(
161 paths::GetAt(paths::kEtcDirectory, paths::kLsbRelease),
162 "CHROMEOS_RELEASE_TRACK=testimage-channel"));
163 EXPECT_TRUE(IsTestImage());
164
165 // Should return false if kCrashTestInProgress is present.
166 ASSERT_TRUE(
167 test_util::CreateFile(paths::GetAt(paths::kSystemRunStateDirectory,
168 paths::kCrashTestInProgress),
169 ""));
170 EXPECT_FALSE(IsTestImage());
171}
172
Satoru Takabayashi9a587522018-10-29 09:36:27 +0900173TEST_F(CrashCommonUtilTest, IsOfficialImage) {
174 EXPECT_FALSE(IsOfficialImage());
175
Satoru Takabayashi9a587522018-10-29 09:36:27 +0900176 // Check if lsb-release is handled correctly.
177 ASSERT_TRUE(test_util::CreateFile(
178 paths::Get("/etc/lsb-release"),
179 "CHROMEOS_RELEASE_DESCRIPTION=10964.0 (Test Build) developer-build"));
180 EXPECT_FALSE(IsOfficialImage());
181
182 ASSERT_TRUE(test_util::CreateFile(
183 paths::Get("/etc/lsb-release"),
184 "CHROMEOS_RELEASE_DESCRIPTION=10964.0 (Official Build) canary-channel"));
185 EXPECT_TRUE(IsOfficialImage());
186}
187
Jeffrey Kardatzkeea333932019-04-12 10:17:51 -0700188TEST_F(CrashCommonUtilTest, GetHardwareClass) {
189 EXPECT_EQ("undefined", GetHardwareClass());
190
191 ASSERT_TRUE(test_util::CreateFile(
192 paths::Get("/sys/devices/platform/chromeos_acpi/HWID"),
193 kHwClassContents));
194 EXPECT_EQ(kHwClassContents, GetHardwareClass());
195}
196
197TEST_F(CrashCommonUtilTest, GetBootModeString) {
198 EXPECT_EQ("missing-crossystem", GetBootModeString());
199
Jeffrey Kardatzkeea333932019-04-12 10:17:51 -0700200 ASSERT_TRUE(
201 test_util::CreateFile(paths::GetAt(paths::kSystemRunStateDirectory,
202 paths::kCrashTestInProgress),
203 ""));
204 EXPECT_EQ("", GetBootModeString());
205}
206
Satoru Takabayashib2ca40d2018-08-09 14:02:04 +0900207TEST_F(CrashCommonUtilTest, GetCachedKeyValue) {
208 ASSERT_TRUE(test_util::CreateFile(paths::Get("/etc/lsb-release"),
209 kLsbReleaseContents));
210 ASSERT_TRUE(test_util::CreateFile(paths::Get("/empty/lsb-release"), ""));
211
212 std::string value;
213 // No directories are specified.
214 EXPECT_FALSE(GetCachedKeyValue(base::FilePath("lsb-release"),
215 "CHROMEOS_RELEASE_VERSION", {}, &value));
216 // A non-existent directory is specified.
217 EXPECT_FALSE(GetCachedKeyValue(base::FilePath("lsb-release"),
218 "CHROMEOS_RELEASE_VERSION",
219 {paths::Get("/non-existent")}, &value));
220
221 // A non-existent base name is specified.
222 EXPECT_FALSE(GetCachedKeyValue(base::FilePath("non-existent"),
223 "CHROMEOS_RELEASE_VERSION",
224 {paths::Get("/etc")}, &value));
225
226 // A wrong key is specified.
227 EXPECT_FALSE(GetCachedKeyValue(base::FilePath("lsb-release"), "WRONG_KEY",
228 {paths::Get("/etc")}, &value));
229
230 // This should succeed.
231 EXPECT_TRUE(GetCachedKeyValue(base::FilePath("lsb-release"),
232 "CHROMEOS_RELEASE_VERSION",
233 {paths::Get("/etc")}, &value));
234 EXPECT_EQ("10964.0.2018_08_13_1405", value);
235
236 // A non-existent directory is included, but this should still succeed.
237 EXPECT_TRUE(GetCachedKeyValue(
238 base::FilePath("lsb-release"), "CHROMEOS_RELEASE_VERSION",
239 {paths::Get("/non-existent"), paths::Get("/etc")}, &value));
240 EXPECT_EQ("10964.0.2018_08_13_1405", value);
241
242 // A empty file is included, but this should still succeed.
243 EXPECT_TRUE(GetCachedKeyValue(
244 base::FilePath("lsb-release"), "CHROMEOS_RELEASE_VERSION",
245 {paths::Get("/empty"), paths::Get("/etc")}, &value));
246 EXPECT_EQ("10964.0.2018_08_13_1405", value);
247}
248
249TEST_F(CrashCommonUtilTest, GetCachedKeyValueDefault) {
250 std::string value;
251 EXPECT_FALSE(
252 GetCachedKeyValueDefault(base::FilePath("test.txt"), "FOO", &value));
253
Jeffrey Kardatzke8ed15d12019-03-21 16:57:20 -0700254 // kEtcDirectory is the second candidate directory.
Satoru Takabayashib2ca40d2018-08-09 14:02:04 +0900255 ASSERT_TRUE(test_util::CreateFile(
Jeffrey Kardatzke8ed15d12019-03-21 16:57:20 -0700256 paths::GetAt(paths::kEtcDirectory, "test.txt"), "FOO=2\n"));
Satoru Takabayashib2ca40d2018-08-09 14:02:04 +0900257 EXPECT_TRUE(
258 GetCachedKeyValueDefault(base::FilePath("test.txt"), "FOO", &value));
259 EXPECT_EQ("2", value);
260
261 // kCrashReporterStateDirectory is the first candidate directory.
262 ASSERT_TRUE(test_util::CreateFile(
Jeffrey Kardatzke8ed15d12019-03-21 16:57:20 -0700263 paths::GetAt(paths::kCrashReporterStateDirectory, "test.txt"),
264 "FOO=1\n"));
Satoru Takabayashib2ca40d2018-08-09 14:02:04 +0900265 EXPECT_TRUE(
266 GetCachedKeyValueDefault(base::FilePath("test.txt"), "FOO", &value));
267 EXPECT_EQ("1", value);
268}
269
Satoru Takabayashi8ce6db82018-08-17 15:18:41 +0900270TEST_F(CrashCommonUtilTest, GetUserCrashDirectories) {
271 auto mock =
272 std::make_unique<org::chromium::SessionManagerInterfaceProxyMock>();
273
274 std::vector<base::FilePath> directories;
275
276 test_util::SetActiveSessions(mock.get(), {});
277 EXPECT_TRUE(GetUserCrashDirectories(mock.get(), &directories));
278 EXPECT_TRUE(directories.empty());
279
280 test_util::SetActiveSessions(mock.get(),
281 {{"user1", "hash1"}, {"user2", "hash2"}});
282 EXPECT_TRUE(GetUserCrashDirectories(mock.get(), &directories));
283 EXPECT_EQ(2, directories.size());
Satoru Takabayashib09f7052018-10-01 15:26:29 +0900284 EXPECT_EQ(paths::Get("/home/user/hash1/crash").value(),
285 directories[0].value());
286 EXPECT_EQ(paths::Get("/home/user/hash2/crash").value(),
287 directories[1].value());
Satoru Takabayashi8ce6db82018-08-17 15:18:41 +0900288}
289
Jeffrey Kardatzke437fa922019-05-09 11:34:32 -0700290TEST_F(CrashCommonUtilTest, GzipFile) {
291 // Create a temp file of semi-structured ASCII data then compress it using our
292 // function and then decompress it and see if we have the same data. Don't use
293 // random data because random data doesn't compress well. :)
294 base::FilePath file;
295 ASSERT_TRUE(base::CreateTemporaryFileInDir(test_dir_, &file));
296 base::FilePath file_copy;
297 ASSERT_TRUE(base::CreateTemporaryFileInDir(test_dir_, &file_copy));
298 std::string content = CreateSemiRandomString(
299 base::RandInt(kRandomDataMinLength, kRandomDataMaxLength));
300 ASSERT_EQ(base::WriteFile(file, content.c_str(), content.length()),
301 content.length());
302 ASSERT_TRUE(base::CopyFile(file, file_copy));
303 base::FilePath zip_file = util::GzipFile(file);
304 EXPECT_EQ(zip_file, file.AddExtension(".gz"));
305 EXPECT_TRUE(VerifyCompression(file_copy, zip_file))
306 << "Random input data: " << content;
307}
308
309TEST_F(CrashCommonUtilTest, GzipStream) {
310 std::string content = CreateSemiRandomString(
311 base::RandInt(kRandomDataMinLength, kRandomDataMaxLength));
312 std::string compressed_content =
313 util::GzipStream(brillo::MemoryStream::OpenCopyOf(
314 content.c_str(), content.length(), nullptr));
315 EXPECT_FALSE(compressed_content.empty());
316 base::FilePath raw_file;
317 ASSERT_TRUE(base::CreateTemporaryFileInDir(test_dir_, &raw_file));
318 base::FilePath compressed_file;
319 ASSERT_TRUE(base::CreateTemporaryFileInDir(test_dir_, &compressed_file));
320 // Remove the file we will decompress to or gzip will fail on decompression.
321 ASSERT_TRUE(base::DeleteFile(compressed_file, false));
322 compressed_file = compressed_file.AddExtension(".gz");
323 ASSERT_EQ(base::WriteFile(raw_file, content.c_str(), content.length()),
324 content.length());
325 ASSERT_EQ(base::WriteFile(compressed_file, compressed_content.c_str(),
326 compressed_content.length()),
327 compressed_content.length());
328 EXPECT_TRUE(VerifyCompression(raw_file, compressed_file))
329 << "Random input data: " << content;
330}
331
Satoru Takabayashie7f6d2a2018-08-08 17:06:29 +0900332} // namespace util