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

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

Issue 2339193003: A collector for postmortem reports (Closed)
Patch Set: Address nits 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 a report from the crashpad database, writing the report, then
170 // finalizing it with the database.
171 base::FilePath minidump_path = temp_dir_.path().AppendASCII("foo-1.dmp");
172 base::File minidump_file(
173 minidump_path, base::File::FLAG_CREATE | base::File::File::FLAG_WRITE);
174 crashpad_report_ = {minidump_file.GetPlatformFile(),
175 crashpad::UUID(UUID::InitializeWithNewTag{}),
176 minidump_path};
177 EXPECT_CALL(database_, PrepareNewCrashReport(_))
178 .Times(1)
179 .WillOnce(DoAll(SetArgPointee<0>(&crashpad_report_),
180 Return(CrashReportDatabase::kNoError)));
181
182 EXPECT_CALL(collector_,
183 WriteReportToMinidump(EqualsProto(*stability_report), _, _,
184 minidump_file.GetPlatformFile()))
185 .Times(1)
186 .WillOnce(Return(true));
187 }
188
189 protected:
190 base::ScopedTempDir temp_dir_;
191 base::FilePath debug_file_;
192 MockCrashReportDatabase database_;
193 MockPostmortemReportCollector collector_;
194 base::FilePath::StringType debug_file_pattern_;
195 std::set<base::FilePath> no_excluded_files_;
196 CrashReportDatabase::NewReport crashpad_report_;
197 };
198
199 TEST_F(PostmortemReportCollectorCollectAndSubmitTest,
200 CollectAndSubmitForUpload) {
201 EXPECT_CALL(database_, FinishedWritingCrashReport(&crashpad_report_, _))
202 .Times(1)
203 .WillOnce(Return(CrashReportDatabase::kNoError));
204
205 // Run the test.
206 int success_cnt = collector_.CollectAndSubmitForUpload(
207 debug_file_.DirName(), debug_file_pattern_, no_excluded_files_,
208 &database_);
209 ASSERT_EQ(1, success_cnt);
210 ASSERT_FALSE(base::PathExists(debug_file_));
211 }
212
213 TEST_F(PostmortemReportCollectorCollectAndSubmitTest,
214 CollectAndSubmitForUploadStuckFile) {
215 // Open the stability debug file to prevent its deletion.
216 base::ScopedFILE file(base::OpenFile(debug_file_, "w"));
217 ASSERT_NE(file.get(), nullptr);
218
219 // Expect Crashpad is notified of an error writing the crash report.
220 EXPECT_CALL(database_, ErrorWritingCrashReport(&crashpad_report_))
221 .Times(1)
222 .WillOnce(Return(CrashReportDatabase::kNoError));
223
224 // Run the test.
225 int success_cnt = collector_.CollectAndSubmitForUpload(
226 debug_file_.DirName(), debug_file_pattern_, no_excluded_files_,
227 &database_);
228 ASSERT_EQ(0, success_cnt);
229 ASSERT_TRUE(base::PathExists(debug_file_));
230 }
231
232 TEST(PostmortemReportCollectorTest, GetDebugStateFilePaths) {
233 base::ScopedTempDir temp_dir;
234 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
235
236 // Create files.
237 std::vector<base::FilePath> expected_paths;
238 std::set<base::FilePath> excluded_paths;
239 {
240 // Matches the pattern.
241 base::FilePath path = temp_dir.path().AppendASCII("foo1.pma");
242 base::ScopedFILE file(base::OpenFile(path, "w"));
243 ASSERT_NE(file.get(), nullptr);
244 expected_paths.push_back(path);
245
246 // Matches the pattern, but is excluded.
247 path = temp_dir.path().AppendASCII("foo2.pma");
248 file.reset(base::OpenFile(path, "w"));
249 ASSERT_NE(file.get(), nullptr);
250 ASSERT_TRUE(excluded_paths.insert(path).second);
251
252 // Matches the pattern.
253 path = temp_dir.path().AppendASCII("foo3.pma");
254 file.reset(base::OpenFile(path, "w"));
255 ASSERT_NE(file.get(), nullptr);
256 expected_paths.push_back(path);
257
258 // Does not match the pattern.
259 path = temp_dir.path().AppendASCII("bar.baz");
260 file.reset(base::OpenFile(path, "w"));
261 ASSERT_NE(file.get(), nullptr);
262 }
263
264 PostmortemReportCollector collector;
265 EXPECT_THAT(
266 collector.GetDebugStateFilePaths(
267 temp_dir.path(), FILE_PATH_LITERAL("foo*.pma"), excluded_paths),
268 testing::UnorderedElementsAreArray(expected_paths));
269 }
270
271 TEST(PostmortemReportCollectorTest, CollectEmptyFile) {
272 // Create an empty file.
273 base::ScopedTempDir temp_dir;
274 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
275 base::FilePath file_path = temp_dir.path().AppendASCII("empty.pma");
276 {
277 base::ScopedFILE file(base::OpenFile(file_path, "w"));
278 ASSERT_NE(file.get(), nullptr);
279 }
280 ASSERT_TRUE(PathExists(file_path));
281
282 // Validate collection: an empty file cannot suppport an analyzer.
283 PostmortemReportCollector collector;
284 std::unique_ptr<StabilityReport> report;
285 ASSERT_EQ(PostmortemReportCollector::ANALYZER_CREATION_FAILED,
286 collector.Collect(file_path, &report));
287 }
288
289 TEST(PostmortemReportCollectorTest, CollectRandomFile) {
290 // Create a file with content we don't expect to be valid for a debug file.
291 base::ScopedTempDir temp_dir;
292 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
293 base::FilePath file_path = temp_dir.path().AppendASCII("invalid_content.pma");
294 {
295 base::ScopedFILE file(base::OpenFile(file_path, "w"));
296 ASSERT_NE(file.get(), nullptr);
297 // Assuming this size is greater than the minimum size of a debug file.
298 std::vector<uint8_t> data(1024);
299 for (size_t i = 0; i < data.size(); ++i)
300 data[i] = i % UINT8_MAX;
301 ASSERT_EQ(data.size(),
302 fwrite(&data.at(0), sizeof(uint8_t), data.size(), file.get()));
303 }
304 ASSERT_TRUE(PathExists(file_path));
305
306 // Validate collection: random content appears as though there is not
307 // stability data.
308 PostmortemReportCollector collector;
309 std::unique_ptr<StabilityReport> report;
310 ASSERT_EQ(PostmortemReportCollector::DEBUG_FILE_NO_DATA,
311 collector.Collect(file_path, &report));
312 }
313
314 namespace {
315
316 // Parameters for the activity tracking.
317 const size_t kFileSize = 2 * 1024;
318 const int kStackDepth = 4;
319 const uint64_t kAllocatorId = 0;
320 const char kAllocatorName[] = "PostmortemReportCollectorCollectionTest";
321
322 } // namespace
323
324 class PostmortemReportCollectorCollectionTest : public testing::Test {
325 public:
326 // Create a proper debug file.
327 void SetUp() override {
328 testing::Test::SetUp();
329
330 // Create a file backed allocator.
331 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
332 debug_file_path_ = temp_dir_.path().AppendASCII("debug_file.pma");
333 std::unique_ptr<PersistentMemoryAllocator> allocator = CreateAllocator();
334 ASSERT_NE(nullptr, allocator);
335
336 size_t tracker_mem_size =
337 ThreadActivityTracker::SizeForStackDepth(kStackDepth);
338 ASSERT_GT(kFileSize, tracker_mem_size);
339
340 // Create some debug data using trackers.
341 std::unique_ptr<ThreadActivityTracker> tracker =
342 CreateTracker(allocator.get(), tracker_mem_size);
343 ASSERT_NE(nullptr, tracker);
344 ASSERT_TRUE(tracker->IsValid());
345
346 const void* dummy_task_origin = reinterpret_cast<void*>(0xCAFE);
347 const int dummy_task_sequence_num = 42;
348 tracker->PushActivity(dummy_task_origin, Activity::ACT_TASK_RUN,
349 ActivityData::ForTask(dummy_task_sequence_num));
350
351 // TODO(manzagop): flesh out the data (more trackers and content).
352 }
353
354 std::unique_ptr<PersistentMemoryAllocator> CreateAllocator() {
355 // Create the memory mapped file.
356 std::unique_ptr<MemoryMappedFile> mmfile(new MemoryMappedFile());
357 bool success = mmfile->Initialize(
358 File(debug_file_path_, File::FLAG_CREATE | File::FLAG_READ |
359 File::FLAG_WRITE | File::FLAG_SHARE_DELETE),
360 {0, static_cast<int64_t>(kFileSize)},
361 MemoryMappedFile::READ_WRITE_EXTEND);
362 if (!success || !mmfile->IsValid())
363 return nullptr;
364
365 // Create a persistent memory allocator.
366 if (!FilePersistentMemoryAllocator::IsFileAcceptable(*mmfile, true))
367 return nullptr;
368 return WrapUnique(new FilePersistentMemoryAllocator(
369 std::move(mmfile), kFileSize, kAllocatorId, kAllocatorName, false));
370 }
371
372 std::unique_ptr<ThreadActivityTracker> CreateTracker(
373 PersistentMemoryAllocator* allocator,
374 size_t tracker_mem_size) {
375 // Allocate a block of memory for the tracker to use.
376 PersistentMemoryAllocator::Reference mem_reference = allocator->Allocate(
377 tracker_mem_size, GlobalActivityTracker::kTypeIdActivityTracker);
378 if (mem_reference == 0U)
379 return nullptr;
380
381 // Get the memory's base address.
382 void* mem_base = allocator->GetAsObject<char>(
383 mem_reference, GlobalActivityTracker::kTypeIdActivityTracker);
384 if (mem_base == nullptr)
385 return nullptr;
386
387 // Make the allocation iterable so it can be found by other processes.
388 allocator->MakeIterable(mem_reference);
389
390 return WrapUnique(new ThreadActivityTracker(mem_base, tracker_mem_size));
391 }
392
393 const base::FilePath& debug_file_path() const { return debug_file_path_; }
394
395 private:
396 base::ScopedTempDir temp_dir_;
397 base::FilePath debug_file_path_;
398 };
399
400 TEST_F(PostmortemReportCollectorCollectionTest, CollectSuccess) {
401 // Validate collection returns the expected report.
402 PostmortemReportCollector collector;
403 std::unique_ptr<StabilityReport> report;
404 ASSERT_EQ(PostmortemReportCollector::SUCCESS,
405 collector.Collect(debug_file_path(), &report));
406 ASSERT_NE(nullptr, report);
407
408 // Build the expected report.
409 StabilityReport expected_report;
410 ProcessState* process_state = expected_report.add_process_states();
411 ThreadState* thread_state = process_state->add_threads();
412 thread_state->set_thread_name(base::PlatformThread::GetName());
413
414 ASSERT_EQ(expected_report.SerializeAsString(), report->SerializeAsString());
415 }
416
417 } // namespace browser_watcher
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698