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

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

Issue 2339193003: A collector for postmortem reports (Closed)
Patch Set: Address Siggi's second round Created 4 years, 3 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::Activity;
32 using base::debug::ActivityData;
33 using base::debug::GlobalActivityTracker;
34 using base::debug::ThreadActivityTracker;
35 using base::File;
36 using base::FilePersistentMemoryAllocator;
37 using base::MemoryMappedFile;
38 using base::PersistentMemoryAllocator;
39 using base::WrapUnique;
40 using crashpad::CrashReportDatabase;
41 using crashpad::Settings;
42 using crashpad::UUID;
43 using testing::_;
44 using testing::Return;
45 using testing::SetArgPointee;
46
47 namespace {
48
49 // Exposes a public constructor in order to create a dummy database.
50 class MockCrashReportDatabase : public CrashReportDatabase {
51 public:
52 MockCrashReportDatabase() {}
53 MOCK_METHOD0(GetSettings, Settings*());
54 MOCK_METHOD1(PrepareNewCrashReport,
55 CrashReportDatabase::CrashReportDatabase::OperationStatus(
56 NewReport** report));
57 MOCK_METHOD2(FinishedWritingCrashReport,
58 CrashReportDatabase::CrashReportDatabase::OperationStatus(
59 CrashReportDatabase::NewReport* report,
60 crashpad::UUID* uuid));
61 MOCK_METHOD1(ErrorWritingCrashReport,
62 CrashReportDatabase::CrashReportDatabase::OperationStatus(
63 NewReport* report));
64 MOCK_METHOD2(LookUpCrashReport,
65 CrashReportDatabase::CrashReportDatabase::OperationStatus(
66 const UUID& uuid,
67 Report* report));
68 MOCK_METHOD1(
69 GetPendingReports,
70 CrashReportDatabase::OperationStatus(std::vector<Report>* reports));
71 MOCK_METHOD1(
72 GetCompletedReports,
73 CrashReportDatabase::OperationStatus(std::vector<Report>* reports));
74 MOCK_METHOD2(GetReportForUploading,
75 CrashReportDatabase::OperationStatus(const UUID& uuid,
76 const Report** report));
77 MOCK_METHOD3(RecordUploadAttempt,
78 CrashReportDatabase::OperationStatus(const Report* report,
79 bool successful,
80 const std::string& id));
81 MOCK_METHOD1(SkipReportUpload,
82 CrashReportDatabase::OperationStatus(const UUID& uuid));
83 MOCK_METHOD1(DeleteReport,
84 CrashReportDatabase::OperationStatus(const UUID& uuid));
85 MOCK_METHOD1(RequestUpload,
86 CrashReportDatabase::OperationStatus(const UUID& uuid));
87 };
88
89 // Used for testing CollectAndSubmitForUpload.
90 class MockPostmortemReportCollector : public PostmortemReportCollector {
91 public:
92 MockPostmortemReportCollector() {}
93
94 // A function that returns a unique_ptr cannot be mocked, so mock a function
95 // that returns a raw pointer instead.
96 CollectionStatus Collect(const base::FilePath& debug_state_file,
97 std::unique_ptr<StabilityReport>* report) override {
98 DCHECK_NE(nullptr, report);
99 report->reset(CollectRaw(debug_state_file));
100 return SUCCESS;
101 }
102
103 MOCK_METHOD3(GetDebugStateFilePaths,
104 std::vector<base::FilePath>(
105 const base::FilePath& debug_info_dir,
106 const base::FilePath::StringType& debug_file_pattern,
107 const std::set<base::FilePath>&));
108 MOCK_METHOD1(CollectRaw, StabilityReport*(const base::FilePath&));
109 MOCK_METHOD4(WriteReportToMinidump,
110 bool(const StabilityReport& report,
111 const crashpad::UUID& client_id,
112 const crashpad::UUID& report_id,
113 base::PlatformFile minidump_file));
114 };
115
116 // Checks if two proto messages are the same based on their serializations. Note
117 // this only works if serialization is deterministic, which is not guaranteed.
118 // In practice, serialization is deterministic (even for protocol buffers with
119 // maps) and such matchers are common in the Chromium code base. Also note that
120 // in the context of this test, false positive matches are the problem and these
121 // are not possible (otherwise serialization would be ambiguous). False
122 // negatives would lead to test failure and developer action. Alternatives are:
123 // 1) a generic matcher (likely not possible without reflections, missing from
124 // lite runtime), 2) a specialized matcher or 3) implementing deterministic
125 // serialization.
126 // TODO(manzagop): switch a matcher with guarantees.
127 MATCHER_P(EqualsProto, message, "") {
128 std::string expected_serialized;
129 std::string actual_serialized;
130 message.SerializeToString(&expected_serialized);
131 arg.SerializeToString(&actual_serialized);
132 return expected_serialized == actual_serialized;
133 }
134
135 } // namespace
136
137 class PostmortemReportCollectorCollectAndSubmitTest : public testing::Test {
138 public:
139 void SetUp() override {
140 testing::Test::SetUp();
141 // Create a dummy debug file.
142 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
143 debug_file_ = temp_dir_.path().AppendASCII("foo-1.pma");
144 {
145 base::ScopedFILE file(base::OpenFile(debug_file_, "w"));
146 ASSERT_NE(file.get(), nullptr);
147 }
148 ASSERT_TRUE(base::PathExists(debug_file_));
149
150 // Expect collection of the debug file paths.
151 debug_file_pattern_ = FILE_PATH_LITERAL("foo-*.pma");
152 std::vector<base::FilePath> debug_files{debug_file_};
153 EXPECT_CALL(collector_,
154 GetDebugStateFilePaths(debug_file_.DirName(),
155 debug_file_pattern_, no_excluded_files_))
156 .Times(1)
157 .WillOnce(Return(debug_files));
158
159 EXPECT_CALL(database_, GetSettings()).Times(1).WillOnce(Return(nullptr));
160
161 // Expect collection to a proto of a single debug file.
162 // Note: caller takes ownership.
163 StabilityReport* stability_report = new StabilityReport();
164 EXPECT_CALL(collector_, CollectRaw(debug_file_))
165 .Times(1)
166 .WillOnce(Return(stability_report));
167
168 // Expect the call to write the proto to a minidump. This involves
169 // requesting
scottmg 2016/09/16 17:07:45 Weird comment wrapping here.
manzagop (departed) 2016/09/16 18:06:41 Done.
170 // a report from the crashpad database, writing the report, then finalizing
171 // it
172 // with the database.
173 base::FilePath minidump_path = temp_dir_.path().AppendASCII("foo-1.dmp");
174 base::File minidump_file(
175 minidump_path, base::File::FLAG_CREATE | base::File::File::FLAG_WRITE);
176 crashpad_report_ = {minidump_file.GetPlatformFile(),
177 crashpad::UUID(UUID::InitializeWithNewTag{}),
178 minidump_path};
179 EXPECT_CALL(database_, PrepareNewCrashReport(_))
180 .Times(1)
181 .WillOnce(DoAll(SetArgPointee<0>(&crashpad_report_),
182 Return(CrashReportDatabase::kNoError)));
183
184 EXPECT_CALL(collector_,
185 WriteReportToMinidump(EqualsProto(*stability_report), _, _,
186 minidump_file.GetPlatformFile()))
187 .Times(1)
188 .WillOnce(Return(true));
189 }
190
191 protected:
192 base::ScopedTempDir temp_dir_;
193 base::FilePath debug_file_;
194 MockCrashReportDatabase database_;
195 MockPostmortemReportCollector collector_;
196 base::FilePath::StringType debug_file_pattern_;
197 std::set<base::FilePath> no_excluded_files_;
198 CrashReportDatabase::NewReport crashpad_report_;
199 };
200
201 TEST_F(PostmortemReportCollectorCollectAndSubmitTest,
202 CollectAndSubmitForUpload) {
203 EXPECT_CALL(database_, FinishedWritingCrashReport(&crashpad_report_, _))
scottmg 2016/09/16 17:07:45 I think I probably would have just used a real dat
manzagop (departed) 2016/09/16 18:06:41 Acknowledged.
204 .Times(1)
205 .WillOnce(Return(CrashReportDatabase::kNoError));
206
207 // Run the test.
208 int success_cnt = collector_.CollectAndSubmitForUpload(
209 debug_file_.DirName(), debug_file_pattern_, no_excluded_files_,
210 &database_);
211 ASSERT_EQ(1, success_cnt);
212 ASSERT_FALSE(base::PathExists(debug_file_));
213 }
214
215 TEST_F(PostmortemReportCollectorCollectAndSubmitTest,
216 CollectAndSubmitForUploadStuckFile) {
217 // Open the stability debug file to prevent its deletion.
218 base::ScopedFILE file(base::OpenFile(debug_file_, "w"));
219 ASSERT_NE(file.get(), nullptr);
220
221 // Expect Crashpad is notified of an error writing the crash report.
222 EXPECT_CALL(database_, ErrorWritingCrashReport(&crashpad_report_))
223 .Times(1)
224 .WillOnce(Return(CrashReportDatabase::kNoError));
225
226 // Run the test.
227 int success_cnt = collector_.CollectAndSubmitForUpload(
228 debug_file_.DirName(), debug_file_pattern_, no_excluded_files_,
229 &database_);
230 ASSERT_EQ(0, success_cnt);
231 ASSERT_TRUE(base::PathExists(debug_file_));
232 }
233
234 TEST(PostmortemReportCollectorTest, GetDebugStateFilePaths) {
235 base::ScopedTempDir temp_dir;
236 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
237
238 // Create files.
239 std::vector<base::FilePath> expected_paths;
240 std::set<base::FilePath> excluded_paths;
241 {
242 // Matches the pattern.
243 base::FilePath path = temp_dir.path().AppendASCII("foo1.pma");
244 base::ScopedFILE file(base::OpenFile(path, "w"));
245 ASSERT_NE(file.get(), nullptr);
246 expected_paths.push_back(path);
247
248 // Matches the pattern, but is excluded.
249 path = temp_dir.path().AppendASCII("foo2.pma");
250 file.reset(base::OpenFile(path, "w"));
251 ASSERT_NE(file.get(), nullptr);
252 ASSERT_TRUE(excluded_paths.insert(path).second);
253
254 // Matches the pattern.
255 path = temp_dir.path().AppendASCII("foo3.pma");
256 file.reset(base::OpenFile(path, "w"));
257 ASSERT_NE(file.get(), nullptr);
258 expected_paths.push_back(path);
259
260 // Does not match the pattern.
261 path = temp_dir.path().AppendASCII("bar.baz");
262 file.reset(base::OpenFile(path, "w"));
263 ASSERT_NE(file.get(), nullptr);
264 }
265
266 PostmortemReportCollector collector;
267 EXPECT_THAT(
268 collector.GetDebugStateFilePaths(
269 temp_dir.path(), FILE_PATH_LITERAL("foo*.pma"), excluded_paths),
270 testing::UnorderedElementsAreArray(expected_paths));
271 }
272
273 TEST(PostmortemReportCollectorTest, CollectEmptyFile) {
274 // Create an empty file.
275 base::ScopedTempDir temp_dir;
276 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
277 base::FilePath file_path = temp_dir.path().AppendASCII("empty.pma");
278 {
279 base::ScopedFILE file(base::OpenFile(file_path, "w"));
280 ASSERT_NE(file.get(), nullptr);
281 }
282 ASSERT_TRUE(PathExists(file_path));
283
284 // Validate collection: an empty file cannot suppport an analyzer.
285 PostmortemReportCollector collector;
286 std::unique_ptr<StabilityReport> report;
287 ASSERT_EQ(PostmortemReportCollector::ANALYZER_CREATION_FAILED,
288 collector.Collect(file_path, &report));
289 }
290
291 TEST(PostmortemReportCollectorTest, CollectRandomFile) {
292 // Create a file with content we don't expect to be valid for a debug file.
293 base::ScopedTempDir temp_dir;
294 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
295 base::FilePath file_path = temp_dir.path().AppendASCII("invalid_content.pma");
296 {
297 base::ScopedFILE file(base::OpenFile(file_path, "w"));
298 ASSERT_NE(file.get(), nullptr);
299 // Assuming this size is greater than the minimum size of a debug file.
300 std::vector<uint8_t> data(1024);
301 for (size_t i = 0; i < data.size(); ++i)
302 data[i] = i % UINT8_MAX;
303 ASSERT_EQ(data.size(),
304 fwrite(&data.at(0), sizeof(uint8_t), data.size(), file.get()));
305 }
306 ASSERT_TRUE(PathExists(file_path));
307
308 // Validate collection: random content appears as though there is not
309 // stability data.
310 PostmortemReportCollector collector;
311 std::unique_ptr<StabilityReport> report;
312 ASSERT_EQ(PostmortemReportCollector::DEBUG_FILE_NO_DATA,
313 collector.Collect(file_path, &report));
314 }
315
316 namespace {
317
318 // Parameters for the activity tracking.
319 const size_t kFileSize = 2 * 1024;
320 const int kStackDepth = 4;
321 const uint64_t kAllocatorId = 0;
322 const char kAllocatorName[] = "PostmortemReportCollectorCollectionTest";
323
324 } // namespace
325
326 class PostmortemReportCollectorCollectionTest : public testing::Test {
327 public:
328 // Create a proper debug file.
329 void SetUp() override {
330 testing::Test::SetUp();
331
332 // Create a file backed allocator.
333 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
334 debug_file_path_ = temp_dir_.path().AppendASCII("debug_file.pma");
335 std::unique_ptr<PersistentMemoryAllocator> allocator = CreateAllocator();
336 ASSERT_NE(nullptr, allocator);
337
338 size_t tracker_mem_size =
339 ThreadActivityTracker::SizeForStackDepth(kStackDepth);
340 ASSERT_GT(kFileSize, tracker_mem_size);
341
342 // Create some debug data using trackers.
343 std::unique_ptr<ThreadActivityTracker> tracker =
344 CreateTracker(allocator.get(), tracker_mem_size);
345 ASSERT_NE(nullptr, tracker);
346 ASSERT_TRUE(tracker->IsValid());
347
348 const void* dummy_task_origin = reinterpret_cast<void*>(0xCAFE);
349 const int dummy_task_sequence_num = 42;
350 tracker->PushActivity(dummy_task_origin, Activity::ACT_TASK_RUN,
351 ActivityData::ForTask(dummy_task_sequence_num));
352
353 // TODO(manzagop): flesh out the data (more trackers and content).
354 }
355
356 std::unique_ptr<PersistentMemoryAllocator> CreateAllocator() {
357 // Create the memory mapped file.
358 std::unique_ptr<MemoryMappedFile> mmfile(new MemoryMappedFile());
359 bool success = mmfile->Initialize(
360 File(debug_file_path_, File::FLAG_CREATE | File::FLAG_READ |
361 File::FLAG_WRITE | File::FLAG_SHARE_DELETE),
362 {0, static_cast<int64_t>(kFileSize)},
363 MemoryMappedFile::READ_WRITE_EXTEND);
364 if (!success || !mmfile->IsValid())
365 return nullptr;
366
367 // Create a persistent memory allocator.
368 if (!FilePersistentMemoryAllocator::IsFileAcceptable(*mmfile, true))
369 return nullptr;
370 return WrapUnique(new FilePersistentMemoryAllocator(
371 std::move(mmfile), kFileSize, kAllocatorId, kAllocatorName, false));
372 }
373
374 std::unique_ptr<ThreadActivityTracker> CreateTracker(
375 PersistentMemoryAllocator* allocator,
376 size_t tracker_mem_size) {
377 // Allocate a block of memory for the tracker to use.
378 PersistentMemoryAllocator::Reference mem_reference = allocator->Allocate(
379 tracker_mem_size, GlobalActivityTracker::kTypeIdActivityTracker);
380 if (mem_reference == 0U)
381 return nullptr;
382
383 // Get the memory's base address.
384 void* mem_base = allocator->GetAsObject<char>(
385 mem_reference, GlobalActivityTracker::kTypeIdActivityTracker);
386 if (mem_base == nullptr)
387 return nullptr;
388
389 // Make the allocation iterable so it can be found by other processes.
390 allocator->MakeIterable(mem_reference);
391
392 return WrapUnique(new ThreadActivityTracker(mem_base, tracker_mem_size));
393 }
394
395 const base::FilePath& debug_file_path() const { return debug_file_path_; }
396
397 private:
398 base::ScopedTempDir temp_dir_;
399 base::FilePath debug_file_path_;
400 };
401
402 TEST_F(PostmortemReportCollectorCollectionTest, CollectSuccess) {
403 // Validate collection returns the expected report.
404 PostmortemReportCollector collector;
405 std::unique_ptr<StabilityReport> report;
406 ASSERT_EQ(PostmortemReportCollector::SUCCESS,
407 collector.Collect(debug_file_path(), &report));
408 ASSERT_NE(nullptr, report);
409
410 // Build the expected report.
411 StabilityReport expected_report;
412 ProcessState* process_state = expected_report.add_process_states();
413 ThreadState* thread_state = process_state->add_threads();
414 thread_state->set_thread_name(base::PlatformThread::GetName());
415
416 ASSERT_EQ(expected_report.SerializeAsString(), report->SerializeAsString());
417 }
418
419 } // namespace browser_watcher
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698