blob: 68bb575f47982a464d2dc255aae6218b05cf4137 [file] [log] [blame]
Miriam Zimmerman18f8d622020-10-02 11:06:32 -07001// Copyright 2020 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/crash_serializer.h"
6
7#include <string>
8#include <tuple>
9#include <utility>
10#include <vector>
11
Miriam Zimmermanc57b2b32020-10-28 15:59:07 -070012#include <inttypes.h>
13
14#include <base/big_endian.h>
Miriam Zimmerman18f8d622020-10-02 11:06:32 -070015#include <base/files/file_util.h>
16#include <base/files/scoped_temp_dir.h>
Miriam Zimmermanc57b2b32020-10-28 15:59:07 -070017#include <base/strings/stringprintf.h>
Miriam Zimmerman18f8d622020-10-02 11:06:32 -070018#include <gmock/gmock.h>
19#include <gtest/gtest.h>
20
21#include "crash-reporter/crash_sender_base.h"
22#include "crash-reporter/crash_sender_paths.h"
23#include "crash-reporter/crash_serializer.pb.h"
24#include "crash-reporter/paths.h"
25#include "crash-reporter/test_util.h"
26
Miriam Zimmermanc57b2b32020-10-28 15:59:07 -070027using test_util::kFakeClientId;
28
Miriam Zimmerman18f8d622020-10-02 11:06:32 -070029namespace crash_serializer {
30namespace {
31
Miriam Zimmermanc57b2b32020-10-28 15:59:07 -070032// Set the file flag which indicates we are mocking crash sending, either
33// successfully or as a failure.
34bool SetMockCrashSending(bool success) {
Gabriel Szlechtman374c6bc2021-02-10 06:16:32 +000035 util::g_force_is_mock = true;
36 util::g_force_is_mock_successful = success;
37 return base::CreateDirectory(paths::Get(paths::kChromeCrashLog).DirName());
Miriam Zimmermanc57b2b32020-10-28 15:59:07 -070038}
Miriam Zimmerman18f8d622020-10-02 11:06:32 -070039
40} // namespace
41
42class CrashSerializerTest : public testing::Test {
43 protected:
44 void SetUp() override {
45 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
46 test_dir_ = temp_dir_.GetPath();
47 paths::SetPrefixForTesting(test_dir_);
48
49 // Make sure the directory for the lock file exists.
50 const base::FilePath lock_file_path =
51 paths::Get(paths::kCrashSenderLockFile);
52 const base::FilePath lock_file_directory = lock_file_path.DirName();
53 ASSERT_TRUE(base::CreateDirectory(lock_file_directory));
54 }
55
56 void TearDown() override { paths::SetPrefixForTesting(base::FilePath()); }
57
Miriam Zimmermanc57b2b32020-10-28 15:59:07 -070058 // Creates a file at |file_path| with contents |content| and sets its access
59 // and modification time to |timestamp|.
60 bool CreateFile(const base::FilePath& file_path,
61 base::StringPiece content,
62 base::Time timestamp) {
63 if (!test_util::CreateFile(file_path, content))
64 return false;
65
66 if (!test_util::TouchFileHelper(file_path, timestamp))
67 return false;
68
69 return true;
70 }
71
72 // Creates test crash files in |crash_directory|. Returns true on success.
73 bool CreateTestCrashFiles(const base::FilePath& crash_directory) {
74 const base::Time now = test_util::GetDefaultTime();
75 const base::TimeDelta hour = base::TimeDelta::FromHours(1);
76
77 // Choose timestamps so that the return value of GetMetaFiles() is sorted
78 // per timestamps correctly.
79 const base::Time old_os_meta_time = now - base::TimeDelta::FromDays(200);
80 const base::Time good_meta_time = now - hour * 4;
81 const base::Time absolute_meta_time = now - hour * 3;
82 const base::Time uploaded_meta_time = now - hour * 2;
83 const base::Time recent_os_meta_time = now - hour;
84 const base::Time devcore_meta_time = now;
85
86 // These should be serialized, since the payload is a known kind and exists.
87 good_meta_ = crash_directory.Append("good.meta");
88 good_log_ = crash_directory.Append("good.log");
89 if (!CreateFile(good_meta_, "payload=good.log\ndone=1\n", good_meta_time))
90 return false;
91 if (!CreateFile(good_log_, "", now))
92 return false;
93
94 // These should be serialized, the payload path is absolute but should be
95 // handled properly.
96 absolute_meta_ = crash_directory.Append("absolute.meta");
97 absolute_log_ = crash_directory.Append("absolute.log");
98 if (!CreateFile(absolute_meta_,
99 "payload=" + absolute_log_.value() + "\n" + "done=1\n",
100 absolute_meta_time))
101 return false;
102 if (!CreateFile(absolute_log_, "", now))
103 return false;
104
105 // These should be serialized, even though the `alreadyuploaded` file
106 // exists.
107 uploaded_meta_ = crash_directory.Append("uploaded.meta");
108 uploaded_log_ = crash_directory.Append("uploaded.log");
109 uploaded_already_ = crash_directory.Append("uploaded.alreadyuploaded");
110 if (!CreateFile(uploaded_meta_, "payload=uploaded.log\ndone=1\n",
111 uploaded_meta_time))
112 return false;
113 if (!CreateFile(uploaded_log_, "", now))
114 return false;
115 if (!CreateFile(uploaded_already_, "", now))
116 return false;
117
118 // This should be ignored as corrupt. Payload can't be /.
119 root_payload_meta_ = crash_directory.Append("root_payload.meta");
120 if (!test_util::CreateFile(root_payload_meta_,
121 "payload=/\n"
122 "done=1\n"))
123 return false;
124
125 // These should be serialized -- serializing devcore files is always OK
126 // (as opposed to sending them, which is only sometimes okay).
127 devcore_meta_ = crash_directory.Append("devcore.meta");
128 devcore_devcore_ = crash_directory.Append("devcore.devcore");
129 if (!CreateFile(devcore_meta_,
130 "payload=devcore.devcore\n"
131 "done=1\n",
132 devcore_meta_time))
133 return false;
134 if (!CreateFile(devcore_devcore_, "", now))
135 return false;
136
137 // This should be ignored, since metadata is corrupted.
138 corrupted_meta_ = crash_directory.Append("corrupted.meta");
139 if (!CreateFile(corrupted_meta_, "!@#$%^&*\ndone=1\n", now))
140 return false;
141
142 // This should be ignored, since no payload info is recorded.
143 empty_meta_ = crash_directory.Append("empty.meta");
144 if (!CreateFile(empty_meta_, "done=1\n", now))
145 return false;
146
147 // This should be ignored, since the payload file does not exist.
148 nonexistent_meta_ = crash_directory.Append("nonexistent.meta");
149 if (!CreateFile(nonexistent_meta_,
150 "payload=nonexistent.log\n"
151 "done=1\n",
152 now))
153 return false;
154
155 // These should be ignored, since the payload is an unknown kind.
156 unknown_meta_ = crash_directory.Append("unknown.meta");
157 unknown_xxx_ = crash_directory.Append("unknown.xxx");
158 if (!CreateFile(unknown_meta_,
159 "payload=unknown.xxx\n"
160 "done=1\n",
161 now))
162 return false;
163 if (!CreateFile(unknown_xxx_, "", now))
164 return false;
165
166 // This should be ignored, since it's incomplete.
167 old_incomplete_meta_ = crash_directory.Append("old_incomplete.meta");
168 if (!CreateFile(old_incomplete_meta_, "payload=good.log\n", now))
169 return false;
170 if (!test_util::TouchFileHelper(old_incomplete_meta_, now - hour * 24))
171 return false;
172
173 // This should be ignored, since it's incomplete.
174 new_incomplete_meta_ = crash_directory.Append("new_incomplete.meta");
175 if (!CreateFile(new_incomplete_meta_, "payload=nonexistent.log\n", now))
176 return false;
177
178 // This should be serialized since the OS timestamp is recent.
179 recent_os_meta_ = crash_directory.Append("recent_os.meta");
180 if (!CreateFile(recent_os_meta_,
181 base::StringPrintf(
182 "payload=recent_os.log\n"
183 "os_millis=%" PRId64 "\n"
184 "done=1\n",
185 (now - base::Time::UnixEpoch()).InMilliseconds()),
186 recent_os_meta_time)) {
187 return false;
188 }
189 recent_os_log_ = crash_directory.Append("recent_os.log");
190 if (!CreateFile(recent_os_log_, "", now))
191 return false;
192
193 // This should be serialized despite the old OS timestamp.
194 old_os_meta_ = crash_directory.Append("old_os.meta");
195 if (!CreateFile(old_os_meta_,
196 base::StringPrintf("payload=good.log\n"
197 "os_millis=%" PRId64 "\n"
198 "done=1\n",
199 ((now - base::Time::UnixEpoch()) -
200 base::TimeDelta::FromDays(200))
201 .InMilliseconds()),
202 old_os_meta_time)) {
203 return false;
204 }
205
206 // Create large metadata with the size of 1MiB + 1byte. This should be
207 // ignored as it's too big.
208 large_meta_ = crash_directory.Append("large.meta");
209 if (!CreateFile(large_meta_, std::string(1024 * 1024 + 1, 'x'), now)) {
210 return false;
211 }
212
213 return true;
214 }
215
Miriam Zimmerman18f8d622020-10-02 11:06:32 -0700216 base::ScopedTempDir temp_dir_;
217 base::FilePath test_dir_;
Miriam Zimmermanc57b2b32020-10-28 15:59:07 -0700218
219 base::FilePath good_meta_;
220 base::FilePath good_log_;
221 base::FilePath absolute_meta_;
222 base::FilePath absolute_log_;
223 base::FilePath uploaded_meta_;
224 base::FilePath uploaded_log_;
225 base::FilePath uploaded_already_;
226 base::FilePath root_payload_meta_;
227 base::FilePath devcore_meta_;
228 base::FilePath devcore_devcore_;
229 base::FilePath empty_meta_;
230 base::FilePath corrupted_meta_;
231 base::FilePath nonexistent_meta_;
232 base::FilePath unknown_meta_;
233 base::FilePath unknown_xxx_;
234 base::FilePath old_incomplete_meta_;
235 base::FilePath new_incomplete_meta_;
236 base::FilePath recent_os_meta_;
237 base::FilePath recent_os_log_;
238 base::FilePath old_os_meta_;
239 base::FilePath large_meta_;
Miriam Zimmerman18f8d622020-10-02 11:06:32 -0700240};
241
Miriam Zimmermanc57b2b32020-10-28 15:59:07 -0700242TEST_F(CrashSerializerTest, PickCrashFiles) {
243 Serializer::Options options;
244 Serializer serializer(std::make_unique<test_util::AdvancingClock>(), options);
245
246 const base::FilePath crash_directory =
247 paths::Get(paths::kSystemCrashDirectory);
248 ASSERT_TRUE(CreateDirectory(crash_directory));
249 ASSERT_TRUE(CreateTestCrashFiles(crash_directory));
250
251 std::vector<util::MetaFile> to_serialize;
252 serializer.PickCrashFiles(crash_directory, &to_serialize);
253 // Everything should still exist
254 EXPECT_TRUE(base::PathExists(good_meta_));
255 EXPECT_TRUE(base::PathExists(good_log_));
256 EXPECT_TRUE(base::PathExists(absolute_meta_));
257 EXPECT_TRUE(base::PathExists(absolute_log_));
258 EXPECT_TRUE(base::PathExists(uploaded_meta_));
259 EXPECT_TRUE(base::PathExists(uploaded_log_));
260 EXPECT_TRUE(base::PathExists(uploaded_already_));
261 EXPECT_TRUE(base::PathExists(root_payload_meta_));
262 EXPECT_TRUE(base::PathExists(devcore_meta_));
263 EXPECT_TRUE(base::PathExists(devcore_devcore_));
264 EXPECT_TRUE(base::PathExists(empty_meta_));
265 EXPECT_TRUE(base::PathExists(corrupted_meta_));
266 EXPECT_TRUE(base::PathExists(nonexistent_meta_));
267 EXPECT_TRUE(base::PathExists(unknown_meta_));
268 EXPECT_TRUE(base::PathExists(unknown_xxx_));
269 EXPECT_TRUE(base::PathExists(old_incomplete_meta_));
270 EXPECT_TRUE(base::PathExists(new_incomplete_meta_));
271 EXPECT_TRUE(base::PathExists(recent_os_meta_));
272 EXPECT_TRUE(base::PathExists(recent_os_log_));
273 EXPECT_TRUE(base::PathExists(old_os_meta_));
274 EXPECT_TRUE(base::PathExists(large_meta_));
275
276 ASSERT_EQ(6, to_serialize.size());
277 // Sort the reports to allow for deterministic testing
278 util::SortReports(&to_serialize);
279 EXPECT_EQ(old_os_meta_.value(), to_serialize[0].first.value());
280 EXPECT_EQ(good_meta_.value(), to_serialize[1].first.value());
281 EXPECT_EQ(absolute_meta_.value(), to_serialize[2].first.value());
282 EXPECT_EQ(uploaded_meta_.value(), to_serialize[3].first.value());
283 EXPECT_EQ(recent_os_meta_.value(), to_serialize[4].first.value());
284 EXPECT_EQ(devcore_meta_.value(), to_serialize[5].first.value());
285}
286
287TEST_F(CrashSerializerTest, SerializeCrashes) {
288 std::vector<util::MetaFile> crashes_to_serialize;
289
290 // Establish the client ID.
291 ASSERT_TRUE(test_util::CreateClientIdFile());
292
293 // Set up mock sending so we use the fake sleep function
294 ASSERT_TRUE(SetMockCrashSending(true));
295
296 // Create the system crash directory, and crash files in it.
297 const base::FilePath system_dir = paths::Get(paths::kSystemCrashDirectory);
298 ASSERT_TRUE(base::CreateDirectory(system_dir));
Miriam Zimmerman693a5622020-11-16 15:45:38 -0800299 const base::FilePath system_meta_file = system_dir.Append("0.0.0.0.0.meta");
300 const base::FilePath system_log = system_dir.Append("0.0.0.0.0.log");
Miriam Zimmermanc57b2b32020-10-28 15:59:07 -0700301 const base::FilePath system_processing =
Miriam Zimmerman693a5622020-11-16 15:45:38 -0800302 system_dir.Append("0.0.0.0.0.processing");
Miriam Zimmermanc57b2b32020-10-28 15:59:07 -0700303 const char system_meta[] =
Miriam Zimmerman693a5622020-11-16 15:45:38 -0800304 "payload=0.0.0.0.0.log\n"
Miriam Zimmermanc57b2b32020-10-28 15:59:07 -0700305 "exec_name=exec_foo\n"
306 "fake_report_id=123\n"
307 "upload_var_prod=foo\n"
308 "done=1\n"
309 "upload_var_reportTimeMillis=1000000\n";
310 ASSERT_TRUE(test_util::CreateFile(system_meta_file, system_meta));
311 ASSERT_TRUE(test_util::CreateFile(system_log, "system log data"));
312 util::CrashInfo system_info;
313 EXPECT_TRUE(system_info.metadata.LoadFromString(system_meta));
314 system_info.payload_file = system_log;
315 system_info.payload_kind = "log";
316 EXPECT_TRUE(base::Time::FromString("25 Apr 2018 1:23:44 GMT",
317 &system_info.last_modified));
318 crashes_to_serialize.emplace_back(system_meta_file, std::move(system_info));
319
320 // Create a user crash directory, and crash files in it.
321 const base::FilePath user_dir = paths::Get("/home/user/hash/crash");
322 ASSERT_TRUE(base::CreateDirectory(user_dir));
Miriam Zimmerman693a5622020-11-16 15:45:38 -0800323 const base::FilePath user_meta_file = user_dir.Append("0.0.0.0.0.meta");
324 const base::FilePath user_log = user_dir.Append("0.0.0.0.0.log");
325 const base::FilePath user_core = user_dir.Append("0.0.0.0.0.core");
326 const base::FilePath user_processing =
327 user_dir.Append("0.0.0.0.0.processing");
Miriam Zimmermanc57b2b32020-10-28 15:59:07 -0700328 const char user_meta[] =
Miriam Zimmerman693a5622020-11-16 15:45:38 -0800329 "payload=0.0.0.0.0.log\n"
Miriam Zimmermanc57b2b32020-10-28 15:59:07 -0700330 "exec_name=exec_bar\n"
331 "fake_report_id=456\n"
332 "upload_var_prod=bar\n"
333 "done=1\n"
334 "upload_var_reportTimeMillis=2000000\n";
335 ASSERT_TRUE(test_util::CreateFile(user_meta_file, user_meta));
336 ASSERT_TRUE(test_util::CreateFile(user_log, "user log data"));
337 ASSERT_TRUE(test_util::CreateFile(user_core, "user core"));
338 util::CrashInfo user_info;
339 EXPECT_TRUE(user_info.metadata.LoadFromString(user_meta));
340 user_info.payload_file = user_log;
341 user_info.payload_kind = "log";
342 EXPECT_TRUE(base::Time::FromString("25 Apr 2018 1:24:01 GMT",
343 &user_info.last_modified));
344 crashes_to_serialize.emplace_back(user_meta_file, std::move(user_info));
345
346 // Set up the serializer.
347 std::vector<base::TimeDelta> sleep_times;
348 Serializer::Options options;
349 options.fetch_coredumps = true;
350 options.sleep_function = base::Bind(&test_util::FakeSleep, &sleep_times);
351 Serializer serializer(std::make_unique<test_util::AdvancingClock>(), options);
352
353 base::FilePath out = test_dir_.Append("SerializeCrashes");
354 ASSERT_TRUE(test_util::CreateFile(out, ""));
355 serializer.set_output_for_testing(out);
356
357 serializer.SerializeCrashes(crashes_to_serialize);
358
359 EXPECT_EQ(2, sleep_times.size());
360
361 // We shouldn't be processing any crashes still.
362 EXPECT_FALSE(base::PathExists(system_processing));
363 EXPECT_FALSE(base::PathExists(user_processing));
364
365 std::string written;
366 ASSERT_TRUE(base::ReadFileToString(out, &written));
367
368 // Deserialize the data.
369 std::vector<crash::FetchCrashesResponse> resps;
370 uint64_t pos = 0;
371 while (pos < written.size()) {
372 std::string size_str = written.substr(pos, sizeof(uint64_t));
373 uint64_t size;
374 base::ReadBigEndian(size_str.data(), &size);
375 pos += sizeof(size);
376
377 // All of our payloads are small, so don't need to combine subsequent
378 // response protos into one.
379 crash::FetchCrashesResponse resp;
380 resp.ParseFromString(written.substr(pos, size));
381 resps.push_back(resp);
382
383 pos += size;
384 }
385 ASSERT_EQ(resps.size(), 5);
386
387 // Verify system crash
388 EXPECT_EQ(resps[0].crash_id(), 0);
389 ASSERT_TRUE(resps[0].has_crash());
390 EXPECT_EQ(resps[0].crash().exec_name(), "exec_foo");
391 EXPECT_EQ(resps[0].crash().prod(), "foo");
392 EXPECT_EQ(resps[0].crash().ver(), "undefined");
393 EXPECT_EQ(resps[0].crash().sig(), "");
394 EXPECT_EQ(resps[0].crash().in_progress_integration_test(), "");
395 EXPECT_EQ(resps[0].crash().collector(), "");
396 EXPECT_EQ(resps[0].crash().collector(), "");
397 int num_fields = resps[0].crash().fields_size();
398 ASSERT_GE(num_fields, 7);
399 EXPECT_EQ(resps[0].crash().fields(6).key(), "guid");
400 EXPECT_EQ(resps[0].crash().fields(6).text(), kFakeClientId);
401
402 EXPECT_EQ(resps[1].crash_id(), 0);
403 ASSERT_TRUE(resps[1].has_blob());
404 EXPECT_EQ(resps[1].blob().key(), "upload_file_log");
Miriam Zimmerman693a5622020-11-16 15:45:38 -0800405 EXPECT_EQ(resps[1].blob().filename(), "0.0.0.0.0.log");
Miriam Zimmermanc57b2b32020-10-28 15:59:07 -0700406 EXPECT_EQ(resps[1].blob().blob(), "system log data");
407
408 // Verify user crash
409 EXPECT_EQ(resps[2].crash_id(), 1);
410 ASSERT_TRUE(resps[2].has_crash());
411 EXPECT_EQ(resps[2].crash().exec_name(), "exec_bar");
412 EXPECT_EQ(resps[2].crash().prod(), "bar");
413 EXPECT_EQ(resps[2].crash().ver(), "undefined");
414 EXPECT_EQ(resps[2].crash().sig(), "");
415 EXPECT_EQ(resps[2].crash().in_progress_integration_test(), "");
416 EXPECT_EQ(resps[2].crash().collector(), "");
417 num_fields = resps[2].crash().fields_size();
418 ASSERT_GE(num_fields, 7);
419 EXPECT_EQ(resps[2].crash().fields(6).key(), "guid");
420 EXPECT_EQ(resps[2].crash().fields(6).text(), kFakeClientId);
421
422 EXPECT_EQ(resps[3].crash_id(), 1);
423 ASSERT_TRUE(resps[3].has_blob());
424 EXPECT_EQ(resps[3].blob().key(), "upload_file_log");
Miriam Zimmerman693a5622020-11-16 15:45:38 -0800425 EXPECT_EQ(resps[3].blob().filename(), "0.0.0.0.0.log");
Miriam Zimmermanc57b2b32020-10-28 15:59:07 -0700426 EXPECT_EQ(resps[3].blob().blob(), "user log data");
427 EXPECT_EQ(resps[4].crash_id(), 1);
428
429 EXPECT_EQ(resps[4].crash_id(), 1);
430 // proto3 doesn't create has_XXX methods for string oneof fields, so don't
431 // check has_core()
432 EXPECT_EQ(resps[4].core(), "user core");
433
434 // The uploaded crash files should not be removed.
435 EXPECT_TRUE(base::PathExists(system_meta_file));
436 EXPECT_TRUE(base::PathExists(system_log));
437 EXPECT_TRUE(base::PathExists(user_meta_file));
438 EXPECT_TRUE(base::PathExists(user_log));
439 EXPECT_TRUE(base::PathExists(user_core));
440}
441
442TEST_F(CrashSerializerTest, WriteFetchCrashesResponse) {
443 crash::FetchCrashesResponse resp;
444 resp.set_crash_id(0x1234'5678'9abc'def0);
445 resp.set_core(std::string("\00\x11\x22\x33", 4));
446 std::string expected;
447 resp.SerializeToString(&expected);
448
449 Serializer::Options options;
450 Serializer serializer(std::make_unique<test_util::AdvancingClock>(), options);
451
452 base::FilePath out = test_dir_.Append("WriteFetchCrashesResponse");
453 ASSERT_TRUE(test_util::CreateFile(out, ""));
454 serializer.set_output_for_testing(out);
455
456 ASSERT_TRUE(serializer.WriteFetchCrashesResponse(resp));
457 std::string actual;
458 ASSERT_TRUE(base::ReadFileToString(out, &actual));
459
460 // Read the size and verify that it matches what we expect.
461 std::string actual_size_str = actual.substr(0, sizeof(uint64_t));
462 uint64_t actual_size;
463 base::ReadBigEndian(actual_size_str.data(), &actual_size);
464 EXPECT_EQ(expected.size(), actual_size);
465
466 // Note that we don't verify that the size in bytes matches, because to do so
467 // we'd either have to:
468 // 1) Reproduce the logic in WriteFetchCrashesResponse that converts the size
469 // to a string, or
470 // 2) Hard-code an expected size, which would be brittle and subject to
471 // breakage if the protobuf serialization format changes at all in future.
472 EXPECT_EQ(expected, actual.substr(sizeof(uint64_t)));
473}
474
475TEST_F(CrashSerializerTest, WriteFetchCrashesResponse_WriteFail) {
476 crash::FetchCrashesResponse resp;
477 resp.set_crash_id(42);
478 resp.set_core("asdf");
479
480 Serializer::Options options;
481 Serializer serializer(std::make_unique<test_util::AdvancingClock>(), options);
482
483 base::FilePath out = test_dir_.Append("WriteFetchCrashesResponse_WriteFail");
484 // Don't create file -- Append in serializer will fail.
485 serializer.set_output_for_testing(out);
486
487 EXPECT_FALSE(serializer.WriteFetchCrashesResponse(resp));
488}
489
490TEST_F(CrashSerializerTest, WriteBlobs_Basic) {
491 std::vector<crash::CrashBlob> blobs;
492 crash::CrashBlob blob1;
493 blob1.set_key("1701d");
494 blob1.set_filename("jean.luc.picard");
495 blob1.set_blob("boldly go");
496 blobs.push_back(blob1);
497
498 crash::CrashBlob blob2;
499 blob2.set_key("nx01");
500 blob2.set_filename("jonathan.archer");
501 blob2.set_blob("temporal cold war");
502 blobs.push_back(blob2);
503
504 Serializer::Options options;
505 Serializer serializer(std::make_unique<test_util::AdvancingClock>(), options);
506
507 base::FilePath out = test_dir_.Append("WriteBlobs_Basic");
508 ASSERT_TRUE(test_util::CreateFile(out, ""));
509 serializer.set_output_for_testing(out);
510
511 ASSERT_TRUE(serializer.WriteBlobs(/*crash_id=*/42, blobs));
512 std::string actual;
513 ASSERT_TRUE(base::ReadFileToString(out, &actual));
514
515 uint64_t pos = 0;
516 for (const auto& blob : blobs) {
517 std::string actual_size_str = actual.substr(pos, sizeof(uint64_t));
518 pos += sizeof(uint64_t);
519 uint64_t actual_size;
520 base::ReadBigEndian(actual_size_str.data(), &actual_size);
521 crash::FetchCrashesResponse resp;
522 resp.ParseFromString(actual.substr(pos, actual_size));
523 EXPECT_EQ(resp.crash_id(), 42);
524 EXPECT_EQ(resp.blob().key(), blob.key());
525 EXPECT_EQ(resp.blob().filename(), blob.filename());
526 EXPECT_EQ(resp.blob().blob(), blob.blob());
527 pos += actual_size;
528 }
529 EXPECT_EQ(pos, actual.size()); // should be at end of string
530}
531
532TEST_F(CrashSerializerTest, WriteBlobs_ManySizes) {
533 Serializer::Options options;
534 options.max_proto_bytes = 18; // choose an arbitrary (but small) maximum
535 Serializer serializer(std::make_unique<test_util::AdvancingClock>(), options);
536
537 base::FilePath out = test_dir_.Append("WriteBlobs_ManySizes");
538 ASSERT_TRUE(test_util::CreateFile(out, ""));
539 serializer.set_output_for_testing(out);
540
541 std::vector<crash::CrashBlob> blobs;
542 for (int i = 0; i < options.max_proto_bytes * 5; i++) {
543 crash::CrashBlob blob;
544 blob.set_key(base::StringPrintf("%d", i));
545 blob.set_filename(base::StringPrintf("%d.blob", i));
546 blob.set_blob(std::string('A', i));
547 blobs.push_back(blob);
548 }
549
550 ASSERT_TRUE(serializer.WriteBlobs(/*crash_id=*/0xc0de, blobs));
551 std::string actual;
552 ASSERT_TRUE(base::ReadFileToString(out, &actual));
553
554 std::vector<crash::CrashBlob> actual_blobs;
555 uint64_t pos = 0;
556 while (pos < actual.size()) {
557 std::string actual_size_str = actual.substr(pos, sizeof(uint64_t));
558 pos += sizeof(uint64_t);
559 uint64_t actual_size;
560 base::ReadBigEndian(actual_size_str.data(), &actual_size);
561 crash::FetchCrashesResponse resp;
562 resp.ParseFromString(actual.substr(pos, actual_size));
563 pos += actual_size;
564
565 EXPECT_EQ(resp.crash_id(), 0xc0de);
566
567 crash::CrashBlob blob = resp.blob();
568 EXPECT_LE(blob.blob().size(), options.max_proto_bytes);
569 if (actual_blobs.size() > 0 && actual_blobs.back().key() == blob.key()) {
570 EXPECT_EQ(actual_blobs.back().filename(), blob.filename());
571 actual_blobs.back().set_blob(actual_blobs.back().blob() + blob.blob());
572 } else {
573 actual_blobs.push_back(blob);
574 }
575 }
576
577 ASSERT_EQ(actual_blobs.size(), blobs.size());
578 for (int i = 0; i < actual_blobs.size(); i++) {
579 EXPECT_EQ(actual_blobs[i].key(), blobs[i].key());
580 EXPECT_EQ(actual_blobs[i].filename(), blobs[i].filename());
581 EXPECT_EQ(actual_blobs[i].blob(), blobs[i].blob());
582 }
583}
584
585TEST_F(CrashSerializerTest, WriteBlobs_Empty) {
586 Serializer::Options options;
587 Serializer serializer(std::make_unique<test_util::AdvancingClock>(), options);
588
589 base::FilePath out = test_dir_.Append("WriteBlobs_Empty");
590 // Don't create file -- we shouldn't write to it.
591 serializer.set_output_for_testing(out);
592
593 std::vector<crash::CrashBlob> blobs;
594 EXPECT_TRUE(serializer.WriteBlobs(/*crash_id=*/0, blobs));
595}
596
597TEST_F(CrashSerializerTest, WriteBlobs_Failure) {
598 Serializer::Options options;
599 Serializer serializer(std::make_unique<test_util::AdvancingClock>(), options);
600
601 base::FilePath out = test_dir_.Append("WriteBlobs_Failure");
602 // Don't create file -- Append in serializer will fail.
603 serializer.set_output_for_testing(out);
604
605 std::vector<crash::CrashBlob> blobs;
606 crash::CrashBlob blob;
607 blob.set_key("key mckeyface");
608 blob.set_filename("key.face");
609 blob.set_blob("asdf");
610 blobs.push_back(blob);
611 EXPECT_FALSE(serializer.WriteBlobs(/*crash_id=*/1, blobs));
612}
613
614TEST_F(CrashSerializerTest, WriteCoredump_Basic) {
615 // Core dumps can and do have null bytes in them.
616 std::string core_contents("\x00\x11\x22\x33", 4);
617 ASSERT_EQ(core_contents.size(), 4);
618
619 crash::FetchCrashesResponse resp;
620 resp.set_crash_id(0x1234'5678'9abc'def0);
621 resp.set_core(core_contents);
622 std::string expected;
623 resp.SerializeToString(&expected);
624
625 base::FilePath core = test_dir_.Append("core");
626 ASSERT_TRUE(test_util::CreateFile(core, core_contents));
627
628 Serializer::Options options;
629 Serializer serializer(std::make_unique<test_util::AdvancingClock>(), options);
630
631 base::FilePath out = test_dir_.Append("WriteCoredump");
632 ASSERT_TRUE(test_util::CreateFile(out, ""));
633 serializer.set_output_for_testing(out);
634
635 ASSERT_TRUE(
636 serializer.WriteCoredump(/*crash_id=*/0x1234'5678'9abc'def0, core));
637 std::string actual;
638 ASSERT_TRUE(base::ReadFileToString(out, &actual));
639
640 std::string actual_size_str = actual.substr(0, sizeof(uint64_t));
641 uint64_t actual_size;
642 base::ReadBigEndian(actual_size_str.data(), &actual_size);
643 EXPECT_EQ(expected.size(), actual_size);
644 EXPECT_EQ(expected, actual.substr(sizeof(uint64_t)));
645}
646
647TEST_F(CrashSerializerTest, WriteCoredump_LargerThanChunkSize) {
648 std::string core_contents("0123456789abcdef");
649 base::FilePath core = test_dir_.Append("core");
650 ASSERT_TRUE(test_util::CreateFile(core, core_contents));
651
652 crash::FetchCrashesResponse resp1;
653 resp1.set_crash_id(1);
654 resp1.set_core("0123456789");
655 std::string expected1;
656 resp1.SerializeToString(&expected1);
657
658 crash::FetchCrashesResponse resp2;
659 resp2.set_crash_id(1); // same crash id
660 resp2.set_core("abcdef");
661 std::string expected2;
662 resp2.SerializeToString(&expected2);
663
664 Serializer::Options options;
665 options.max_proto_bytes = 10;
666 Serializer serializer(std::make_unique<test_util::AdvancingClock>(), options);
667
668 base::FilePath out = test_dir_.Append("WriteCoredump_LargerThanChunkSize");
669 ASSERT_TRUE(test_util::CreateFile(out, ""));
670 serializer.set_output_for_testing(out);
671
672 ASSERT_TRUE(serializer.WriteCoredump(/*crash_id=*/1, core));
673 std::string actual;
674 ASSERT_TRUE(base::ReadFileToString(out, &actual));
675
676 uint64_t pos = 0;
677 std::string actual_size_str1 = actual.substr(0, sizeof(uint64_t));
678 pos += sizeof(uint64_t);
679 uint64_t actual_size1;
680 base::ReadBigEndian(actual_size_str1.data(), &actual_size1);
681 EXPECT_EQ(expected1.size(), actual_size1);
682 EXPECT_EQ(expected1, actual.substr(pos, actual_size1));
683 pos += actual_size1;
684
685 std::string actual_size_str2 = actual.substr(pos, sizeof(uint64_t));
686 pos += sizeof(uint64_t);
687 uint64_t actual_size2;
688 base::ReadBigEndian(actual_size_str2.data(), &actual_size2);
689 EXPECT_EQ(expected2.size(), actual_size2);
690 EXPECT_EQ(expected2, actual.substr(pos));
691}
692
693// Verify that core dump splitting works at many different core sizes (with
694// different relationships to the chunk size).
695TEST_F(CrashSerializerTest, WriteCoredump_ManySizes) {
696 const int kChunkSize = 10;
697 Serializer::Options options;
698 options.max_proto_bytes = kChunkSize;
699 Serializer serializer(std::make_unique<test_util::AdvancingClock>(), options);
700
701 for (int core_size = 1; core_size <= kChunkSize * 5; core_size++) {
702 std::string core_contents('0', core_size);
703 base::FilePath core = test_dir_.Append("core");
704 ASSERT_TRUE(test_util::CreateFile(core, core_contents));
705
706 base::FilePath out = test_dir_.Append("WriteCoredump_ManySizes");
707 ASSERT_TRUE(test_util::CreateFile(out, ""));
708 serializer.set_output_for_testing(out);
709
710 ASSERT_TRUE(serializer.WriteCoredump(/*crash_id=*/1, core));
711 std::string actual;
712 ASSERT_TRUE(base::ReadFileToString(out, &actual));
713
714 std::string assembled_core;
715 crash::FetchCrashesResponse resp;
716 uint64_t pos = 0;
717 while (pos < actual.size()) {
718 std::string actual_size_str = actual.substr(0, sizeof(uint64_t));
719 pos += sizeof(uint64_t);
720 uint64_t actual_size;
721 base::ReadBigEndian(actual_size_str.data(), &actual_size);
722
723 resp.ParseFromString(actual.substr(pos, actual_size));
724 EXPECT_EQ(resp.crash_id(), 1) << "core size: " << core_size;
725 EXPECT_LE(resp.core().size(), kChunkSize) << "core size: " << core_size;
726 assembled_core += resp.core();
727 pos += actual_size;
728 }
729 EXPECT_EQ(assembled_core, core_contents) << "core size: " << core_size;
730 }
731}
732
733TEST_F(CrashSerializerTest, WriteCoredump_Nonexistent) {
734 Serializer::Options options;
735 Serializer serializer(std::make_unique<test_util::AdvancingClock>(), options);
736 EXPECT_FALSE(serializer.WriteCoredump(/*crash_id=*/0,
737 test_dir_.Append("nonexistent.core")));
738}
739
Miriam Zimmerman18f8d622020-10-02 11:06:32 -0700740enum MissingFile {
741 kNone,
742 kPayloadFile,
743 kLogFile,
744 kTextFile,
745 kBinFile,
746 kCoreFile,
747};
748
749class CrashSerializerParameterizedTest
750 : public CrashSerializerTest,
751 public ::testing::WithParamInterface<
752 std::tuple<bool, bool, MissingFile>> {
753 protected:
754 void SetUp() override {
755 std::tie(absolute_paths_, fetch_core_, missing_file_) = GetParam();
756 CrashSerializerTest::SetUp();
757 }
758 bool absolute_paths_;
759 bool fetch_core_;
760 MissingFile missing_file_;
761};
762
Miriam Zimmermanc57b2b32020-10-28 15:59:07 -0700763TEST_P(CrashSerializerParameterizedTest, SerializeCrash) {
Miriam Zimmerman18f8d622020-10-02 11:06:32 -0700764 const base::FilePath system_dir = paths::Get(paths::kSystemCrashDirectory);
765 ASSERT_TRUE(base::CreateDirectory(system_dir));
766
Miriam Zimmerman693a5622020-11-16 15:45:38 -0800767 const base::FilePath payload_file_relative("0.0.0.0.0.payload");
Miriam Zimmerman18f8d622020-10-02 11:06:32 -0700768 const base::FilePath payload_file_absolute =
769 system_dir.Append(payload_file_relative);
770 const std::string payload_contents = "foobar_payload";
771 if (missing_file_ != kPayloadFile) {
772 ASSERT_TRUE(test_util::CreateFile(payload_file_absolute, payload_contents));
773 }
774 const base::FilePath& payload_file =
775 absolute_paths_ ? payload_file_absolute : payload_file_relative;
776
Miriam Zimmerman693a5622020-11-16 15:45:38 -0800777 const base::FilePath log_file_relative("0.0.0.0.0.log");
Miriam Zimmerman18f8d622020-10-02 11:06:32 -0700778 const base::FilePath log_file_absolute = system_dir.Append(log_file_relative);
779 const std::string log_contents = "foobar_log";
780 if (missing_file_ != kLogFile) {
781 ASSERT_TRUE(test_util::CreateFile(log_file_absolute, log_contents));
782 }
783 const base::FilePath& log_file =
784 absolute_paths_ ? log_file_absolute : log_file_relative;
785
786 const base::FilePath text_var_file_relative("data.txt");
787 const base::FilePath text_var_file_absolute =
788 system_dir.Append(text_var_file_relative);
789 const std::string text_var_contents = "upload_text_contents";
790 if (missing_file_ != kTextFile) {
791 ASSERT_TRUE(
792 test_util::CreateFile(text_var_file_absolute, text_var_contents));
793 }
794 const base::FilePath& text_var_file =
795 absolute_paths_ ? text_var_file_absolute : text_var_file_relative;
796
797 const base::FilePath file_var_file_relative("data.bin");
798 const base::FilePath file_var_file_absolute =
799 system_dir.Append(file_var_file_relative);
800 const std::string file_var_contents = "upload_file_contents";
801 if (missing_file_ != kBinFile) {
802 ASSERT_TRUE(
803 test_util::CreateFile(file_var_file_absolute, file_var_contents));
804 }
805 const base::FilePath& file_var_file =
806 absolute_paths_ ? file_var_file_absolute : file_var_file_relative;
807
Miriam Zimmerman693a5622020-11-16 15:45:38 -0800808 const base::FilePath core_file_relative("0.0.0.0.0.core");
Miriam Zimmerman18f8d622020-10-02 11:06:32 -0700809 const base::FilePath core_file_absolute =
810 system_dir.Append(core_file_relative);
811 const std::string core_contents = "corey_mccoreface";
812 if (missing_file_ != kCoreFile) {
813 ASSERT_TRUE(test_util::CreateFile(core_file_absolute, core_contents));
814 }
815
816 brillo::KeyValueStore metadata;
817 metadata.SetString("exec_name", "fake_exec_name");
818 metadata.SetString("ver", "fake_chromeos_ver");
819 metadata.SetString("upload_var_prod", "fake_product");
820 metadata.SetString("upload_var_ver", "fake_version");
821 metadata.SetString("sig", "fake_sig");
822 metadata.SetString("upload_var_guid", "SHOULD_NOT_BE_USED");
823 metadata.SetString("upload_var_foovar", "bar");
824 metadata.SetString("upload_var_in_progress_integration_test", "test.Test");
825 metadata.SetString("upload_var_collector", "fake_collector");
826 metadata.SetString("upload_text_footext", text_var_file.value());
827 metadata.SetString("upload_file_log", log_file.value());
828 metadata.SetString("upload_file_foofile", file_var_file.value());
829 metadata.SetString("error_type", "fake_error");
830
831 util::CrashDetails details = {
Miriam Zimmerman693a5622020-11-16 15:45:38 -0800832 .meta_file = base::FilePath(system_dir).Append("0.0.0.0.0.meta"),
Miriam Zimmerman18f8d622020-10-02 11:06:32 -0700833 .payload_file = payload_file,
834 .payload_kind = "fake_payload",
835 .client_id = kFakeClientId,
836 .metadata = metadata,
837 };
838
839 Serializer::Options options;
840 options.fetch_coredumps = fetch_core_;
841
842 Serializer serializer(std::make_unique<test_util::AdvancingClock>(), options);
843
844 crash::CrashInfo info;
845 std::vector<crash::CrashBlob> blobs;
846 base::FilePath core_path;
847 EXPECT_EQ(serializer.SerializeCrash(details, &info, &blobs, &core_path),
848 missing_file_ != kPayloadFile);
849
850 if (missing_file_ == kPayloadFile) {
851 return;
852 }
853
854 // We'd really like to set up a proto with the expected values and
855 // EXPECT_THAT(info, EqualsProto(expected_info)), but EqualsProto is
856 // unavailable in chromium OS, so do it one field at a time instead.
857 EXPECT_EQ(info.exec_name(), "fake_exec_name");
858 EXPECT_EQ(info.prod(), "fake_product");
859 EXPECT_EQ(info.ver(), "fake_version");
860 EXPECT_EQ(info.sig(), "fake_sig");
861 EXPECT_EQ(info.in_progress_integration_test(), "test.Test");
862 EXPECT_EQ(info.collector(), "fake_collector");
863
864 int num_fields = 8;
865 if (missing_file_ != kTextFile) {
866 num_fields++;
867 }
868
869 ASSERT_EQ(info.fields_size(), num_fields);
870
871 int field_idx = 0;
872 EXPECT_EQ(info.fields(field_idx).key(), "board");
873 EXPECT_EQ(info.fields(field_idx).text(), "undefined");
874 field_idx++;
875
876 EXPECT_EQ(info.fields(field_idx).key(), "hwclass");
877 EXPECT_EQ(info.fields(field_idx).text(), "undefined");
878 field_idx++;
879
880 EXPECT_EQ(info.fields(field_idx).key(), "sig2");
881 EXPECT_EQ(info.fields(field_idx).text(), "fake_sig");
882 field_idx++;
883
884 EXPECT_EQ(info.fields(field_idx).key(), "image_type");
885 EXPECT_EQ(info.fields(field_idx).text(), "");
886 field_idx++;
887
888 EXPECT_EQ(info.fields(field_idx).key(), "boot_mode");
889 EXPECT_EQ(info.fields(field_idx).text(), "missing-crossystem");
890 field_idx++;
891
892 EXPECT_EQ(info.fields(field_idx).key(), "error_type");
893 EXPECT_EQ(info.fields(field_idx).text(), "fake_error");
894 field_idx++;
895
896 EXPECT_EQ(info.fields(field_idx).key(), "guid");
897 EXPECT_EQ(info.fields(field_idx).text(), "00112233445566778899aabbccddeeff");
898 field_idx++;
899
900 if (missing_file_ != kTextFile) {
901 EXPECT_EQ(info.fields(field_idx).key(), "footext");
902 EXPECT_EQ(info.fields(field_idx).text(), "upload_text_contents");
903 field_idx++;
904 }
905
906 EXPECT_EQ(info.fields(field_idx).key(), "foovar");
907 EXPECT_EQ(info.fields(field_idx).text(), "bar");
908 field_idx++;
909
910 int num_blobs = 1;
911 if (missing_file_ != kBinFile) {
912 num_blobs++;
913 }
914 if (missing_file_ != kLogFile) {
915 num_blobs++;
916 }
917
918 ASSERT_EQ(blobs.size(), num_blobs);
919
920 int blob_idx = 0;
921 EXPECT_EQ(blobs[blob_idx].key(), "upload_file_fake_payload");
922 EXPECT_EQ(blobs[blob_idx].blob(), "foobar_payload");
923 EXPECT_EQ(blobs[blob_idx].filename(), payload_file_relative.value());
924 blob_idx++;
925
926 if (missing_file_ != kBinFile) {
927 EXPECT_EQ(blobs[blob_idx].key(), "foofile");
928 EXPECT_EQ(blobs[blob_idx].blob(), "upload_file_contents");
929 EXPECT_EQ(blobs[blob_idx].filename(), file_var_file_relative.value());
930 blob_idx++;
931 }
932
933 if (missing_file_ != kLogFile) {
934 EXPECT_EQ(blobs[blob_idx].key(), "log");
935 EXPECT_EQ(blobs[blob_idx].blob(), "foobar_log");
936 EXPECT_EQ(blobs[blob_idx].filename(), log_file_relative.value());
937 blob_idx++;
938 }
939
940 if (missing_file_ != kCoreFile && fetch_core_) {
941 EXPECT_EQ(core_path, core_file_absolute);
942 } else {
943 EXPECT_EQ(core_path, base::FilePath());
944 }
945}
946
947INSTANTIATE_TEST_SUITE_P(CrashSerializerParameterizedTestInstantiation,
948 CrashSerializerParameterizedTest,
949 testing::Combine(testing::Bool(),
950 testing::Bool(),
951 testing::Values(kNone,
952 kPayloadFile,
953 kLogFile,
954 kTextFile,
955 kBinFile,
956 kCoreFile)));
957
958} // namespace crash_serializer