Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(367)

Side by Side Diff: components/browser_watcher/postmortem_report_collector_unittest.cc

Issue 2128683002: Collect unclean shutdown debug information (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@tracker
Patch Set: Merge Created 4 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2016 The Chromium 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 "components/browser_watcher/postmortem_report_collector.h"
6
7 #include <stdint.h>
8
9 #include <memory>
10 #include <set>
11 #include <string>
12 #include <utility>
13 #include <vector>
14
15 #include "base/debug/activity_tracker.h"
16 #include "base/files/file.h"
17 #include "base/files/file_path.h"
18 #include "base/files/file_util.h"
19 #include "base/files/memory_mapped_file.h"
20 #include "base/files/scoped_file.h"
21 #include "base/files/scoped_temp_dir.h"
22 #include "base/memory/ptr_util.h"
23 #include "base/metrics/persistent_memory_allocator.h"
24 #include "base/threading/platform_thread.h"
25 #include "testing/gmock/include/gmock/gmock.h"
26 #include "testing/gtest/include/gtest/gtest.h"
27 #include "third_party/crashpad/crashpad/client/crash_report_database.h"
28
29 namespace browser_watcher {
30
31 using base::debug::GlobalActivityTracker;
32 using base::debug::ThreadActivityTracker;
33 using base::File;
34 using base::FilePersistentMemoryAllocator;
35 using base::MemoryMappedFile;
36 using base::PersistentMemoryAllocator;
37 using base::WrapUnique;
38 using crashpad::CrashReportDatabase;
39 using crashpad::Settings;
40 using crashpad::UUID;
41 using testing::_;
42 using testing::Return;
43 using testing::SetArgPointee;
44
45 namespace {
46
47 // Exposes a public constructor in order to create a dummy database.
48 class MockCrashReportDatabase : CrashReportDatabase {
49 public:
50 MockCrashReportDatabase() {}
51 MOCK_METHOD0(GetSettings, Settings*());
52 MOCK_METHOD1(PrepareNewCrashReport,
53 CrashReportDatabase::CrashReportDatabase::OperationStatus(
54 NewReport** report));
55 MOCK_METHOD2(FinishedWritingCrashReport,
56 CrashReportDatabase::CrashReportDatabase::OperationStatus(
57 CrashReportDatabase::NewReport* report,
58 crashpad::UUID* uuid));
59 MOCK_METHOD1(ErrorWritingCrashReport,
60 CrashReportDatabase::CrashReportDatabase::OperationStatus(
61 NewReport* report));
62 MOCK_METHOD2(LookUpCrashReport,
63 CrashReportDatabase::CrashReportDatabase::OperationStatus(
64 const UUID& uuid,
65 Report* report));
66 MOCK_METHOD1(
67 GetPendingReports,
68 CrashReportDatabase::OperationStatus(std::vector<Report>* reports));
69 MOCK_METHOD1(
70 GetCompletedReports,
71 CrashReportDatabase::OperationStatus(std::vector<Report>* reports));
72 MOCK_METHOD2(GetReportForUploading,
73 CrashReportDatabase::OperationStatus(const UUID& uuid,
74 const Report** report));
75 MOCK_METHOD3(RecordUploadAttempt,
76 CrashReportDatabase::OperationStatus(const Report* report,
77 bool successful,
78 const std::string& id));
79 MOCK_METHOD1(SkipReportUpload,
80 CrashReportDatabase::OperationStatus(const UUID& uuid));
81 MOCK_METHOD1(DeleteReport,
82 CrashReportDatabase::OperationStatus(const UUID& uuid));
83 };
84
85 // Used for testing CollectAndSubmitForUpload.
86 class MockPostmortemReportCollector : public PostmortemReportCollector {
87 public:
88 MockPostmortemReportCollector(
89 const base::FilePath& debug_dir,
90 const base::FilePath::StringType& debug_file_pattern,
91 std::unique_ptr<crashpad::CrashReportDatabase> database)
92 : PostmortemReportCollector(debug_dir,
93 debug_file_pattern,
94 std::move(database)) {}
95
96 // A function that returns a unique_ptr cannot be mocked, so mock a function
97 // that returns a raw pointer instead.
98 std::unique_ptr<StabilityReport> Collect(
99 const base::FilePath& debug_state_file) override {
100 return std::unique_ptr<StabilityReport>(CollectRaw(debug_state_file));
101 }
102
103 MOCK_METHOD1(GetDebugStateFilePaths,
104 std::vector<base::FilePath>(const std::set<base::FilePath>&));
105 MOCK_METHOD1(CollectRaw, StabilityReport*(const base::FilePath&));
106 MOCK_METHOD4(WriteReportToMinidump,
107 bool(const StabilityReport& report,
108 const crashpad::UUID& client_id,
109 const crashpad::UUID& report_id,
110 FILE* minidump_file));
111 };
112
113 // Checks if two proto messages are the same.
114 MATCHER_P(EqualsProto, message, "") {
115 std::string expected_serialized;
116 std::string actual_serialized;
117 message.SerializeToString(&expected_serialized);
118 arg.SerializeToString(&actual_serialized);
119 return expected_serialized == actual_serialized;
120 }
121
122 } // namespace
123
124 TEST(PostmortemReportCollectorTest, CollectAndSubmitForUpload) {
125 // Create a dummy debug file, to validate deletion.
126 base::ScopedTempDir temp_dir;
127 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
128 base::FilePath debug_file = temp_dir.path().AppendASCII("foo-1.pma");
129 {
130 base::ScopedFILE file(base::OpenFile(debug_file, "w"));
131 ASSERT_NE(file.get(), nullptr);
132 }
133 ASSERT_TRUE(base::PathExists(debug_file));
134
135 // Create mocks.
136 // Note: the database is owned by the collector.
137 MockCrashReportDatabase* database = new MockCrashReportDatabase();
138 MockPostmortemReportCollector collector(
139 debug_file.DirName(), FILE_PATH_LITERAL("foo-*.pma"),
140 std::unique_ptr<CrashReportDatabase>(database));
141
142 // Expect collection of the debug file paths.
143 std::set<base::FilePath> no_excluded_files;
144 std::vector<base::FilePath> debug_files{debug_file};
145 EXPECT_CALL(collector, GetDebugStateFilePaths(no_excluded_files))
146 .Times(1)
147 .WillOnce(Return(debug_files));
148
149 EXPECT_CALL(*database, GetSettings()).Times(1).WillOnce(Return(nullptr));
150
151 // Expect collection to a proto of a single debug file.
152 // Note: caller takes ownership.
153 StabilityReport* stability_report = new StabilityReport();
154 EXPECT_CALL(collector, CollectRaw(debug_file))
155 .Times(1)
156 .WillOnce(Return(stability_report));
157
158 // Expect the call to write the proto to a minidump. This involves requesting
159 // a report from the crashpad database, writing the report, then finalizing it
160 // with the database.
161 base::FilePath minidump_path = temp_dir.path().AppendASCII("foo-1.dmp");
162 base::ScopedFILE minidump_fd(base::OpenFile(minidump_path, "wb"));
163 CrashReportDatabase::NewReport crashpad_report = {
164 minidump_fd.get(), crashpad::UUID(UUID::InitializeWithNewTag{}),
165 minidump_path};
166 EXPECT_CALL(*database, PrepareNewCrashReport(_))
167 .Times(1)
168 .WillOnce(DoAll(SetArgPointee<0>(&crashpad_report),
169 Return(CrashReportDatabase::kNoError)));
170
171 EXPECT_CALL(collector, WriteReportToMinidump(EqualsProto(*stability_report),
172 _, _, minidump_fd.get()))
173 .Times(1)
174 .WillOnce(Return(true));
175
176 EXPECT_CALL(*database, FinishedWritingCrashReport(&crashpad_report, _))
177 .Times(1)
178 .WillOnce(Return(CrashReportDatabase::kNoError));
179
180 // Run the test.
181 collector.CollectAndSubmitForUpload(no_excluded_files);
182 ASSERT_FALSE(base::PathExists(debug_file));
183 }
184
185 TEST(PostmortemReportCollectorTest, GetDebugStateFilePaths) {
186 base::ScopedTempDir temp_dir;
187 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
188
189 // Create files.
190 std::vector<base::FilePath> expected_paths;
191 std::set<base::FilePath> excluded_paths;
192 {
193 // Matches the pattern.
194 base::FilePath path = temp_dir.path().AppendASCII("foo1.pma");
195 base::ScopedFILE file(base::OpenFile(path, "w"));
196 ASSERT_NE(file.get(), nullptr);
197 expected_paths.push_back(path);
198
199 // Matches the pattern, but is excluded.
200 path = temp_dir.path().AppendASCII("foo2.pma");
201 file.reset(base::OpenFile(path, "w"));
202 ASSERT_NE(file.get(), nullptr);
203 ASSERT_TRUE(excluded_paths.insert(path).second);
204
205 // Matches the pattern.
206 path = temp_dir.path().AppendASCII("foo3.pma");
207 file.reset(base::OpenFile(path, "w"));
208 ASSERT_NE(file.get(), nullptr);
209 expected_paths.push_back(path);
210
211 // Does not match the pattern.
212 path = temp_dir.path().AppendASCII("bar.baz");
213 file.reset(base::OpenFile(path, "w"));
214 ASSERT_NE(file.get(), nullptr);
215 }
216
217 MockCrashReportDatabase db;
218 /*PostmortemReportCollector collector(
219 temp_dir.path(), FILE_PATH_LITERAL("foo*.pma"),
220 WrapUnique(new MockCrashReportDatabase()));
221
222 EXPECT_THAT(collector.GetDebugStateFilePaths(excluded_paths),
223 testing::UnorderedElementsAreArray(expected_paths));*/
224 }
225
226 TEST(PostmortemReportCollectorTest, CollectEmptyFile) {
227 // Create an empty file.
228 base::ScopedTempDir temp_dir;
229 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
230 base::FilePath file_path = temp_dir.path().AppendASCII("empty.pma");
231 {
232 base::ScopedFILE file(base::OpenFile(file_path, "w"));
233 ASSERT_NE(file.get(), nullptr);
234 }
235 ASSERT_TRUE(PathExists(file_path));
236
237 // Validate collection returns nullptr.
238 PostmortemReportCollector collector;
239 ASSERT_EQ(nullptr, collector.Collect(file_path));
240 }
241
242 TEST(PostmortemReportCollectorTest, CollectRandomFile) {
243 // Create a file with content we don't expect to be valid for a debug file.
244 base::ScopedTempDir temp_dir;
245 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
246 base::FilePath file_path = temp_dir.path().AppendASCII("invalid_content.pma");
247 {
248 base::ScopedFILE file(base::OpenFile(file_path, "w"));
249 ASSERT_NE(file.get(), nullptr);
250 // Assuming this size is greater than the minimum size of a debug file.
251 std::vector<uint8_t> data(1024);
252 for (size_t i = 0; i < data.size(); ++i)
253 data[i] = i % UINT8_MAX;
254 ASSERT_EQ(data.size(),
255 fwrite(&data.at(0), sizeof(uint8_t), data.size(), file.get()));
256 }
257 ASSERT_TRUE(PathExists(file_path));
258
259 // Validate collection returns nullptr.
260 PostmortemReportCollector collector;
261 ASSERT_EQ(nullptr, collector.Collect(file_path));
262 }
263
264 namespace {
265
266 // Parameters for the activity tracking.
267 const size_t kFileSize = 2 * 1024;
268 const int kStackDepth = 4;
269 const uint64_t kAllocatorId = 0;
270 const char kAllocatorName[] = "PostmortemReportCollectorCollectionTest";
271
272 } // namespace
273
274 class PostmortemReportCollectorCollectionTest : public testing::Test {
275 public:
276 // Create a proper debug file.
277 void SetUp() override {
278 testing::Test::SetUp();
279
280 // Create a file backed allocator.
281 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
282 debug_file_path_ = temp_dir_.path().AppendASCII("debug_file.pma");
283 std::unique_ptr<PersistentMemoryAllocator> allocator = CreateAllocator();
284 ASSERT_NE(nullptr, allocator);
285
286 size_t tracker_mem_size =
287 ThreadActivityTracker::SizeForStackDepth(kStackDepth);
288 ASSERT_GT(kFileSize, tracker_mem_size);
289
290 // Create some debug data using trackers.
291 std::unique_ptr<ThreadActivityTracker> tracker =
292 CreateTracker(allocator.get(), tracker_mem_size);
293 ASSERT_NE(nullptr, tracker);
294 ASSERT_TRUE(tracker->IsValid());
295
296 const void* dummy_task_origin = reinterpret_cast<void*>(0xCAFE);
297 const int dummy_task_sequence_num = 42;
298 tracker->PushActivity(
299 dummy_task_origin, ThreadActivityTracker::ACT_TASK_RUN,
300 ThreadActivityTracker::ActivityData::ForTask(dummy_task_sequence_num));
301
302 // TODO(manzagop): flesh out the data (more trackers and content).
303 }
304
305 std::unique_ptr<PersistentMemoryAllocator> CreateAllocator() {
306 // Create the memory mapped file.
307 std::unique_ptr<MemoryMappedFile> mmfile(new MemoryMappedFile());
308 bool success = mmfile->Initialize(
309 File(debug_file_path_, File::FLAG_CREATE | File::FLAG_READ |
310 File::FLAG_WRITE | File::FLAG_SHARE_DELETE),
311 {0, static_cast<int64_t>(kFileSize)},
312 MemoryMappedFile::READ_WRITE_EXTEND);
313 if (!success || !mmfile->IsValid())
314 return nullptr;
315
316 // Create a persistent memory allocator.
317 if (!FilePersistentMemoryAllocator::IsFileAcceptable(*mmfile, true))
318 return nullptr;
319 return WrapUnique(new FilePersistentMemoryAllocator(
320 std::move(mmfile), kFileSize, kAllocatorId, kAllocatorName, false));
321 }
322
323 std::unique_ptr<ThreadActivityTracker> CreateTracker(
324 PersistentMemoryAllocator* allocator,
325 size_t tracker_mem_size) {
326 // Allocate a block of memory for the tracker to use.
327 PersistentMemoryAllocator::Reference mem_reference = allocator->Allocate(
328 tracker_mem_size, GlobalActivityTracker::kTypeIdActivityTracker);
329 if (mem_reference == 0U)
330 return nullptr;
331
332 // Get the memory's base address.
333 void* mem_base = allocator->GetAsObject<char>(
334 mem_reference, GlobalActivityTracker::kTypeIdActivityTracker);
335 if (mem_base == nullptr)
336 return nullptr;
337
338 // Make the allocation iterable so it can be found by other processes.
339 allocator->MakeIterable(mem_reference);
340
341 return WrapUnique(new ThreadActivityTracker(mem_base, tracker_mem_size));
342 }
343
344 const base::FilePath& debug_file_path() const { return debug_file_path_; }
345
346 private:
347 base::ScopedTempDir temp_dir_;
348 base::FilePath debug_file_path_;
349 };
350
351 TEST_F(PostmortemReportCollectorCollectionTest, CollectSuccess) {
352 // Validate collection returns the expected report.
353 PostmortemReportCollector collector;
354 std::unique_ptr<StabilityReport> report =
355 collector.Collect(debug_file_path());
356 ASSERT_NE(nullptr, report);
357
358 // Build the expected report.
359 StabilityReport expected_report;
360 ProcessState* process_state = expected_report.add_process_states();
361 ThreadState* thread_state = process_state->add_threads();
362 thread_state->set_thread_name(base::PlatformThread::GetName());
363
364 ASSERT_EQ(expected_report.SerializeAsString(), report->SerializeAsString());
365 }
366
367 } // namespace browser_watcher
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698